In this challenge, we are given a 32-bit ELF called x96.
After disassembling it in IDA, we get the following code at the entry point.
LOAD:8048054 start proc far
LOAD:8048054 dec eax
LOAD:8048055 mov ax, cs
LOAD:8048058 cmp ax, 23h
LOAD:804805C jnz loc_80481A5
LOAD:8048062 push eax
LOAD:8048063 or al, 13h
LOAD:8048065 push eax
LOAD:8048066 push offset dword_804806C
LOAD:804806B retf
The retf instruction at the end pops the IP, followed by the CS register. Looking at the preceding instructions, we can see that the value of CS register becomes 0x33 and that of IP becomes 0x804806c, which is actually the next instruction in the code.
However, IDA fails to identify it as code. After doing some research, I came across the fact that when CS is set to 0x33 the instructions are interpreted as 64-bit. You can read more about it here. Another interesting artefact of interpreting the code as 64-bit with IDA thinking it as 32-bit is that we see a lot of dec eax instructions. Coincidentally I remembered a tweet related to this that I had stored in my bookmarks.
So in order to understand the code we need IDA to interpret it as 64-bit. We can do this by selecting the area that we want to generate code for in the text-view, then going to Edit > Segment > Create Segment. Then in the dialog box that follows, make sure to select 64-bit segment. Once we have repurposed that area we can go to Edit > Code to generate the 64-bit code for that segment.
64seg:804806C loc_804806C:
64seg:804806C mov rax, 0DF3A0F66090F1B37h
64seg:8048076 mov rdi, 0E9F4E2EBE86423CAh
64seg:8048080 xor eax, eax
64seg:8048082 xor rdi, rdi
64seg:8048085 mov rsi, 80481F6h
64seg:804808C mov rdx, 24h
64seg:8048093 syscall ; LINUX - sys_read
64seg:8048095 mov rdx, offset sub_8048175
64seg:804809C mov ecx, 0
64seg:80480A1
64seg:80480A1 loc_80480A1:
64seg:80480A1 mov rbx, 358D0150819CF3C4h
64seg:80480AB ror rbx, cl
64seg:80480AE cmp ecx, 24h
64seg:80480B1 jz loc_8048154
64seg:80480B7 mov al, [ecx+80481F6h]
64seg:80480BE xor al, bl
64seg:80480C0 mov r15, 0B8E8AE0F00000000h
64seg:80480CA cmp al, ds:byte_80481C3[ecx]
64seg:80480D1 jz short loc_8048115
64seg:80480D3 push 0
64seg:80480D5 mov rax, 2300000000h
64seg:80480DF shr rax, 18h
64seg:80480E3 mov dword ptr [rsp+4], 0
64seg:80480EB mov [rsp+7], ah
64seg:80480EF mov dword ptr [rsp], offset unk_80480F8
64seg:80480F6 retfq
Beyond this, we get another string of data which IDA did not recognize as code. However, this time it is 32-bit code. So we can simply hit Edit > Code to get the intended code.
_32seg:80480F8 loc_80480F8:
_32seg:80480F8 dec eax
_32seg:80480F9 mov eax, 0
_32seg:80480FE mov edx, offset loc_80481A5
_32seg:8048103 inc ecx
_32seg:8048104 dec eax
_32seg:8048105 mov eax, 23h
_32seg:804810A push eax
_32seg:804810B dec eax
_32seg:804810C or al, 13h
_32seg:804810E push eax
_32seg:804810F push offset loc_80480A1
_32seg:8048114 retf
_32seg:8048114 _32seg ends
After we have the right code, the program is trivially easy to crack. It first checks the user input against the XOR of a set of values in the data section at address 0x80481C3, with a rotating value in a register — all of this is included in the code above.
Here I have written a python script to get the flag:
rbx = 0x358D0150819CF3C4
data = [0xa2, 0x8e, 0x90, 0x1f, 0x47, 0xf0, 0xfc, 0x9f, 0x87, 0x26, \
0x48, 0xaf, 0xa2, 0xd4, 0x2c, 0x4e, 0xaf, 0x91, 0x0d, 0x46, \
0x74, 0x7c, 0x59, 0x77, 0xb1, 0x1f, 0x52, 0x23, 0x3c, 0xe8, \
0x1d, 0xcc, 0x60, 0xcc, 0x67, 0x57]
def ror(n,d):
return (n>>d)|(n<<(64-d)) & 0xFFFFFFFFFFFFFFFF
flag = ''
for i in range(0x24):
flag += chr(data[i] ^ (ror(rbx,i)&0xff))
print(flag)
Flag: flag{n3xt_t1m3_w3_jump_t0_r34l_m0d3}