The challenge written javascript, basically it is a flag validator challenge but it utilize threading and some recursive way to validate the flag. In this case i tried to find the code that will validate wether our input is correct or not. Looking at Util.js i found interesting code
Trying to dump used variable in line 9 i found interesting variable which is g. I notice that g will validate the value from backward and it will return True if our input is correct for each character. The total return from Solver function is 32 which is same like in the modulus value in callReadFileSync function. So the next step is i just bruteforce for each character by running the challenge and check the g variable. Here is the solver i used
Add line 10 to printout g variable
Run the solver and it will get the full flag at the end
Flag: DEAD{CPS7yl3_4nd_3vn7_10op_h3ll}
FlagChecker (430 pts)
Description
A simple flag checker
Solution
Given ELF 64 bit, open it using IDA. Looking at available strings i found interesting string that related to pyinstaller.
Based on above information i tried to extract the pyc file using pyinstxtractor.
Lets use pycdc to deocmpile main.pyc file
Although it was not fully decompile the pyc but we still can understand the flow.
Some variable are initialize as global variable
import checker library
check the value of chk variable
run checker.check
print value of t xored with static value (array)
If we try to do the xor with above information we will get the output below
In this step my assumption is t variable will be overwritten with some value. Also our input will be processed by some function because t variable and our input (FLAG variable) are global variable. The executed code after receiving our input are importing check library and run check.check, so lets check the library.
From image above we can see that there is no checker.pyc and the only file named checker is shared object file. It is possible to load shared object file in python, so lets try to decompile it using IDA. After opened it in IDA, search function with name checker because the called function is checker. Modify some function and variable based to make it easier to understand
PyObject_GetAttrString_OBF will do string deobfuscation/decode for the second argument
Our input will be stored in input variable and the size should be 32
input[31] should 125 or "}"
The first 5 bytes of input (index 0 - 4) should be "DEAD{"
So if our input contains DEAD{...} and it has length 32 it will change the chk variable to 4919 which is same like 0x1337. PyObject_GetAttrString_OBF just do negation of the second argument, below is the implementation in python
The function will be added in flight through function PyModule_AddFunctions. The second argument will pointing to check function. Double click off_6500 and take a look on off_6500+8, there will be function address.
Static analyze the code and take some notes
Now we know that we need to satisfy the comparison. Because the function that process our input is sub_1DBC lets take a look on it.
The algorithm doesnt directly shown up, lets take a look on PROCESSED_INPUT variable. PROCESSED_INPUT variable has so many cross references, go to one of the cross reference we will see function like below.
From code above we can see that our input will processed by rotate left instruction, looking at another function there will be some arithmetic or logic operator also. My approach during the competition is to set breakpoint on all available operator and then trace the operation. Set breakpoint on all available operator using idapython
After that setup debugger for .so files
guest
run linux_server64 on linux as root (1)
run python3.10 main.pyc (4)
trigger breakpoint by input value (7)
host
Select debugger (2)
Remote linux debugger
process options (3)
set hostname to IP Address of guest
attach to process (5)
select python3 main.pyc
continue the process (6)
click same on debugger warning popup (8)
After that we will see image like below
run automation below to trace the instruction
Generate output for 2 different input then use script below to parse the output
Use the comment and uncomment versoin of op = op.replace(key, hex(data2[key])) then analyze it. In this step my objective is to recover the instruction and put it as python code, below is the reconstructed version.
Looking at all operations above i notice that all of them are reversible, so lets put it backward and reverse the operation.
For below part i reverse it manually
First part
Last part
Put all the code together and run it
Flag: DEAD{run_pybyt3c0d3_w1th_C_4P1!}
Another solution
I'll try to analyze code on PyCode_New
TBU
No Math Rev (480 pts)
Description
Enough of this mathematics, how about trying some actual reverse engineering?
arr = [11,248,141,150,27,187,195,106,182,233,9,79,134,101,53,240,196,183,141,83,84,118,226,6,251,74,94,37,117,90]
t = [92,138,226,248,124,187,195,106,182,233,9,79,134,101,53,240,196,183,141,83,84,118,226,6,251,74,94,37,117,90]
for i in range(len(arr)):
t[i] ^= arr[i]
print(bytes(t))
from idaapi import *
import idautils
import idc
def print_hex(arr):
tmp = []
for i in arr:
tmp.append(hex(i))
print(tmp)
def set_bp(ea, cond):
idaapi.add_bpt(ea, 4, BPT_DEFAULT)
def get_inst(address):
insn = insn_t()
size = decode_insn(insn, address)
inst = idc.print_insn_mnem(address)
operand = []
for j in range(3):
tmp = idc.print_operand(address, j)
if(tmp == ''):
break
operand.append(tmp)
return inst + " " + ", ".join(operand), operand, size
def bp_operator(address):
start_address = address
print(hex(start_address))
for _ in range(10):
inst, operand, size = get_inst(start_address)
if "mov" not in inst and "lea" not in inst:
break
start_address += size
return start_address
address = 0x66E0
list_target = [i.frm for i in idautils.XrefsTo(address)]
list_target.sort()
list_target = list_target[:-2]
new_list_target = [[] for _ in range(3)]
for i in range(24):
new_list_target[0].append(list_target[i])
for i in range(24, 36):
new_list_target[1].append(list_target[i])
for i in range(0, len(new_list_target[0]), 3):
set_bp(bp_operator(new_list_target[0][i]), 'view_regs()')
for i in range(0, len(new_list_target[1]), 4):
set_bp(bp_operator(new_list_target[1][i]), 'view_regs()')
from idaapi import *
import idautils
import idc
def print_hex(arr):
tmp = []
for i in arr:
tmp.append(hex(i))
print(tmp)
def get_inst(address):
insn = insn_t()
size = decode_insn(insn, address)
inst = idc.print_insn_mnem(address)
operand = []
for j in range(3):
tmp = idc.print_operand(address, j)
if(tmp == ''):
break
operand.append(tmp)
return inst + " " + ", ".join(operand), operand, size
tracer = []
for _ in range(404):
print(_)
rv = idaapi.regval_t()
idaapi.get_reg_val('RIP', rv)
inst, operand, size = get_inst(rv.ival)
dict = {}
for opn in operand:
idaapi.get_reg_val(opn, rv)
dict[opn] = rv.ival
tracer.append([inst, dict])
idaapi.continue_process()
idaapi.wait_for_next_event(idaapi.WFNE_SUSP, -1)
print(tracer)