2023 年の 12 月 2 日ごろに開催されていた TUCTF に 0nePadding で参加しました。
簡単に Writeup を書いていきます。
もくじ
- What Are You Doing In My Swamp?(Forensic)
- State of the Git(Forensic)
- Table Encryption(Crypto)
- Custom ECB Cipher(Crypto)
- まとめ
What Are You Doing In My Swamp?(Forensic)
This challenge is like ogres, it has layers
問題バイナリとして与えられたファイルはマジックナンバーが削られていて画像ファイルとして参照できなかったため、JPG のマジックナンバーを手動で追加しました。
その結果、以下のような画像を参照できるようになりました。
ここからヒントがなかったので少々悩みましたが、最終的に layers
というパスワードを使用して steghide を使用したところ以下のメッセージを取得できました。
あとはこの GFXGU{LtIvh_zIv_oRpv_lmrOmh}
を Atbash Cipher にかけると正しい Flag を取得できました。
State of the Git(Forensic)
All the cool kids are embracing state of the art IAC technology, and we are rushing to catch up! We have a new system that we are testing out, but we are not sure how secure it is. Can you check it out for us?
問題バイナリとして .git ディレクトリといくつかのコードを含むアーカイブファイルが与えられます。
与えられたリポジトリを Github に push してみると、592 もの commit が存在していることがわかります。
ほとんどの commit のコミットメッセージは delete や add 、release、fix などから始まっています。
github 上で見るとブランチは 1 つしか出ませんでした。
これは私が完全にやらかしてましたが、ローカルリポジトリ内の main ブランチした github 上に push していなかったために、いくら github 上でコードを調べても Flag が見つからなくなってしまっていました。
ローカルブランチを確認するといくつかのブランチが存在していることがわかります。
ローカルブランチを release に変更した上で git log を grep してみると、1 つだけおかしなコミットメッセージが見つかりました。
この commit を調査すると、password に Base64 エンコードされた Flag が埋め込まれていました。
$ git --no-pager show c7ac66f
commit c7ac66fbd99c8850706c99b27475222f8dfe3d29
Author: MrLadas <97653268+MrLadas@users.noreply.github.com>
Date: Tue Nov 21 23:28:37 2023 +0000
jqnljvtngtrtpqdkvccfpqyskwnayzgdhurvuwdkxjtcldzhjcksiaagimzdyoflpodbgzfimxumbouesdkivaolamntydqtmwwj
diff --git a/terraform.tfstate b/terraform.tfstate
new file mode 100644
index 0000000..955de90
--- /dev/null
+++ b/terraform.tfstate
@@ -0,0 +1,103 @@
+{
+ "version": 4,
+ "terraform_version": "1.6.4",
+ "serial": 3,
+ "lineage": "0d21a79d-34f7-89e7-57f4-9266570147f4",
+ "outputs": {},
+ "resources": [
+ {
+ "mode": "managed",
+ "type": "droplet",
+ "name": "ctfd-dev-01",
+ "provider": "provider[\"registry.terraform.io/digitalocean/digitalocean\"]",
+ "instances": [
+ {
+ "schema_version": 0,
+ "attributes": {
+ "arch": "amd64",
+ "bwlimit": 0,
+ "clone": null,
+ "clone_storage": null,
+ "cmode": "tty",
+ "console": true,
+ "cores": 1,
+ "cpulimit": 0,
+ "cpuunits": 1024,
+ "description": "",
+ "features": [],
+ "force": false,
+ "full": null,
+ "hagroup": "",
+ "hastate": "",
+ "hookscript": "",
+ "hostname": "ctfd-dev-01",
+ "id": "aws/ctfd-dev-01",
+ "ignore_unpack_errors": false,
+ "lock": "",
+ "memory": 512,
+ "mountpoint": [],
+ "nameserver": "",
+ "network": [
+ {
+ "bridge": "vmbr0",
+ "firewall": true,
+ "gw": "192.168.1.1",
+ "gw6": "",
+ "hwaddr": "BC:24:11:15:79:0A",
+ "ip": "192.168.5.250/16",
+ "ip6": "",
+ "mtu": 0,
+ "name": "eth0",
+ "rate": 0,
+ "tag": 0,
+ "trunks": "",
+ "type": "veth"
+ }
+ ],
+ "onboot": true,
+ "ostemplate": "",
+ "ostype": "ubuntu",
+ "password": "VFVDVEZ7NzNycjRmMHJtX1M3QTczLTF5XzUzY3IzNzV9Cg==", // ZG9wX3YxXzA3ZmJjODgwY2YwNTNhOTE5Nzk4MDdkZmFhZjhhZDVjOTg4MGFiYWUxZjhkZjJjY2VjZTk2Njk0MmFmNDE0MDgK < Change this before going !
+ "pool": "Production",
+ "protection": false,
+ "restore": false,
+ "rootfs": [
+ {
+ "acl": false,
+ "quota": false,
+ "replicate": false,
+ "ro": false,
+ "shared": false,
+ "size": "8G",
+ "storage": "do-block-storage",
+ "volume": "do-block-storage:ctfd-dev-01/rootfs"
+ }
+ ],
+ "searchdomain": "",
+ "ssh_public_keys": null,
+ "start": true,
+ "startup": "",
+ "swap": 512,
+ "tags": "",
+ "template": false,
+ "timeouts": null,
+ "tty": 2,
+ "unique": false,
+ "unprivileged": true,
+ "unused": []
+ },
+ "sensitive_attributes": [
+ [
+ {
+ "type": "get_attr",
+ "value": "password"
+ }
+ ]
+ ],
+ "private": "sdkawewgfjfakqpwoqpretwenfwejweahwhuqhewdfhewf"
+ }
+ ]
+ }
+ ],
+ "check_results": null
+}
これをデコードすると Flag を取得できます。
$ echo VFVDVEZ7NzNycjRmMHJtX1M3QTczLTF5XzUzY3IzNzV9Cg== | base64 -d
TUCTF{73rr4f0rm_S7A73-1y_53cr375}
Table Encryption(Crypto)
You can’t crack my file! I am the exclusive owner of the encryption key!
問題バイナリとして table_encryption.xml.enc という名前の暗号化されたバイナリファイルが与えられます。
ファイル名以外にヒントがない状況でしたので、とりあえず XML のプロローグで XOR を取ってみると元のキーを復元することができました。
あとは以下の通り復元した XOR キーで暗号化を解除すると Flag を取得できました。
Custom ECB Cipher(Crypto)
I have designed a simple algorithm which I believe it’s secure. I am confident you cannot compromise my message with the x value.
問題バイナリとして以下の Python スクリプトが与えられます。
from Crypto.Util import number
flag = b"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
def convert(msg):
msg = msg ^ msg >> x
msg = msg ^ msg << 13 & 275128763
msg = msg ^ msg << 20 & 2186268085
msg = msg ^ msg >> 14
return msg
def transform(message):
assert len(message) % 4 == 0
new_message = b''
for i in range(int(len(message) / 4)):
block = message[i * 4 : i * 4 +4]
block = number.bytes_to_long(block)
block = convert(block)
block = number.long_to_bytes(block, 4)
new_message += block
return new_message
c = transform(flag[6:-1]).hex()
print('c =', c)
'''
c = e34a707c5c1970cc6375181577612a4ed07a2c3e3f441d6af808a8acd4310b89bd7e2bb9
'''
Flag の括弧内の文字列を 4 バイトごとに分割して convert 関数に送り、暗号化されたバイト列が c となっているようです。
また、x は不明ですが convert 関数の引数が 4 バイトになることは確定しているので、ここは 32 未満のいずれかの値になると予想できます。
当初は convert 関数の処理を下から順に逆算して最後に x の総当たりを試せばよいのではないかと考えていましたが、and 演算は破壊的な演算なので復号無理では?などとなり解けませんでした。
Writeup を読むと、どうもこの convert 関数の演算は破壊的ではなく、簡単に復号できるようでした。
まず最後の行から見ていきます。
以下のコードは、メッセージの下位 18 bit をそのメッセージ自身の上位 18 bit で XOR するコードです。
msg = msg ^ msg >> 14
あまり直感的では内容に思えますが、どうやらこのような「元のメッセージをシフトして XOR する」ような処理をおこなった場合、同じ演算を繰り返すことで最終的に元のメッセージを復元できるタイミングがあるようです。
実際に計算の過程をトレースしてみるとメッセージが 32bit の場合は msg = msg ^ msg >> 14
を 4 回行うと元の値に戻ることがわかりました。
そして、次の AND を含む計算も、(なぜか)破壊的な演算になっておらず、同じ演算をもう一度繰り返すことで元の値に復元できることを確かめられます。
これによって、あとは x の値をブルートフォースするだけで元のメッセージを復元できるようになります。
以下の Solver で Flag を復元できました。
from Crypto.Util import number
def inverse(msg,x):
for i in range(3):
msg = msg ^ msg >> 14
for i in range(1):
msg = msg ^ msg << 20 & 2186268085
for i in range(1):
msg = msg ^ msg << 13 & 275128763
for i in range(3):
msg = msg ^ msg >> x
return msg
data = number.long_to_bytes(0xe34a707c5c1970cc6375181577612a4ed07a2c3e3f441d6af808a8acd4310b89bd7e2bb9)
for x in range(32):
flag = b""
for i in range(0,len(data),4):
c = number.bytes_to_long(data[i:i+4])
flag += number.long_to_bytes(inverse(c,x))
print(flag)
まとめ
今回は Rev が全く解けなかったので精進します。。