Angr_CTF Writeups

kazma 成大資安社 創辦人/社長

Angr_CTF Writeups 1

這篇文章會來整理透過 angr_ctf 這些 labs 學到有關於 angr 的一些技巧和使用方式,如果有發現任何錯誤或是改進的建議歡迎聯絡我!

Introduction

About Angr

Angr 是一個由 ShellPhish 開發並用 Python 編寫的框架,專門用來自動化二進制分析,有很多廣泛的應用,也很多提供豐富好用的 API。
除了應用在 CTF 上之外,也很常拿來協助學術研究以及業界的漏洞挖掘和惡意程式分析,總之就是很強大的工具就對了 XD。
Sources:

Core Principles

Symbolic Exection

Symbolic Exection 是 Angr 的一個主要特色,使用符號來取代具體的值來作為變量,如此可以讓他探索可能的執行路徑,尋找漏洞或是其他我們設定的條件。

Binary Lifting

Binary Lifting 是可以把 machine code 轉換成另一種中間表示法稱為 VEX,這種表示法更適合進行分析因為減少了許多複雜性。

Control Flow Graph Analysis

Angr 可以建構程式的控制流程圖,一種表示所有可能路徑的圖,這對於理解城市的結構和尋找潛在執行路徑很有幫助。

Data Flow Analysis

通過分析資料如何在程式中流動,Angr 能夠識別變量和來源和影響範圍,這對於識別沒有初始化的漏洞特別有用。

Constraint Solving

Angr 可以使用約束求解器來解決在符號執行過程中收集的條件,來確定特定的路徑是否可行。

Plugin Architecture

Angr 有很多好用的插件可以裝,可以根據需求自由的擴展功能。

Installation

今天練習的題目來自下面這個 repositories:
https://github.com/jakespringer/angr_ctf

題目都放在 angr_ctf/dist/ 下面,附上指令:

1
2
git clone https://github.com/jakespringer/angr_ctf.git
pip install angr

安裝 angr 的時候可能會出現有些套件的版本不符合要求,大家再依自己的狀況調整就行,那我們趕緊開始。

Labs

00_angr_find

這題就是逆向 CTF 很常出現的 password 題,執行起來如下:

1
2
3
└─$ ./00_angr_find
Enter the password: 1234
Try again.

首先跟 pwntools 一樣我們需要先把 binary 載進來然後讓 angr 可以對他做分析:

1
2
import angr
p = Project('./00_angr_find', load_options = {'auto_load_libs': False}, main_opts = {'base_addr': 0x804850})

load_options = {'auto_load_libs': False}: 是設定參數讓他不要載入其他的依賴文件,可以降低複雜性跟執行時間。
main_opts = {'base_addr': 0x804850}: 這裡在設定 entrypoint,這裡示範用 r2 找,直接打開就會看到了:

1
2
└─$ r2 00_angr_find
[0x08048450]>

再來會模擬從入口點開始執行的狀態,然後會使用 SimulationManager 這個模擬管理器來控制程式的執行和探索路徑之類的行爲:

1
2
state = p.factory.entry_state()
simgr = p.factory.simgr(state)

再來我告訴 SimulationManager 我想要程式輸出密碼正確的訊息,所以我給他那個 flow 上的隨便一個地址,一樣可以用靜態分析來找地址:
good_job

1
simgr.explore(find = 0x8048675)

最後我們來看 simgr.found 是否有找到到達指定路徑的狀態,路徑可能不只一條,所以 found 會是一個 list,而我們試著在其中一個成功的狀態中去查看 stdin 應該要是什麼,所以會使用 posix.dumps(0) 來查看。
POSIX(Portable Operating System Interface)是一個由 IEEE 定義的一系列 API 標準,簡單來說他可以使我們在不同操作系統上確定應用程式的行為一致,也可以使用這些接口來模擬應用程式如何與操作系統交互。

1
2
3
4
5
if simgr.found:
sol = simgr.found[0]
print(sol.posix.dumps(0))
else:
print('QQ')

exploit00.py:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
from angr import *

p = Project('./00_angr_find', load_options = {'auto_load_libs': False}, main_opts = {'base_addr': 0x804850})

state = p.factory.entry_state()
simgr = p.factory.simgr(state)

simgr.explore(find = 0x8048675)

if simgr.found:
sol = simgr.found[0]
print(sol.posix.dumps(0))
else:
print('QQ')

執行結果:

1
2
3
4
5
6
7
8
9
10
11
└─$ python exploit00.py
WARNING | 2024-04-30 04:26:52,924 | cle.loader | 00_angr_find: base_addr was specified but the object is not PIC. specify force_rebase=True to override
WARNING | 2024-04-30 04:26:53,021 | angr.storage.memory_mixins.default_filler_mixin | The program is accessing register with an unspecified value. This could indicate unwanted behavior.
WARNING | 2024-04-30 04:26:53,021 | 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 | 2024-04-30 04:26:53,021 | angr.storage.memory_mixins.default_filler_mixin | 1) setting a value to the initial state
WARNING | 2024-04-30 04:26:53,021 | angr.storage.memory_mixins.default_filler_mixin | 2) adding the state option ZERO_FILL_UNCONSTRAINED_{MEMORY,REGISTERS}, to make unknown regions hold null
WARNING | 2024-04-30 04:26:53,021 | angr.storage.memory_mixins.default_filler_mixin | 3) adding the state option SYMBOL_FILL_UNCONSTRAINED_{MEMORY,REGISTERS}, to suppress these messages.
WARNING | 2024-04-30 04:26:53,021 | angr.storage.memory_mixins.default_filler_mixin | Filling register edi with 4 unconstrained bytes referenced from 0x80486b1 (__libc_csu_init+0x1 in 00_angr_find (0x80486b1))
WARNING | 2024-04-30 04:26:53,023 | angr.storage.memory_mixins.default_filler_mixin | Filling register ebx with 4 unconstrained bytes referenced from 0x80486b3 (__libc_csu_init+0x3 in 00_angr_find (0x80486b3))
WARNING | 2024-04-30 04:26:54,235 | angr.storage.memory_mixins.default_filler_mixin | Filling memory at 0x7ffeff60 with 4 unconstrained bytes referenced from 0x8100000 (strcmp+0x0 in extern-address space (0x0))
b'JXWVXRKX'

可以看到雖然最後有輸出密碼,但是噴了一堆警告,主要是因為在模擬程式執行是一個很複雜的狀況,有很多複雜的參數可以設定,如果設定的不完全可能會影響準確性,而閱讀這些也可以幫助我們之後更高效的學習使用 angr 讓分析更順利。
Result:

1
2
3
└─$ ./00_angr_find
Enter the password: JXWVXRKX
Good Job.

01_angr_avoid

這題跟上面那題很像,但是當我們用 r2 或是 main 試著圖形化來分析他的流程會發生下面的狀況:
main
他的圖太大張了,因為 node 超過 1000 個,所以當我們試著用剛剛的方式去嘗試找到成功的路徑的時候會發現腳本跑很久都沒結果,那這邊就會學到一個參數是 avoid 可以提早告知 angr 不要去嘗試那些很複雜而且會失敗的路徑,而這題也很貼心地告訴我們有一個 avoid_me 函式,把它加到我們的腳本裡面 exploit01.py:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
from angr import *

p = Project('./01_angr_avoid')
state = p.factory.entry_state()
simgr = p.factory.simgr(state)

find = 0x080485E0
avoid = 0x80485A8
simgr.explore(find = find, avoid = avoid)

if simgr.found:
flag = simgr.found[0]
print(flag.posix.dumps(0))
else:
print("not found qq")

Result without warnings:

1
2
3
4
5
└─$ python exploit01.py
b'HUJOZMYS'
└─$ ./01_angr_avoid
Enter the password: HUJOZMYS
Good Job.

02_find_condition

我們也可以只用 strings 來找到正確和錯誤的字串,來當成條件式:
exploit02.py:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
from angr import *

p = Project('./02_angr_find_condition')
state = p.factory.entry_state()
simgr = p.factory.simgr(state)

def find_condition(simgr):
output = simgr.posix.dumps(1)
return b'Good Job.' in output

def avoid_condition(simgr):
output = simgr.posix.dumps(1)
return b'Try again.' in output

simgr.explore(find = find_condition, avoid = avoid_condition)

if simgr.found:
print(simgr.found[0].posix.dumps(0))
else:
print('QQ')

Result:

1
2
3
└─$ ./02_angr_find_condition
Enter the password: HETOBRCU
Good Job.

03_angr_symbolic_registers

這題雖然用跟上一題一樣腳本也會過,但他想教的觀念是:
我們可以通過空降在程式的某個位置直接創建 symbolic variable 給指定的暫存器,來避開 angr 不擅長處理複雜格式的輸入。
用法如下,exploit03.py:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
from angr import *
from claripy import *
import sys

p = Project('./03_angr_symbolic_registers')

start = 0x08048980
state = p.factory.blank_state(addr = start)

p0 = BVS('p0', 32)
p1 = BVS('p1', 32)
p2 = BVS('p2', 32)

state.regs.eax = p0
state.regs.ebx = p1
state.regs.edx = p2

simgr = p.factory.simgr(state)

def find(simgr):
output = simgr.posix.dumps(1)
return b'Good Job.' in output

def avoid(simgr):
output = simgr.posix.dumps(1)
return b'Try again.' in output

simgr.explore(find = find, avoid = avoid)

if simgr.found:
sol = simgr.found[0]
sol0 = format(sol.solver.eval(p0), 'x')
sol1 = format(sol.solver.eval(p1), 'x')
sol2 = format(sol.solver.eval(p2), 'x')

flag = sol0 + " " + sol1 + " " + sol2
print(flag)
else:
print('qq')

Result:

1
2
3
└─$ ./03_angr_symbolic_registers
Enter the password: b9ffd04e ccf63fe8 8fd4d959
Good Job.

下集待續…

  • Title: Angr_CTF Writeups
  • Author: kazma
  • Created at : 2024-04-30 15:21:23
  • Updated at : 2024-05-05 00:51:51
  • Link: https://kazma.tw/2024/04/30/Angr-CTF-Writeups/
  • License: This work is licensed under CC BY-NC-SA 4.0.
 Comments