Failures
Firstly I use IDA Pro to open it, being informed that it is AMD64 and compressed by UPX. So I download UPX394w
to uncompress it. Then in IDA Pro you can follow XREF of the string “Who Are You?” and find the main logic stream:
Notice that
call cs:qword_1400067B0
But if you go to 0x1400067B0
you may probably see
Now it’s time to do dynamic analysis to see what’s going on. So I use x64dbg and follow it til
And F7
to step in then you will see the encryption algorithm:
It seems that the encryption algorithm is chained by lots of jmp
instructions. So what to do next? The flag’s length is 8 so brute force in the dictionary of [a-zA-Z0-9]
may be possible (if you are enough lucky) but anyway it is not a good idea.
So I plan to extract the encryption algorithm instructions and manage to write out the decryption algorithm. I don’t know how to extract instructions from x64dbg, so I plan to find them in IDA Pro and copy them out.
First, I search in HxD for one specific instruction and follow the file offset I get from HxD in IDA Pro. And I find them:
I copy all of them into Sublime Text and apply Regex replacing function on them. Finally the instruction batch is like
Pay attention to your regular expression, case sensitive option and so on.
Tips:
\s+$
stands for one empty line.
Now look at the encryption algorithm and it is easy to write out the decryption algorithm:
First, use tac
on Linux to fetch the inverse text (inverse.asm).
Second, I write decryption.py
to generate decryption assembly language code (ans.asm):
f = open("inverse.asm")
z = f.read()
f.close()
ff = open("ans.asm", "w")
w = z.split("\n")
l = len(w)
for i in range(l):
if w[i] == '':
continue
if w[i][:3] == 'sub':
s = "add" + w[i][3:]
ff.write(w[i+1] + '\n')
ff.write(s + '\n')
elif w[i][:3] == 'add':
s = "sub" + w[i][3:]
ff.write(w[i+1] + '\n')
ff.write(s + '\n')
elif w[i][:3] == 'rol':
s = 'ror' + w[i][3:]
ff.write(s + '\n')
elif w[i][:3] == 'ror':
s = 'rol' + w[i][3:]
ff.write(s + '\n')
elif w[i][:3] == 'xor':
ff.write(w[i+1] + '\n')
ff.write(w[i] + '\n')
elif w[i][:3] == 'mov':
continue
else:
print("unknown: " + w[i])
ff.close()
Then plug ans.asm into the location of {Content}
in run.asm below:
[section .text]
global main
main:
push rbp
mov rbp, rsp
{Content}
mov rsp, rbp
pop rbp
ret
And compile on Linux:
nasm -f elf64 run.asm
gcc -o run run.o
Then use GDB and make breakpoint at last and use p/x $rbx
to see the flag.
What I get is f4k3f4k3
. But it is not the correct flag (just a fake).
Input it in the uncompressed binary, it seems correct:
But in the original binary, it is wrong:
In my opinion, the organizers play tricks that they modify the UPX compress algorithm’s implementation on their binary.
I also try to find OEP in x64dbg and then use Scylla to dump and rebuild the binary. But it seems that there exists anti-debug technique in the binary and the dumpped binary also gives me f4k3f4k3
.
Why did I fail?
Success
I failed to pass the challenge during the game. I have an idea that if I can dynamically follow the logic stream and dump the logic stream instructions out it may be effective. But I did not know how to realize it.
After seeing writeup by tonix0114, with CE (Cheat Engine) I make it finally even though my method seems not so graceful.
Based on what I did before, now I just need to extract instructions dynamically using CE:
Address 1C0000
are learned from x64dbg.
Notice that the assembly language instructions you get above should be adjusted in order to run on Linux. So you should use something like Sublime Text and Regex to modify them.
Then use tac
and apply decryption.py
on what you get.
After compilation, now use GDB to run it:
Finally you get:
Other Method
tonix0114 wrote a simple Python script to simulate the assembly language interpreter:
with open("instruction") as f:
data = f.readlines()[::-1] # decrypt
def ROL(data, shift, size=32):
shift %= size
remains = data >> (size - shift)
body = (data << shift) - (remains << size )
return (body + remains)
def ROR(data, shift, size=32):
shift %= size
body = data >> shift
remains = (data << (size - shift)) - (body << size)
return (body + remains)
"""
enc
-> mov eax, 0x123123123
-> add ebx, eax
enc[::-1]
-> add ebx, eax
-> mov eax, 0x123123123
dec(enc[::-1])
-> mov eax, 0x123123123
-> sub ebx, eax
"""
calc = []
for i in range(len(data)):
ins, op_str = data[i].split(" - ")[-1].split(" ")
if ins == "mov":
temp = calc.pop()
calc.append(ins + " " + op_str)
calc.append(temp)
else:
calc.append(ins + " " + op_str)
rax = 0
rbx = 0x6BA8F103D6E0FF17
check = " & 0xffffffffffffffff"
for i in range(0, len(calc)):
ins, op_str = calc[i].split(" ")
op1, op2 = op_str.replace("\n", "").split(",")
# minus
if op2[0] == "-":
op2 = hex(int(op2,16) & 0xff).replace("0x","")
# mov hex
if op2 not in ["rax", "rbx"]:
op2 = "0x" + op2
if ins == "mov":
exec(op1 +" = "+ op2 + check)
elif ins == "sub":
exec(op1 + " = (" + op1 + " + " + op2 + ")" + check)
elif ins == "add":
exec(op1 + " = (" + op1 + " - " + op2 + ")" + check)
elif ins == "xor":
exec(op1 + " = (" + op1 + " ^ " + op2 + ")" + check)
elif ins == "ror":
exec(op1 + " = " + "ROL(" + op1 + "," + op2 + ", 64)" + check)
elif ins == "rol":
exec(op1 + " = " + "ROR(" + op1 + "," + op2 + ", 64)" + check)
else:
print ins
flag = ""
while rbx:
flag += chr(rbx & 0xff)
rbx >>= 8
print flag