Year3000 [RE - Nullcon Hackim2020 CTF]
Description
1One day when I came home at lunchtime I heard a funny noise Went out to the
2back yard to find out If it was one of those rowdy boys Stood there was my
3neighbor called Peter And a flux capacitor. I guess there must be quite some
4entropy in a flux capacitor...
5
6Netcat Link : nc re.ctf.nullcon.net 1234
we were given 3000 stripped ELF binaries some 64bit binaries and 32bit binaries. the description of the challenge did’nt explained what exactly the task was, so i tried to connect to the given netcat link.
1x3ero0 :: year3000 » nc re.ctf.nullcon.net 1234
21252.bin
so it was clear that for each binary we need to send the correct password of the binary. lets reverse engineer one of the binaries.
public start
start proc near
xor ebp, ebp
pop esi
mov ecx, esp
and esp, 0FFFFFFF0h
push eax
push esp ; stack_end
push edx ; rtld_fini
call sub_542
add ebx, 1AA0h
lea eax, (nullsub_1 - 1FC0h)[ebx]
push eax ; fini
lea eax, (sub_7B0 - 1FC0h)[ebx]
push eax ; init
push ecx ; ubp_av
push esi ; argc
push ds:(off_1FF8 - 1FC0h)[ebx] ; main
call ___libc_start_main
hlt
start endp
i calculated the offset of main function through gdb and it was sub_6D0
.text:000006D0
.text:000006D0 ; =============== S U B R O U T I N E =======================================
.text:000006D0
.text:000006D0 ; Attributes: bp-based frame fuzzy-sp
.text:000006D0
.text:000006D0 sub_6D0 proc near ; DATA XREF: .got:off_1FF8↓o
.text:000006D0
.text:000006D0 var_74 = dword ptr -74h
.text:000006D0 s = byte ptr -70h
.text:000006D0 var_C = dword ptr -0Ch
.text:000006D0
.text:000006D0 ; __unwind {
.text:000006D0 lea ecx, [esp+4]
.text:000006D4 and esp, 0FFFFFFF0h
.text:000006D7 push dword ptr [ecx-4]
.text:000006DA push ebp
.text:000006DB mov ebp, esp
.text:000006DD push ebx
.text:000006DE push ecx
.text:000006DF sub esp, 70h
.text:000006E2 call sub_550
.text:000006E7 add ebx, 18D9h
.text:000006ED mov eax, large gs:14h
.text:000006F3 mov [ebp+var_C], eax
.text:000006F6 xor eax, eax
.text:000006F8 mov eax, ds:(stdin_ptr - 1FC0h)[ebx]
.text:000006FE mov eax, [eax]
.text:00000700 push 0 ; n
.text:00000702 push 2 ; modes
.text:00000704 push 0 ; buf
.text:00000706 push eax ; stream
.text:00000707 call _setvbuf
.text:0000070C add esp, 10h
.text:0000070F mov eax, ds:(stdout_ptr - 1FC0h)[ebx]
.text:00000715 mov eax, [eax]
.text:00000717 push 0 ; n
.text:00000719 push 2 ; modes
.text:0000071B push 0 ; buf
.text:0000071D push eax ; stream
.text:0000071E call _setvbuf
.text:00000723 add esp, 10h
.text:00000726 mov eax, ds:(stdin_ptr - 1FC0h)[ebx]
.text:0000072C mov eax, [eax]
.text:0000072E sub esp, 4
.text:00000731 push eax ; stream
.text:00000732 push 64h ; 'd' ; n
.text:00000734 lea eax, [ebp+s]
.text:00000737 push eax ; s
.text:00000738 call _fgets
.text:0000073D add esp, 10h
.text:00000740 sub esp, 0Ch
.text:00000743 lea eax, [ebp+s]
.text:00000746 push eax
.text:00000747 call sub_64D
.text:0000074C add esp, 10h
.text:0000074F mov [ebp+var_74], eax
.text:00000752 cmp [ebp+var_74], 0
.text:00000756 jz short loc_76C
.text:00000758 sub esp, 0Ch
.text:0000075B lea eax, (aWellDone - 1FC0h)[ebx] ; "Well done"
.text:00000761 push eax ; s
.text:00000762 call _puts
.text:00000767 add esp, 10h
.text:0000076A jmp short loc_77E
.text:0000076C ; ---------------------------------------------------------------------------
.text:0000076C
.text:0000076C loc_76C: ; CODE XREF: sub_6D0+86↑j
.text:0000076C sub esp, 0Ch
.text:0000076F lea eax, (aYouHaveFailed - 1FC0h)[ebx] ; "You have failed"
.text:00000775 push eax ; s
.text:00000776 call _puts
.text:0000077B add esp, 10h
.text:0000077E
.text:0000077E loc_77E: ; CODE XREF: sub_6D0+9A↑j
.text:0000077E cmp [ebp+var_74], 1
.text:00000782 setnz al
.text:00000785 movzx eax, al
.text:00000788 mov edx, [ebp+var_C]
.text:0000078B xor edx, large gs:14h
.text:00000792 jz short loc_799
.text:00000794 call sub_820
.text:00000799 ; ---------------------------------------------------------------------------
.text:00000799
.text:00000799 loc_799: ; CODE XREF: sub_6D0+C2↑j
.text:00000799 lea esp, [ebp-8]
.text:0000079C pop ecx
.text:0000079D pop ebx
.text:0000079E pop ebp
.text:0000079F lea esp, [ecx-4]
.text:000007A2 retn
.text:000007A2 ; } // starts at 6D0
.text:000007A2 sub_6D0 endp
.text:000007A2
lets look at sub_64D
.text:0000064D ; =============== S U B R O U T I N E =======================================
.text:0000064D
.text:0000064D ; Attributes: bp-based frame
.text:0000064D
.text:0000064D sub_64D proc near ; CODE XREF: sub_6D0+77↓p
.text:0000064D
.text:0000064D var_15 = byte ptr -15h
.text:0000064D var_14 = dword ptr -14h
.text:0000064D var_10 = dword ptr -10h
.text:0000064D var_C = dword ptr -0Ch
.text:0000064D var_4 = dword ptr -4
.text:0000064D arg_0 = dword ptr 8
.text:0000064D
.text:0000064D ; __unwind {
.text:0000064D push ebp
.text:0000064E mov ebp, esp
.text:00000650 push ebx
.text:00000651 sub esp, 00000014h
.text:00000654 call sub_7A3
.text:00000659 add eax, 1967h
.text:0000065E mov [ebp+var_C], 54h ; 'T'
.text:00000665 mov [ebp+var_15], 63h ; 'c'
.text:00000669 mov [ebp+var_14], 1
.text:00000670 mov [ebp+var_10], 0
.text:00000677 jmp short loc_696
.text:00000679 ; ---------------------------------------------------------------------------
.text:00000679
.text:00000679 loc_679: ; CODE XREF: sub_64D+4F↓j
.text:00000679 mov ecx, [ebp+var_10]
.text:0000067C mov edx, [ebp+arg_0]
.text:0000067F add edx, ecx
.text:00000681 movzx edx, byte ptr [edx]
.text:00000684 cmp [ebp+var_15], dl
.text:00000687 jz short loc_692
.text:00000689 mov [ebp+var_14], 0
.text:00000690 jmp short loc_69E
.text:00000692 ; ---------------------------------------------------------------------------
.text:00000692
.text:00000692 loc_692: ; CODE XREF: sub_64D+3A↑j
.text:00000692 add [ebp+var_10], 1
.text:00000696
.text:00000696 loc_696: ; CODE XREF: sub_64D+2A↑j
.text:00000696 mov edx, [ebp+var_10]
.text:00000699 cmp edx, [ebp+var_C]
.text:0000069C jl short loc_679
.text:0000069E
.text:0000069E loc_69E: ; CODE XREF: sub_64D+43↑j
.text:0000069E mov ecx, [ebp+var_C]
.text:000006A1 mov edx, [ebp+arg_0]
.text:000006A4 add ecx, edx
.text:000006A6 sub esp, 4
.text:000006A9 push 4 ; n
.text:000006AB lea edx, (dword_2008 - 1FC0h)[eax]
.text:000006B1 push edx ; s2
.text:000006B2 push ecx ; s1
.text:000006B3 mov ebx, eax
.text:000006B5 call _memcmp
.text:000006BA add esp, 10h
.text:000006BD test eax, eax
.text:000006BF jz short loc_6C8
.text:000006C1 mov [ebp+var_14], 0
.text:000006C8
.text:000006C8 loc_6C8: ; CODE XREF: sub_64D+72↑j
.text:000006C8 mov eax, [ebp+var_14]
.text:000006CB mov ebx, [ebp+var_4]
.text:000006CE leave
.text:000006CF retn
.text:000006CF ; } // starts at 64D
.text:000006CF sub_64D endp
.text:000006CF
if you see this function and notice that at 0x0000065e a number is loaded into dword [local_ch] and at 0x00000665 a character is loaded into byte [local_15h].
.text:0000065E mov [ebp+var_C], 54h ; 'T'
.text:00000665 mov [ebp+var_15], 63h ; 'c'
the following loop after this simply checks if the user input is ‘c’ *0x54 then comes a memcmp at 0x6b5. now to statically analyse the arguments passed to memcmp we need to figure out whats going at 0x6b0
.text:000006AB lea edx, (unk_2008 - 1FC0h)[eax]
now lets see what exactly is the value of eax at this instruction. we scroll a little bit up
.text:00000654 call sub_7A3
.text:00000659 add eax, 1967h
lets see what this function is at 0x7a3
.text:000007A3 sub_7A3 proc near ; CODE XREF: sub_64D+7↑p
.text:000007A3 ; __unwind {
.text:000007A3 mov eax, [esp+0]
.text:000007A6 retn
.text:000007A6 ; } // starts at 7A3
.text:000007A6 sub_7A3 endp
.text:000007A6
it returns the address which was pushed on the stack when this function was called. basically eax = address_of_next_instruction_after_call_0x7a3, so eax = 0x00000659 and then eax += 0x1967, so eax = 0x1fc0. okay now lets look at the instruction at 0x000006ab again
; from radare2
0x000006ab lea edx, dword [eax + 0x48]
so it becomes
; from radare2
0x000006ab lea edx, dword [0x1fc0 + 0x48] ; 0x2008
and you can actually see that ida already calculated the offset for us. so memcmp checks that the bytes after ‘c’ * 0x54 with the dword value at 0x2008 which is
.data:00002008 dword_2008 dd 0A7C6D169h
also one more thing to point out is that this value is always stored at the end of .data section. so the correct password for 3.bin is
1import struct
2print c * 0x54 + struct.pack(<I, 0xA7C6D169)
3
output ->
1x3ero0 :: year3000 » python solve_3.py | ./3.bin
2Well done
so i quickly wrote a script to fetch these values and generate a correct password for all the binaries both 64 bit binaries or 32 bit binaries.
exploit script –>
1from pwn import *
2import base64
3
4
5context.log_level = "critical"
6
7def solve(filename):
8
9 file = open(filename, "rb").read()
10
11 ELF64_Coun = 0x816
12 ELF64_Valu = 0x81d
13
14 ELF32_Coun = 0x65e
15 ELF32_Valu = 0x665
16
17
18 ELF64_Addr = 0x1010
19 ELF32_Addr = 0x1008
20
21
22 Format = ord(file[0x04])
23 if (Format == 2):
24
25 context.update(arch='amd64', os='linux')
26
27 code_count = file[ELF64_Coun : ELF64_Coun+7]
28 code_value = file[ELF64_Valu : ELF64_Valu+4]
29
30 disa_count = disasm(code_count)
31 disa_value = disasm(code_value)
32
33 code_count = int(disa_count.split('0x')[-1], 16)
34 code_value = chr(int(disa_value.split('0x')[-1], 16))
35
36 #value = u64(file[ELF64_Addr : ELF64_Addr+8])
37 value = file[ELF64_Addr : ELF64_Addr+8]
38 value_n = u64(value)
39
40 flag = code_value * code_count + value
41
42 elif(Format == 1):
43
44 context.update(arch='i386', os='linux')
45
46 code_count = file[ELF32_Coun : ELF32_Coun+7]
47 code_value = file[ELF32_Valu : ELF32_Valu+4]
48
49 disa_count = disasm(code_count)
50 disa_value = disasm(code_value)
51
52 code_count = int(disa_count.split('0x')[-1], 16)
53 code_value = chr(int(disa_value.split('0x')[-1], 16))
54
55 #value = u32(file[ELF32_Addr:ELF32_Addr+4])
56 value = file[ELF32_Addr:ELF32_Addr+4]
57 value_n = u32(value)
58 flag = code_value * code_count + value
59
60 #print "[!] file : " + filename
61 #print "[!] count: " + hex(code_count)
62 #print "[!] value: " + code_value
63 #print "[!] numbe: " + hex(value_n)
64
65 return flag
66
67
68def main():
69
70
71 dump = ""
72 i = 0
73 p = remote("re.ctf.nullcon.net", 1234)
74 a = ""
75 temp = ""
76 while('hackim' not in dump):
77
78 # i noticed that only 10 files were asked
79 if(i==10):
80 print "Flag: " + p.recvline()
81 p.close()
82 exit()
83 file = p.recvline().strip()
84
85 a = p.recvuntil("> ")
86 solver = solve(file)
87 # .encode('base64') doesnt work with bytes
88 p.sendline(base64.b64encode(solver))
89 #print str(solver).encode('base64')
90 temp = p.recvline()
91 if("Well done" in temp):
92 print "[0x%.4x]\t" % i+ file + '\t: Well Done'
93 else:
94 print "[+] Failed:\t" + file + "\tdumping recieved data"
95 exit()
96 i += 1
97
98if __name__ == "__main__":
99 main()
and running this script gives us the flag