angstromctf 2018 writeup

CTFから一時期離れていたこともあり,記憶を呼び覚ましつつアウトプットして定着させたいという目的で, 記事をまとめていこうと思う.

直近でangstromctf 2018に参加. 個人的に,初心者向けとしてとてもよいバランスだったと思う. チーム P01TERGEISTとして出場. 1125 ポイント.100点以上の問題をもっと解けるようになりたい. 解きたかった問題はWeired Message, curly-cue, ofb, ssh, MadLibs, md5. 他のメンバーが解いていて自分で試していないものは省略する.

MISC

Waldo1

flags.zip というファイルが渡されるので,展開するとpngファイルが5つある. flag5.pngの画像内にflagがある. f:id:sorasyl:20180325133408p:plain

irc

チャンネルに参加すると上部にflagがあった.

Waldo2

問題ファイルを展開すると500個のファイルが渡される.拡張子はすべてjpgだが,waldo339.jpgの 中身はテキストファイルであった.異なるファイルを見つけるためにmd5sumコマンドを使用してもよい.

$ file waldo339.jpg
waldo339.jpg:  ASCII text
$ cat waldo339.jpg 
actf{r3d_4nd_wh1t3_str1p3s}

That's Not Mu Name...

pdfファイルを渡されるが開くことができない. fileコマンドで確認してみる.

$ file gettysburg.pdf 
gettysburg.pdf: Microsoft OOXML

Microsoft OOXMLをググってみるとhttps://ja.wikipedia.org/wiki/Office_Open_XMLの仕様に wordファイルをzip展開したファイル構成が書かれている. そこで,問題ファイルの中身をunzipコマンドで確認する.

$ unzip -l gettysburg.pdf 
Archive:  gettysburg.zip
  Length      Date    Time    Name
---------  ---------- -----   ----
      573  2018-03-14 23:47   _rels/.rels
      518  2018-03-14 23:47   docProps/app.xml
      731  2018-03-14 23:47   docProps/core.xml
      531  2018-03-14 23:47   word/_rels/document.xml.rels
      208  2018-03-14 23:47   word/settings.xml
      969  2018-03-14 23:47   word/fontTable.xml
     8994  2018-03-14 23:47   word/document.xml
     2387  2018-03-14 23:47   word/styles.xml
     1118  2018-03-14 23:47   [Content_Types].xml
---------                     -------
    16029                     9 files

似たようなファイル構成が見つかったので,libreofficeのWriterでファイルを見てみる. リンカーンゲティスバーグ演説に続いてflagがあった.

actf{thanks_mr_lincoln_but_who_even_uses_word_anymore}

File Transfer

pcapファイルが渡されるので,問題名からファイルを取り出すのだとわかる. wiresharkでpcapを開いてFile->Export ObjectsからHTTPを選択するとファイルがあった. f:id:sorasyl:20180325140859p:plain fileコマンドで確認するとJPEGファイルだったので,開くとflagがあった. f:id:sorasyl:20180325141034j:plain

gif

画像ファイルが渡される.strings,exiftool,binwalkとコマンドを試していったら ファイルの中に別のファイルがありそうだと分かった.

$ binwalk -e jiggs.gif 

