news 2026/9/27 9:13:54

CTF-Wiki 逆向工具指南:angr 混合執行引擎的安裝、核心 API 與自動化分析實戰

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
CTF-Wiki 逆向工具指南:angr 混合執行引擎的安裝、核心 API 與自動化分析實戰
  • 文档
  • 网络安全
  • 教程

【免费下载链接】ctf-wiki

Come and join us, we need you!

项目地址:https://gitcode.com/gh_mirrors/ct/ctf-wiki
点击查看免费下载

angr 是一套以 Python 撰寫的跨平台開源二進位混合執行(Concolic,即 concrete + symbolic)引擎,本指南基於 CTF-Wiki 倉庫中 angr.md 一文整理而成,完整覆蓋其安裝流程、Project/SimState/SimulationManager三大核心物件、Claripy 位向量與約束求解、模擬檔案系統、函數 Hook 與 SimProcedure 等實戰 API。讀完本指南,你將能獨立使用 angr 對 CTF 逆向題目進行自動化分析,以符號執行取代逐條閱讀組合語言來節省解題時間。

什麼是 angr

angr 是一個使用 Python 編寫的跨平台開源二進位混合執行引擎——「混合」指的是 Concolic(concrete + symbolic),即它將具體執行(concrete execution)與符號執行(symbolic execution)結合在一起:程式既在真實的機器狀態下執行,同時又允許輸入以「符號變數」的形式參與運算,再由約束求解器反推出滿足特定條件的輸入值。

angr 爲我們提供了一系列實用的二進位分析工具,在 CTF 逆向題目當中,其強大的混合執行引擎可以幫助我們更好地進行自動化分析,從而大幅度節省解題時間。在 CTF-Wiki 倉庫中,angr 與 Unicorn Engine 一同被歸類於「模擬執行」工具分類下(見 docs/zh-tw/mkdocs.yml 中- 模擬執行:一節),是逆向工具鏈中「不逐條讀指令、讓機器自己找答案」的代表性方案。

安裝

angr 本體可以直接透過 pip 進行安裝:

$ pip3 install angr

除本體外,angr 開發團隊還維護了兩個常用的周邊工具:

  • angr-management:圖形化的 angr 介面,安裝後直接在終端輸入angr-management即可直接啟動:
$ pip3 install angr-management
  • angrop:可自動收集 ROP gadget 以及構建 ROP chain 的工具:
$ pip3 install angrop

在 CTF-Wiki 倉庫的 Pwn 環境搭建範例中,angr 也被整合進了逆向/Pwn 的標準工具鏈。參見 環境搭建文件 中的 Dockerfile 範例,其透過python3 -m pip install一次性安裝了pwntools、z3-solver、unicorn、capstone、angr等一系列工具,因此在該 Docker 鏡像建置完成後,angr 已可直接import使用。

基本用法

本節講述 angr 的基本用法以及常用 API。需要注意的是,angr 的 API 版本演進較快,本文中的輸出以範例執行時(2022-2023 年前後)的 angr 版本爲準,若你安裝的版本更新,部分類名或輸出格式可能略有差異,但核心用法保持一致。

Project:一切分析的起點

若要使用 angr 分析一個二進位檔案,第一步是建立一個angr.Project類——我們一切後續操作都將基於這個類實例展開:

>>> import angr >>> bin_path = './test' # file to be analyzed >>> proj = angr.Project(bin_path) WARNING | 2022-11-23 19:25:30,006 | cle.loader | The main binary is a position-independent executable. It is being loaded with a base address of 0x400000.

上述 WARNING 來自 angr 的加載器元件cle.loader:當目標爲 PIE(position-independent executable)時,angr 會以0x400000爲基礎位址載入。這條警告通常不影響分析,只需在設定 find 位址時留意此基礎位址即可。

透過一個 project,我們可以獲取對應二進位檔案的基本資訊:

