UTC-CTF '19 Teaser [Stacks-RE]

banner

Introduction

So Here I’m, finally writing after 2 months :)
I’ve been a bit busy in October and November and I did participate in 2 onsite CTF competetitions.

We didn’t win but had an awesome experience and made new friends.
But most of the time in November I was studying for my mid sem. :(

Also my Team Dc1ph3R is organizing an international 20 hr Online Jeopardy Style CTF for the first time and so obviously I spent some time creating challenges for it too.
Its named as Inferno CTF and we’ll be giving away pentesterlab pro sub to the winners.
More details about it here.
Go check it out!!

I participated in many other CTFs in this period but I didn’t solve any challenge which was interesting and also worth sharing with you guys.


TLDR;

Me and my friend participated in UTC-CTF ‘19 Teaser and secured the 10th rank. This post will be a detailed writeup depicting my method of solving the problem stack from Reversing category. FYI I did not solve it during the CTF. This crackme is a perfect example of how one can use a debugger script to automate tedious tasks. The crackme is stripped and has ptrace as an anti-debug check. We input a flag and it is being checked using some mathematical operations such as xor. Here I have used radare’s r2pipe to write a python script.

Challenge

Download stack


Initial Analysis

1
ELF 64-bit LSB executable, x86-64, version 1 (SYSV), statically linked, stripped

Ahh the binary is stripped :(

Lets run it.

Lets take a look at what gdb has to offer..

Ohh wait, Obviously we know that we’ll not get any symbols but the binary is not even running so there might be an anti-debug check.
A quick check with strace reveals the ptrace call.

Ptrace detected!!

So yeah it’ll be easy for us if we patch the ptrace call in order to explore further.
I then moved over to IDA.
PS: I’m using IDAv7.4 Demo
Switch to the Text View to get some info.
I found the location of ptrace syscall as IDA identifies it.

RBX Register is being compared after the call as follows:

1
2
3
4
5
0x40111F  syscall   ; LINUX - sys_ptrace
0x401121 mov rbx, 0xFFFFFFFFFFFFFFFF
0x401128 cmp rax, rbx
0x40112B jnz short loc_40112F
0x40112D jmp short loc_4010EF ; exit()

We can successfully bypass the ptrace anti-debug check by replacing the following instruction with a NOP, so that cmp does not return 0 as both RAX, RBX will remain 0 and condition will be True.

1
.text:000401121    mov rbx, 0FFFFFFFFFFFFFFFFh

I tried out Ghidra for patching too but idk why it doesn’t work for me and I get a segfault for every exported binary.
If someone found a workaround to fix it, please lmk in the comments.

Finally I patched the binary with pwntools.

1
2
3
4
from pwn import *
elf = ELF('./stack')
elf.asm(0x401121,'nop')
elf.save('./stack_patched')

In-Depth Debugging based Analysis

Now we can easily load our patched binary in GDB.
It’ll be easy to follow along if you have GDB-Peda installed.

run the binary in gdb and press CTRL+C when it asks for input.
and then step over and now enter a fake flag.
So now, RAX contains 0xa ie. len of our input including the null char.

After stepping over some more instructions you’ll notice that our fake flag’s first character ie. ‘u’ hex value (0x75) has been moved into RBX‘s higher half ie. BH register
Here we have 32 bit registers so they are as follows:

More about them here.

We also observe some hexdata in RSI which is being referenced from just below the Incorrect text.
This remains same for any input though.

So the first byte from this data offset ie. 0x4f will move into AH (RAX‘s higher half)
The algorithm is depicted by the following assembly code.

At last it is xoring BH and DH, then storing the result in BH. But how do we get DH register value.. that’s the whole point here.
As ECX is mostly used as a counter register it is changing values everytime and also its value is dependent on R10 at some point ie. in sub_401132.

Moving further, BH and AH are subtracted.

And if result comes out to be 0 then it is fine else it’ll exit which is stated by assembly code at loc_4010CC.

So if we know AH and DH values, we can xor them and get characters for our flag but DH is not easy to find.

Keep on stepping and notice the values of the register especially RCX as DH is dependent on it.
I made some notes and this was how it iterates over our input flag.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
-------0-------
RCX: 0x4020f0 --> 0x3a (':') (for flag[0])
RDX: 0x403a32 (DH=0x3a)
AH = 0x4f
Check if (flag[0] ^ DH == AH)
RCX: hex(0x4020f0 + 8*4)
R10: value at RCX
RCX += R10

So flag[0] = chr( AH ^ DH)
= chr(0x4f ^0x3a)
= 'u'
-------1-------
RCX: 0x40215f --> 0x16 (for flag[1])
RDX: 0x401632 (DH=0x16)
AH = 0x62
Check if (flag[1] ^ DH == AH)
RCX += hex( 0x40215f + 8*4)
R10: value at RCX
RCX += R10

So flag[1] = chr( AH ^ DH)
= chr(0x62 ^0x16)
= 't'

But as the no. of solves kept increasing, I decided to ditch this tedious approach (or maybe it was wrong here) and thought of automating the debugging process to get DH value.

Automation with r2pipe

I chose radare r2pipe to write a short python script which sets a breakpoint just after xoring BH & DH.
With this method I was able to get AH and DH value and at the same time I used a little trick which helped me not to provide stdin everytime it fails.

Here I set BH = AH, after I get AH value in each iteration and the subtraction returns 0 everytime. Therefore, there is no case of failure :)

Resources: r2pipe docs

PS: Create a file named “stack_patched.rr2” which is just used to assist in not breaking the script when it asks for input.

1
2
3
4
#!/usr/bin/rarun2
program=./stack_patched
stdin=""
stdout=
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import r2pipe
r = r2pipe.open('stack_patched',flags=['-2'])
bp = 0x40103a # after "xor bh, dh"
r.cmd('e dbg.profile=stack_patched.rr2')
flag=""
r.cmd('doo')
r.cmd('db {}'.format(bp))
for i in range(35):
r.cmd('dc')
ah = r.cmd('dr ah')
dh = r.cmd('dr dh')
next_char = chr(int(ah,16)^int(dh,16))
flag += next_char
print '[{}] {}'.format(i,flag)
r.cmd('dr bh={}'.format(ah))

This script yeilds our flag!!

1
utc{stAcks_Ar3_WACK!!!_HEHEXDXD}

And B00mYa we earned it !!

drawing

Also the challenge source was disclosed by the admins on github.

Github Source rev-stacks

See yall in next writeup