Flare On 2014 - Challenge 3 Writeup
Prepare to get nop'ed!!
The file has no file extension though there are plenty of ways to check what kind of file it is.
One would be using file
on Linux:
0nop•~/Desktop» file such_evil
1such_evil: PE32 executable (console) Intel 80386 (stripped to external PDB), for MS Windows
Alternatively, you could look at the hex-representation and check for magic bytes or signatures:
Or you use a tool like Detect It Easy
:
All those methods showed that it's a stripped Windows binary, so let's add the .exe
file extension.
To get started I dropped the binary into IDA Free and investigated the ASM code. Especially the function sub_401000
looked like a mess (and because of that also interesting).
There weren't any jumps or branches but just one big node with a lot of local variables and operations manipulating the EAX register.
At the very bottom there were some instructions besides the:
mov eax, VALUE
mov [ebp+OFFSET], al
0mov [ebp+var_1], al
1lea eax, [ebp+var_201]
2call eax
3mov eax, 0
4jmp $+5
Everything looks like some shellcode gets loaded into memory and then executed.
To dump the shellcode a breakpoint was set at the call eax
instruction and the memory was saved to a file:
bp 000000000040249B ; g
.writemem C:\Users\Admin\Desktop\dump.mem eax L4000
The dump.mem
file then was loaded into IDA; in order to convert it to Assembly code, the Code
function was used:
Stage 1:
The static analysis showed that a loop takes place XORing a value with the constant 66h
.
0 mov esi, [esp]
1 add esi, 1Ch
2 mov ecx, 1DFh
3loc_10:
4 cmp ecx, 0
5 jz short loc_1C
6 xor byte ptr [esi], 66h
7 inc esi
8 dec ecx
9 jmp short loc_10
We can assume that the decryption process starts at seg000:00000021
because ESI contains the value of EIP in the beginning and then the value 1C
gets added to it:
Python decrypt script:
0#!/usr/bin/python3
1
2cipher = [7, 8, 2, 0x46, 0x15, 9, 0x46, 0xF, 0x12, 0x46, 4, 3, 1, 0xF, 8, 0x15]
3
4for x in cipher:
5 print(chr(x ^ 0x66), end='')
6print('')
Plain text:
and so it begins
After that, it became messier so I took a step back and started over with a different tool-set.
Just like before, I used the memory dump that was created using WinDbg; afterwards I used a simple Python script to print out each character as the hexadecimal representation:
0with open('such_evil.mem', 'rb') as f:
1 content = f.read()
2 for x in content:
3 print(f"0x{x:02x}", end=' ')
Next, I took a look at IDA and searched for the last few bytes of the shellcode:
I went back to the output of the Python script and truncated everything after the byte sequence 0x1C 0x95 0xC9 0x00
which left be with the following shellcode:
00xe8 0x00 0x00 0x00 0x00 0x8b 0x34 0x24 0x83 0xc6 0x1c 0xb9 0xdf 0x01
10x00 0x00 0x83 0xf9 0x00 0x74 0x07 0x80 0x36 0x66 0x46 0x49 0xeb 0xf4
20xe9 0x10 0x00 0x00 0x00 0x07 0x08 0x02 0x46 0x15 0x09 0x46 0x0f 0x12
30x46 0x04 0x03 0x01 0x0f 0x08 0x15 0x0e 0x13 0x15 0x66 0x66 0x0e 0x15
40x07 0x13 0x14 0x0e 0x08 0x09 0x16 0x07 0xef 0x85 0x8e 0x66 0x66 0x66
50x66 0xed 0x52 0x42 0xe5 0xa0 0x4b 0xef 0x97 0xe7 0xa7 0xea 0x67 0x66
60x66 0xef 0xbe 0xe5 0xa6 0x6c 0x5f 0xbe 0x13 0x63 0xef 0x85 0xe5 0xa5
70x62 0x5f 0xa8 0x12 0x6e 0xec 0x75 0x56 0x70 0x25 0x20 0x8d 0x8d 0x8f
80x57 0x66 0x66 0x66 0x6f 0x6c 0x62 0x27 0x67 0x62 0x72 0x70 0x6a 0x35
90x7c 0x66 0x36 0x60 0x70 0x73 0x33 0x7a 0x7c 0x65 0x2f 0x6c 0x72 0x27
100x66 0x68 0x33 0x70 0x72 0x78 0x66 0x29 0x7e 0x66 0x67 0x63 0x33 0x7d
110x7d 0x35 0x7c 0x61 0x73 0x27 0x65 0x66 0x7a 0x7a 0x67 0xfd 0x08 0x09
120x16 0x07 0x9e 0x33 0x37 0x97 0xd5 0x0b 0xb1 0x31 0x17 0x07 0x15 0x84
130xea 0x14 0x6d 0x1b 0x89 0x3f 0x74 0x48 0x79 0x40 0x90 0xd2 0x17 0x96
140xe1 0x0d 0xfd 0xea 0xfa 0xc8 0x7f 0x53 0x71 0x5a 0xe9 0xce 0x74 0x48
150x79 0x40 0xe1 0xcb 0xef 0xc2 0x02 0x34 0x45 0x61 0x48 0x20 0x5f 0x3c
160x07 0x3f 0x0c 0x23 0x1b 0x3b 0x0d 0x28 0x05 0x7b 0x1e 0x3e 0x02 0x2f
170x09 0x60 0x1e 0x20 0x10 0x3e 0x16 0x7a 0xed 0xad 0x9c 0x48 0x79 0x40
180x71 0xd0 0x4b 0x76 0xe9 0x80 0x57 0xc9 0x86 0xc9 0xbe 0x85 0x71 0x5a
190x64 0xc7 0xac 0xcb 0xb9 0x58 0x48 0x83 0x0a 0x57 0xe3 0xa5 0xf9 0x83
200x73 0x71 0xb1 0x27 0x79 0xd0 0x77 0x7e 0x62 0x0b 0x3f 0xab 0x9a 0xb2
210x62 0x52 0x6a 0x46 0x66 0x58 0x73 0x00 0x38 0x15 0x39 0x00 0x21 0x5f
220x25 0x15 0x24 0x1e 0x32 0x1e 0x1f 0x5b 0x70 0x42 0x7a 0x1a 0x7b 0x18
230x7e 0x10 0x75 0x15 0x60 0x55 0x3a 0x55 0x0d 0x60 0x78 0x17 0x61 0x4d
240x7c 0x5a 0x7a 0x46 0x26 0x40 0x65 0x0d 0x31 0x0b 0x6f 0x4b 0x72 0x09
250x71 0x52 0xd8 0xd1 0xe3 0x72 0x0b 0x2a 0x17 0xa4 0x30 0x18 0xdc 0xfa
260x2f 0xb6 0xe7 0xf0 0x94 0x06 0x16 0x2d 0x16 0xf2 0xce 0xa2 0x8a 0x3d
270x37 0xb8 0x63 0x21 0x9b 0xdf 0x81 0xed 0x40 0x18 0xcc 0x59 0x03 0xf5
280x43 0x54 0x06 0x7c 0x4b 0x8d 0xf8 0x63 0xe4 0xf2 0x5a 0x76 0xfa 0x4a
290xe6 0x53 0x62 0x90 0x66 0x13 0xff 0x0c 0x60 0x88 0x4d 0x38 0xff 0x5e
300xf1 0x77 0x7b 0x7d 0x40 0xe1 0xf0 0x8e 0x7b 0x7c 0x5b 0xd4 0x30 0x39
310x2a 0x9e 0xf6 0x38 0x49 0x1f 0xf0 0x28 0x99 0x95 0x4b 0xf2 0x61 0xdb
320x62 0xd0 0x56 0x48 0x05 0x22 0x12 0x29 0x8a 0xd2 0x45 0x49 0x20 0x75
330x0d 0x3f 0x48 0xac 0xf3 0x29 0x52 0x07 0xa3 0x34 0xbb 0x7f 0x05 0x98
340x10 0x58 0x72 0xc8 0xe6 0x67 0x9d 0xe0 0x75 0x88 0x1b 0x66 0x55 0x73
350x76 0x24 0x1c 0x7f 0x19 0x0d 0x46 0x2f 0x25 0x35 0x14 0x8d 0x80 0xb2
360x2e 0x4b 0x01 0x80 0x32 0x1c 0x95 0xc9 0x00
Luckily cutter
- the GUI of radare2 - allows you to import and analyze shellcode:
In the Load Options
it is important to manually set the bits to 32
as by default it will dissassemble using 64
:
Using cutter we get a nice little graph view of the shellcode:
Let's take a look at the decryption process once again. ECX is used as a counter, counting downwards from 0x1DF
. After the for-loop ends, execution will jump to offset 0x31
.
I decrypted the whole shellcode using cyberchef and dropped the first 49 (0x31) bytes to obtain the second shellcode:
068 75 73 00 00 68 73 61 75 72 68 6e 6f 70 61 89 e3 e8 00 00 00 00 8b
134 24 83 c6 2d 89 f1 81 c1 8c 01 00 00 89 d8 83 c0 0a 39 d8 75 05 89
2e3 83 c3 04 39 ce 74 08 8a 13 30 16 43 46 eb eb e9 31 00 00 00 09 0a
304 41 01 04 14 16 0c 53 1a 00 50 06 16 15 55 1c 1a 03 49 0a 14 41 00
40e 55 16 14 1e 00 4f 18 00 01 05 55 1b 1b 53 1a 07 15 41 03 00 1c 1c
501 9b 6e 6f 70 61 f8 55 51 f1 b3 6d d7 57 71 61 73 e2 8c 72 0b 7d ef
659 12 2e 1f 26 f6 b4 71 f0 87 6b 9b 8c 9c ae 19 35 17 3c 8f a8 12 2e
71f 26 87 ad 89 a4 64 52 23 07 2e 46 39 5a 61 59 6a 45 7d 5d 6b 4e 63
81d 78 58 64 49 6f 06 78 46 76 58 70 1c 8b cb fa 2e 1f 26 17 b6 2d 10
98f e6 31 af e0 af d8 e3 17 3c 02 a1 ca ad df 3e 2e e5 6c 31 85 c3 9f
10e5 15 17 d7 41 1f b6 11 18 04 6d 59 cd fc d4 04 34 0c 20 00 3e 15 66
115e 73 5f 66 47 39 43 73 42 78 54 78 79 3d 16 24 1c 7c 1d 7e 18 76 13
1273 06 33 5c 33 6b 06 1e 71 07 2b 1a 3c 1c 20 40 26 03 6b 57 6d 09 2d
1314 6f 17 34 be b7 85 14 6d 4c 71 c2 56 7e ba 9c 49 d0 81 96 f2 60 70
144b 70 94 a8 c4 ec 5b 51 de 05 47 fd b9 e7 8b 26 7e aa 3f 65 93 25 32
1560 1a 2d eb 9e 05 82 94 3c 10 9c 2c 80 35 04 f6 00 75 99 6a 06 ee 2b
165e 99 38 97 11 1d 1b 26 87 96 e8 1d 1a 3d b2 56 5f 4c f8 90 5e 2f 79
1796 4e ff f3 2d 94 07 bd 04 b6 30 2e 63 44 74 4f ec b4 23 2f 46 13 6b
1859 2e ca 95 4f 34 61 c5 52 dd 19 63 fe 76 3e 14 ae 80 01 fb 86 13 ee
197d 00 33 15 10 42 7a 19 7f 6b 20 49 43 53 72 eb e6 d4 48 2d 67 e6 54
207a f3 af 66
Stage 2:
Once again I loaded it into cutter; First, a string (nopasaurus
) was pushed onto the stack, then a loop was entered XORing everything after that decryption routing with the string.
The execution was moved to 0x74 afterwards, so I went back to cyberchef and decrypted the shellcode accordingly:
67
was used for the upper limit of the drop bytes recipe as 0x74 - 0x31 = 0n67
.
Obtained string from the output: get ready to get nop'ed so damn hard in the paint
Just like before, the shellcoded was truncated to get rid of all the bytes before the call instruction:
0e8 00 00 00 00 8b 34 24 83 c6 1e b9 38 01 00 00 83 f9 00 7e 0e 81 36
162 4f 6c 47 83 c6 04 83 e9 04 eb ed ef cf 6c 47 62 4f e1 c7 62 4f 6c
247 f2 df fc d7 0a 3d 53 66 5d 27 4c 28 14 2a 04 2a 0d 3c 18 2f 16 6f
30d 2b 0a 26 1f 67 0b 27 03 2a 05 6f e5 a4 8a 4f 6c 47 62 c4 58 63 e1
489 41 ce 93 ce ad 91 62 4f 6c ce ba cc ac 5f 5b 97 19 42 eb ac ef 84
566 76 a2 33 6a c5 7f 77 74 0c 2a ac 89 a6 71 47 62 4f 70 5f 66 07 2b
601 2a 15 29 56 33 12 31 19 21 0a 0c 4e 78 4b 6c 1d 6e 1f 6d 04 66 00
768 5c 2c 52 18 67 6b 03 72 58 74 53 6c 41 33 47 76 19 22 1e 67 42 64
80e 64 55 cb c5 f0 67 03 23 01 a3 25 1f cf ee 3c a3 ef f9 82 01 03 2a
905 e6 dd b7 82 34 21 bf 76 26 88 cb 92 f8 48 11 da 5e 16 f2 50 40 15
1069 43 84 ee 64 f1 f5 49 62 e9 5f ee 5a 74 97 73 14 ec 18 73 9d 45 31
11e9 59 e4 70 68 69 53 f4 f8 87 6d 7b 4e d3 23 2d 39 8b fe 31 5f 18 e5
122f 8a 81 58 e7 69 d2 74 d7 43 4f 16 36 01 3c 82 db 53 4e 35 72 1e 2b
135b b9 fb 20 44 00 b6 33 a8 6b 16 8d 18 51 64 cf f3 60 8e f4 66 9d 13
146f 43 74 63 23 0f 6b 0a 18 4e 26 33 32 01 8a 93 a6 3d 5e 09 89 24 1b
1580 ce 13
Stage 3:
Once again, that shellcode was imported into cutter, although this time cutter wasn't able to display a graph view:
This time it was a simple loop with an XOR-operating that instead of using a string just XORed everything with the static value 0x476c4f62
.
After some trial and error I figured out that the decryption only works if you flip the static value around (because of the endianess) => 0x624f6c47
Stage 4:
On this stage the string omg it is almost over?!?
got pushed onto the stack before the well know for loop feat. XOR was entered.
For now, I just more or less guessed the bytes to drop as I couldn't figure it out that easily by reading the ASM code:
Finally, this revealed the e-mail aka. the flag: