# Aurora Stealer - d01a **d01a.github.io/aurora-stealer/** Mohamed Adel April 12, 2023 ## Contents Aurora Stealer [Mohamed Adel included in Malware Analysis](https://d01a.github.io/) 2023-04-12 2414 words 12 minutes views ## Introduction Aurora Stealer is an information stealer Written in GO. It is a commercial stealer that costs around 250$ per month. The malware can steal Browser password and saved cookies, crypto information (Desktop and Web), Telegram, Steam and Specific files from the victim machine and can take a screenshot from it. ----- ## Basic information The icon of the executable gives us a hit about how this is spreading. It has Photoshop icon, most probably it was spreading using Malvertising. ----- ### Binary Identification [First we want to know some basic information about the file so I will use DiE to do so.](https://github.com/horsicq/Detect-It-Easy) ----- It was identified as GO binary. .symtab is a legacy section in GO binaries. In GO binaries prior to Version 1.3 .symtab section hold the symbol table but it is no longer filed with anything useful. Without Symbols, the reversing will be so hard as a simple Hello world program in GO has about 2000 function this is a result of that GO compiler statically linking all the needed libraries. Later, I will try to tackle this problem using existed Tools. An important aspect of the basic Triaging of a Malware is to check the readable Strings of the file. But GO is different in everything. The strings has a part of that too. In GO, the strings are stored in Unicode format without null terminating character so many tools will handle that wrong. Also, the existence of this large number of library functions will make it worse. The resulting number of lines using strings utility in Die is 7371 line. We can reduce this number by matching for the library functions like the following Regex ``` .* (runtime|\/usr|\/root).* \n? ``` this matches the lines that contains runtime, usr and root. this filters around 2500 line but still around 5000 line. these lines contains the function imported in program, you can check them but it will be so exhausting to get information from it. Let’s Continue our analysis using the disassembler. ----- ### Code Analysis I will upload the sample to IDA to explore it. In the old versions of IDA, Library functions will not be recognized and renamed. Also the types will be mostly wrong. To handle this there is some tools you can use to fix the types and names. I’ve used [GoReSym.](https://github.com/mandiant/GoReSym) This is a standalone executable you can run with following parameters ``` GoReSym_win.exe -t -d -p > fix.json ``` for more info about the available parameters, Check the repo of the tool. content of the output is in JSON format so I saved it to use it in this [IDA Script to rename the](https://github.com/mandiant/GoReSym/blob/master/IDAPython/goresym_rename.py) functions and correct the types in IDA database. NOTE: -t parameter fix the types information but if you the decompiler will fail to decompile it. [If you want to know how this tool is working, Check this article. Basically it search for](https://www.mandiant.com/resources/blog/golang-internals-symbol-recovery) ``` pclntab structure by searching for a magic header and follow the pointer to symbols table. // pcHeader holds data used by the pclntab lookups. type pcHeader struct { magic uint32 /* go12magic = 0xfffffffb go116magic = 0xfffffffa go118magic = 0xfffffff0 go120magic = 0xfffffff1 */ pad1, pad2 uint8 // 0,0 minLC uint8 // min instruction size ptrSize uint8 // size of a ptr in bytes nfunc int // number of functions in the module nfiles uint // number of entries in the file tab textStart uintptr // base for function entry PC offsets in this module, equal to moduledata.text funcnameOffset uintptr // offset to the funcnametab variable from pcHeader cuOffset uintptr // offset to the cutab variable from pcHeader filetabOffset uintptr // offset to the filetab variable from pcHeader pctabOffset uintptr // offset to the pctab variable from pcHeader ``` ----- ``` pclnOffset uintptr // offset to the pclntab variable from pcHeader } ``` [This is also used by the go parser itself in order to locate the function, For more info here](https://github.com/golang/go/blob/master/src/debug/gosym/pclntab.go#L211) [Another set of scripts available we can use it doing the same thing is Alphagolang](https://github.com/SentineLabs/AlphaGolang) I will use Alphagolang here but both will provide similar result. First I used [recreate_pclntab.py script to recreate pclntab structure.](https://github.com/SentineLabs/AlphaGolang/blob/main/1.recreate_pclntab.py) [Second, I used function_discovery_and_renaming.py script to rename the functions.](https://github.com/SentineLabs/AlphaGolang/blob/main/2.function_discovery_and_renaming.py) Third, I used [categorize_go_folders.py to categorize the functions and pack them in folders,](https://github.com/SentineLabs/AlphaGolang/blob/main/3.categorize_go_folders.py) This will be very helpful to focus on user-code. Fourth, I used [string_cast.py to fix string references.](https://github.com/SentineLabs/AlphaGolang/blob/main/4.string_cast.py) Fifth, I used [extract_types.py to correct the types information by applying C like types to the](https://github.com/SentineLabs/AlphaGolang/blob/main/5.extract_types.py) used structures. ----- The result Now, We have a better environment so we can start exploring the code efficiently. ### Calling Conventions in GO In function calls, GO has a different calling convention. All the argument are passed using the stack from the left to right. The following assembly code is in Go assembler format ``` func testConv(x,y int) int {return x+y} testConv: MOVQ 0x8(SP), AX ; get arg x MOVQ 0x10(SP), CX ; get arg y ADDQ CX, AX ; %ax < x + y MOVQ AX, 0x20(SP) ; return x+y-z RET ``` the compiler have to make sure that there is enough space on the stack to accommodate all the arguments and return values. ### Strings in GO Go stores strings in a Unicode -UTF-8- format without null terminating characters in a section contain all the strings but. ----- Strings in go stored in structure of value and length pair called StringHeader. So, in all the function where a string argument is passed, you will see an extra argument contain the length of the string. ``` type StringHeader struct { Data uintptr Len int } ``` First we start with main_init function (sub_595590). In GO, init() is a predefined function that takes no argument, Return no values. And Runs before any code in the package. The block number 1 shows that it loads some DLLs and functions. ----- **DLL** **Function** user32.dll GetDesktopWindow user32.dll EnumDisplayMonitors user32.dll GetMonitorInfoW user32.dll EnumDisplaySettingsW kernel32.dll LocalFree Crypt32.dll CryptUnprotectData In Block number 2, It Reads the the environment Variable USERPROFILE and concatenate ``` \\APPDATA\\LOCAL\ and \\APPDATA\\ROAMING\ and save the new string to the memory. ``` In block 3, It did the same thing to get the Paths C:\\Users\\{user}\\APPDATA\\ROAMING, ``` \\ but it replaces the string C:\\Users with C:\\windows.old\Users with Replace ``` function from strings package ``` func Replace(original string, old string, new string, n int) string //where n is the number of times replacing occures. -1 for replace all ``` this location is created when the user update from one version to another and it contains all the old information from the previous installation. moving to main_main (sub_595470). It creates a new procedure by making a call to newproc function from runtime package. ### Connect To server following the code to main_ConnectToServer (sub_58ABE0). This function has some interesting functionality we will explore next. ----- In block 1, the malware sleeps for 1000000000 nanoseconds -I tried a simple program with the same call to sleep and it was equivalent to time.Nanosecond Then it establishes a TCP connection to 82.115.223.249:8081 IP address using function ``` Dial from net package. Then it Reads the Received packet. the Dial function in GO returns ``` 2 values, Conn interface and Error, which IDA cannot recognize so, I will follow my intuition. If the connection returned error, it will try to reconnect again. In Block 2, The connection was established but it first checks the response from the remote IP. If it was blocked due to the geo location, as the IP is Russian, it will try to reconnect. If the response was WORK string, the connection is established successfully and the malware can continue with its functionality as shown in block 3 and 4 ### Collect victim information Moving to main_GetInfoUser() (sub_58B880). The first Lines in this subroutine takes us to another function, main_MachineID (sub_5897A0) ----- The malware Runs the command cmd.exe /c wmic csproduct get uuid to get UUID of the device. Returning to main_GetInfoUser . It retrieves the screen width and height using win32 API GetSystemMetrics, GO allow using third-party packages directly from GitHub and the the cause of the function naming. The screen resolution is represented in the format x . ----- The next call is to main_GetOS (sub_58A530). This function retrieves the OS version using wmic command wmic os get Caption . and filter the output based on the form it is printed to format is in a space separated string. Returning back to main_GetInfoUser a call to main_getGPU (sub_58A200) is made. ----- The GPU information retrieved by executing the command cmd /C wmic path ``` win32_VideoController get name ``` Using the same method in main_getCPU (sub_589F10). It gets CPU information with command cmd /c wmic cpu get name in main_sysTotalMemory (sub_58B550)It gets the memory status by executing ``` GlobalMemoryStatusEx function. main_CMD_SHELL is called to execute cmd /c systeminfo that gets all the specs of the ``` device. ----- That was the last thing the function main_GetInfoUser do. Back in main_main, the function main_grab (sub_593E80) is called. This function responsible for doing the main goal of the malware, Stealing. ----- ``` panic function is used to check for unexpected errors. common use of panic is to abort ``` if a function returns an error value that we don’t want to handle ----- ### File grabber Going to the first function main_file_grabber (sub_594110) this function search for a specific file taken from the C2 server and it is base64 encoded and in JSON format. Then, It search for the file in some predefined directories and location. the function io_ioutil_ReadDir reads the content of the directory and stores the output in a ``` fs.fileinfo structure, sorted by the filename ``` ----- ``` type FileInfo interface { Name() string // base name of the file Size() int64 // length in bytes for regular files; system dependent for others Mode() FileMode // file mode bits ModTime() time.Time // modification time IsDir() bool // abbreviation for Mode().IsDir() Sys() any // underlying data source (can return nil) } ``` Then it walks through the returned structure and reads the file of interest then it encode the file content in Base64 and adds the tags used in the JSON formatted packet content to be sent to the remote system ----- ### Browser data We will visit SendToServer latter. Now, lets go back to the caller function and explore the next function, main_Grab_func3 (sub_58F0B0). This function goes through the %APPDATA%Roaming directory and calls another function. the function path_filepath_Walk walks the directory from the Root passed in the second parameter calling a function fn.WinDirFunc at each file and directory in it including the Root. ``` func Walk(root string, fn WalkFunc) error type WalkFunc func(path string, info fs.FileInfo, err error) error ``` ----- So, Next one to visit is WalkFunc used main_Grab_func3_2 (sub_58DED0). This function steals the Browser information stored For Chromium based browsers it gets the Local State file and calls main_getMasterKey that as the name suggest, Gets the master key and decode it .then, decrypts it by calling ``` CryptUnprotectData which is called from main_xDecrypt ``` It handles the case of using Opera and Firefox browsers Back to the caller function, The malware steals the password and cookies from the browser data and adds the tags of the JSON file to be sent to the C2 server. ----- ### Crypto Then, It goes through the %USERPROFILE% searching for any Crypto wallets information It Looks for PC applications and Web based wallets and add its associated type and name to the JSON data to be sent ### Screenshot Capture function main_Grab_func_7 (sub_591D50) is used to take a screenshot from the victim system The PNG file is then base64 encoded and add the value to the tag screenshot to be sent. ### Telegram Data The next targeted information is Telegram, It did the same procedure discussed before with telegram data folder at main_Grab_func_6 (sub_591980) ----- ``` WalkFunc → main_Grab_func_6_2 (sub_591120) ### Steam data ``` function main_Grab_func9 (sub_593B30) steals steam data in the same way ### send To server ``` main_SendToServer_NEW (sub_594DD0) is used to send the collected data to the server. ``` ----- The collected information stored in JSON format. the Data then compressed using gzip compression algorithm and encoded with Base64 encoding to be sent to the server using the previously established TCP connection. ## Network Analysis [we can look at the network communication using PCAP file provided by Any Run sandbox.](https://app.any.run/tasks/e67ff1df-1b55-451e-bdad-23849a10b650/) By opening the file in Wireshark and filter using the IP 82.115.223.249 Following the TCP stream ----- The first packet received is WORK indicates that the connection is successful and the malware then begin to collect the required data and compress it and send it to the server. At the last packet received from the the C2 server is Thanks. we can use Cyberchef to decode and decompress the data. ----- ----- the Error list include the files that the malware cannot read or access. On of the packets has a very large size, as the screenshot field has a very large Base64 encoded data ----- the screenshot: [Sample JSON file can be found here https://pastebin.com/YpTwAC94](https://pastebin.com/YpTwAC94) ## Conclusion Aurora stealer is a new commercial infostealer. Most of it’s capabilities are typical things that can be found in most of the stealers. it can grab Browser saved password/cookies and Cryptocurrency wallets information from Desktop applications and Web based wallets. Also, it can grab a files from the victim machine and take a screenshot. The communication with C2 server is done over TCP protocol. Most of these things can be found in most of the stealer But being written in GO makes it special, even it has a plaintext strings, The reversing process is quite annoying as most of the tools cannot handle GO binaries in a right way. ----- ## IOCs: 29339458f4a33ee922f25d36b83f19797a15a279634e9c44ebd3816866a541cb 82.115.223[.]249:8081 ## Yara Rule ``` rule aurora_stealer{ meta: malware = "Aurora stealer" hash = "29339458f4a33ee922f25d36b83f19797a15a279634e9c44ebd3816866a541cb" reference = "https://d01a.github.io/" Author = "d01a" description = "detect Aurora stealer" strings: $is_go = "Go build" ascii $a1 = "C:\\Windows.old\\Users\\" ascii $a2 = "\\AppData\\Roaming\\" ascii $a3 = "wmic csproduct get uuid" ascii $a4 = "wmic cpu get name" ascii $a5 = "systeminfo" ascii $a6 = "coNNNECTIONGWQFGQW" ascii $fun1 = "main.Grab" ascii $fun2 = "main.getMasterKey" ascii $fun3 = "main.SendToServer_NEW" ascii $fun4 = "main.ConnectToServer" ascii $fun5 = "main.xDecrypt" ascii $fun6 = "main.GetDisplayBounds" ascii condition: uint16(0) == 0x5a4d and ( $is_go and (4 of ($a*)) and (4 of ($fun*)) ) } ``` ----- ## References -----