>>> proj.arch # architecture of the binary file <Arch AMD64 (LE)> >>> hex(proj.entry) # entry point of the binary file '0x401060' >>> proj.filename # name of the binary file './test'

其中arch是一個archiinfo.Arch類實例,其包含了執行該檔案的 CPU 資訊等各種數據:

  • arch.bits與arch.bytes:CPU 的字長(單位爲位/字節)。
  • arch.name:架構名,例如X86。
  • arch.memory_endness:端序,大端爲Endness.BE,小端爲Endness.LE。

順帶一提,原始碼中還有一個「中端序」Endness.ME——在 x86 的 16-bit 字元組定址等特殊場景下會出現這種大小端混合的表示方式。

factory:實用類工廠

project.factory爲我們提供了一些實用類的構造器,是日常使用頻率最高的入口之一。

block:基本塊

angr 以基本塊(basic block)爲單位分析程式碼,我們可以透過project.factory.block(address)獲取給定位址所在的基本塊——一個Block類實例:

>>> block = proj.factory.block(proj.entry) # extract the basic block >>> block.pp() # pretty-print of disassemble code of the block _start: 401060 endbr64 401064 xor ebp, ebp 401066 mov r9, rdx 401069 pop rsi 40106a mov rdx, rsp 40106d and rsp, 0xfffffffffffffff0 401071 push rax 401072 push rsp 401073 lea r8, [__libc_csu_fini] 40107a lea rcx, [__libc_csu_init] 401081 lea rdi, [main] 401088 call qword ptr [0x403fe0] >>> block.instructions # instructions in the block 12 >>> block.instruction_addrs # addr of each instruction (4198496, 4198500, 4198502, 4198505, 4198506, 4198509, 4198513, 4198514, 4198515, 4198522, 4198529, 4198536)
  • block.pp():以 pretty-print 形式輸出該基本塊的反組譯結果,便於快速檢視。
  • block.instructions:基本塊中包含的指令數。
  • block.instruction_addrs:每一條指令的位址元組。
state:模擬執行狀態

angr 使用SimState類表示一個模擬的程式狀態(simulated program state)。我們對程式的各種操作,本質上就是從一個 state 步進到另一個 state 的過程。

使用project.factory.entry_state()可以獲取程式入口處的初始執行狀態;使用project.factory.blank_state(addr)則可以獲取從指定位址開始執行的「空白狀態」(不會載入程式入口的初始化流程,適合從中間位址直接開跑):

>>> state = proj.factory.entry_state() >>> state = proj.factory.blank_state(0xdeadbeef)

state 物件上掛載了一系列資訊獲取接口:

  • state.regs:寄存器狀態組,其中每個寄存器都是一個位向量(BitVector),可透過寄存器名稱來訪問對應寄存器(例如state.regs.esp -= 12)。
  • state.mem:該狀態的記憶體訪問接口,可直接透過state.mem[addr].type完成記憶體訪問。例如寫入:state.mem[0x1000].long = 4;讀取時還需指定.resolved(返回位向量)或.concrete(返回實際 Python 值),例如state.mem[0x1000].long.concrete。
  • state.memory:另一種形式的記憶體訪問接口:
    • state.memory.load(addr, size_in_bytes):獲取該位址上指定大小的位向量。
    • state.memory.store(addr, bitvector):將一個位向量存儲到指定位址。
  • state.posix:POSIX 相關的環境接口,例如state.posix.dumps(fileno)可獲取對應檔案描述符(fileno)上的字元流——1是標準輸出,0是標準輸入。

除了上述資訊獲取接口外,還有求解方法對應的接口state.solver,將在後續章節中講解。

simulation_manager:模擬執行器

angr 將「對一組狀態執行」的方法獨立成一個SimulationManager類。以下兩種寫法等效:

>>> proj.factory.simgr(state) <SimulationManager with 1 active> >>> proj.factory.simulation_manager(state) <SimulationManager with 1 active>

