Hack The Box(HTB) - CTF Try Out : FlagCasino Walkthrough

 FlagCasino Walkthrough


進入到 Reversing 類型的 CTF,這一題顯示如下

又是一題僅有檔案沒有機器的 CTF



下載後看來是個執行檔



執行後是個機器人要你下注,輸入數字、?、help 等都直接跳出



測試到 flag 的格式 HTB{.........} 發現終於有回應了

看來就是輸入正確的 flag 就過關了



解法一 : 慢慢一個一個數字與大小寫英文輸入測試,答案就出來了

但我如果知道 flag 是甚麼的話,幹嘛還要一一測試

這太浪費時間了,無法這麼做

解法二 : 寫一個程式自動去跑將答案跑出來

不過到底有幾碼這是個問題,所以看來還是要拆解程式看看有無甚麼可以發現的

先分析一下檔案,是個 ELF shared library 檔案



使用 ghidra 建立專案匯入檔案 casino




用 Open With --> CodeBrowser 開啟該檔案


在 Decompile: main 視窗查看邏輯



整段 Code 如下

undefined8 main(void)


{

  int iVar1;

  char local_d;

  uint local_c;

  

  puts("[ ** WELCOME TO ROBO CASINO **]");

  puts(

      "     ,     ,\n    (\\____/)\n     (_oo_)\n       (O)\n     __||__    \\)\n  []/______\\[] /\n   / \\______/ \\/\n /    /__\\\n(\\   /____\\\n---------------------"

      );

  puts("[*** PLEASE PLACE YOUR BETS ***]");

  local_c = 0;

  while( true ) {

    if (0x1c < local_c) {

      puts("[ ** HOUSE BALANCE $0 - PLEASE COME BACK LATER ** ]");

      return 0;

    }

    printf("> ");

    iVar1 = __isoc99_scanf(&DAT_001020fc,&local_d);

    if (iVar1 != 1) break;

    srand((int)local_d);

    iVar1 = rand();

    if (iVar1 != *(int *)(check + (long)(int)local_c * 4)) {

      puts("[ * INCORRECT * ]");

      puts("[ *** ACTIVATING SECURITY SYSTEM - PLEASE VACATE *** ]");

                    /* WARNING: Subroutine does not return */

      exit(-2);

    }

    puts("[ * CORRECT *]");

    local_c = local_c + 1;

  }

                    /* WARNING: Subroutine does not return */

  exit(-1);

}


1 : 顯示歡迎畫面與下注提示,只是氣氛用。

puts("[ ** WELCOME TO ROBO CASINO **]");

puts(/* 機器人 ASCII Art 省略 */);

puts("[*** PLEASE PLACE YOUR BETS ***]");


2. 迴圈控制變數

uint round = 0;          // local_c:目前進行到第幾輪(0-based)

char input;              // local_d:玩家每回輸入 1 byte


3. 主迴圈 : 28 個迴圈,所以總數字看來有 29 個

依據 flag 格式來看 HTB{.......},扣除前後與大括號,中間有 24 個數值

while (1) {

    if (round > 0x1c) {                  // 0x1c == 28

        puts("[ ** HOUSE BALANCE $0 - PLEASE COME BACK LATER ** ]");

        return 0;                        // 全 28 輪都過關才會走到這裡 → 成功

    }


    printf("> ");                        // 提示 → 玩家輸入一個字元

    if (__isoc99_scanf("%c", &input) != 1)

        exit(-1);                        // 讀取失敗就直接結束


    srand((int)input);                   // **重設 RNG 種子為剛輸入的單一字節**

    int r = rand();                      // 拿第一筆隨機數


    if (r != check[round]) {             // check[] 是全域 int[28] 對照表

        puts("[ * INCORRECT * ]");

        puts("[ *** ACTIVATING SECURITY SYSTEM - PLEASE VACATE *** ]");

        exit(-2);                        // 只要有一輪不對 → 立即 Game Over

    }


    puts("[ * CORRECT * ]");             // 這一輪比對成功

    round++;                             // 進到下一輪

}


glibc 的 rand() 第一次呼叫就會使用

x1=(1103515245×seed+12345)mod231 x_1 = (1103515245 × \text{seed} + 12345) \bmod 2^{31}

所以對於 固定種子 結果唯一,方便把「輸入字元」→「檢查值」做 1-對-1 映射。



4. 關卡條件 :

共 28 輪
0 … 0x1c(28)全部對才跳出迴圈顯示成功句子 → 旗標。
每輪重新 srand()
把原本 31-bit LCG 的「長期狀態」打斷成 28 個獨立的小挑戰;只要列舉 0-255 就能找到使 rand() 輸出匹配的種子。
Input = char
scanf("%c") 會讀一個 signed char(-128…127),再轉成 intsrand()。攻擊時要注意正負號對應。


結論 :

程式其實是「28 次 猜種子 遊戲」:你輸入 1 byte,程式用它重設亂數種子並比對 rand() 的輸出。因為 byte 空間只有 256 種,對照表又硬編在檔案裡,所以可以脫機爆破出每一輪要輸的字元,拼起來就是最終的 HTB flag。


