0x00sec CTF Writeup - Forward then Reverse

ctf

#1

#0x00sec CTF Writeup - Forward then Reverse

So yeah, I finally got time to write a writeup for @Deshi’s CTF but I sadly only have stuff written down for the RE challenge (mainly because the other stuff I solved was pretty easy and didn’t require more than a quick google search or simple stuff like ROT-n).
Considering me and @Towel were the only ones who solved it in time this might be interesting to some people so I decided to post it anyways.

##First look

So we have a ELF x86-64 binary called counter.

[email protected]:/media/sf_SharedSpace/current$ ./counter
usage: ./counter password
[email protected]:/media/sf_SharedSpace/current$ 
[email protected]:/media/sf_SharedSpace/current$ ./counter test
[email protected]:/media/sf_SharedSpace/current$ 

So it requires one parameter but doesn’t return anything when the wrong password is entered.

After running ‘strings counter’ we see that is contains a text for the correct password which means that we only get output when the amount of arguments is wrong or the right password is entered.

..
usage: %s password
Great! EKO{%s}
..

##Further Investigation

Ok. So the 1st argument is most likely in someway compared with the inputed string.
So how about we start this in gdb, break at the entry point and then set a memory read breakpoint at the entered password.
The entered password is argv[1] (as argv[0] = the name under which the binary was called) which means the address of it is at
rsp+16 when the program is started.