比較重要的兩個方法:

  • simgr.step():以基本塊爲單位的單步執行。
  • simgr.explore():進行路徑探索,找到滿足相應條件的狀態。

simgr.explore()的默認參數是find,即期望條件。當模擬執行器在路徑探索的過程中發現當前狀態滿足該條件時,該狀態會被放到simgr.found列表中;若無法找到,則該列表爲空。

期望條件通常可以是一個目標位址,例如希望執行到0x80492F0:

>>> simgr.explore(find=0x80492F0) # explore to a specific address WARNING | 2023-07-17 04:04:28,825 | angr.storage.memory_mixins.default_filler_mixin | The program is accessing memory with an unspecified value. This could indicate unwanted behavior. WARNING | 2023-07-17 04:04:28,825 | angr.storage.memory_mixins.default_filler_mixin | angr will cope with this by generating an unconstrained symbolic variable and continuing. You can resolve this by: WARNING | 2023-07-17 04:04:28,826 | angr.storage.memory_mixins.default_filler_mixin | 1) setting a value to the initial state WARNING | 2023-07-17 04:04:28,826 | angr.storage.memory_mixins.default_filler_mixin | 2) adding the state option ZERO_FILL_UNCONSTRAINED_{MEMORY,REGISTERS}, to make unknown regions hold null WARNING | 2023-07-17 04:04:28,826 | angr.storage.memory_mixins.default_filler_mixin | 3) adding the state option SYMBOL_FILL_UNCONSTRAINED_{MEMORY,REGISTERS}, to suppress these messages. WARNING | 2023-07-17 04:04:28,826 | angr.storage.memory_mixins.default_filler_mixin | Filling memory at 0x7ffeff60 with 4 unconstrained bytes referenced from 0x819af30 (strcmp+0x0 in libc.so.6 (0x9af30)) WARNING | 2023-07-17 04:04:28,826 | angr.storage.memory_mixins.default_filler_mixin | Filling memory at 0x7ffeff70 with 12 unconstrained bytes referenced from 0x819af30 (strcmp+0x0 in libc.so.6 (0x9af30)) <SimulationManager with 1 active, 16 deadended, 1 found>

上述 WARNING 描述的是「程式訪問了未指定值的記憶體」的常見情況:angr 會自動產生一個不受約束的符號變數並繼續執行。若想消除這類警告,可按提示三種方式處理:① 在初始 state 中設定對應值;② 增加 state optionZERO_FILL_UNCONSTRAINED_{MEMORY,REGISTERS}使未知區域置零;③ 增加SYMBOL_FILL_UNCONSTRAINED_{MEMORY,REGISTERS}以靜默處理。從執行結果<SimulationManager with 1 active, 16 deadended, 1 found>可以看到,探索結束後滿足條件的 1 個狀態已被放入found。

期望條件也可以是自定義的、以狀態爲參數的布爾函數。例如,若想尋找一條輸出了指定字串的執行路徑,可以判斷該字串是否出現在標準輸出的字元流中——透過state.posix.dumps(檔案描述符)獲取對應描述符上的字元流:

>>> def foo(state): ... return b"Good" in state.posix.dumps(1) ... >>> simgr.explore(find=foo) <SimulationManager with 17 deadended, 1 found>

除了find參數外,也可以指定avoid參數——模擬器執行中應當要避開的條件。當一個狀態符合該條件時,其會被放在.avoided列表中並不再往後執行。類似地,avoid參數可以是某個位址,也可以是自定義的布爾函數。

此外,還可以透過指定num_find參數來指定需要尋找的符合條件的狀態數量;若未指定,則會在.found列表中儲存所有符合條件的狀態。

Claripy:位向量與求解引擎

Claripy是 angr 的求解引擎(solver engine),其內部會無縫混合使用幾種後端(concrete bitvectors、SAT solvers 等)。對我們而言一般不需要直接與之交互,但通常會使用它提供的一些接口來構造位向量。

bitvector:位向量

位向量(bitvector)是 angr 求解引擎中的一個重要部分,它表示一組位(a sequence of bits)。

可以透過claripy.BVV(int_value, size_in_bits)或claripy.BVV(string_value)創建帶有具體值(concrete value)的指定位元長度位向量值(bitvector value):

>>> bvv = claripy.BVV(b'arttnba3') >>> bvv <BV64 0x617274746e626133> >>> bvv2 = claripy.BVV(0xdeadbeef, 32) >>> bvv2 <BV32 0xdeadbeef>

相同長度的位向量可以直接進行運算;對於不同長度的位向量,則需要先透過.zero_extend(extended_bits)完成位擴展(0 填充)後再運算。需要注意的是,位向量的值運算同樣存在溢位:

>>> bvv2 = bvv2.zero_extend(32) >>> bvv + bvv2 <BV64 0x617274754d102022> >>> bvv * bvv <BV64 0x9842ff8e63f3b029>

位向量除了代表具體值的bitvector value以外,還有代表符號變數(symbolic variable)的bitvector symbol,可以透過claripy.BVS(name, size_in_bits)創建帶名字的指定位元長度位向量符號:

>>> bvs = claripy.BVS("x", 64) >>> bvs <BV64 x_0_64> >>> bvs2 = claripy.BVS("y", 64) >>> bvs2 <BV64 y_1_64>

位向量符號與位向量值之間同樣可以進行運算,組合成更加複雜的運算式:

>>> bvs3 = (bvs * bvs2 + bvv) / bvs >>> bvs3 <BV64 (x_0_64 * y_1_64 + 0x617274746e626133) / x_0_64>

可以透過.op與.args獲得位向量的運算類型與參數:

>>> bvv.op 'BVV' >>> bvs.op 'BVS' >>> bvs3.op '__floordiv__' >>> bvs3.args (<BV64 x_0_64 * y_1_64 + 0x617274746e626133>, <BV64 x_0_64>) >>> bvv.args (7021802812440994099, 64)

可以看到:BVV的.args由(數值, 位元數)組成,而一個複合運算式(如__floordiv__)的.args則是它的兩個運算元——這正是 Claripy 以抽象語法樹(AST)形式表示符號運算式的方式。

狀態求解

前面提到state.solver提供了一些基於狀態的求解接口,例如 solver 同樣有創建位向量的.BVV()與.BVS()接口。

當需要對位向量符號求解出具體值時,可以先將位向量符號存放到狀態的記憶體/寄存器中,之後用 simgr 探索到對應的狀態,再使用state.solver.eval()成員函數來獲取對應位向量在當前狀態下的值。以下是一個完整例子:

bvs_to_solve = claripy.BVS('bvs_to_solve', 64) init_state = proj.factory.entry_state() init_state.memory.store(0xdeadbeef, bvs_to_solve) simgr = proj.factory.simgr(init_state) simgr.explore(find = 0xbeefdead) solver_state = simgr.found[0] print(solver_state.solver.eval(bvs_to_solve))

其流程是:建立符號變數 → 存入記憶體 → 建立模擬執行器 → 探索到目標位址 → 從found中取出狀態 → 用solver.eval()求出符號變數在該狀態(含其約束)下的具體值。

記憶體操作

對於一個狀態的記憶體,可使用state.memory的對應接口進行操作:

  • state.memory.load(addr, size_in_bytes):獲取該位址上指定大小的位向量。
  • state.memory.store(addr, bitvector):將一個位向量儲存到指定位址。

需要注意的是,若要儲存具體值,則需要透過endness參數指定大小端序。

Emulated Filesystem:模擬檔案系統

在 angr 中與檔案系統間的操作是透過SimFile對象完成的。SimFile是對儲存的抽象模型,一個SimFile對象可以表示一系列的位元組、符號等。