寫一段 Python Code 如下自動去跑

#!/usr/bin/env python3

import ctypes, struct


libc = ctypes.CDLL("libc.so.6")

libc.srand.argtypes = [ctypes.c_uint]

libc.rand.restype = ctypes.c_int


# 28 個 int,直接從檔案 offset 0x3080 讀出

table = struct.unpack("<28I", open("casino", "rb").read()[0x3080:0x3080+28*4])


flag = []

for target in table:

    for c in range(256):           # 全 1-byte 種子

        libc.srand(ctypes.c_uint(ctypes.c_int8(c).value).value)

        if libc.rand() == target:  # 唯一對應

            flag.append(chr(c))

            break

print("flag =", "".join(flag) + "}")


取名叫 "python_run"



執行後獲得 flag : HTB{r4nd_1s_v3ry_pr3d1ct4bl3}



整題白話說明如下

遊戲規則 :

  1. 每回合 給機器人一張字卡(上面印 1 個字元,像 H、T、B…)。

  2. 機器人收到字卡後,會把字卡藏進口袋,立刻做一次心算,得出一個很大的數字。

  3. 牠把這個數字跟自己事先準備好的「答案表」做比對:

    • 如果吻合,牠說:「✔️ 對了,再來下一張!」

    • 不吻合就直接把你趕走。

  4. 一共要連續猜對 28 次,才能拿到獎品──旗標(flag)。

重點:機器人心算的規則沒有變,只跟「你給的那張字卡」有關。


破綻在哪裡?

  • 機器人心算用的公式是一個固定且簡單的算式。

  • 最要命的是,牠的「答案表」就直接放在機器人身體裡,沒有鎖!
    (我們把檔案打開,就能看到 28 個對答案。)

  • 既然心算公式固定+答案表也偷看得到,
    那麼 每一張字卡該用哪個字母,就可以事先算出來


如何破解 :

  1. 打開機器人 → 把「28 格答案表」抄下來。

  2. 做張對照表

    • 把 256 種可能字卡 (所有字元) 一張張塞給機器人試算。

    • 找到「哪張字卡 → 算出來會對應答案表的第 1 格」。

    • 重複 28 次,得到 28 張正確字卡。



依據程式來看

新數字 = (紙條 × 1 103 515 245 + 12 345) 再取餘數

這就是電腦裡 rand() 的核心公式,稱為 線性同餘生成器 LCG。

作者把 28 個印表機號碼(對應 28 輪)直接貼在牆上,長這樣(節錄前 5 筆):

輪次牆上的號碼
1608 905 406
2183 990 277
3286 129 175
4128 959 393
51 795 081 523
...... (共 28 個)

我們的任務:找到「哪張紙條 → 印出該號碼」;把它循序送進程式就通關。



開始對照──逐格試 256 張紙條就夠了

為什麼只要 256 張?

  • 程式每一輪都用 1 個位元組(byte) 當種子。

  • 一個 byte 只有 256 種可能值(–128 ~ 127)。

    • 可以把它想成 256 張編號 0~255 的紙條。

操作流程(以第 1 輪為例)

  1. 拿紙條 #0 → 塞給機器人 → 得到號碼 1。

  2. 比對牆上:不是 608 905 406 → 換下一張。

  3. 反覆到 紙條 #72 時,印出的號碼終於是 608 905 406!

  4. 那就記下:第 1 輪正確紙條 = #72

因為「印表機公式」在 0~255 這個小範圍內不會重複,所以 一定至多只會有 1 張紙條對得上
只要 256 次以內必能找到。


全部 28 輪

把這樣的「盲盒試紙條」流程做 28 次,就得到一條表格:

輪次對上的紙條編號轉成字元 (ASCII)
172H
284T
366B
4123{
5114r
.........
28125}

把字元串起來,就是旗標

依序排列剛才 28 個字元:

H T B { r 4 n d _ 1 s _ v 3 r y _ p r 3 d 1 c t 4 b l 3 }

合體後得到最終答案:

HTB{r4nd_1s_v3ry_pr3d1ct4bl3}



結論 :

  1. 低熵種子

    • 只用 1 byte 當種子,搜尋空間極小(256 種)。

  2. 公式已公開

    • rand() 的 LCG 參數(1 103 515 245 與 12 345)是眾所皆知、寫在手冊裡的常數。

  3. 答案直接外洩

    • 把 28 個目標數字硬寫在程式的資料區,不經任何加密。

只要同時滿足以上三點,就像出考試題目卻把答案抄在黑板上──自然人人都能把正確輸入反推回來。

Python Code 只是把「牆上貼的 28 個號碼」丟回同一台固定公式的印表機,逐張試 256 張紙條,找出哪張能列印出對應號碼;把 28 張紙條的 ASCII 字元串起來,就拿到 flag






留言

這個網誌中的熱門文章

Challenge 0 - Secura(2)

Challenge 0 - Secura(1)

Challenge 8 - Poseidon(0)