|----------------------------------|
| argc  |  argv[0] | argv[1] | ... |
|----------------------------------|
| $rsp  |  $rsp+8  | $rsp+16 | ... |
|----------------------------------|
* Source 1
[email protected]:/media/sf_SharedSpace/current$ gdb counter
Reading symbols from /media/sf_SharedSpace/current/counter...(no debugging symbols found)...done.
(gdb) info files
Symbols from "/media/sf_SharedSpace/current/counter".
Local exec file:
	`/media/sf_SharedSpace/current/counter', file type elf64-x86-64.
	Entry point: 0x4003f0
	..
(gdb) b *0x4003f0
Breakpoint 1 at 0x4003f0
(gdb) run teststring
Starting program: /media/sf_SharedSpace/current/counter 
Breakpoint 1, 0x00000000004003f0 in ?? ()
(gdb) p/a *((long*)($rsp+16))
$1 = 0x7fffffffe3eb
(gdb) rwatch *0x7fffffffe3eb
Hardware read watchpoint 2: *0x7fffffffe3eb

So now we continue to run the program and see where we end:
(note that it might be necessary to skip a few hits because of internal processing which is uninteresting for getting the result)

(gdb) x/4i $rip-3
   0x43d2c3:	movsx  esi,BYTE PTR [rdx]
=> 0x43d2c6:	cmp    esi,0x53
   0x43d2cc:	sete   dil
   0x43d2d0:	and    dil,0x1
(gdb) p/c $esi
$2 = 116 't'
(gdb) p/c 0x53
$3 = 83 'S'

Ok we see the first character (‘t’) of our dummy password (“teststring”) and we see it gets compared with ‘S’ (After that the program stops).
Let’s see what happens when our password starts with ‘S’ (I entered ‘run String’):

(gdb) x/40i $rip-4
   0x43e0b6:	movsx  edi,BYTE PTR [rsi+0x1]
=> 0x43e0ba:	mov    r8d,edi
   0x43e0bd:	xor    r8d,0xffffffff
   ..
   0x43e168:	movsx  edx,BYTE PTR [rsi]
   0x43e16b:	cmp    edx,0x1c
(gdb) p/c $edi
$4 = 116 't'

So the second character is somehow changed through a seemingly complex process (which I cut out as it’s not relevant) and then compared to a value.
The process for comparing all other characters looks similar but the complicated changing process is different for each character (oh noes).
Also some comparing calculations between refer to characters already checked.

|------------------------------------------------|
| char[0] -> char[1] -> char[2] -> char[1] -> .. |
|------------------------------------------------|
* not the actually verification sequence

##How I solved it

Well now I knew enough to solve this:

  • Setting memory read break point at password
  • Continue till a relevant memory access is reported
  • Read the address of the character currently checked
  • Try every character till the next compare instruction says they are equal (brute force)
  • Repeat this for all compares till the program ends and then print out the string

To do that I wrote a script which runs from inside gdb (please be merciful it’s 90% the original script I used with the comments I made while writing it):

def tillNextCMP(): #skip till next compare
	while True:
		str = gdb.execute("x/i $rip",False,True)
		opcode = str.split(":")[1].strip().split(" ")[0].strip()
		if opcode.startswith("cmp") == True:
			return
		gdb.execute("ni")
		
def getV1(): #get 1st parameter of the given compare statement
	v1 = (gdb.execute("x/i $rip",False,True).split(":")[1].strip().split(" ")[4].strip().split(",")[0].strip())
	return v1
def getV2(): #get 2nd parameter of the given compare statement
	v2 = (gdb.execute("x/i $rip",False,True).split(":")[1].strip().split(" ")[4].strip().split(",")[1].strip())
	return v2
#Program starts here
gdb.execute("set pagination off") #gdb would ask for pressing enter if this isn't deactivated
gdb.execute("set confirm off") #this stops gdb from asking for permission
gdb.execute("d") #delete old breakpoints

brutePass = list("^"*128) #the array containing the password

import string
bruteForce = string.ascii_letters+string.digits+"{}().,-_+!&*#=?%/\\<>" #this is the list of brute force character
for charNum in range(0,1024):
	found = False
	for charCon in bruteForce:
		if found == True: #stop checking other characters if solution was found
			break
		gdb.execute("d") #clean up old breakpoints
		gdb.execute("b *0x004003f0") #entry of program
		gdb.execute("run '%s'" % ''.join(brutePass))  #run with brutepass as parameter
		tmp = long(gdb.parse_and_eval("((long*)($rsp+16))")) #get address of the second argument as rwatch requires this for setting a memory breakpoint
		stringPos = long(gdb.parse_and_eval("*((long*)($rsp+16))")) #get address of string (2nd argument)
		gdb.execute("rwatch *%d" % (tmp)) #sets a read memory watch to the string
		gdb.execute("continue") #continue the program

		for i in range(charNum): #skips the already solved parts
			gdb.execute("continue")	
			
		save = (gdb.execute("x/i $rip",False,True).split(":")[1].strip().split(" ")[4].strip().replace("[","").replace("]","").replace("r","$r").strip()) #gets the string of the location of the character to read
		reg = "$"+(gdb.execute("x/i $rip",False,True).split(":")[1].strip().split(" ")[2].split(",")[0].strip()) #get the register stuff it getting written to
		gdb.execute("ni") #process mov instruction
		tmp = int(long(gdb.parse_and_eval(save))-stringPos) #offset of character - string offset => index of character being processed
		gdb.execute("set %s = '%s'" % (reg, charCon)) #sets the character to the testing character
		tillNextCMP() #skips till the next compare instruction
		cValue = (int(gdb.parse_and_eval("$"+getV1()))&0xFFFFFFFF) #gets the value of the register containing data
		wValue = (int(getV2().split("x")[1], 16)) #get the fixed value this is getting compared with
		if cValue == wValue: #if values identical => character correct
			brutePass[tmp] = charCon #sets character in string
			password = open("memory.txt", "w")
			password.write(''.join(brutePass).replace("^", "")) #saves password to memory.txt
			password.close()
			found = True	#don't process more characters
		gdb.execute("continue")	

gdb.execute("set confirm on") #Restores GDB
gdb.execute("set pagination on")
gdb.execute("d") 

Run by starting ‘gdb counter’ and then using the command ‘source script.py’.
Now lots of stuff happens on screen, let this run until nothing new appears on screen. (this might take a few minutes)
The script should have created a file named ‘memory.txt’ which contains the flag.

Resulting password, enter this and you get the flag

St4t1c_4n4lyS1s_randomstring1234
=> 'Great! EKO{St4t1c_4n4lyS1s_randomstring1234}'

##Conclusion

Well anyways that’s how I solved it. Maybe this helped someone.
Thanks to @Deshi for organizing such a fun event! :smiley:

##Link to Binary
https://transfer.sh/nlZJl/counter

##Sources:


#2

Learned so many things from this writeup. Thanks @Leeky
I’m still very new to RE, but I understood almost everything from this writeup :smile:


#3

great writeup :thumbsup: any chance you can upload the binary?


#4

sure! Here


#5

as an alternative to the method above, we also can bruteforce the password by counting the instructions executed which can be done using intel’s pin tool or Linux’s performance analysis tool (perf). this method exploit the fact that the program exits when incorrect character is guessed and executes more instructions after each correct guess. so the idea is to iterate through all printable characters and store the character which executes most instructions. then, repeat for the next letter in the password.

as i don’t have pin tool installed, here’s a poc using perf

import string
import sys
from subprocess import Popen, PIPE, STDOUT

argv1 = ""

while True:
    max_ins = (0, 0)
    for c in string.printable:
        cmd = ["perf", "stat", "-x", ",", "-e", "instructions:u", "./counter", argv1 + c]
        out, _ = Popen(cmd, stdout=PIPE, stderr=STDOUT).communicate()
        if "Great!" in out:
            Popen(["./counter", argv1 + c])
            sys.exit()
        ins_count = int(out.split(',')[0])
        if ins_count > max_ins[0]:
            max_ins = (ins_count, c)
    argv1 = argv1 + max_ins[1]


#6

Wow this is much simpler than my approach. I knew that there were tools for this but I had no clue how they are called so my approach for this was stepping through with gdb and counting (although I didn’t think about using it here).
Thank you very much for posting this!


#7

glad that i can help :grinning:


#8

nice write-up @Leeky :slight_smile: !
Logical way to solve it after your made observations, which made it easy to follow.

Also nice additions from you @mkhdznfq I didn’t know about that and will definitely try that on my own!


(Full Snack Developer) #9

Great writeup, @Leeky! I haven’t seen any examples of scripting inside GDB before, let along with python. This makes debugging and reversing so much more sane now


(system) #10

This topic was automatically closed after 30 days. New replies are no longer allowed.