home / ctf / x96
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. 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 didn't 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 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)