DECIMAL       HEXADECIMAL     DESCRIPTION
--------------------------------------------------------------------------------
0             0x0             PNG image, 500 x 323, 8-bit/color RGB, non-interlaced
211           0xD3            Unix path: /www.w3.org/1999/02/22-rdf-syntax-ns#">
148050        0x24252         PNG image, 500 x 323, 8-bit/color RGB, non-interlaced
148261        0x24325         Unix path: /www.w3.org/1999/02/22-rdf-syntax-ns#">
292577        0x476E1         PNG image, 500 x 323, 8-bit/color RGB, non-interlaced
292788        0x477B4         Unix path: /www.w3.org/1999/02/22-rdf-syntax-ns#">
441489        0x6BC91         PNG image, 500 x 323, 8-bit/color RGB, non-interlaced
441700        0x6BD64         Unix path: /www.w3.org/1999/02/22-rdf-syntax-ns#">
581765        0x8E085         PNG image, 500 x 323, 8-bit/color RGB, non-interlaced
581976        0x8E158         Unix path: /www.w3.org/1999/02/22-rdf-syntax-ns#">
725461        0xB11D5         PNG image, 500 x 323, 8-bit/color RGB, non-interlaced
725672        0xB12A8         Unix path: /www.w3.org/1999/02/22-rdf-syntax-ns#">
857131        0xD142B         PNG image, 500 x 323, 8-bit/color RGB, non-interlaced
857342        0xD14FE         Unix path: /www.w3.org/1999/02/22-rdf-syntax-ns#">
987856        0xF12D0         PNG image, 500 x 323, 8-bit/color RGB, non-interlaced
988067        0xF13A3         Unix path: /www.w3.org/1999/02/22-rdf-syntax-ns#">

binwalkのオプションe(extract)では画像が取り出せなかった.何かやり方があるのか. ddコマンドでもできそうだが,バイトの指定が手間になる.取り出す方法はないかと foremostコマンドを試したら,作成されたoutput/pngディレクトリの中に画像があった.

$ tree output
output
├── audit.txt
└── png
    ├── 00000000.png
    ├── 00000289.png
    ├── 00000571.png
    ├── 00000862.png
    ├── 00001136.png
    ├── 00001416.png
    ├── 00001674.png
    └── 00001929.png

1 directory, 9 files

画像を確認していくと00001136.pngファイル内にflagが書いてあった. f:id:sorasyl:20180325142152p:plain

slots

ソースとncの接続先情報が与えられる. 接続してみるとスロットゲームができる.

$ nc web.angstromctf.com 3002
Welcome to Fruit Slots!
We've given you $10.00 on the house.
Once you're a high roller, we'll give you a flag.
You have $10.00.
Enter your bet: 1
🍐 : 🍈 : 🍈
🍉 : 🍇 : 🍒 ◀
🍒 : 🍌 : 🍒
You lost everything.
Play more to become a high roller!
You have $9.00.
Enter your bet: 9
🍐 : 🍌 : 🍒
🍇 : 🍌 : 🍒 ◀
🍒 : 🍇 : 🍉
You lost everything.
You have no money left. Low roller.

ソースを見ると入力値に対しての操作が改行を除くこととfloat()でエラーが出ないかのチェックだけだった. よってfloat()でエラーが出ないような入力値を調べることにした. https://docs.python.jp/3/library/functions.html#float どうやら,infinityやnanという文字列もfloat()は受け付けるらしい. nanを入力したところ,flagが出た.

$ nc web.angstromctf.com 3002
Welcome to Fruit Slots!
We've given you $10.00 on the house.
Once you're a high roller, we'll give you a flag.
You have $10.00.
Enter your bet: nan
🍐 : 🍈 : 🍈
🍉 : 🍇 : 🍒 ◀
🍒 : 🍌 : 🍒
You lost everything.
Wow, you're a high roller!
A flag: actf{fruity}

理由はfloat('nan')はif文チェックで以下のようになるため.

$ python
Python 2.7.13 (default, Nov 24 2017, 17:33:09) 
[GCC 6.3.0 20170516] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> 'nan' < 0
False
>>> float('nan') <= 0
False
>>> float('nan') <= 10
False
>>> money = 10 - 0 - float('nan')
>>> money
nan
>>> money <=0 
False
>>> money < 1000000000 
False

CRYPTO

Warmup

問題文:myjd{ij_fkwizq}

flagの形式になっていることから換字式暗号だと考えて,ROTやAfiineを試した. http://rumkin.com/tools/cipher/affine.php サイトを使って,a:11,b:24のときflagが読めた.

actf{it_begins}

Back to Base0ics

PART2に手こずったのでsubmitが結構遅かった問題. 問題文は次の通り.

