krev - BsidesDelhi CTF
In this challenge, the goal is to reverse a NetBSD kernel module and get the flag. We are provided with a zip file which contains the kernel module, NetBSD disc img, and couple of scripts to setup qemu and gdb for the challenge. It would have been easier to solve the challenge dynamically by setting up qemu and gdb but due to no experience with NetBSD reversing i proceeded to analyze the kernel module statically with IDA.
Taking a look at the strings doesn’t reveal much, but there are interesting functions in the kernel module. The two functions which looked interesting were get_flag_ready and chall1_read.
So I decided to start the analyses from chall1_read. The disassembly of chall1_read is pretty interesting.
.text:0800037D cmp byte ptr [eax], 67h
.text:08000380 jz short loc_80003C0
...........
.text:080003C0 loc_80003C0:
.text:080003C0 cmp byte ptr [eax+1], 69h
.text:080003C4 jnz short loc_8000382
.text:080003C6 cmp byte ptr [eax+2], 76h
.text:080003CA jnz short loc_8000382
.text:080003CC cmp byte ptr [eax+3], 65h
.text:080003D0 jnz short loc_8000382
.text:080003D2 cmp byte ptr [eax+4], 5Fh
.text:080003D6 jnz short loc_8000382
.text:080003D8 cmp byte ptr [eax+5], 74h
.text:080003DC jnz short loc_8000382
.text:080003DE cmp byte ptr [eax+6], 68h
.text:080003E2 jnz short loc_8000382
.text:080003E4 cmp byte ptr [eax+7], 69h
.text:080003E8 jnz short loc_8000382
.text:080003EA cmp byte ptr [eax+8], 73h
.text:080003EE jnz short loc_8000382
.text:080003F0 cmp byte ptr [eax+9], 5Fh
.text:080003F4 jnz short loc_8000382
.text:080003F6 cmp byte ptr [eax+0Ah], 74h
.text:080003FA jnz short loc_8000382
.text:080003FC cmp byte ptr [eax+0Bh], 6Fh
.text:08000400 jnz short loc_8000382
.text:08000402 cmp byte ptr [eax+0Ch], 5Fh
.text:08000406 jnz loc_8000382
.text:0800040C cmp byte ptr [eax+0Dh], 67h
.text:08000410 jnz loc_8000382
.text:08000416 cmp byte ptr [eax+0Eh], 65h
.text:0800041A jnz loc_8000382
.text:08000420 cmp byte ptr [eax+0Fh], 74h
.text:08000424 jnz loc_8000382
.text:0800042A cmp byte ptr [eax+10h], 5Fh
.text:0800042E jnz loc_8000382
.text:08000434 cmp byte ptr [eax+11h], 66h
.text:08000438 jnz loc_8000382
.text:0800043E cmp byte ptr [eax+12h], 6Ch
.text:08000442 jnz loc_8000382
.text:08000448 cmp byte ptr [eax+13h], 61h
.text:0800044C jnz loc_8000382
.text:08000452 cmp byte ptr [eax+14h], 67h
.text:08000456 jnz loc_8000382
.text:0800045C call get_flag_ready
So the input here is getting compared with “give_this_to_get_flag” and then after that get_flag_ready is being called. Now taking a look inside get_flag_ready function reveals that 40 bytes are placed on stack (local array) at the starting of the function. There are 2 functions which are called after this namely, md5hash and sha1hash. I think the crucial part of this challenge was determining what were md5hash and sha1hash functions doing.
Code Snippet from md5hash:
.text:0800016F push ebp
.text:08000170 mov ebp, esp
.text:08000172 push esi
.text:08000173 push ebx
.text:08000174 sub esp, 78h
.text:08000177 lea ebx, [ebp+var_60]
.text:0800017A mov [esp], ebx
.text:0800017D call MD5Init
.text:08000182 mov esi, s
.text:08000188 mov [esp], esi ; s
.text:0800018B call strlen
.text:08000190 mov [esp+8], eax
.text:08000194 mov [esp+4], esi
.text:08000198 mov [esp], ebx
.text:0800019B call MD5Update
.text:080001A0 mov [esp+4], ebx
.text:080001A4 lea esi, [ebp+var_70]
.text:080001A7 mov [esp], esi
.text:080001AA call MD5Final
.text:080001AF mov ebx, offset byte_80006B4
After some analysis, it was clear that this function is calculating md5 hash of a string but now the question was which string. As we can see above strlen there is an operand “s”, that is an operand name generated by IDA and it references the input string which was being compared to “give_this_to_get_flag“. This can be confirmed by checking the chall1_read function. So now its clear that md5hash function calculates the md5 hash of “give_this_to_get_flag” which is 29c5d56f77a0d0369c55101c53005050.
Code Snippet from sha1hash:
.text:0800021D mov ecx, eax
.text:0800021F add cl, al
.text:08000221 sbb edx, offset unk_80006B7
.text:08000227 mov [esp+8], edx
.text:0800022B mov dword ptr [esp+4], offset byte_80006B4
.text:08000233 mov [esp], ebx
.text:08000236 call SHA1Update
.text:0800023B mov [esp+4], ebx
.text:0800023F lea esi, [ebp+var_78]
.text:08000242 mov [esp], esi
.text:08000245 call SHA1Final
Now if we look carefully inside sha1hash function, we can see that it accesses the previously saved md5 hash and computes the sha1 hash of the md5 string. The sha1hash is 001b6a634bee73d9fe2d88bb4435fb1ee3ad7918.
After computing both md5 and sha1 (of md5) we are back in get_flag_ready_function. We can see from above image that the binary xor’s the earlier copied 40 bytes with the sha1 string we got from the sha1hash function. Here is a simple script to get the flag:
flag = [86, 92, 80, 5, 77, 15, 83, 71, 118, 87, 33, 58, 94, 6, 59, 13, 17, 22, 2, 9, 11, 103, 27, 82, 65, 107, 64, 93, 86, 23, 93, 1, 58, 4, 19, 29, 104, 80, 6, 69]
sha1hash = "001b6a634bee73d9fe2d88bb4435fb1ee3ad7918"
for i in range(0, 40):
flag[i] ^= ord(sha1hash[i])
print ''.join(map(chr, flag))
This gives us the flag: flag{netB5D_i5_4ws0m3_y0u_sh0uld_7ry_i7}