#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.
leeky@backbox:/media/sf_SharedSpace/current$ ./counter
usage: ./counter password
leeky@backbox:/media/sf_SharedSpace/current$
leeky@backbox:/media/sf_SharedSpace/current$ ./counter test
leeky@backbox:/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
leeky@backbox:/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!
##Link to Binary
https://transfer.sh/nlZJl/counter
##Sources: