File is packed, drop it in unpac.me, get unpacked PE. The PE does some antidebug checks and ends up doing some RC4 decryption of the flag. If the anti debug fails, the key will be incorrect.
patch the unpacked PE with EBFE after the RC4 so it does an infinite loop:
x = bytearray(open("daa6f5823995e8ffe4fd9de4f86358ec6e0b112c38c78c36f1ae0054277e43c8", "rb").read())
x[0x162b] = 0xeb
x[0x162c] = 0xfe
open("lol.exe", "wb").write(x)
end of main
will look like
local_10 = CONCAT13((char)uVar17,(undefined3)local_10);
uVar5 = FUN_7ffa1166();
local_c = CONCAT11(local_c._1_1_,uVar5);
RC4((char *)&local_1c,(int)&local_50,pbVar6);
do {
/* WARNING: Do nothing block with infinite loop */
} while( true );
}
just run the exe and dump the VM mem:
% virsh dump win10 dmp.bin
% strings dmp.bin| grep ASCW
ASCWG{What_A_ReverseEngineering_Beast_13337_-_-}
Modify the assembly listing a bit so NASM is happy with it, like:
section .data
.LC0:
db "Enter The Flag:", 0
.LC1:
db "%s", 0
.LC2:
db "Wrong Flag", 0
.LC3:
db "Correct Flag", 0
section .bss
flag_input resb 50
section .text
global flag_check
extern printf, puts, __isoc99_scanf
flag_check:
push rbp
mov rbp, rsp
sub rsp, 464
mov QWORD [rbp-112], 0
mov QWORD [rbp-104], 0
etc...
create bla.c
int flag_check(void);
int main()
{
flag_check();
}
compile
% nasm -f elf64 bla.asm -o your_filename.o
% gcc -c bla.c
% gcc bla.o your_filename.o -no-pie -o lol
reverse the lol and solve:
array1 = b'Z\x84\x06E\xae\xcb\xe8\xf3W\xfe\xa6=^A\x08\xd03"!\x81 \xdd\x00\xa0#\xafq\x04\x8b\xf5\x18\x1d\xe1\x0fe\t\xceBx>\xc37\xca\x8fd2\xe0\xac\xde\x91|*\xc0\x07\xf4\x95\x9f@S\xe5g\xb6zRN?\x83K\xc9\x82r.v\x1c\xf1\x1e\xcc\xb7\xd7\xc7\x8a\x10y\x1aM\x195\x16}C+\xcd\x86\xabD\x92\xd4\x0e\x98\x14\xb9\x9b\xa7$\x1b<\xe2:\xd3\xf0\xfdOw\xd1\xa3\x0cH\x80j\xda\xbd\xd8G[\xfa\x96\x0b\xec\xcfI\xd9\x11\x7f\xb1\'\xe7\xc5\xb2c\xe6(6\xb3]\xfb\xdc\xa8p%\xf6\xb0\x9c\xa5_\xb89\xe4\x85\xa9\xfc\x13\x02Q0\xf2i\xfft\xbfY\xb5F\x17\xc2Xa\x99\xeb\xa4\x9e\x89\xeel\xef\xa2\x90s\x8cT\xbcm\xdb,\xd6\xe3\xa1\x8dP\xf74\xd5\xf9\x01{\x8e\xbehkU\x9d-\xed/\x93\x15\x1f\xc4\x88\xaa\xf8\r\\\xeaV\x03\xc1\x9a8\x05obJ\x12\xdf`\x94)u~\xad\xe9\n1\xb4\xbb\xba\x87;&\xd2nf\xc8L\x97\xc6'
array2 = b"689df84315f73e8c01efe535"
array3 = b'\x01\xa3\x1e\xc9\x89\x1b\xd8\xa2\xadd\x06l\x05\xea\xbeP$y\xc3\xf9*\xc0\xb9\xef#xG\xa5\x06\x99\x05\xbb\xfcJ\x06Z\xfe{\x84\x97\xec\x0e\xbd\x17\xb3\xf3\xc8\xf3\x3a\xef'
def find(n):
for x in range(256):
v10 = array1[x]
v9 = array2[n % 24]
if (n % 24) & 1 != 0:
v9 ^= 0xff
v10 ^= v9
v10 = array1[v10]
if v10 & 1 != 0:
v10 ^= 0x42
v10 ^= 0xff
if v10 == array3[n]:
return x
return -1
output = ""
for x in range(len(array3)):
output += chr(find(x))
print(output)
% python slv.py
ASCWG{What_do_you_see_before_it_is_over?_998bd0d4}
if first started to bruteforce counting instructions with vagrant but it got stuck at the last 3 chars, so switched strat...
main
is a decoy, the meat in a _DT_INIT
constructor.
The bin forks:
- child process will load a shellcode in memory and jump to it, the shellcode decrypts itself.
- parent uses
ptrace
to control the child
classic nanomite case.
The child part looks like this:
pid = fork();
if (pid == 0) {
decrypted_code = (undefined8 *)mmap(NULL,0x6d0,7,0x22,-1,0);
*decrypted_code = _LAB_00104080;
decrypted_code[0xd9] = DAT_00104748;
lVar3 = (long)decrypted_code - (long)(undefined8 *)((ulong)(decrypted_code + 1) & 0xfffffffffffffff8);
puVar5 = (undefined8 *)(&LAB_00104080 + -lVar3);
puVar6 = (undefined8 *)((ulong)(decrypted_code + 1) & 0xfffffffffffffff8);
for (uVar4 = (ulong)((int)lVar3 + 0x6d0U >> 3); uVar4 != 0; uVar4 = uVar4 - 1) {
*puVar6 = *puVar5;
puVar5 = puVar5 + (ulong)bVar1 * -2 + 1;
puVar6 = puVar6 + (ulong)bVar1 * -2 + 1;
}
ptrace(PTRACE_TRACEME,0,0,0);
local_40 = decrypted_code;
(*(code *)decrypted_code)(&input_flag);
/* WARNING: Subroutine does not return */
exit(0);
}
with a debugger, we can skip over the fork() and make sure eax=0, then follow inside the mmap
'ed region and then dump the whole region
The parent controler looks like:
while( true ) {
_Var2 = waitpid(pid,(int *)&wstatus,0);
if (_Var2 == -1) break;
status = (int)wstatus >> 8;
/* INT3 */
if (status == SIGTRAP) {
ptrace(PTRACE_GETREGS,(ulong)pid,0,®s);
uVar4 = ptrace(PTRACE_PEEKTEXT,(ulong)pid,regs.rip,0);
local_30 = uVar4 & 0xffffffff00000000 | 0x90909090;
bVar1 = int3(regs.r11 & 0xff,regs.r10 & 0xffffffff);
regs.r11 = (ulonglong)bVar1;
ptrace(PTRACE_SETREGS,(ulong)pid,0,®s);
}
else {
/* UD2 */
if (status == SIGILL) {
ptrace(PTRACE_GETREGS,(ulong)pid,0,®s);
uVar4 = ptrace(PTRACE_PEEKTEXT,(ulong)pid,regs.rip,0);
local_30 = uVar4 & 0xffffffffffff0000 | 0x9090;
ptrace(PTRACE_POKETEXT,(ulong)pid,regs.rip,local_30);
ptrace(PTRACE_GETREGS,(ulong)pid,0,®s);
bVar1 = ud2(regs.r11 & 0xff,regs.r10 & 0xffffffff);
regs.r11 = (ulonglong)bVar1;
ptrace(PTRACE_SETREGS,(ulong)pid,0,®s);
}
else {
/* idiv rcx */
if (status == SIGFPE) {
ptrace(PTRACE_GETREGS,(ulong)pid,0,®s);
bVar1 = add(regs.r11 & 0xff,regs.r10 & 0xff);
regs.r11 = (ulonglong)bVar1;
ptrace(PTRACE_SETREGS,(ulong)pid,0,®s);
uVar4 = ptrace(PTRACE_PEEKTEXT,(ulong)pid,regs.rip,0);
local_30 = uVar4 & 0xffffffffff000000 | 0x909090;
ptrace(PTRACE_POKETEXT,(ulong)pid,regs.rip,local_30);
}
else if (status == SIGSEGV) {
ptrace(PTRACE_GETREGS,(ulong)pid,0,®s);
bVar1 = xor(regs.r11 & 0xff,regs.r10 & 0xff);
regs.r11 = (ulonglong)bVar1;
ptrace(PTRACE_SETREGS,(ulong)pid,0,®s);
uVar4 = ptrace(PTRACE_PEEKTEXT,(ulong)pid,regs.rip,0);
local_30 = uVar4 & 0xffffffff00000000 | 0x90909090;
ptrace(PTRACE_POKETEXT,(ulong)pid,regs.rip,local_30);
uVar4 = ptrace(PTRACE_PEEKTEXT,(ulong)pid,regs.rip + 4,0);
local_30 = uVar4 & 0xffffffff00000000 | 0x90909090;
ptrace(PTRACE_POKETEXT,(ulong)pid,regs.rip + 4,local_30);
}
else if (status == 0x1f) {
ptrace(PTRACE_GETREGS,(ulong)pid,0,®s);
uVar4 = ptrace(PTRACE_PEEKTEXT,(ulong)pid,regs.rip,0);
local_30 = uVar4 & 0xffffffff00000000 | 0x90909090;
/* r13 must be 0 */
check_and_exit(regs.r13 & 0xff);
}
else if (status == 0x12) {
ptrace(PTRACE_GETREGS,(ulong)pid,0,®s);
bVar1 = FUN_00101276(regs.r14 & 0xff,regs.r10 & 0xff);
regs.r11 = (ulonglong)bVar1;
ptrace(PTRACE_SETREGS,(ulong)pid,0,®s);
}
else {
/* kill */
if (status == 0x1c) {
ptrace(PTRACE_GETREGS,(ulong)pid,0,®s);
bVar1 = kill(regs.r14 & 0xff,regs.r10 & 0xff);
regs.r11 = (ulonglong)bVar1;
ptrace(PTRACE_SETREGS,(ulong)pid,0,®s);
}
else if (((wstatus & 0x7f) == 0) || (status == SIGCHLD)) {
win = 0xbeedf8f1cd;
uStack_180 = 0xceb9ea;
uStack_17d = 0x99fdebf0fc;
for (j = 0; j < 0xd; j = j + 1) {
*(byte *)((long)&win + (long)j) = *(byte *)((long)&win + (long)j) ^ 0x99;
}
puts((char *)&win);
}
}
}
}
ptrace(PTRACE_CONT,(ulong)pid,0,0);
}
basically it waits for the child to crash, and based on the signal, it will modify the child context and resumes execution
we can disassemble the dumped shellcode:
from capstone import *
import sys
def disasm(code):
md = Cs(CS_ARCH_X86, CS_MODE_64)
for i in md.disasm(code, 0x0):
op_str = i.op_str.replace("qword", "").replace("dword", "").replace("byte", "").replace("ptr ", "")
print("0x%x:\t%s\t%s" %(i.address, i.mnemonic, op_str))
with open(sys.argv[1], "rb") as fp:
fp.seek(0x24) # ignore decryption stub
data = fp.read()
disasm(data)
it spits out something like
0x0: mov r12b, 0xc9
0x3: xor r11, r11
0x6: rol r12b, 5
0xa: xor r12b, 0x2d
0xe: mov r11b, [r9] # load flag char
0x11: mov r10, 0x1c
0x18: ud2 # trigger parent with SIGILL (0x4), r11 will be modded
0x1a: inc r9 # next flag char
0x1d: xor r11b, r12b
0x20: or r13b, r11b # r13 remain 0, so r11 must be 0 (easy to autosolve)
0x23: mov r12b, 0xcf
0x26: xor r11, r11
0x29: rol r12b, 5
0x2d: xor r12b, 0x2d
0x31: mov r11b, [r9]
0x34: mov r14b, [r9]
0x37: mov r10, 6
0x3e: mov rax, 0x3e
0x45: mov rdi, 0
0x4c: mov rsi, 0x1c
0x53: syscall # eax=0x3e, rsi=0x1c, rdi=0 > kill(0, 0x1c) -> trigger parents with 0x1c signal
0x55: inc r9
0x58: xor r11b, r12b
0x5b: or r13b, r11b
0x5e: mov r12b, 0x2a
0x61: xor r11, r11
0x64: rol r12b, 5
0x68: xor r12b, 0x2d
0x6c: mov r11b, [r9]
0x6f: mov r10, 0x15
0x76: int3 # SIGTRAP
0x77: inc r9
.....
when reversing we find out that:
- r9 is a pointer to the inputflag
- r13 must remains 0 at all time if the flag is correct
- r11 is modified by the parent when the child crash.
there's few possible signals from the child:
- SIGILL -> UD2
- SIGTRAP -> INT3
- SIGFPE -> IDIV ECX with ECX == 0
- SIGSEGV -> MOV RAX, [0]
- signals numbers 0x12 and 0x1c, raised by syscall 0x3e (kill)
based on the signal, the parent will do "something" and resume execution.
due to the very low number of different instructions, i used a shitty emulator (tm) parsing the disassembly, plugged to a z3 solver to save me from using my brain trying to actually reverse that:
from z3 import *
rol = lambda val, r_bits, max_bits: \
(val << r_bits%max_bits) & (2**max_bits-1) | \
((val & (2**max_bits-1)) >> (max_bits-(r_bits%max_bits)))
def fix(name):
# r12b -> r12
name = name.rstrip(',')
if not name.startswith("0x"):
return name.rstrip("b")
return name
class Emu:
def __init__(self):
self.flag = []
for x in range(40):
c = BitVec("f%d"%x, 8)
self.flag.append(c)
self.st = {
"r9": 0,
"r11": 0,
}
self.found = ''
def val(self, name):
if name.startswith("0x"):
return int(name[2:], 16)
try:
return int(name)
except:
return self.st[fix(name)]
def solve(self, x):
s = Solver()
s.add(x)
assert s.check() == sat
m = s.model()
self.found += chr(m[m[0]].as_long())
print("flag: " + self.found)
def emu(self, code):
for l in code:
l = l.split()[1:]
print(l)
op = l[0]
match op:
case 'mov':
dst = fix(l[1])
src = fix(l[2])
if src.startswith("["):
# load flag
if src == '[r9]':
self.st[dst] = self.flag[self.st["r9"]]
# SIGSEGV
elif src == '[0]':
self.st["r11"] ^= self.st["r10"]
else:
raise
else:
self.st[dst] = self.val(src)
case 'xor':
dst = fix(l[1])
src = fix(l[2])
self.st[dst] ^= self.val(src)
case 'rol':
dst = fix(l[1])
src = fix(l[2])
self.st[dst] = rol(self.st[dst], self.val(src), 8)
case 'inc':
dst = fix(l[1])
self.st[dst] += 1
case 'or':
# always or r13, r11
# r13 must remains, so r11 must be 0
# send that to z3
self.solve(self.st["r11"] == 0)
case 'idiv':
# SIGFPE
self.st["r11"] += self.st["r10"]
case 'ud2':
# SIGILL
left = (-(self.st["r10"] & 7) & 7)
right = self.st["r10"] & 7
self.st["r11"] = RotateLeft(self.st["r11"], left) | RotateRight(self.st["r11"], right)
case 'syscall':
sysnr = self.st["rax"]
#print(hex(sysnr))
# kill
if sysnr == 0x3e:
signum = self.st["rsi"]
#print(hex(signum))
if signum == 0x1c:
r10 = self.st["r10"] ^ 0x44
left = (-(r10 & 7) & 7)
right = r10 & 7
self.st["r11"] = RotateLeft(self.st["r14"], left) | RotateRight(self.st["r14"], right)
elif signum == 0x12:
r10 = self.st["r10"] ^ 0x41
left = r10 & 7
right = (-(r10 & 7) & 7)
self.st["r11"] = RotateLeft(self.st["r11"], left) | RotateRight(self.st["r11"], right)
else:
raise
# the end
elif sysnr == 0x12c:
print("-----------------")
print(self.found)
import sys
sys.exit(1)
else:
raise
case 'int3':
# SIGTRAP
#r11 = (int)(uint)r11 >> (-(r10 & 7) & 7) | (uint)r11 << (r10 & 7)
left = self.st["r10"] & 7
right = (-(self.st["r10"] & 7) & 7)
self.st["r11"] = RotateLeft(self.st["r11"], left) | RotateRight(self.st["r11"], right)
case _:
raise
print(self.st)
if __name__ == "__main__":
e = Emu()
e.emu(open("shellc.asm").readlines())
when running it spits the flag at the end:
python emu.py
['mov', 'r12b,', '0xc9']
{'r9': 0, 'r11': 0, 'r12': 201}
['xor', 'r11,', 'r11']
{'r9': 0, 'r11': 0, 'r12': 201}
['rol', 'r12b,', '5']
{'r9': 0, 'r11': 0, 'r12': 57}
['xor', 'r12b,', '0x2d']
{'r9': 0, 'r11': 0, 'r12': 20}
['mov', 'r11b,', '[r9]']
{'r9': 0, 'r11': f0, 'r12': 20}
['mov', 'r10,', '0x1c']
{'r9': 0, 'r11': f0, 'r12': 20, 'r10': 28}
['ud2']
{'r9': 0, 'r11': RotateLeft(f0, 4) | RotateRight(f0, 4), 'r12': 20, 'r10': 28}
['inc', 'r9']
{'r9': 1, 'r11': RotateLeft(f0, 4) | RotateRight(f0, 4), 'r12': 20, 'r10': 28}
['xor', 'r11b,', 'r12b']
{'r9': 1, 'r11': (RotateLeft(f0, 4) | RotateRight(f0, 4)) ^ 20, 'r12': 20, 'r10': 28}
['or', 'r13b,', 'r11b']
flag: A
.....
['xor', 'r12b,', '0x2d']
{'r9': 37, 'r11': (RotateLeft(f36, 0) | RotateRight(f36, 0)) ^
56 ^
(RotateLeft(f36, 0) | RotateRight(f36, 0)) ^
56, 'r12': 95, 'r10': 16, 'r14': f35, 'rax': 62, 'rdi': 0, 'rsi': 28}
['mov', 'r11b,', '[r9]']
{'r9': 37, 'r11': f37, 'r12': 95, 'r10': 16, 'r14': f35, 'rax': 62, 'rdi': 0, 'rsi': 28}
['mov', 'r10,', '0xa']
{'r9': 37, 'r11': f37, 'r12': 95, 'r10': 10, 'r14': f35, 'rax': 62, 'rdi': 0, 'rsi': 28}
['ud2']
{'r9': 37, 'r11': RotateLeft(f37, 6) | RotateRight(f37, 2), 'r12': 95, 'r10': 10, 'r14': f35, 'rax': 62, 'rdi': 0, 'rsi': 28}
['inc', 'r9']
{'r9': 38, 'r11': RotateLeft(f37, 6) | RotateRight(f37, 2), 'r12': 95, 'r10': 10, 'r14': f35, 'rax': 62, 'rdi': 0, 'rsi': 28}
['xor', 'r11b,', 'r12b']
{'r9': 38, 'r11': (RotateLeft(f37, 6) | RotateRight(f37, 2)) ^ 95, 'r12': 95, 'r10': 10, 'r14': f35, 'rax': 62, 'rdi': 0, 'rsi': 28}
['or', 'r13b,', 'r11b']
flag: ASCWG{N0w_1_4m_B3c0m3_D34th_500861fd8}
{'r9': 38, 'r11': (RotateLeft(f37, 6) | RotateRight(f37, 2)) ^ 95, 'r12': 95, 'r10': 10, 'r14': f35, 'rax': 62, 'rdi': 0, 'rsi': 28}
['mov', 'rax,', '0x12c']
{'r9': 38, 'r11': (RotateLeft(f37, 6) | RotateRight(f37, 2)) ^ 95, 'r12': 95, 'r10': 10, 'r14': f35, 'rax': 300, 'rdi': 0, 'rsi': 28}
['syscall']
-----------------
ASCWG{N0w_1_4m_B3c0m3_D34th_500861fd8}