✝️ John 16:20
Truly, truly, I say to you, you will weep and lament, but the world will rejoice. You will be sorrowful, but your sorrow will turn into joy.
## Intro
I thought it would be a good idea to learn arm64 assembly: [[arm_assembly_reference]] so I could know how arm systems really work, so I did that. Now since I also have a macbook I also thought it was a good idea to understand how macos binaries work. I have also been wanting to get better at reversing malware so I picked out a macos sample: https://bazaar.abuse.ch/sample/2d0dda75bfc90e7ffda72640eb32c7ff9f51c90c30f4a6d1e05df93e58848f36/
## Overview
The sample is the dropped payload of https://bazaar.abuse.ch/sample/96b06165f432a54f5588ac19f3e356b2f107c181545dc6f1dfb14c65b2c0d7ac/ The sample is a mach-o arm64 executable written in Golang (version 1.20.3): https://go.dev/ The malware attempts to steal information such as files, crypto wallets, and browser data.
## Golang concepts
Strings in golang are stored like: `[POINTER TO THE STRING DATA][LENGTH OF THE STRING]`, so we have the the pointer part, which is simply a pointer to the raw bytes, and we have the length. Now, slices in golang are stored like: `[POINTER TO THE SLICE DATA][LENGTH OF THE SLICE][CAPACITY OF THE SLICE]`. There is the pointer part, which points to the raw data, we also have the length part and the capacity part.
When a function returns a string on it puts a pointer to the data in `X0` and the length of the string in `X1`. When a slice is returned, the pointer to the data is in `X0`, the length is in `X1`, and the capacity is in `X2`. Functions in golang can return errors with types to see if the function failed, so if we had a function that returned either a string or an error, the string would be in `X0` and `X1` but the error would be stored in `X2` and `X3`. If we had a function that returned a slice or an error the slice would be in `X0`, `X1`, and `X2`, while the error would be in `X3` and `X4`. If a function returned a number or an error the number would be in `X0` while the error would be in `X1` and `X2`.
## Some important notes
I am going to be extremely detailed and I will note things and I will never mention them again, even if they are used implicitly such as the length of some strings. When doing a string operation you need the pointer to the string data and the length, so when I mention "oh X is done with this pointer to the string data", the length is going to be used even if I do not mention it. As another example, I might say "`qword_1337XXXXX` stores a pointer to the string data and `qword_1338XXXXX` stores the length of the string." Later in the text I might say "`qword_1337XXXXX` + the resulting string from some operation is stored in `[SP,#0x1337+var_7331]` and the length is stored in `[SP,#0x1337+var_7339]`." I may never mention the length of a string again, even if it is used. When a string is used, the length of the string is used with the pointer to the string data even if i do not mention it, it is still used! I am still going to note the length of the string and other similar details for other people who might be interested in reversing this sample.
## Execution flow (sorry about the wall of text)
`main.init` is executed and stores a pointer to the string data `cthulhu_team` at `_main.userlog` while the length of the string is stored in `qword_10170A998`. Then a pointer to the string data `Launcher` is stored at `_main.buildid` and the length of the string is stored `qword_10170A908`. There is a string slice created with the strings `cpp` and `txt`, and the length of the slice is stored in `qword_10170B438`, the capacity of the slice in `qword_10170B440`, and a pointer at `_main.filegrabber_exts`. The global `_main.buf` is used as a buffer for `_main.writer`, which is used to manage a in memory zip file, to store files. The length of the result of `os.Getenv` called with the string `USER` is stored in `qword_10170A988` and pointer to the result string data is stored in `_main.user`. A pointer to the string data `/Users/` + `_main.user` + `/Library/Application Support/` is stored in `_main.library` and the length is stored in `qword_10170A968`. A pointer to the string data `/Users/` + `_main.user` + `/Desktop` is stored in `_main.desktop` and the length is stored in `qword_10170A928`. A pointer to the string data `/Users/` + `_main.user` + `/Documents` is stored in `_main.documents` and the length is stored in `qword_10170A938`. A pointer to the string data `_main.library` + `Firefox/Profiles/` is stored in `_main.firefox` and the length of that string in `qword_10170A958`. A pointer to the string data `_main.library` + `Google/Chrome/` is stored in `_main.chrome` and the length of that string in `qword_10170A918`. A pointer to the string data `_main.library` + `BraveSoftware/Brave-Browser/` is stored in `_main.brave` and the length of that string in `qword_10170A8F8`. A pointer to the string data `_main.library` + `Microsoft Edge/` is stored in `_main.edge` and the length of that string in `qword_10170A948`. The value of `qword_10170A918` is stored in `qword_10170BB68` and the value of `_main.chrome` is stored in `qword_10170BB60`. The value of `qword_10170A8F8` is stored in `qword_10170BB78` and the value of `_main.brave` is stored in `qword_10170BB70`. The value of `_main.edge` is stored in `qword_10170BB80` and the value of `qword_10170A948` is stored in `qword_10170BB88`. A pointer to the string data `/Users/` + `_main.user` + `/Group Containers/6N38VWS5BX.ru.keepcoder.Telegram/appstore/` is stored in `_main.telegram` and the length is stored in `qword_10170A978`. A pointer to the string data `/Users/` + `_main.user` + `/.electrum/wallets/` is stored in `qword_10170BDA0` and the length is stored `qword_10170BDA8`. A pointer to the string data `_main.library` + `Coinomi/wallets/` is stored in `qword_10170BDB0` and the length is stored `qword_10170BDB8`. A pointer to the string data `_main.library` + `Exodus/` is stored in `qword_10170BDC0` and the length is stored `qword_10170BDC8`. A pointer to the string data `_main.library` + `atomic/Local Storage/leveldb/` is stored in `qword_10170BDD0` and the length is stored `qword_10170BDD8`. Then `main.main` is called which calls the following functions in order: `main.keychain`, `main.GrabWallets`, `main.GrabChrome`, `main.GrabFirefox`, `main.FileGrabber`, `main.systeminfo`, `main.sendlog`, and finally `main.doAlert`. Before I continue let me introduce you to a few functions: `main.getpassfromservice`, `main.GetUserPassword`, `main.geterr`, and `main.write`. The function `main.getpassfromservice` takes `4` arguments from register `X0` to register `X3`, register `X0` holds a pointer to the string data, register `X1` holds the length of that string, register `X2` holds a pointer to another string data, and the register `X3` holds the length of that string. Inside the function `main.getpassfromservice`, the function `strings.Index` is called with the same arguments given to `main.getpassfromservice`. The function `strings.Index` returns the index of the first instance of the second string's bytes found in the first string's bytes assuming that data is there, if not `-1` is returned. If the result of `strings.Index` is `-1` then an empty string is returned via setting `X0` and `X1` to `XZR` and then simply returning. If the result was not `-1` the function `strings.Split` is called with the first string that `strings.Index` was called with, at the offset of the result of `strings.Index` + the length of the second string and `Password: `. Now, from that call we have a slice of strings and if the length of the slice is less than or equal to `1` we return an empty string by setting `X0` and `X1` to `XZR` and returning. If the result is greater than `1` the function `strings.TrimSpace` is called with element `1` of the slice which returns a string with leading and trailing whitespace removed. A pointer to the result string data is stored in `[SP,#0x40+var_10]` and the length of the string is stored in `[SP,#0x40+var_18]`. Next `strings.Fields` is called with that result string, which makes a slice that contains the string separated by whitespace but the whitespace is not included in the slice. If the length of the resulting slice is less than or equal to `1` then `[SP,#0x40+var_10]` is loaded into `X0` and `[SP,#0x40+var_18]` is loaded into `X1` and the function returns. If the result is greater than `1` the zeroth element of the string slice is returned. The function `main.GetUserPassword` attempts to run the command `osascript -e 'display dialog "MacOS wants to access System Preferences Please enter your password." with title "System Preferences" with icon file "System:Library:CoreServices:CoreTypes.bundle:Contents:Resources:ToolbarAdvanced.icns" default answer "" giving up after 30 with hidden answer ¬'`. If it cannot run the command an empty string is returned. If the command ran, the function `strings.Split` is called with the result of the command as a string and the string `text returned:`. If the length of the returned slice was less than or equal to `1` we return an empty string by setting `X0` and `X1` to `XZR`. If the result was greater than `1`, element `1` of the slice is split by `,` and if the resulting slice's length is less than or equal to `1` an empty string is returned, otherwise the zeroth element of the slice is returned. Now the function `main.geterr` simply returns and changes nothing. The function `main.write` reads data from a the first argument, being treated as a file and uses `_main.writer` to write the contents of that file into a new file, which is the second argument, inside the in memory zip file. Now we can talk about the functions `main.main` calls, so as I said before the functions `main.keychain`, `main.GrabWallets`, `main.GrabChrome`, `main.GrabFirefox`, `main.FileGrabber`, `main.systeminfo`, `main.sendlog`, and `main.doAlert` are called in order then the function returns. So first of all, the function `main.keychain` is called and it calls `main.GetUserPassword` and stores a pointer to the data in `[SP,#0x100+var_48]` and the length in `[SP,#0x100+var_98]`. The file `/Users/` + `_main.user` + `/unix1` is attempted to be created or accessed, if it was accessed it is truncated. If it cannot be created or accessed the function returns. The data the slice data pointer `_main.bin` points to, with a length and capacity of `0x1321350`, is written to the file that was just created or accessed. The command `chmod 777` is used on the file `/Users/` + `_main.user` + `/unix1`. If the command failed the function returns. The file `/Users/` + `_main.user` + `/unix1` is executed with the arguments `/Users/` + `_main.user` + `/library/Keychains/login.keychain-db` and the string where the pointer to the data is from `[SP,#0x100+var_48]` and the length of the string from `[SP,#0x100+var_98]`. If the command failed then the function returns. The output pointer to the slice data from the ran command is stored in `[SP,#0x100+var_50]` and the length of the slice is stored in `[SP,#0x100+var_90]`. The function `main.getpassfromservice` is called with `[SP,#0x100+var_50]` as a string for the first argument with `[SP,#0x100+var_90]` as the length, and the string `Chrome Safe Storage` as the second argument. A pointer to the returned string data is stored in `[SP,#0x100+var_38]` and the length in `[SP,#0x100+var_88]`. The function `main.getpassfromservice` is called with `[SP,#0x100+var_50]` as a string for the first argument with `[SP,#0x100+var_90]` as the length, and the string `Brave Safe Storage` as the second argument. A pointer to the returned string data is stored in `[SP,#0x100+var_30]` and the length in `[SP,#0x100+var_80]`. The function `main.getpassfromservice` is called with `[SP,#0x100+var_50]` as a string for the first argument with `[SP,#0x100+var_90]` as the length, and the string `Microsoft Edge Safe Storage` as the second argument. A pointer to the returned string data is stored in `[SP,#0x100+var_40]` and the length in `[SP,#0x100+var_90]`. A pointer to the string data `"` + `[SP,#0x100+var_38]` is stored at `qword_10170BB20` and the length is stored at `qword_10170BB28`. A pointer to the string data `"` + `[SP,#0x100+var_30]` is stored at `qword_10170BB30` and the length is stored at `qword_10170BB38`. A pointer to the string data `"` + `[SP,#0x100+var_40]` is stored at `qword_10170BB40` and the length is stored at `qword_10170BB48`. The file `keychain.txt` is created inside the memory zip file using `_main.writer`. The string `MacOS Password:` + `[SP,#0x100+var_48]` + `\n\n` + `[SP,#0x100+var_50]`, converted to a byte slice, is written to `keychain.txt` in the memory zip file. The file `/Users/` + `_main.user` + `/unix1` is removed and the function returns. The function `main.GrabWallets` is called next. The function `main.write` is called with the string `_main.library` + `Binance/app-store.json` and the string `Wallets/Binance/app-store.json`. Now, the contents of the global `qword_101700598` which is `4`, is stored in `[SP,#0x100+var_58]`. There is a loop from `0` to the contents of `[SP,#0x100+var_58]`, which is `4`. For the loop, the counter is stored at `[SP,#0x100+var_A8]`, and increases in intervals of `1`, the global `qword_10170BDA0` (NOT the contents) is stored in `[SP,#0x100+var_10]` at first, but increases in intervals of `16`, the data at the address that `[SP,#0x100+var_10]` holds is stored in `[SP,#0x100+var_50]` and the data at the address that `[SP,#0x100+var_10]` holds, plus `8`, is stored in `[SP,#0x100+var_B0]`, that is the length. Now, the function `os.ReadDir` is called with the string data pointer that `[SP,#0x100+var_50]` holds and the length being in `SP,#0x100+var_B0]`. If the call to `os.ReadDir` failed, we simply continue the loop. If the call did not fail, the length of the amount of entires in the directory is stored in `[SP,#0x100+var_98]`. There is another loop inside the original loop, that starts at `0` and goes to the amount that `[SP,#0x100+var_98]` stores. The loop counter is stored at `[SP,#0x100+var_60]` and increases by `1`. A pointer to the list of directory entires is stored in `[SP,#0x100+var_18]` and increases by `16`. A method table pointer is stored in `[SP,#0x100+var_88]`. An object pointer for the directory entry is stored in `[SP,#0x100+var_40]`. Now when a method from the method table is called, it operates on `[SP,#0x100+var_40]`. Think of `[SP,#0x100+var_40]` like the C++ `this` pointer. Now, the contents of `[SP,#0x100+var_88]` + `0x28` is called with the contents of `[SP,#0x100+var_40]` as the argument. The method corresponds to `Name()` and the pointer to the string data is stored in `[SP,#0x100+var_20]` and the length is stored in `[SP,#0x100+var_68]`. The same method is called again and the pointer to the data is stored in `[SP,#0x100+var_28]` and the length is stored in `[SP,#0x100+var_70]`. A wallet string is selected from the string list:
```
__data:0000000101704AC0 sElectrum DCQ aElectrum ; str
__data:0000000101704AC8 DCQ 8 ; len ; "Electrum"
__data:0000000101704AD0 sCoinomi DCQ aCoinomi ; str ; "Coinomi"
__data:0000000101704AD8 DCQ 7 ; len
__data:0000000101704AE0 sExodus DCQ aExodus ; str ; "Exodus"
__data:0000000101704AE8 DCQ 6 ; len
__data:0000000101704AF0 sAtomic DCQ aAtomic ; str ; "Atomic"
__data:0000000101704AF8 DCQ 6 ; len
```
Now, a pointer to that wallet string data is stored in `[SP,#0x100+var_30]` and the length is stored in `[SP,#0x100+var_78]`. A pointer to the string data `[SP,#0x100+var_50]` + `[SP,#0x100+var_20]` is stored in `[SP,#0x100+var_20]` and the length is stored in `[SP,#0x100+var_68]`. A pointer to the string data `Wallets/` + `[SP,#0x100+var_30]` + `/` + `[SP,#0x100+var_28]` is stored in `[SP,#0x100+var_20]` and the length is stored in `[SP,#0x100+var_68]`. Now, `main.write` is called with `[SP,#0x100+var_20]`, `[SP,#0x100+var_68]` AND `[SP,#0x100+var_20]`, `[SP,#0x100+var_68]`. Now, the contents of `[SP,#0x100+var_88]` + `0x28` is called with the contents of `[SP,#0x100+var_40]` as the argument. If the length of the string is equal to `13`, we continue the inner loop. Now, if the length is not equal to `13` the string contents are checked if they are equal to `0x772E7375646F7865`, if they are not equal the inner loop continues, the next `8` bytes of the string are checked to be `0x656C6C61`, if they are not equal the inner loop continues, the next byte is checked if it is equal to `0x74`, if they are not equal the loop continues. Now if all those things were equal, instead of continuing to the top, the contents of `[SP,#0x100+var_88]` + `0x28` is called with the contents of `[SP,#0x100+var_40]` as the argument. The function `os.ReadDir` is called with `[SP,#0x100+var_50]` + the result of that method call. If that function call failed, we continue the inner loop. If the function call did not fail the amount of directory entries is stored in `[SP,#0x100+var_A0]`, and there is ANOTHER loop inside the inner loop, let us call it the inner inner loop. The inner inner loop is from `0` to `[SP,#0x100+var_A0]`, with the counter being in `[SP,#0x100+var_68]` and the counter increases by `1` on each iteration. Now, in the inner inner loop, a pointer to the list of directory entires is stored in `[SP,#0x100+var_38]` and increases by `16` on each iteration. A method table pointer is stored in `[SP,#0x100+var_90]`, an object pointer for the directory entry is stored in `[SP,#0x100+var_48]`. The method at `[SP,#0x100+var_88]` + `0x28` is called with `[SP,#0x100+var_40]`, which is the `Name()` method. The pointer to the string data is stored in `[SP,#0x100+var_20]` and the length is stored in `[SP,#0x100+var_70]`. Now, the method at `[SP,#0x100+var_90]` + `0x28` is called with `[SP,#0x100+var_48]`, which is the `Name()` method. The pointer to the string data is stored in `[SP,#0x100+var_28]` and the length is stored in `[SP,#0x100+var_78]`. The method at `[SP,#0x100+var_90]` + `0x28` is called with `[SP,#0x100+var_48]`. The pointer to the string data is stored in `[SP,#0x100+var_30]` and the length is stored in `[SP,#0x100+var_80]`. A pointer to the string data `[SP,#0x100+var_50]` + `[SP,#0x100+var_20]` + `/` + `[SP,#0x100+var_28]` is stored in `[SP,#0x100+var_20]` and the length in `[SP,#0x100+var_70]`. The pointer to the string data `Wallets/Exodus/exodus.wallet/` + `[SP,#0x100+var_30]` is stored in `[SP,#0x100+var_20]` and the length in `[SP,#0x100+var_70]`. Now, `main.write` is called with both of the arguments being that string. After that inner inner loop, the inner loop continues, and after that the main loop continues, and after the main loop the function returns. (TODO)
## Data collection
(TODO)
## Persistence
(TODO)
## Network communication
(TODO)
## Analysis of `_main.bin`
Now, remember `_main.bin`? It was the data pointer part of a slice, used in the function `main.keychain` the data it pointed to was written to `/Users/` + `_main.user` + `/unix1`. Now `0x1321350` bytes were written to that file, and the file was made executable. The file was ran with the the arguments `/Users/` + `_main.user` + `/library/Keychains/login.keychain-db` and and the string where the pointer to the data is from `[SP,#0x100+var_48]` and the length of the string from `[SP,#0x100+var_98]`. Now, as you know `[SP,#0x100+var_48]` stored a pointer to the returned string data from `main.GetUserPassword` and `[SP,#0x100+var_98]` held the length of the string. (TODO)