可以透過angr.SimFile()創建模擬檔案,下面示範創建帶有具體值與符號變數的 SimFile:

>>> import angr, claripy >>> sim_file = angr.SimFile('a_file', content = "flag{F4k3_f1@9!}\n") >>> bvs = claripy.BVS('bvs', 64) >>> sim_file2 = angr.SimFile('another_file', bvs, size=8) # size in bytes there
  • content參數:檔案的具體內容(字串或位元組串)。
  • 第二個位置參數若傳入位向量,則檔案內容包含符號變數,此時需以size指定檔案大小(單位爲位元組)——這在分析「程序從檔案讀取輸入並進行比對」的題目時非常有用:讓檔案內容成爲符號變數,angr 即可自動求出能通過比對的內容。

模擬檔案需要與特定的狀態進行關聯,透過state.fs.insert(sim_file)或sim_file.set_state(state)可將 SimFile 插入到一個狀態的檔案系統中:

>>> state.fs.insert('test_file', sim_file)

還可以直接從檔案中讀取內容:

>>> pos = 0 >>> data, actural_read, pos = sim_file.read(pos, 0x100)

其中read(pos, size)返回(讀到的資料, 實際讀取長度, 新的位置)三元組,pos是當前讀取位置。

對於流(Streams,例如標準 IO、TCP 連接等)類型的檔案,可以用angr.SimPackets()創建:

>>> sim_packet = angr.SimPackets('my_packet') >>> sim_packet <angr.storage.file.SimPackets object at 0x7f75626a2e80>

SimPackets 以「封包」的形式抽象網路流/互動式輸入輸出,適合模擬需要持續收發資料的場景。

Constraints:約束條件

前面講到位向量之間可以進行運算,類似地,位向量之間也可以進行比較運算,其結果爲Bool類型的對象:

>>> bvv = claripy.BVV(0xdeadbeef, 32) >>> bvv2 = claripy.BVV(0xdeadbeef, 32) >>> bvv == bvv2 <Bool True> >>> bvs = claripy.BVS('bvs', 32) >>> bvs == bvv + bvv2 <Bool bvs_0_32 == 0xbd5b7dde> >>> bvs2 = claripy.BVS('bvs2', 32) >>> bvs2 > bvs * bvv + bvv2 <Bool bvs2_1_32 > bvs_0_32 * 0xdeadbeef + 0xdeadbeef>

對於帶有符號值的比較而言,Bool類型的對象直接表示了對應的式子,因此可以作爲約束條件被添加到一個狀態當中。透過state.solver.add()爲對應狀態添加約束:

>>> state.solver.add(bvs == bvv + bvv2) >>> state.solver.add(bvs2 > bvs * bvv + bvv2) >>> state.solver.eval(bvs2) # get the concrete value under constraints

添加約束後再使用state.solver.eval(),即可求出在這些約束共同限制下的具體值——這就是 angr 求解的經典三步:造變數 → 加約束 → 求值。

除了Bool類以外,Claripy 還提供了一些以位向量作爲結果的運算操作,例如條件運算式claripy.If():

>>> claripy.If(bvs == bvs2, bvs, bvs2) <BV32 if bvs_0_32 == bvs2_1_32 then bvs_0_32 else bvs2_1_32>

If(condition, true_expr, false_expr)返回一個「條件成立取前者、否則取後者」的符號運算式,可用於在符號層面構造分支邏輯。

Function hook:函數 Hook

有的時候我們需要 hook 掉某個函數,此時可以使用project.hook(addr = call_insn_addr, hook = my_function, length = n)來 hook 掉對應的 call 指令:

  • call_insn_addr:被 hook 的 call 指令的位址。
  • my_function:我們的自定義 Python 函數。
  • length:call 指令的長度(hook 完成後需要跳過該指令繼續執行)。

自定義函數應爲接收state作爲參數的函數。angr 提供了 decorator 語法糖,因此以下兩種寫法都可以:

# method 1 @project.hook(0x1234, length=5) def my_hook_func(state): # do something, this is an example state.regs.eax = 0xdeadbeef # method 2 def my_hook_func2(state): # do something, this is an example state.regs.eax = 0xdeadbeef proj.hook(addr = 0x5678, hook = my_hook_func2, length = 5)

兩種方式等價:method 1 利用 decorator 直接掛在函數定義上,method 2 則在定義後手動呼叫project.hook()。hook 的典型用途包括:跳過sleep等耗時/無關函數、直接替換某個驗證函數的返回邏輯、或是對指定函數的返回值做符號化處理。

Simulated Procedure:模擬函數過程

在 angr 中,angr.SimProcedure類用來表示在一個狀態上的一個執行過程——即函數實際上是「一個 SimProcedure」。我們可以透過創建一個繼承自angr.SimProcedure的類並重寫run()方法的方式來表示一個自定義函數,其中run()方法的參數即爲該函數所接收的參數:

class MyProcedure(angr.SimProcedure): def run(self, arg1, arg2): # do something, this's an example return self.state.memory.load(arg1, arg2)

自定義函數過程主要用於對檔案中的原有函數進行替換,例如 angr 缺省會用內置的一些 SimProcedure 來替換掉一些庫函數(如strcmp、puts等),這樣符號執行就不會「陷進」庫函數的內部實現,而能在符號層面直接處理其語義。

若我們已經有該二進位檔案的符號表,可以直接使用project.hook_symbol(symbol_str, sim_procedure_instance)來自動 hook 掉檔案中所有的對應符號,其中run()方法的參數爲被替換函數所接收的參數,示例如下:

import angr import claripy class MyProcedure(angr.SimProcedure): def run(self, arg1, arg2): # do something, this's an example return self.state.memory.load(arg1, arg2) proj = angr.Project('./test') proj.hook_symbol('func_to_hook', MyProcedure())

透過hook_symbol,只要知道函數名,就不必手動查位址,angr 會自動在每次呼叫該符號處執行我們的 SimProcedure。

在 SimProcedure 的run()過程中,還可以使用一些有用的成員函數來控制執行流程:

  • ret(expr):函數返回。
  • jump(addr):跳轉到指定地址。
  • exit(code):終止程序。
  • call(addr, args, continue_at):呼叫檔案中的函數。
  • inline_call(procedure, *args):內聯地呼叫另一個 SimProcedure。

stash:狀態的分組管理

在 angr 中,不同的狀態被組織到 simulation manager 的不同的stash當中,我們可以按照自己的需求對狀態進行步進、過濾、合併、移動等操作。

stash 類型

angr 一共有以下幾種內建 stash:

  • simgr.active:活躍的狀態列表。在未指定替代的情況下,會被模擬器默認執行。
  • simgr.deadended:死亡的狀態列表。當一個狀態無法再被繼續執行時(例如沒有有效指令、無效的指令指針、不滿足其所有的後繼 successors)便會被歸入該列表。
  • simgr.pruned:被剪枝的狀態列表。在指定了LAZY_SOLVES時,狀態僅在必要時檢查可滿足性;當一個狀態在指定了LAZY_SOLVES時被發現是不可滿足的(unsat),狀態層(state hierarchy)將被遍歷以確認在其歷史中最先變爲不滿足的時刻,該點及其所有後代都會被剪枝(pruned)並放入該列表。
  • simgr.unconstrained:不受約束的狀態列表。當創建SimulationManager時指定了save_unconstrained=True,則被認爲不受約束的(unconstrained,即指令指針被用戶數據或其他來源的符號化數據控制)狀態會被歸入該列表。
  • simgr.unsat:不可滿足的狀態列表。當創建SimulationManager時指定了save_unsat=True,則被認爲無法被滿足的(unsatisfiable,即存在約束衝突的狀態,例如在同一時刻要求輸入既是"AAAA"又是"BBBB")狀態會被歸入該列表。