Part 1: 011000010110001101110100011001100111101100110000011011100110010101011111011101000111011100110000010111110110011000110000
Part 2: 165 162 137 145 151 147 150 164 137 163 151 170 164 63 63 
Part 3: 6e5f7468317274797477305f733178
Part 4: dHlmMHVyX25vX20wcmV9

Flag is the concatenation of the four decoded parts.

それぞれ2進数,8進数,16進数,64進数(Base64)の文字列として読んで,ASCII文字に直すとそれぞれ次のようになる.

actf{0ne_tw0_f0
ur_eight_sixt33
n_th1rtytw0_s1x
tyf0ur_no_m0re}

8進数はなかなか使わない.他のPartの答えから,Part2の答えを推測してsubmitされたら弾かれたので, Part2を解いたらleetの法則がPart2だけ違う...引っかかってしまった.

XOR

問題文

fbf9eefce1f2f5eaffc5e3f5efc5efe9fffec5fbc5e9f9e8f3eaeee7

flagのフォーマットがactf{}なので,XORしたときにこのフォーマットになるように鍵を見つければいい.問題文に鍵は1バイトと 書かれている.

q = "fbf9eefce1f2f5eaffc5e3f5efc5efe9fffec5fbc5e9f9e8f3eaeee7"
b = bin(int(q, 16))[2:]
list = [b[i:i+8] for i in range(0, len(b), 8)]
ans = ''
for c in list:
    ans += chr(int(c,2) ^ 154)
print ans
$ python solve_xor.py 
actf{hope_you_used_a_script}

Intro to RSA

p, qが分かっているので普通にRSAでcをデコードできる.

import gmpy
import sys

p = 169524110085046954319747170465105648233168702937955683889447853815898670069828343980818367807171215202643149176857117014826791242142210124521380573480143683660195568906553119683192470329413953411905742074448392816913467035316596822218317488903257069007949137629543010054246885909276872349326142152285347048927
q = 170780128973387404254550233211898468299200117082734909936129463191969072080198908267381169837578188594808676174446856901962451707859231958269401958672950141944679827844646158659922175597068183903642473161665782065958249304202759597168259072368123700040163659262941978786363797334903233540121308223989457248267
e = 65537

c = 4531850464036745618300770366164614386495084945985129111541252641569745463086472656370005978297267807299415858324820149933137259813719550825795569865301790252501254180057121806754411506817019631341846094836070057184169015820234429382145019281935017707994070217705460907511942438972962653164287761695982230728969508370400854478181107445003385579261993625770566932506870421547033934140554009090766102575218045185956824020910463996496543098753308927618692783836021742365910050093343747616861660744940014683025321538719970946739880943167282065095406465354971096477229669290277771547093476011147370441338501427786766482964

phi = (p-1)*(q-1)
d = gmpy.invert(e, phi)
m = pow(c, d, p*q)
print hex(m)[2:].decode('hex')
$ python solve.py 
actf{rsa_is_reallllly_fun!!!!!!}

WEB

Source Me 1

ソースを見るとadminのパスワードが書いてあった.

<!-- Shh, don't tell anyone. The admin password is f7s0jkl -->

get me

submitを押すと次のようになる.

 Hey, you're not authorized!

URLに?auth=falseとあるので,trueに書き換えるとflagがでる.

Here you go: actf{why_did_you_get_me}

Sequel

star warsのファンクラブサイト(偽)のログイン画面がある.SQLiで一番簡単な' OR 1=1 -- を入れるとログインできた.

actf{sql_injection_more_like_prequel_injection}

Source Me 2

ソースを見るとmd5ハッシュでパスワードを比較しているのでmd5ハッシュ値ググる

bdc87b9c894da5168059e00ebffb9077 password1234

パスワードがpassword1234であると分かったのでログインしてflagを得た.

 Welcome, admin. Here is your flag: actf{md5_hash_browns_and_pasta_sauce}

以下はうろ覚えなので簡単にまとめる.

RE

run me

実行するだけ.実行権限がなければchmodでつける

Rev1

正しいパスワードを見つける問題.

