MalwareTech Challenge - shellcode2.exe
I have been teaching myself to reverse engineer binary programs so that I can use these skills to reverse engineer malware. I have been learning assembly code, and playing with new tools such as ghidra and radare2/cutter.
I found that @MalwareTech had some great binary analysis challenges on his blog and decided to check them out.
This write up covers the fifth challenge shellcode2.exe: ‘https://www.malwaretech.com/challenges-shellcode2’
Lets open this binary in cutter and analyze it with radare2. Lets take a look at the entry function:
/ (fcn) entry0 342
| entry0 ();
| ; var LPVOID s1 @ ebp-0xc0
| ; var LPCSTR lpText @ ebp-0xbc
| ; var int32_t var_b8h @ ebp-0xb8
| ; var int32_t var_28h @ ebp-0x28
| ; var int32_t var_27h @ ebp-0x27
| ; var int32_t var_26h @ ebp-0x26
| ; var int32_t var_25h @ ebp-0x25
| ; var int32_t var_24h @ ebp-0x24
| ; var int32_t var_23h @ ebp-0x23
| ; var int32_t var_22h @ ebp-0x22
| ; var int32_t var_21h @ ebp-0x21
| ; var int32_t var_20h @ ebp-0x20
| ; var int32_t var_1fh @ ebp-0x1f
| ; var int32_t var_1eh @ ebp-0x1e
| ; var int32_t var_1dh @ ebp-0x1d
| ; var int32_t var_1ch @ ebp-0x1c
| ; var int32_t var_1bh @ ebp-0x1b
| ; var int32_t var_1ah @ ebp-0x1a
| ; var int32_t var_19h @ ebp-0x19
| ; var int32_t var_18h @ ebp-0x18
| ; var int32_t var_17h @ ebp-0x17
| ; var int32_t var_16h @ ebp-0x16
| ; var int32_t var_15h @ ebp-0x15
| ; var int32_t var_14h @ ebp-0x14
| ; var int32_t var_13h @ ebp-0x13
| ; var int32_t var_12h @ ebp-0x12
| ; var int32_t var_11h @ ebp-0x11
| ; var int32_t var_10h @ ebp-0x10
| ; var int32_t var_fh @ ebp-0xf
| ; var int32_t var_eh @ ebp-0xe
| ; var int32_t var_dh @ ebp-0xd
| ; var int32_t var_ch @ ebp-0xc
| ; var int32_t var_bh @ ebp-0xb
| ; var int32_t var_ah @ ebp-0xa
| ; var int32_t var_9h @ ebp-0x9
| ; var int32_t var_8h @ ebp-0x8
| ; var int32_t var_7h @ ebp-0x7
| ; var int32_t var_6h @ ebp-0x6
| ; var int32_t var_5h @ ebp-0x5
| ; var LPVOID var_4h @ ebp-0x4
| 0x00402270 push ebp
| 0x00402271 mov ebp, esp
| 0x00402273 sub esp, 0xc0
| 0x00402279 lea ecx, [var_b8h]
| 0x0040227f call sym.shellcode2.exe___0MD5__QAE_XZ
| 0x00402284 mov byte [var_28h], 0x12 ; 18
| 0x00402288 mov byte [var_27h], 0x24 ; '$' ; 36
| 0x0040228c mov byte [var_26h], 0x28 ; '(' ; 40
| 0x00402290 mov byte [var_25h], 0x34 ; '4' ; 52
| 0x00402294 mov byte [var_24h], 0x5b ; '[' ; 91
| 0x00402298 mov byte [var_23h], 0x23 ; '#' ; 35
| 0x0040229c mov byte [var_22h], 0x26 ; '&' ; 38
| 0x004022a0 mov byte [var_21h], 0x20 ; 32
| 0x004022a4 mov byte [var_20h], 0x35 ; '5' ; 53
| 0x004022a8 mov byte [var_1fh], 0x37 ; '7' ; 55
| 0x004022ac mov byte [var_1eh], 0x4c ; 'L' ; 76
| 0x004022b0 mov byte [var_1dh], 0x28 ; '(' ; 40
| 0x004022b4 mov byte [var_1ch], 0x76 ; 'v' ; 118
| 0x004022b8 mov byte [var_1bh], 0x26 ; '&' ; 38
| 0x004022bc mov byte [var_1ah], 0x33 ; '3' ; 51
| 0x004022c0 mov byte [var_19h], 0x37 ; '7' ; 55
| 0x004022c4 mov byte [var_18h], 0x3a ; ':' ; 58
| 0x004022c8 mov byte [var_17h], 0x27 ; ''' ; 39
| 0x004022cc mov byte [var_16h], 0x3d ; '=' ; 61
| 0x004022d0 mov byte [var_15h], 0x6e ; 'n' ; 110
| 0x004022d4 mov byte [var_14h], 0x25 ; '%' ; 37
| 0x004022d8 mov byte [var_13h], 0x48 ; 'H' ; 72
| 0x004022dc mov byte [var_12h], 0x6f ; 'o' ; 111
| 0x004022e0 mov byte [var_11h], 0x3c ; '<' ; 60
| 0x004022e4 mov byte [var_10h], 0x58 ; 'X' ; 88
| 0x004022e8 mov byte [var_fh], 0x3a ; ':' ; 58
| 0x004022ec mov byte [var_eh], 0x68 ; 'h' ; 104
| 0x004022f0 mov byte [var_dh], 0x2c ; ',' ; 44
| 0x004022f4 mov byte [var_ch], 0x43 ; 'C' ; 67
| 0x004022f8 mov byte [var_bh], 0x73 ; 's' ; 115
| 0x004022fc mov byte [var_ah], 0x10 ; 16
| 0x00402300 mov byte [var_9h], 0xe ; 14
| 0x00402304 mov byte [var_8h], 0x10 ; 16
| 0x00402308 mov byte [var_7h], 0x6b ; 'k' ; 107
| 0x0040230c mov byte [var_6h], 0x10 ; 16
| 0x00402310 mov byte [var_5h], 0x6f ; 'o' ; 111
| 0x00402314 push 0x10 ; 16
| 0x00402316 push 0 ; DWORD dwFlags
| 0x00402318 call dword [sym.imp.KERNEL32.dll_GetProcessHeap] ; 0x403010 ; ",1" ; HANDLE GetProcessHeap(void)
| 0x0040231e push eax ; HANDLE hHeap
| 0x0040231f call dword [sym.imp.KERNEL32.dll_HeapAlloc] ; 0x40300c ; " 1" ; LPVOID HeapAlloc(HANDLE hHeap, DWORD dwFlags, SIZE_T dwBytes)
| 0x00402325 mov dword [var_4h], eax
| 0x00402328 mov eax, dword [var_4h]
| 0x0040232b mov ecx, dword sym.imp.KERNEL32.dll_LoadLibraryA ; [0x403008:4]=0x3110 reloc.KERNEL32.dll_LoadLibraryA
| 0x00402331 mov dword [eax], ecx
| 0x00402333 mov edx, dword [var_4h]
| 0x00402336 mov eax, dword sym.imp.KERNEL32.dll_GetProcAddress ; [0x403004:4]=0x30fe reloc.KERNEL32.dll_GetProcAddress
| 0x0040233b mov dword [edx + 4], eax
| 0x0040233e mov ecx, dword [var_4h]
| 0x00402341 lea edx, [var_28h]
| 0x00402344 mov dword [ecx + 8], edx
| 0x00402347 mov eax, dword [var_4h]
| 0x0040234a mov dword [eax + 0xc], 0x24 ; '$' ; [0x24:4]=-1 ; 36
| 0x00402351 push 0x40 ; '@' ; 64 ; DWORD flProtect
| 0x00402353 push 0x1000 ; DWORD flAllocationType
| 0x00402358 push 0x248 ; 584 ; SIZE_T dwSize
| 0x0040235d push 0 ; LPVOID lpAddress
| 0x0040235f call dword [sym.imp.KERNEL32.dll_VirtualAlloc] ; 0x403000 ; LPVOID VirtualAlloc(LPVOID lpAddress, SIZE_T dwSize, DWORD flAllocationType, DWORD flProtect)
| 0x00402365 mov dword [s1], eax
| 0x0040236b push 0x248 ; 584 ; size_t n
| 0x00402370 push 0x404040 ; '@@@' ; "U\x89\xe5\x81\xec\xbc\x01" ; const void *s2
| 0x00402375 mov ecx, dword [s1]
| 0x0040237b push ecx ; void *s1
| 0x0040237c call sub.ntdll.dll_memcpy ; void *memcpy(void *s1, const void *s2, size_t n)
| 0x00402381 add esp, 0xc
| 0x00402384 push dword [var_4h]
| 0x00402387 call dword [s1]
| 0x0040238d lea edx, [var_28h]
| 0x00402390 push edx
| 0x00402391 lea ecx, [var_b8h]
| 0x00402397 call sym.shellcode2.exe__digestString_MD5__QAEPADPAD_Z
| 0x0040239c mov dword [lpText], eax
| 0x004023a2 push 0x30 ; '0' ; 48 ; UINT uType
| 0x004023a4 push str.We_ve_been_compromised ; 0x403038 ; "We've been compromised!" ; LPCSTR lpCaption
| 0x004023a9 mov eax, dword [lpText]
| 0x004023af push eax ; LPCSTR lpText
| 0x004023b0 push 0 ; HWND hWnd
| 0x004023b2 call dword [sym.imp.USER32.dll_MessageBoxA] ; 0x40301c ; "~1" ; int MessageBoxA(HWND hWnd, LPCSTR lpText, LPCSTR lpCaption, UINT uType)
| 0x004023b8 push 0 ; UINT uExitCode
| 0x004023ba call dword [sym.imp.KERNEL32.dll_ExitProcess] ; 0x403014 ; void ExitProcess(UINT uExitCode)
| 0x004023c0 xor eax, eax
| 0x004023c2 mov esp, ebp
| 0x004023c4 pop ebp
\ 0x004023c5 ret
Right away it looks like we are dealing with quite a few variables. Lets look at the meat and potatoes:
| 0x00402314 push 0x10 ; 16
| 0x00402316 push 0 ; DWORD dwFlags
| 0x00402318 call dword [sym.imp.KERNEL32.dll_GetProcessHeap] ; 0x403010 ; ",1" ; HANDLE GetProcessHeap(void)
| 0x0040231e push eax ; HANDLE hHeap
| 0x0040231f call dword [sym.imp.KERNEL32.dll_HeapAlloc] ; 0x40300c ; " 1" ; LPVOID HeapAlloc(HANDLE hHeap, DWORD dwFlags, SIZE_T dwBytes)
| 0x00402325 mov dword [var_4h], eax
| 0x00402328 mov eax, dword [var_4h]
| 0x0040232b mov ecx, dword sym.imp.KERNEL32.dll_LoadLibraryA ; [0x403008:4]=0x3110 reloc.KERNEL32.dll_LoadLibraryA
| 0x00402331 mov dword [eax], ecx
| 0x00402333 mov edx, dword [var_4h]
| 0x00402336 mov eax, dword sym.imp.KERNEL32.dll_GetProcAddress ; [0x403004:4]=0x30fe reloc.KERNEL32.dll_GetProcAddress
| 0x0040233b mov dword [edx + 4], eax
| 0x0040233e mov ecx, dword [var_4h]
| 0x00402341 lea edx, [var_28h]
| 0x00402344 mov dword [ecx + 8], edx
| 0x00402347 mov eax, dword [var_4h]
| 0x0040234a mov dword [eax + 0xc], 0x24 ; '$' ; [0x24:4]=-1 ; 36
| 0x00402351 push 0x40 ; '@' ; 64 ; DWORD flProtect
| 0x00402353 push 0x1000 ; DWORD flAllocationType
| 0x00402358 push 0x248 ; 584 ; SIZE_T dwSize
| 0x0040235d push 0 ; LPVOID lpAddress
| 0x0040235f call dword [sym.imp.KERNEL32.dll_VirtualAlloc] ; 0x403000 ; LPVOID VirtualAlloc(LPVOID lpAddress, SIZE_T dwSize, DWORD flAllocationType, DWORD flProtect)
| 0x00402365 mov dword [s1], eax
| 0x0040236b push 0x248 ; 584 ; size_t n
| 0x00402370 push 0x404040 ; '@@@' ; "U\x89\xe5\x81\xec\xbc\x01" ; const void *s2
| 0x00402375 mov ecx, dword [s1]
| 0x0040237b push ecx ; void *s1
| 0x0040237c call sub.ntdll.dll_memcpy ; void *memcpy(void *s1, const void *s2, size_t n)
| 0x00402381 add esp, 0xc
| 0x00402384 push dword [var_4h]
| 0x00402387 call dword [s1]
Right away a lot of this looks very familiar to the last challenge. We are grabbing the heap and allocating a piece of it:
| 0x00402314 push 0x10 ; 16
| 0x00402316 push 0 ; DWORD dwFlags
| 0x00402318 call dword [sym.imp.KERNEL32.dll_GetProcessHeap] ; 0x403010 ; ",1" ; HANDLE GetProcessHeap(void)
| 0x0040231e push eax ; HANDLE hHeap
| 0x0040231f call dword [sym.imp.KERNEL32.dll_HeapAlloc] ; 0x40300c ; " 1" ; LPVOID HeapAlloc(HANDLE hHeap, DWORD dwFlags, SIZE_T dwBytes)
| 0x00402325 mov dword [var_4h], eax
| 0x00402328 mov eax, dword [var_4h]
The heap is then taken from the eax register and stored in a dword var_4h and then put back into eax.
| 0x0040232b mov ecx, dword sym.imp.KERNEL32.dll_LoadLibraryA ; [0x403008:4]=0x3110 reloc.KERNEL32.dll_LoadLibraryA
| 0x00402331 mov dword [eax], ecx
| 0x00402333 mov edx, dword [var_4h]
| 0x00402336 mov eax, dword sym.imp.KERNEL32.dll_GetProcAddress ; [0x403004:4]=0x30fe reloc.KERNEL32.dll_GetProcAddress
| 0x0040233b mov dword [edx + 4], eax
| 0x0040233e mov ecx, dword [var_4h]
| 0x00402341 lea edx, [var_28h]
| 0x00402344 mov dword [ecx + 8], edx
| 0x00402347 mov eax, dword [var_4h]
| 0x0040234a mov dword [eax + 0xc], 0x24 ; '$' ; [0x24:4]=-1 ; 36
It then looks like we are storing some functions as dwords and moving them to registers. This looks to be getting our functions setup in our heap.
After all of this we are allocating page space, copying our shellcode over, and then executing it.
| 0x00402351 push 0x40 ; '@' ; 64 ; DWORD flProtect
| 0x00402353 push 0x1000 ; DWORD flAllocationType
| 0x00402358 push 0x248 ; 584 ; SIZE_T dwSize
| 0x0040235d push 0 ; LPVOID lpAddress
| 0x0040235f call dword [sym.imp.KERNEL32.dll_VirtualAlloc] ; 0x403000 ; LPVOID VirtualAlloc(LPVOID lpAddress, SIZE_T dwSize, DWORD flAllocationType, DWORD flProtect)
| 0x00402365 mov dword [s1], eax
| 0x0040236b push 0x248 ; 584 ; size_t n
| 0x00402370 push 0x404040 ; '@@@' ; "U\x89\xe5\x81\xec\xbc\x01" ; const void *s2
| 0x00402375 mov ecx, dword [s1]
| 0x0040237b push ecx ; void *s1
| 0x0040237c call sub.ntdll.dll_memcpy ; void *memcpy(void *s1, const void *s2, size_t n)
| 0x00402381 add esp, 0xc
| 0x00402384 push dword [var_4h]
| 0x00402387 call dword [s1]
It looks like our shellcode is 584 bytes starting at 0x404040. Cutter has a neat feature where you can copy in a byte array and it will parse it as shellcode:
You can also just click on the offset in the original code and view the dissassembly there. Lets look at the disassembly:
0x00404040 push ebp
0x00404041 mov ebp, esp
0x00404043 sub esp, 0x1bc
0x00404049 mov byte [ebp - 0x58], 0x6d ; 'm' ; 109
0x0040404d mov byte [ebp - 0x57], 0x73 ; 's' ; 115
0x00404051 mov byte [ebp - 0x56], 0x76 ; 'v' ; 118
0x00404055 mov byte [ebp - 0x55], 0x63 ; 'c' ; 99
0x00404059 mov byte [ebp - 0x54], 0x72 ; 'r' ; 114
0x0040405d mov byte [ebp - 0x53], 0x74 ; 't' ; 116
0x00404061 mov byte [ebp - 0x52], 0x2e ; '.' ; 46
0x00404065 mov byte [ebp - 0x51], 0x64 ; 'd' ; 100
0x00404069 mov byte [ebp - 0x50], 0x6c ; 'l' ; 108
0x0040406d mov byte [ebp - 0x4f], 0x6c ; 'l' ; 108
0x00404071 mov byte [ebp - 0x4e], 0
0x00404075 mov byte [ebp - 0xa0], 0x6b ; 'k' ; 107
0x0040407c mov byte [ebp - 0x9f], 0x65 ; 'e' ; 101
0x00404083 mov byte [ebp - 0x9e], 0x72 ; 'r' ; 114
0x0040408a mov byte [ebp - 0x9d], 0x6e ; 'n' ; 110
0x00404091 mov byte [ebp - 0x9c], 0x65 ; 'e' ; 101
0x00404098 mov byte [ebp - 0x9b], 0x6c ; 'l' ; 108
0x0040409f mov byte [ebp - 0x9a], 0x33 ; '3' ; 51
0x004040a6 mov byte [ebp - 0x99], 0x32 ; '2' ; 50
0x004040ad mov byte [ebp - 0x98], 0x2e ; '.' ; 46
0x004040b4 mov byte [ebp - 0x97], 0x64 ; 'd' ; 100
0x004040bb mov byte [ebp - 0x96], 0x6c ; 'l' ; 108
0x004040c2 mov byte [ebp - 0x95], 0x6c ; 'l' ; 108
0x004040c9 mov byte [ebp - 0x94], 0
0x004040d0 mov byte [ebp - 0x1b8], 0x66 ; 'f' ; 102
0x004040d7 mov byte [ebp - 0x1b7], 0x6f ; 'o' ; 111
0x004040de mov byte [ebp - 0x1b6], 0x70 ; 'p' ; 112
0x004040e5 mov byte [ebp - 0x1b5], 0x65 ; 'e' ; 101
0x004040ec mov byte [ebp - 0x1b4], 0x6e ; 'n' ; 110
0x004040f3 mov byte [ebp - 0x1b3], 0
0x004040fa mov byte [ebp - 0x4c], 0x66 ; 'f' ; 102
0x004040fe mov byte [ebp - 0x4b], 0x72 ; 'r' ; 114
0x00404102 mov byte [ebp - 0x4a], 0x65 ; 'e' ; 101
0x00404106 mov byte [ebp - 0x49], 0x61 ; 'a' ; 97
0x0040410a mov byte [ebp - 0x48], 0x64 ; 'd' ; 100
0x0040410e mov byte [ebp - 0x47], 0
0x00404112 mov byte [ebp - 0xc], 0x66 ; 'f' ; 102
0x00404116 mov byte [ebp - 0xb], 0x73 ; 's' ; 115
0x0040411a mov byte [ebp - 0xa], 0x65 ; 'e' ; 101
0x0040411e mov byte [ebp - 9], 0x65 ; 'e' ; 101
0x00404122 mov byte [ebp - 8], 0x6b ; 'k' ; 107
0x00404126 mov byte [ebp - 7], 0
0x0040412a mov byte [ebp - 0x60], 0x66 ; 'f' ; 102
0x0040412e mov byte [ebp - 0x5f], 0x63 ; 'c' ; 99
0x00404132 mov byte [ebp - 0x5e], 0x6c ; 'l' ; 108
0x00404136 mov byte [ebp - 0x5d], 0x6f ; 'o' ; 111
0x0040413a mov byte [ebp - 0x5c], 0x73 ; 's' ; 115
0x0040413e mov byte [ebp - 0x5b], 0x65 ; 'e' ; 101
0x00404142 mov byte [ebp - 0x5a], 0
0x00404146 mov byte [ebp - 0x78], 0x47 ; 'G' ; 71
0x0040414a mov byte [ebp - 0x77], 0x65 ; 'e' ; 101
0x0040414e mov byte [ebp - 0x76], 0x74 ; 't' ; 116
0x00404152 mov byte [ebp - 0x75], 0x4d ; 'M' ; 77
0x00404156 mov byte [ebp - 0x74], 0x6f ; 'o' ; 111
0x0040415a mov byte [ebp - 0x73], 0x64 ; 'd' ; 100
0x0040415e mov byte [ebp - 0x72], 0x75 ; 'u' ; 117
0x00404162 mov byte [ebp - 0x71], 0x6c ; 'l' ; 108
0x00404166 mov byte [ebp - 0x70], 0x65 ; 'e' ; 101
0x0040416a mov byte [ebp - 0x6f], 0x46 ; 'F' ; 70
0x0040416e mov byte [ebp - 0x6e], 0x69 ; 'i' ; 105
0x00404172 mov byte [ebp - 0x6d], 0x6c ; 'l' ; 108
0x00404176 mov byte [ebp - 0x6c], 0x65 ; 'e' ; 101
0x0040417a mov byte [ebp - 0x6b], 0x4e ; 'N' ; 78
0x0040417e mov byte [ebp - 0x6a], 0x61 ; 'a' ; 97
0x00404182 mov byte [ebp - 0x69], 0x6d ; 'm' ; 109
0x00404186 mov byte [ebp - 0x68], 0x65 ; 'e' ; 101
0x0040418a mov byte [ebp - 0x67], 0x41 ; 'A' ; 65
0x0040418e mov byte [ebp - 0x66], 0
0x00404192 mov byte [ebp - 0x7c], 0x72 ; 'r' ; 114
0x00404196 mov byte [ebp - 0x7b], 0x62 ; 'b' ; 98
0x0040419a mov byte [ebp - 0x7a], 0
0x0040419e mov eax, dword [ebp + 8] ; [0x8:4]=-1 ; 8
0x004041a1 mov ecx, dword [eax]
0x004041a3 mov dword [ebp - 4], ecx
0x004041a6 mov ecx, dword [eax + 4] ; [0x4:4]=-1 ; 4
0x004041a9 mov dword [ebp - 0x44], ecx
0x004041ac lea ecx, [ebp - 0x58]
0x004041af push ecx
0x004041b0 call dword [ebp - 4]
0x004041b3 mov dword [ebp - 0x3c], eax
0x004041b6 lea edx, [ebp - 0xa0]
0x004041bc push edx
0x004041bd call dword [ebp - 4]
0x004041c0 mov dword [ebp - 0x84], eax
0x004041c6 lea eax, [ebp - 0x78]
0x004041c9 push eax
0x004041ca mov ecx, dword [ebp - 0x84]
0x004041d0 push ecx
0x004041d1 call dword [ebp - 0x44]
0x004041d4 mov dword [ebp - 0x10], eax
0x004041d7 lea edx, [ebp - 0x1b8]
0x004041dd push edx
0x004041de mov eax, dword [ebp - 0x3c]
0x004041e1 push eax
0x004041e2 call dword [ebp - 0x44]
0x004041e5 mov dword [ebp - 0x80], eax
0x004041e8 lea ecx, [ebp - 0xc]
0x004041eb push ecx
0x004041ec mov edx, dword [ebp - 0x3c]
0x004041ef push edx
0x004041f0 call dword [ebp - 0x44]
0x004041f3 mov dword [ebp - 0xa4], eax
0x004041f9 lea eax, [ebp - 0x4c]
0x004041fc push eax
0x004041fd mov ecx, dword [ebp - 0x3c]
0x00404200 push ecx
0x00404201 call dword [ebp - 0x44]
0x00404204 mov dword [ebp - 0x90], eax
0x0040420a lea edx, [ebp - 0x60]
0x0040420d push edx
0x0040420e mov eax, dword [ebp - 0x3c]
0x00404211 push eax
0x00404212 call dword [ebp - 0x44]
0x00404215 mov dword [ebp - 0x64], eax
0x00404218 push 0x104 ; 260
0x0040421d lea ecx, [ebp - 0x1b0]
0x00404223 push ecx
0x00404224 push 0
0x00404226 call dword [ebp - 0x10]
0x00404229 lea edx, [ebp - 0x7c]
0x0040422c push edx
0x0040422d lea eax, [ebp - 0x1b0]
0x00404233 push eax
0x00404234 call dword [ebp - 0x80]
0x00404237 add esp, 8
0x0040423a mov dword [ebp - 0x40], eax
0x0040423d push 0
0x0040423f push 0x4e ; 'N' ; 78
0x00404241 mov ecx, dword [ebp - 0x40]
0x00404244 push ecx
0x00404245 call dword [ebp - 0xa4]
0x0040424b add esp, 0xc
0x0040424e mov edx, dword [ebp - 0x40]
0x00404251 push edx
0x00404252 push 1 ; 1
0x00404254 push 0x26 ; '&' ; 38
0x00404256 lea eax, [ebp - 0x38]
0x00404259 push eax
0x0040425a call dword [ebp - 0x90]
0x00404260 add esp, 0x10
0x00404263 mov ecx, dword [ebp - 0x40]
0x00404266 push ecx
0x00404267 call dword [ebp - 0x64]
0x0040426a add esp, 4
0x0040426d mov edx, dword [ebp + 8] ; [0x8:4]=-1 ; 8
0x00404270 mov ecx, dword [edx + 0xc] ; [0xc:4]=-1 ; 12
0x00404273 mov edi, dword [edx + 8] ; [0x8:4]=-1 ; 8
0x00404274 .string "z\b1\xd2\x8aD" ; len=7 ;-- "z\b1ҊD":
: 0x0040427b enter 0x430, 0x17 ; 1072
: 0x0040427f inc edx
: 0x00404280 cmp edx, ecx
`=< 0x00404282 jne 0x404278
0x00404284 mov esp, ebp
0x00404286 pop ebp
0x00404287 ret
The first thing we see is a large stack string being setup: Lets copy 0x00404049 to 0x00404196 to a file and run some commandline-fu on it to make it a bit easier on the eyes:
cat stack_string.txt | cut -d';' -f2 | sed -e 's/ //g' -e 's/\'//g' -e 's/^ //g' -e 's/^0x00404.*/ /g' | tr '\n' '\0'
msvcrt.dll kernel32.dll fopen fread fseek fclose GetModuleFileNameA rb
Much better. It looks like our stack string contains 2 dll names (msvcrt.dll, kernel32.dll) some basic c++ file commands (fopen, fread, fseek, fclose) and a function (GetModuleFileNameA). The last 2 characters “rb” are most likely parameters we are gonna pass to fopen which will tell it readonly and present the file as a binary object. I think its safe to assume we are going to be opening a file, grabbing some bytes, and creating our flag from that. Lets pull up the docs. Besides the functions and dlls above lets also get the docs for the two functions we saved as dwords earlier.
Retrieves the fully qualified path for the file that contains the specified module. The module must have been loaded by the current process.
DWORD GetModuleFileNameA(
HMODULE hModule,
LPSTR lpFilename,
DWORD nSize
);
Loads the specified module into the address space of the calling process. The specified module may cause other modules to be loaded.
HMODULE LoadLibraryA(
LPCSTR lpLibFileName
);
Retrieves the address of an exported function or variable from the specified dynamic-link library (DLL).
FARPROC GetProcAddress(
HMODULE hModule,
LPCSTR lpProcName
);
MSVCRT.DLL is the C standard library for the Visual C++ (MSVC) compiler from version 4.2 to 6.0. It provides programs compiled by these versions of MSVC with most of the standard C library functions. These include string manipulation, memory allocation, C-style input/output calls, and others.
KERNEL32.DLL exposes to applications most of the Win32 base APIs, such as memory management, input/output (I/O) operations, process and thread creation, and synchronization functions. Many of these are implemented within KERNEL32.DLL by calling corresponding functions in the native API, exposed by NTDLL.DLL.
Opens a file indicated by filename and returns a file stream associated with that file. mode is used to determine the file access mode.
std::FILE* fopen( const char* filename, const char* mode );
Reads up to count objects into the array buffer from the given input stream stream as if by calling std::fgetc size times for each object, and storing the results, in the order obtained, into the successive positions of buffer, which is reinterpreted as an array of unsigned char. The file position indicator for the stream is advanced by the number of characters read.
std::size_t fread( void* buffer, std::size_t size, std::size_t count, std::FILE* stream );
Sets the file position indicator for the file stream stream. If the stream is open in binary mode, the new position is exactly offset bytes measured from the beginning of the file if origin is SEEK_SET, from the current file position if origin is SEEK_CUR, and from the end of the file if origin is SEEK_END. Binary streams are not required to support SEEK_END, in particular if additional null bytes are output.
int fseek( std::FILE* stream, long offset, int origin );
Closes the given file stream. Any unwritten buffered data are flushed to the OS. Any unread buffered data are discarded.
int fclose( std::FILE* stream );
Lets list these out with their pointers for easy reference later:
ebp 0x58 msvcrt.dll
ebp 0xa0 kernel32.dll
ebp 0x1b8 fopen
ebp 0x4c fread
ebp 0xc fseek
ebp 0x60 fclose
ebp 0x78 GetModuleFileNameA
ebp 0x7c rb
Alright, so we have a pretty good guess as to what this shellcode is going to do. Lets examine the rest of it and see if we are right:
0x0040419e mov eax, dword [ebp + 8] ; [0x8:4]=-1 ; 8
0x004041a1 mov ecx, dword [eax]
0x004041a3 mov dword [ebp - 4], ecx
0x004041a6 mov ecx, dword [eax + 4] ; [0x4:4]=-1 ; 4
0x004041a9 mov dword [ebp - 0x44], ecx
0x004041ac lea ecx, [ebp - 0x58]
0x004041af push ecx
0x004041b0 call dword [ebp - 4]
0x004041b3 mov dword [ebp - 0x3c], eax
We are calling LoadLibraryA after pushing msvcrt.dll to the stck. We are then saving the return to ebp 0x3c. Lets update our reference:
ebp 0x58 msvcrt.dll
ebp 0xa0 kernel32.dll
ebp 0x1b8 fopen
ebp 0x4c fread
ebp 0xc fseek
ebp 0x60 fclose
ebp 0x78 GetModuleFileNameA
ebp 0x7c rb
dword [ebp - 4] LoadLibraryA
dword [ebp - 0x44] GetProcAddress
dword [ebp - 0x3c] msvcrt.dll module handle
0x004041b6 lea edx, [ebp - 0xa0]
0x004041bc push edx
0x004041bd call dword [ebp - 4]
0x004041c0 mov dword [ebp - 0x84], eax
Next we call LoadLibraryA after pushing kernel32.dll to the stack. We are then saving the return to ebp 0x84. Lets update our reference and move on.
ebp 0x58 msvcrt.dll
ebp 0xa0 kernel32.dll
ebp 0x1b8 fopen
ebp 0x4c fread
ebp 0xc fseek
ebp 0x60 fclose
ebp 0x78 GetModuleFileNameA
ebp 0x7c rb
dword [ebp - 4] LoadLibraryA
dword [ebp - 0x44] GetProcAddress
dword [ebp - 0x3c] msvcrt.dll module handle
dword [ebp - 0x84] kernel32.dll module handle
0x004041c6 lea eax, [ebp - 0x78]
0x004041c9 push eax
0x004041ca mov ecx, dword [ebp - 0x84]
0x004041d0 push ecx
0x004041d1 call dword [ebp - 0x44]
0x004041d4 mov dword [ebp - 0x10], eax
Next we are pushing GetModuleFileNameA, and then kernel32.dll handle to the stack. We are then calling GetProcAddress(kernel32.dll, GetModuleFileNameA). This returns the address of GetModuleFileNameA and we are saving that in ebp 0x10.
ebp 0x58 msvcrt.dll
ebp 0xa0 kernel32.dll
ebp 0x1b8 fopen
ebp 0x4c fread
ebp 0xc fseek
ebp 0x60 fclose
ebp 0x78 GetModuleFileNameA
ebp 0x7c rb
dword [ebp - 4] LoadLibraryA
dword [ebp - 0x44] GetProcAddress
dword [ebp - 0x3c] msvcrt.dll module handle
dword [ebp - 0x84] kernel32.dll module handle
dword [ebp - 0x10] GetModuleFileNameA address
0x004041d7 lea edx, [ebp - 0x1b8]
0x004041dd push edx
0x004041de mov eax, dword [ebp - 0x3c]
0x004041e1 push eax
0x004041e2 call dword [ebp - 0x44]
0x004041e5 mov dword [ebp - 0x80], eax
Next up it looks like we are pushing fopen, and msvcrt.dll handle to the stack. Then we are calling GetProcAddress on them. The return address for fopen is saved as ebp 0x80.
ebp 0x58 msvcrt.dll
ebp 0xa0 kernel32.dll
ebp 0x1b8 fopen
ebp 0x4c fread
ebp 0xc fseek
ebp 0x60 fclose
ebp 0x78 GetModuleFileNameA
ebp 0x7c rb
dword [ebp - 4] LoadLibraryA
dword [ebp - 0x44] GetProcAddress
dword [ebp - 0x3c] msvcrt.dll module handle
dword [ebp - 0x84] kernel32.dll module handle
dword [ebp - 0x10] GetModuleFileNameA address
dword [ebp - 0x80] fopen address
0x004041e8 lea ecx, [ebp - 0xc]
0x004041eb push ecx
0x004041ec mov edx, dword [ebp - 0x3c]
0x004041ef push edx
0x004041f0 call dword [ebp - 0x44]
0x004041f3 mov dword [ebp - 0xa4], eax
Here we are getting the address to fseek as ebp 0xa4.
0x004041f9 lea eax, [ebp - 0x4c]
0x004041fc push eax
0x004041fd mov ecx, dword [ebp - 0x3c]
0x00404200 push ecx
0x00404201 call dword [ebp - 0x44]
0x00404204 mov dword [ebp - 0x90], eax
Here we are getting the address to fread as ebp 0x90.
0x0040420a lea edx, [ebp - 0x60]
0x0040420d push edx
0x0040420e mov eax, dword [ebp - 0x3c]
0x00404211 push eax
0x00404212 call dword [ebp - 0x44]
0x00404215 mov dword [ebp - 0x64], eax
Here we are getting the address to fclose as ebp 0x64. Lets update these and go on to the next call.
ebp 0x58 msvcrt.dll
ebp 0xa0 kernel32.dll
ebp 0x1b8 fopen
ebp 0x4c fread
ebp 0xc fseek
ebp 0x60 fclose
ebp 0x78 GetModuleFileNameA
ebp 0x7c rb
dword [ebp - 4] LoadLibraryA
dword [ebp - 0x44] GetProcAddress
dword [ebp - 0x3c] msvcrt.dll module handle
dword [ebp - 0x84] kernel32.dll module handle
dword [ebp - 0x10] GetModuleFileNameA address
dword [ebp - 0x80] fopen address
dword [ebp - 0xa4] fseek address
dword [ebp - 0x90] fread address
dword [ebp - 0x64] fclose address
0x00404218 push 0x104 ; 260
0x0040421d lea ecx, [ebp - 0x1b0]
0x00404223 push ecx
0x00404224 push 0
0x00404226 call dword [ebp - 0x10]
Here we are calling GetModuleFileNameA(0, ebp 0x1b0, 260)
DWORD GetModuleFileNameA(
HMODULE hModule,
LPSTR lpFilename,
DWORD nSize
);
We are sending an hModule of 0:
A handle to the loaded module whose path is being requested. If this parameter is NULL, GetModuleFileName retrieves the path of the executable file of the current process.
lpFilename is listed in the docs as follows
A pointer to a buffer that receives the fully qualified path of the module. If the length of the path is less than the size that the nSize parameter specifies, the function succeeds and the path is returned as a null-terminated string.
So ebp 0x1b0 is the fully qualified path to the running process.
ebp 0x58 msvcrt.dll
ebp 0xa0 kernel32.dll
ebp 0x1b8 fopen
ebp 0x4c fread
ebp 0xc fseek
ebp 0x60 fclose
ebp 0x78 GetModuleFileNameA
ebp 0x7c rb
dword [ebp - 4] LoadLibraryA
dword [ebp - 0x44] GetProcAddress
dword [ebp - 0x3c] msvcrt.dll module handle
dword [ebp - 0x84] kernel32.dll module handle
dword [ebp - 0x10] GetModuleFileNameA address
dword [ebp - 0x80] fopen address
dword [ebp - 0xa4] fseek address
dword [ebp - 0x90] fread address
dword [ebp - 0x64] fclose address
dword [ebp - 0x1b0] current process path
0x00404229 lea edx, [ebp - 0x7c]
0x0040422c push edx
0x0040422d lea eax, [ebp - 0x1b0]
0x00404233 push eax
0x00404234 call dword [ebp - 0x80]
0x00404237 add esp, 8
0x0040423a mov dword [ebp - 0x40], eax
0x0040423d push 0
0x0040423f push 0x4e ; 'N' ; 78
0x00404241 mov ecx, dword [ebp - 0x40]
0x00404244 push ecx
0x00404245 call dword [ebp - 0xa4]
0x0040424b add esp, 0xc
0x0040424e mov edx, dword [ebp - 0x40]
0x00404251 push edx
0x00404252 push 1 ; 1
0x00404254 push 0x26 ; '&' ; 38
0x00404256 lea eax, [ebp - 0x38]
0x00404259 push eax
0x0040425a call dword [ebp - 0x90]
0x00404260 add esp, 0x10
0x00404263 mov ecx, dword [ebp - 0x40]
0x00404266 push ecx
0x00404267 call dword [ebp - 0x64]
Now we are opening the current process to read only, as a binary file. We are then calling fseek(filestream,0x4e,0). Looking at the docs this means we have moved the pointer in the file to offset 0x4e. Next we are calling fread(ebp 0x38, 0x26, 1, filestream). The docs let us know that we are reading out 1 object that is 38 bytes. After this we are closing the filestream.
Lets open the binary up in a hex editor and grab 38 bytes starting at 0x4e:
00000040 0e 1f ba 0e 00 b4 09 cd 21 b8 01 4c cd 21 54 68 |........!..L.!Th|
00000050 69 73 20 70 72 6f 67 72 61 6d 20 63 61 6e 6e 6f |is program canno|
00000060 74 20 62 65 20 72 75 6e 20 69 6e 20 44 4f 53 20 |t be run in DOS |
00000070 6d 6f 64 65 2e 0d 0d 0a 24 00 00 00 00 00 00 00 |mode....$.......|
We get a string back from the header. “This program cannot be run in DOS mode.”
0x0040426a add esp, 4
0x0040426d mov edx, dword [ebp + 8] ; [0x8:4]=-1 ; 8
0x00404270 mov ecx, dword [edx + 0xc] ; [0xc:4]=-1 ; 12
0x00404273 mov edi, dword [edx + 8] ; [0x8:4]=-1 ; 8
0x00404274 .string "z\b1\xd2\x8aD" ; len=7 ;-- "z\b1ҊD":
: 0x0040427b enter 0x430, 0x17 ; 1072
: 0x0040427f inc edx
: 0x00404280 cmp edx, ecx
`=< 0x00404282 jne 0x404278
0x00404284 mov esp, ebp
0x00404286 pop ebp
0x00404287 ret
It looks like we are moving our string to edx and the first 36 bytes to ecx. Something looks broken in this disassembly view, im not sure if I failed when importing it, or if radare2 just wasn’t able to handle it. Lets open Ghidra to take a look at the last few lines:
0040426d 8b 55 08 MOV EDX,dword ptr [EBP + 0x8]
00404270 8b 4a 0c MOV ECX,dword ptr [EDX + 0xc]
00404273 8b 7a 08 MOV EDI,dword ptr [EDX + 0x8]
00404276 31 d2 XOR EDX,EDX
That is more of what I would expect. We are taking the header we read and XORing it with something. There was a stack string at the begining of the binary that I completely ignored. Lets grab it and see if thats what we need.
| 0x00402284 mov byte [var_28h], 0x12 ; 18
| 0x00402288 mov byte [var_27h], 0x24 ; '$' ; 36
| 0x0040228c mov byte [var_26h], 0x28 ; '(' ; 40
| 0x00402290 mov byte [var_25h], 0x34 ; '4' ; 52
| 0x00402294 mov byte [var_24h], 0x5b ; '[' ; 91
| 0x00402298 mov byte [var_23h], 0x23 ; '#' ; 35
| 0x0040229c mov byte [var_22h], 0x26 ; '&' ; 38
| 0x004022a0 mov byte [var_21h], 0x20 ; 32
| 0x004022a4 mov byte [var_20h], 0x35 ; '5' ; 53
| 0x004022a8 mov byte [var_1fh], 0x37 ; '7' ; 55
| 0x004022ac mov byte [var_1eh], 0x4c ; 'L' ; 76
| 0x004022b0 mov byte [var_1dh], 0x28 ; '(' ; 40
| 0x004022b4 mov byte [var_1ch], 0x76 ; 'v' ; 118
| 0x004022b8 mov byte [var_1bh], 0x26 ; '&' ; 38
| 0x004022bc mov byte [var_1ah], 0x33 ; '3' ; 51
| 0x004022c0 mov byte [var_19h], 0x37 ; '7' ; 55
| 0x004022c4 mov byte [var_18h], 0x3a ; ':' ; 58
| 0x004022c8 mov byte [var_17h], 0x27 ; ''' ; 39
| 0x004022cc mov byte [var_16h], 0x3d ; '=' ; 61
| 0x004022d0 mov byte [var_15h], 0x6e ; 'n' ; 110
| 0x004022d4 mov byte [var_14h], 0x25 ; '%' ; 37
| 0x004022d8 mov byte [var_13h], 0x48 ; 'H' ; 72
| 0x004022dc mov byte [var_12h], 0x6f ; 'o' ; 111
| 0x004022e0 mov byte [var_11h], 0x3c ; '<' ; 60
| 0x004022e4 mov byte [var_10h], 0x58 ; 'X' ; 88
| 0x004022e8 mov byte [var_fh], 0x3a ; ':' ; 58
| 0x004022ec mov byte [var_eh], 0x68 ; 'h' ; 104
| 0x004022f0 mov byte [var_dh], 0x2c ; ',' ; 44
| 0x004022f4 mov byte [var_ch], 0x43 ; 'C' ; 67
| 0x004022f8 mov byte [var_bh], 0x73 ; 's' ; 115
| 0x004022fc mov byte [var_ah], 0x10 ; 16
| 0x00402300 mov byte [var_9h], 0xe ; 14
| 0x00402304 mov byte [var_8h], 0x10 ; 16
| 0x00402308 mov byte [var_7h], 0x6b ; 'k' ; 107
| 0x0040230c mov byte [var_6h], 0x10 ; 16
| 0x00402310 mov byte [var_5h], 0x6f ; 'o' ; 111
Now that we have this string lets grab the first 36 out of the header string We saw that ecx was being set to the first 36 bytes of the header string, and our xor key is 36 bytes so that should work out. Lets see if we can get our flag with Python:
In [1]: header = [0x54, 0x68, 0x69, 0x73, 0x20, 0x70,
...: 0x72, 0x6f, 0x67, 0x72, 0x61, 0x6d,
...: 0x20, 0x63, 0x61, 0x6e, 0x6e, 0x6f,
...: 0x74, 0x20, 0x62, 0x65, 0x20, 0x72,
...: 0x75, 0x6e, 0x20, 0x69, 0x6e, 0x20,
...: 0x44, 0x4f, 0x53, 0x20, 0x6d, 0x6f]
In [2]: xorkey = [0x12, 0x24, 0x28, 0x34, 0x5b, 0x23,
...: 0x26, 0x20, 0x35, 0x37, 0x4c, 0x28,
...: 0x76, 0x26, 0x33, 0x37, 0x3a, 0x27,
...: 0x3d, 0x6e, 0x25, 0x48, 0x6f, 0x3c,
...: 0x58, 0x3a, 0x68, 0x2c, 0x43, 0x73,
...: 0x10, 0x0e, 0x10, 0x6b, 0x10, 0x6f]
In [3]: flag = ''
In [18]: for i in range(36):
...: flag = flag + chr(header[i] ^ xorkey[i])
...:
In [19]: print(flag)
FLAG{STORE-EVERYTHING-ON-THE-STACK}
Flag 5 found. There are a few more challenges from MalwareTech, if I end up doing them I will be sure to post about it here. Other than that there are a few other CTF style reversing challenges I may look into. I also think its time to fire up the lab and document some actual malware reversing.