還有一種不是 stash 的狀態列表——errored:若在執行中產生了錯誤,則狀態與其產生的錯誤會被包裹在一個ErrorRecord實例中(可透過record.state與record.error訪問),該 record 會被插入到errored中,我們可以透過record.debug()啟動一個調試視窗。

stash 操作

可以使用stash.move()在 stash 之間轉移放置狀態:

>>> simgr.move(from_stash = 'unconstrained', to_stash = 'active')

在轉移中還可以透過指定filter_func參數來進行過濾:

>>> def filter_func(state): ... return b'arttnba3' in state.posix.dumps(1) ... >>> simgr.move(from_stash = 'unconstrained', to_stash = 'active', filter_func = filter_func)

stash 本質上就是一個 list,因此在初始化時可以透過字典的方式指定每個 stash 的初始內容:

>>> simgr = proj.factory.simgr(init_state, ... stashes = { ... 'active':[init_state], ... 'found':[], ... })

stash 機制讓「把不受約束的狀態撈回來繼續探索」「把已找到的狀態隔離保存」等操作成爲標準流程,是編寫進階自動化腳本時的重要工具。

結語與延伸閱讀

angr 將「符號執行 + 具體執行 + 約束求解」整合進一套簡潔的 Python API 中:以Project載入二進位檔案,以SimState描述程式狀態,以SimulationManager驅動路徑探索,以Claripy完成位向量運算與求解,再輔以SimFile、SimProcedure、hook 與 stash 等機制處理檔案輸入、庫函數與狀態管理——掌握了這套核心 API,即可對大量 CTF 逆向題目實現「讓程式自己找出正確輸入」的自動化解題。

在本倉庫中,你可以繼續閱讀以下相關資料深入學習:

  • 模擬執行工具:Unicorn Engine:另一個以 CPU 模擬器思路實現的執行工具,與 angr 的符號執行思路互補。
  • 約束求解工具:Z3:angr 的 Claripy 底層同樣依賴 SAT/SMT 求解器,理解 Z3 有助於理解求解原理。
  • Pwn 環境搭建 Dockerfile:其中已整合安裝angr等工具的完整環境範例。
  • 逆向工具分類目錄可見 docs/zh-tw/mkdocs.yml 中的「模擬執行」小節,了解 angr 在整個 CTF-Wiki 逆向工具鏈中的定位。
  • 文档
  • 网络安全
  • 教程

【免费下载链接】ctf-wiki

Come and join us, we need you!

项目地址:https://gitcode.com/gh_mirrors/ct/ctf-wiki
点击查看免费下载

相关推荐

上一篇:yuzu Switch模拟器:不花一分钱,30分钟在PC上跑Switch游戏
下一篇:ESPectre 路线图全解读:从 v3.0 共享 Wi-Fi CSI 感知平台到 v5.0 IEEE 802.11bf 的演进规划

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/27 9:01:46

远程桌面的UKey安全重定向怎么做:安当UKey在工程落地中的拆解

一、为什么远程桌面下的 UKey 是个棘手问题 过去十年&#xff0c;集中式办公在政务、能源、金融与高端制造行业快速普及。运维人员用瘦客户机连上云桌面处理工单&#xff0c;调度人员在调度大厅通过远程接入方式操作远端的 SCADA 前置机&#xff0c;设计工程师在异地用云桌面打…

作者头像 李华
网站建设 2026/9/27 8:51:53

XMind 用久了会遇到的 5 类问题,和我的进阶用法

写在前面 XMind 是我用得最久的效率工具之一&#xff0c;从读书笔记到项目拆解&#xff0c;几乎每天都在用。用得越久&#xff0c;越发现新手期根本意识不到的一些问题——不是软件坏了&#xff0c;是没摸清它的脾气。这篇把常见问题和几个进阶用法一起写出来&#xff0c;帮你…

作者头像 李华