$ ./rev1_32 
Welcome to your first Reverse Engineering challenge!
What is the password to this file? Enter password here: aaaa
Sorry, the password isn't aaaa. Try again!

ltraceコマンドで実行してみると比較している文字列が分かった.

$ ltrace ./rev1_32 
__libc_start_main(0x80485db, 1, 0xffa92e64, 0x80486e0 <unfinished ...>
puts("Welcome to your first Reverse En"...Welcome to your first Reverse Engineering challenge!
)            = 53
printf("What is the password to this fil"...)          = 56
fgets(What is the password to this file? Enter password here: a
"a\n", 64, 0xf76cd5a0)                           = 0xffa92d6c
strlen("a\n")                                          = 2
strcmp("s3cret_pa55word", "a")                         = 1
printf("Sorry, the password isn't %s. Tr"..., "a"Sorry, the password isn't a. Try again!
)     = 40
+++ exited (status 0) +++

注目はstrcmp()."s3cret_pa55word"を入力すればいいと分かる.

Rev2

ディスアセンブルするとLevel1では入力値を0x11d7と比較している

|           ; "Level 1: What number am I thinking of: "
|           0x08048522      push str.Level_1:_What_number_am_I_thinking_of:
|           0x08048527      call sym.imp.printf
|           0x0804852c      add esp, 0x10
|           0x0804852f      sub esp, 8
|           0x08048532      lea eax, [local_1ch]
|           0x08048535      push eax
|           0x08048536      push 0x8048730
|           0x0804853b      call sym.imp.__isoc99_scanf
|           0x08048540      add esp, 0x10
|           0x08048543      mov eax, dword [local_1ch]
|           0x08048546      cmp eax, 0x11d7
|       ,=< 0x0804854b      je 0x804856b

よって0x11d7を10進に直した4567で通る.

Level2では2つの2桁の数字を要求している.

|      ||   0x080485da      imul eax, edx
|      ||   0x080485dd      mov dword [local_10h], eax
|      ||   ; [0xd67:4]=-1
|      ||   ; 3431
|      ||   0x080485e0      cmp dword [local_10h], 0xd67
|     ,===< 0x080485e7      je 0x8048608

入力された2つの値の積が0xd67であるかをチェックしているので因数を調べる.

In [1]: import sympy

In [2]: sympy.factorint(int("0xd67", 16))
Out[2]: {47: 1, 73: 1}

47と73を入れればflagが出る.

$ ./rev2_32 
Welcome to Rev2! You'll probably want to use a dissassembler or gdb.
Level 1: What number am I thinking of: 4567
Level 2: Which two two-digit numbers will solve this level. Enter the two numbers separated by a single space (num1 should be the lesser of the two): 47 73
Congrats, you passed Rev2! The flag is: actf{4567_47_73}

Rev3

入力値をencode関数で変換し,"egzloxi|ixw]dkSe]dzSzccShejSi3q"と一致するかを比較している. encode関数は次の通りで,これくらいなら解読できるのでdecode関数を作る. f:id:sorasyl:20180325154729p:plain

HopperならC言語ライクに見ることもできる.

int encode(int arg0, int arg1) {
    var_8 = arg1;
    var_4 = arg0;
    var_C = strlen(var_4);
    var_10 = 0x0;
    do {
            eax = var_10;
            if (eax >= var_C) {
                break;
            }
            *(int8_t *)(var_8 + var_10) = (*(int8_t *)(var_4 + var_10) & 0xff ^ 0x9) - 0x3;
            var_10 = var_10 + 0x1;
    } while (true);
    return eax;
}

decode関数は次の通り.

def decode(encoded):
    ans = ''
    for c in encoded:
        tmp = (ord(c)+0x3) ^ 0x9
        ans += str(chr(tmp))

    print ans

if __name__ == '__main__':
    decode("egzloxi|ixw]dkSe]dzSzccShejSi^3q")
$ python solve_rev3.py 
actf{reversing_aint_too_bad_eh?}

BINARY

Accumulator

省略

Cookie Jar

省略

Number Guess

省略

Rop to the Top

省略