hell86
- Author : ttlhacker challenge files
Description : x86_64 linux binary (tested on debian 9 and ubuntu 18.04, should run on any distro). Takes one command line argument and outputs “OK!” if it’s correct, “Wrong” if it’s not. Partially written in C, actual verification routine is assembly. Don’t patch the binary, of course - find the correct input.
Lets open the binary in radare2 and we’ll look at the disassembly of the main function
┌ 110: int main (int argc, char **argv);
│ ; arg int argc @ rdi
│ ; arg char **argv @ rsi
│ 0x00000fc0 55 push rbp ; [14] -r-x section size 4002 named .text
│ 0x00000fc1 53 push rbx
│ 0x00000fc2 4889f5 mov rbp, rsi ; argv
│ 0x00000fc5 89fb mov ebx, edi ; argc
│ 0x00000fc7 4883ec08 sub rsp, 8
│ 0x00000fcb e8f6090000 call sigaltstack
│ 0x00000fd0 84c0 test al, al
│ 0x00000fd2 ba01000000 mov edx, 1
│ ┌─< 0x00000fd7 744f je 0x1028
│ │ 0x00000fd9 e887090000 call sigill_handler
│ │ 0x00000fde 84c0 test al, al
│ │ 0x00000fe0 ba02000000 mov edx, 2
│ ┌──< 0x00000fe5 7441 je 0x1028
│ ││ 0x00000fe7 4863fb movsxd rdi, ebx
│ ││ 0x00000fea 4889ee mov rsi, rbp
│ ││ 0x00000fed e89e010000 call flag_verification
│ ││ 0x00000ff2 4883f801 cmp rax, 1
│ ││ 0x00000ff6 488d3dda1000. lea rdi, str.Wrong ; 0x20d7 ; "Wrong"
│ ┌───< 0x00000ffd 7422 je 0x1021
│ │││ 0x00000fff 4883f802 cmp rax, 2
│ │││ 0x00001003 488d3dd31000. lea rdi, str.hell86_crackme__Please_pass_the_flag_as_a_command_line_argument. ; 0x20dd ; "[hell86 crackme] Please pass the flag as a command-line argument."
│ ┌────< 0x0000100a 7415 je 0x1021
│ ││││ 0x0000100c 4885c0 test rax, rax
│ ││││ 0x0000100f 488d3dbd1000. lea rdi, [0x000020d3] ; "OK!"
│ ││││ 0x00001016 488d05021100. lea rax, str.You_have_encountered_a_bug ; 0x211f ; "You have encountered a bug"
│ ││││ 0x0000101d 480f45f8 cmovne rdi, rax
│ ││││ ; CODE XREFS from main @ 0xffd, 0x100a
│ └└───> 0x00001021 e80affffff call sym.imp.puts ; int puts(const char *s)
│ ││ 0x00001026 31d2 xor edx, edx
│ ││ ; CODE XREFS from main @ 0xfd7, 0xfe5
│ └└─> 0x00001028 89d0 mov eax, edx
│ 0x0000102a 5a pop rdx
│ 0x0000102b 5b pop rbx
│ 0x0000102c 5d pop rbp
└ 0x0000102d c3 ret
The main function calls 3 functions respectively
sigaltstack function⌗
The first function allocates 0x2000 bytes on the heap for an alternate stack for the signal handlers and the stack pointer points to the new stack which had been allocated also the flags have been set to 0 and later on it calls the sigaltstack function and if it encounters any errors it free’s the allocated memory.
This is an example for defining an alternate stack
#include <signal.h>
...
if ((sigstk.ss_sp = malloc(SIGSTKSZ)) == NULL)
/* Error return. */
sigstk.ss_size = SIGSTKSZ;
sigstk.ss_flags = 0;
if (sigaltstack(&sigstk,(stack_t *)0) < 0)
perror("sigaltstack");
The actual function
┌ 89: sigaltstack ();
│ ; var void *var_8h @ rsp+0x8
│ ; var int64_t var_18h @ rsp+0x18
│ 0x000019c6 53 push rbx
│ 0x000019c7 bf00200000 mov edi, 0x2000 ; "X\xbb\xff\xff\xff\xff\xff\xff\xb8\n" ; size_t size
│ 0x000019cc 4883ec20 sub rsp, 0x20
│ 0x000019d0 e87bf5ffff call sym.imp.malloc ; void *malloc(size_t size)
│ 0x000019d5 31d2 xor edx, edx
│ 0x000019d7 4885c0 test rax, rax
│ ┌─< 0x000019da 743b je 0x1a17
│ │ 0x000019dc 488d7c2408 lea rdi, [var_8h]
│ │ 0x000019e1 4889c3 mov rbx, rax
│ │ 0x000019e4 b906000000 mov ecx, 6
│ │ 0x000019e9 31c0 xor eax, eax
│ │ 0x000019eb 31f6 xor esi, esi
│ │ 0x000019ed f3ab rep stosd dword [rdi], eax
│ │ 0x000019ef 488d7c2408 lea rdi, [var_8h]
│ │ 0x000019f4 48c744241800. mov qword [var_18h], 0x2000 ; [0x2000:8]=0xffffffffffffbb58 ; "X\xbb\xff\xff\xff\xff\xff\xff\xb8\n"
│ │ 0x000019fd 48895c2408 mov qword [var_8h], rbx
│ │ 0x00001a02 e899f5ffff call sym.imp.sigaltstack
│ │ 0x00001a07 85c0 test eax, eax
│ │ 0x00001a09 b201 mov dl, 1
│ ┌──< 0x00001a0b 740a je 0x1a17
│ ││ 0x00001a0d 4889df mov rdi, rbx ; void *ptr
│ ││ 0x00001a10 e86bf5ffff call sym.imp.free ; void free(void *ptr)
│ ││ 0x00001a15 31d2 xor edx, edx
│ ││ ; CODE XREFS from sigaltstack @ 0x19da, 0x1a0b
│ └└─> 0x00001a17 4883c420 add rsp, 0x20
│ 0x00001a1b 88d0 mov al, dl
│ 0x00001a1d 5b pop rbx
└ 0x00001a1e c3 ret
sigill handler⌗
This function initializes the SIGILL
handler it has two flags set SA_ONSTACK
and SA_SIGINFO
what does these flags do ????
The man page for sigaction clearly mentions that :
SA_ONSTACK If set and an alternate signal stack has been declared with sigaltstack(), the signal shall be delivered to the calling process on that stack. Otherwise, the signal shall be delivered on the current stack.
SA_SIGINFO If cleared and the signal is caught, the signal-catching function shall be entered as:
void func(int signo);
where signo is the only argument to the signal-catching function. In this case, the application shall use the sa_handler member to describe the signal-catching function and the application shall not modify the sa_sigaction member.
If SA_SIGINFO is set and the signal is caught, the signal-catching function shall be entered as:
void func(int signo, siginfo_t *info, void *context);
where two additional arguments are passed to the signal-catching function. The second argument shall point to an object of type siginfo_t explaining the reason why the signal was generated; the third argument can be cast to a pointer to an object of type ucontext_t to refer to the receiving thread’s context that was interrupted when the signal was delivered. In this case, the application shall use the sa_sigaction member to describe the signal-catching function and the application shall not modify the sa_handler member.
┌ 97: sigill_handler ();
│ ; var struct sigaction *act @ rsp+0x8
│ ; var int64_t var_10h @ rsp+0x10
│ ; var int64_t var_90h @ rsp+0x90
│ 0x00001965 53 push rbx
│ 0x00001966 31c0 xor eax, eax
│ 0x00001968 b926000000 mov ecx, 0x26 ; '&'
│ 0x0000196d 4881eca00000. sub rsp, 0xa0
│ 0x00001974 488d7c2408 lea rdi, [act]
│ 0x00001979 f3ab rep stosd dword [rdi], eax
│ 0x0000197b 488d05c4ffff. lea rax, [0x00001946]
│ 0x00001982 488d7c2410 lea rdi, [var_10h]
│ 0x00001987 c78424900000. mov dword [var_90h], 0x8000004 ; [0x8000004:4]=-1
│ 0x00001992 4889442408 mov qword [act], rax
│ 0x00001997 e8c4f5ffff call sym.imp.sigfillset
│ 0x0000199c 31d2 xor edx, edx
│ 0x0000199e 85c0 test eax, eax
│ ┌─< 0x000019a0 7519 jne 0x19bb
│ │ 0x000019a2 488d5c2408 lea rbx, [act]
│ │ 0x000019a7 31d2 xor edx, edx ; struct sigaction *oldact
│ │ 0x000019a9 bf04000000 mov edi, 4 ; int signum
│ │ 0x000019ae 4889de mov rsi, rbx ; const struct sigaction *act
│ │ 0x000019b1 e8daf5ffff call sym.imp.sigaction ; int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact)
│ │ 0x000019b6 85c0 test eax, eax
│ │ 0x000019b8 0f94c2 sete dl
│ │ ; CODE XREF from sigill_handler @ 0x19a0
│ └─> 0x000019bb 4881c4a00000. add rsp, 0xa0
│ 0x000019c2 88d0 mov al, dl
│ 0x000019c4 5b pop rbx
└ 0x000019c5 c3 ret
lets dig deep down the handler routine
The handler holds a struct uncontext and in which uc_mcontext holds the GPR’s (general purpose registers) for hell86 and as we see it uses the same registers of the x64 architecture for hell86
gef➤ ptype ucontext_t
type = struct ucontext_t {
unsigned long uc_flags;
struct ucontext_t *uc_link;
stack_t uc_stack;
mcontext_t uc_mcontext;
sigset_t uc_sigmask;
struct _libc_fpstate __fpregs_mem;
unsigned long long __ssp[4];
}
gef➤ ptype mcontext_t
type = struct {
gregset_t gregs;
fpregset_t fpregs;
unsigned long long __reserved1[8];
}
gef➤ ptype gregset_t
type = long long [23]
;DATA XREF from fcn.00001965 @ 0x197b
0x00001946 488b82a80000. mov rax, qword [rdx + 0xa8] ; rax = addr of ud2
0x0000194d 488d7228 lea rsi, [rdx + 0x28] ; rsi = register(ucontext_t)
0x00001951 488d7802 lea rdi, [rax + 2] ; rdi = the next bytes after the encountered ud2
0x00001955 4883c00e add rax, 0xe ; rax = points to the enocountered ud2 + 0xe = next ud2
0x00001959 488982a80000. mov qword [rdx + 0xa8], rax
0x00001960 e97b050000 jmp 0x1ee0
; CODE XREF from fcn.00001192 @ +0x7ce
0x00001ee0 0fb65708 movzx edx, byte [rdi + 8]
0x00001ee4 488d05951120. lea rax, [0x00203080]
0x00001eeb ff24d0 jmp qword [rax + rdx*8]
0x00001eee 6690 nop
The byte at rdi+8
indexes a jump table at 0x00203080
and the jump table which consists of 50 instructions for the vm to use and they are shown below
0x00203080 0x0000000000001a1f 0x0000000000001a20 ........ .......
0x00203090 0x0000000000001a39 0x0000000000001a52 9.......R.......
0x002030a0 0x0000000000001a6c 0x0000000000001a87 l...............
0x002030b0 0x0000000000001aa2 0x0000000000001abe ................
0x002030c0 0x0000000000001ae6 0x0000000000001ada ................
0x002030d0 0x0000000000001afa 0x0000000000001b12 ................
0x002030e0 0x0000000000001b2b 0x0000000000001b43 +.......C.......
0x002030f0 0x0000000000001b5c 0x0000000000001b73 \.......s.......
0x00203100 0x0000000000001b8b 0x0000000000001ba3 ................
0x00203110 0x0000000000001bba 0x0000000000001bd2 ................
0x00203120 0x0000000000001be9 0x0000000000001c01 ................
0x00203130 0x0000000000001c1a 0x0000000000001c2e ................
0x00203140 0x0000000000001c43 0x0000000000001c54 C.......T.......
0x00203150 0x0000000000001c6d 0x0000000000001c86 m...............
0x00203160 0x0000000000001c9f 0x0000000000001cb3 ................
0x00203170 0x0000000000001cd2 0x0000000000001cf1 ................
0x00203180 0x0000000000001d10 0x0000000000001d2f ......../.......
0x00203190 0x0000000000001d4e 0x0000000000001d6d N.......m.......
0x002031a0 0x0000000000001d87 0x0000000000001da1 ................
0x002031b0 0x0000000000001db9 0x0000000000001dcf ................
0x002031c0 0x0000000000001de5 0x0000000000001e07 ................
0x002031d0 0x0000000000001e9c 0x0000000000001ebe ................
0x002031e0 0x0000000000001e1e 0x0000000000001e32 ........2.......
0x002031f0 0x0000000000001e49 0x0000000000001e60 I.......`.......
0x00203200 0x0000000000001e74 0x0000000000001e88 t...............
FLag Function⌗
This is the actual flag verification function, the first instruction is actually a ud2
instruction. so what does a ud2 instruction does? 🤔
opcode | memonic | description |
---|---|---|
0f 0b | UD2 | raise invalid opcode exception |
┌ 2: flag_verification ();
└ 0x00001190 0f0b ud2
; CALL XREF from main @ 0xfed
┌ 28: fcn.00001192 (int64_t arg4);
│ ; arg int64_t arg4 @ rcx
│ 0x00001192 0200 add al, byte [rax]
│ 0x00001194 0000 add byte [rax], al
│ 0x00001196 0000 add byte [rax], al
│ 0x00001198 0000 add byte [rax], al
│ 0x0000119a 090d00000f0b or dword [0x0b0f11a0], ecx ; [0xb0f11a0:4]=-1 ; arg4
│ 0x000011a0 0200 add al, byte [rax]
│ 0x000011a2 0000 add byte [rax], al
│ 0x000011a4 0000 add byte [rax], al
│ 0x000011a6 0000 add byte [rax], al
│ 0x000011a8 2400 and al, 0
│ 0x000011aa 0800 or byte [rax], al
The structure of the instructions for hell86 would look like this
struct ins {
uint16_t ud2;
uint64_t imm;
uint8_t opcode;
uint8_t dest_reg;
uint8_t source_reg_1;
uint8_t source_reg_2;
};
As we now know the routine of the handler its pretty easy to reverse the flag verification function so that being set we’ll need to make a disassembler and a script to reverse the flag function in order to get the flag
Disassembler⌗
we can use objdump or gdb to dump the flag verification function from the binary
▲ reversing/vm/hell86 gef hell86
GEF for linux ready, type `gef' to start, `gef config' to configure
92 commands loaded for GDB 10.1 using Python engine 3.9
Reading symbols from hell86...
(No debugging symbols found in hell86)
gef➤ dump binary memory flag_func 0x1190 0x1946
As it was my first time creating a diassembler i got some help from a previously made disassembler.
#!/usr/bin/env python3
def disassemble(instructions):
registers = ['r8', 'r9', 'r10', 'r11', 'r12', 'r13', 'r14', 'r15', 'rdi', 'rsi',
'rbp', 'rbx', 'rdx', 'rax', 'rcx', 'rsp', 'rip']
asm_instr = [
'nop', # no operation
'{1:s} = {2:s} + {3:s}', # addition
'{1:s} = {2:s} - {3:s}', # subtract
'{1:s} = {2:s} * {3:s}', # multiply
'{1:s} = {2:s} / {3:s}', # division (quotient)
'{1:s} = {2:s} % {3:s}', # division (reminder)
'{1:s} = {2:s} >> {3:s}', # right shift
'{1:s} = {2:s} << {3:s}', # left shift
'{1:s} = -{2:s}', # negation
'{1:s} = 0x{0:x}', # mov a, b
'{1:s} = byte [{2:s}+0x{0:x}]', # movzx
'{1:s} = byte [{2:s}+0x{0:x}]', # movsx
'{1:s} = word [{2:s}+0x{0:x}]', # movzx
'{1:s} = word [{2:s}+0x{0:x}]', # movsx
'{1:s} = dword [{2:s}+0x{0:x}]', # movzx
'{1:s} = dword [{2:s}+0x{0:x}]', # movsx
'{1:s} = qword [{2:s}+0x{0:x}]', # mov
'mov byte [{2:s}+0x{0:x}], {3:s}',
'mov word [{2:s}+0x{0:x}], {3:s}',
'mov dword [{2:s}+0x{0:x}], {3:s}',
'mov qword [{2:s}+0x{0:x}], {3:s}',
'push {2:s}',
'push 0x{0:x}',
'pop {1:s}',
'{1:s} = {2:s}',
'{1:s} = {2:s} | {3:s}',
'{1:s} = {2:s} & {3:s}',
'{1:s} = {2:s} ^ {3:s}',
'{1:s} = ~ {2:s}',
'cmp.s {1:s}, {2:s}, {3:s}', # smaller
'cmp.se {1:s}, {2:s}, {3:s}', # smaller or equal
'cmp.g {1:s}, {2:s}, {3:s}', # greater
'cmp.ge {1:s}, {2:s}, {3:s} ', # greater or equal
'{1:s} == {3:s} ; {1:s} = 1', #equal
'cmp.in {1:s}, {2:s}, {3:s}', #inequal
'cmp.eq {1:s}, {2:s}, 0x{0:x}', # if equal to const
'{2:s} != 0x{0:x} ; {1:s} = 1', # if inequal to const
'cmp.z {1:s}, {2:s} ; {1:s} = {2:s} == 0 ',
'if {2:s} = 0 jmp 0x{0:x}',
'if {2:s} != 0 jmp 0x{0:x}',
'call 0x{0:x}',
'ret',
'if {2:s} != 0 ret',
'if {2:s} = 0 ret',
'lea {1:s}, [{2:s} + 0x{0:x}]',
'{1:s}, {2:s} >> 0x{0:x}', # rshift
'{1:s}, {2:s} << 0x{0:x}', # lshift
'{1:s}, {2:s} | 0x{0:x}', # or
'{1:s}, {2:s} & 0x{0:x}', # and
'{1:s}, {2:s} ^ 0x{0:x}' # xor
]
#print(len(assembly_mapping))
addr = 0x1190
for i in instructions:
print('{:>10s}'.format('0x{:x}: '.format(addr)), end='')
instr = int.from_bytes(i[2:10], byteorder='little')
opcode = i[10]
arg1 = registers[i[11]]
arg2 = registers[i[12]]
arg3 = registers[i[13]]
print(asm_instr[opcode].format(instr, arg1, arg2, arg3))
addr += 14
with open('flag_func', 'rb') as f:
flag_func = f.read()
instructions = [flag_func[i*14:(i+1)*14] for i in range(len(flag_func)//14)]
disassemble(instructions)
Now lets take a look at the actual disassembly of the vm
0x1190: rax = 0x2
0x119e: rdi != 0x2 ; r8 = 1
0x11ac: if r8 != 0 ret
0x11ba: lea rsi, [rsi + 0x8]
0x11c8: rdi = qword [rsi+0x0]
0x11d6: rip = 0x11e4
0x11e4: push rbp
0x11f2: rbp = rsp
0x1200: lea rsp, [rsp + 0xfffffffffffffff0]
0x120e: mov qword [rbp+0xfffffffffffffff0], rdi
0x121c: call 0x17da
0x122a: rax != 0x24 ; rax = 1
0x1238: if rax != 0 jmp 0x13ce
0x1246: rdi = 0x20cd
0x1254: call 0x17da
0x1262: mov qword [rbp+0xfffffffffffffff8], rax
0x1270: rdi = qword [rbp+0xfffffffffffffff0]
0x127e: rsi = 0x20cd
0x128c: rdx = rax
0x129a: call 0x182e
0x12a8: if rax != 0 jmp 0x13ce
0x12b6: rdi = qword [rbp+0xfffffffffffffff0]
0x12c4: rsi = byte [rdi+0x23]
0x12d2: rsi != 0x7d ; rsi = 1
0x12e0: if rsi != 0 jmp 0x13ce
0x12ee: rsi = qword [rbp+0xfffffffffffffff8]
0x12fc: rdi = rdi + rsi
0x130a: rsi = -rsi
0x1318: lea rsi, [rsi + 0x23]
0x1326: push rsi
0x1334: call 0x1406
0x1342: pop rsi
0x1350: if rax = 0 jmp 0x13ce
0x135e: rdi = rax
0x136c: push rdi
0x137a: call 0x15fe
0x1388: pop rdi
0x1396: push rax
0x13a4: call 0x0
0x13b2: pop rax
0x13c0: rip = 0x13dc
0x13ce: rax = 0x1
0x13dc: rsp = rbp
0x13ea: pop rbp
0x13f8: ret
0x1406: rax = 0x0
0x1414: if rsi = 0 ret
0x1422: push rdi
0x1430: push rsi
0x143e: rdi, rsi << 0x3
0x144c: call 0x0
0x145a: pop rsi
0x1468: pop rdi
0x1476: if rax = 0 ret
0x1484: r8 = rax
0x1492: r9 = rax
0x14a0: push r9
0x14ae: push r8
0x14bc: push rdi
0x14ca: push rsi
0x14d8: rsi = byte [rdi+0x0]
0x14e6: rdi = 0x20a0
0x14f4: call 0x18c8
0x1502: pop rsi
0x1510: pop rdi
0x151e: pop r8
0x152c: pop r9
0x153a: if rax = 0 jmp 0x15c6
0x1548: r10 = 0x20a0
0x1556: rax = rax - r10
0x1564: mov qword [r9+0x0], rax
0x1572: lea r9, [r9 + 0x8]
0x1580: lea rdi, [rdi + 0x1]
0x158e: lea rsi, [rsi + 0xffffffffffffffff]
0x159c: if rsi != 0 jmp 0x14a0
0x15aa: rax = r8
0x15b8: ret
0x15c6: rdi = r8
0x15d4: call 0x0
0x15e2: rax = 0x0
0x15f0: ret
0x15fe: rax = 0x1
0x160c: if rsi = 0 ret
0x161a: r8 = qword [rdi+0x0]
0x1628: r8 != 0x16 ; r8 = 1
0x1636: if r8 != 0 ret
0x1644: push rdi
0x1652: push rsi
0x1660: call 0x1724
0x166e: pop rsi
0x167c: pop rdi
0x168a: lea rsi, [rsi + 0xffffffffffffffff]
0x1698: push rdi
0x16a6: rdx, rsi << 0x3
0x16b4: rsi = 0x1fa0
0x16c2: call 0x182e
0x16d0: pop rdi
0x16de: r8 = rax
0x16ec: rax = 0x1
0x16fa: if r8 != 0 ret
0x1708: rax = 0x0
0x1716: ret
0x1724: if rsi = 0 ret
0x1732: lea rsi, [rsi + 0xffffffffffffffff]
0x1740: if rsi = 0 ret
0x174e: r8 = qword [rdi+0x0]
0x175c: r9 = qword [rdi+0x8]
0x176a: r8 = r9 - r8
0x1778: r8 = r8 ^ rsi
0x1786: r9 = r8 * r8
0x1794: r8 = r9 * r8
0x17a2: mov qword [rdi+0x0], r8
0x17b0: lea rdi, [rdi + 0x8]
0x17be: lea rsi, [rsi + 0xffffffffffffffff]
0x17cc: rip = 0x1740
0x17da: rax = 0x0
0x17e8: r10 = byte [rdi+0x0]
0x17f6: if r10 = 0 ret
0x1804: lea rdi, [rdi + 0x1]
0x1812: lea rax, [rax + 0x1]
0x1820: rip = 0x17e8
0x182e: rax = 0x0
0x183c: if rdx = 0 ret
0x184a: r8 = byte [rdi+0x0]
0x1858: r9 = byte [rsi+0x0]
0x1866: r8 = r8 ^ r9
0x1874: rax = rax | r8
0x1882: lea rdx, [rdx + 0xffffffffffffffff]
0x1890: lea rdi, [rdi + 0x1]
0x189e: lea rsi, [rsi + 0x1]
0x18ac: if rdx != 0 jmp 0x184a
0x18ba: ret
0x18c8: rax = rdi
0x18d6: r8 = byte [rax+0x0]
0x18e4: if r8 = 0 jmp 0x192a
0x18f2: r8 == rsi ; r8 = 1
0x1900: if r8 != 0 ret
0x190e: lea rax, [rax + 0x1]
0x191c: rip = 0x18d6
0x192a: rax = 0x0
0x1938: ret
Flag verification⌗
The actual verification goes like this
- if argc != 2 ; returns
- if input != len(flag) ; returns
- if input doesn’t start with
FLAG{
; returns - if input doesn’t end with
}
; returns - if flag[0] !=
x
; returns - now it verifies our flag with a global array of and checks if the indices of flag are correct
- now it compares it with a array at
0x1fa0
and if they are equal returns 0 and printsOK!
if its correct or else it printsWrong
To verify the indices of the flag of which is between 30 characters with the global array it uses this algorithm
iterates over 30 - 1 times cause the len of the flag will be reduced as we already know the first character which is x
r8 = qword [rdi+0x0]
r9 = qword [rdi+0x8]
r8 = r9 - r8
r8 = r8 ^ rsi
r9 = r8 * r8
r8 = r9 * r8
mov qword [rdi+0x0], r8
The solve script using z3
#!/usr/bin/env python3
from z3 import *
characters = 'abdfgehikmanoqrstucvwlxyz-01h23p456u78j9-_.+'
hash = [
5832, -29791, -8000, 13824, -6859, 5832, -29791, 24389, -10648, -8, 24389, -13824, -17576, 2744,
-17576, 19683, -32768, 729, 19683, -1, 729, 1000, 125, -5832, 512, 512, -6859, 8000, -8000
]
x = [BitVec(f'x[{i}]', 16) for i in range(30)] # 30 char of input
s = Solver()
s.add(x[0] == 22) # first char = x
indices = []
for i in range(29): # len of flag - 1
r8 = x[i]
r9 = x[i+1]
r8 = r9 - r8
r8 = r8 ^ (29 - i) # rsi = 29 - i
r9 = r8 * r8
r8 = r9 * r8
indices.append(r8)
i+=1
for i in range(29):
s.add(indices[i] == hash[i])
s.add(And(x[i] >= 0, x[i] < 44))
flag = ''
if s.check() == sat:
m = s.model()
for i in range(30):
obj = x[i]
flag += characters[m[obj].as_long()]
print(s.assertions())
print(f"FLAG{{{flag}}}")
And voila when we run the script we get the flag. We’ll check it actual binary which should print OK!
if its a valid flag
▲ vm/hell86/solve ./solve
FLAG{x86-1s-s0-fund4m3nt4lly-br0k3n}
▲ vm/hell86/solve ./hell86 FLAG{x86-1s-s0-fund4m3nt4lly-br0k3n}
OK!
That was an awesome crackme