元々は はじめての Ghidra Script で CTF の問題を解く の記事で紹介していた Ghidra Script のサンプル集でしたが、数が増えてきたのとこれから本格的に Ghidra を使っていこうと思っていることもあり、新しく別の記事に分割することにしました。
使ってる感じ、Python(Jython) ではなく Java でスクリプトを書けるようにした方がよさそうな気がしていますが、とりあえず現在のところは Python でスクリプトを作成しています。
ランタイムは Ghidrathon ではなく Ghidra のデフォルトのものを使用しています。
もくじ
- 関数のデコンパイル結果を取得する
- 特定のアドレスの関数、データを取得する
- 関数内の Call を列挙する
- 指定アドレス範囲内で実行される Call 関数が呼び出す関数名を列挙する
- ディスアセンブル結果から連続してハードコードされたバイト配列を取得する
- 特定のアドレスの構造体情報を識別して値を取得する
- 特定のアドレスに構造体を割り当て、値を取得する
- 任意のアドレスレンジのバイトデータをファイルとして保存する
- まとめ
関数のデコンパイル結果を取得する
以下のスクリプトでは、Listing ウインドウで選択中の関数のデコンパイル結果を出力できます。
from ghidra.app.decompiler import DecompInterface
# Decompile インターフェースを取得
decomp = DecompInterface()
decomp.openProgram(currentProgram)
# currentAddress には、Listing で選択している行のアドレスが自動的に参照される
# そのため、事前にターゲットになる関数のアドレスを選択しておく
func = fpapi.getFunctionContaining(currentAddress)
decomp_results = decomp.decompileFunction(func, 30, monitor)
if decomp_results.decompileCompleted():
pp = PrettyPrinter(fn, decomp_results.getCCodeMarkup())
code = pp.print(False).getC()
print(code)
else:
print("There was an error in decompilation!")
特定のアドレスの関数、データを取得する
以下のスクリプトでは、オフセットを指定して任意のアドレスの関数名を取得したり、指定のアドレスのデータを取得したりできます。
# Listing 情報を取得(ghidra.program.database.ListingDB)
listing = currentProgram.getListing()
# オフセットを指定して GenericAddress オブジェクトを取得
fpapi = FlatProgramAPI(currentProgram)
addr = fpapi.toAddr(0x1024aa)
# 指定したアドレスを含む関数を取得
func = fpapi.getFunctionContaining(addr)
print(func.getName()) # 関数名の表示
# データを取得するアドレスを指定
addr = fpapi.toAddr(0x102cd1)
# 指定アドレスのデータを取得
data = listing.getDataAt(addr)
print(data.getValue()) # ghidra.program.model.scalar.Scalar
# 指定 AddressSet の範囲で DataIterator オブジェクトを取得
# 指定の範囲で任意の AddressSet を取得する
from ghidra.program.model.address import Address, AddressSet
factory = currentProgram.getAddressFactory()
# 指定のオフセットから 0x100 分の範囲を指定
addr_set = AddressSet()
addr_set.add(addr, addr.add(0x100))
# 指定の範囲のデータを先頭から取得
data_iterator = listing.getData(addr_set, True)
for data in data_iterator:
print(data.getValue())
# 特定のアドレスのオペコードとオペランドを取得
addr = toAddr(0x1000000)
inst = getInstructionAt(addr)
# オペランドの取得
inst.getDefaultOperandRepresentation(0)
inst.getDefaultOperandRepesentation(1)
# 次の行の情報を取得できる
inst.getNext()
inst.getDefaultOperandRepresentation(0)
inst.getDefaultOperandRepesentation(1)
関数内の Call を列挙する
以下のコードでは関数内の Call 命令を列挙できます。
関数内の呼び出し順序を維持したい場合やそうでない場合で取得方法を変える必要があります。
# currentAddress is ghidra.program.model.address.GenericAddress
# currentAddress には、Listing で選択している行のアドレスが自動的に参照される
# そのため、事前にターゲットになる関数のアドレスを選択しておく
func_mgr = currentProgram.getFunctionManager()
func = func_mgr.getFunctionContaining(currentAddress)
# 関数内の呼び出しアドレスを列挙する(呼び出し順ではない)
calls = func.getCalledFunctions(monitor)
for c in calls:
print(c)
# Listing 情報を取得(ghidra.program.database.ListingDB)
listing = currentProgram.getListing()
# 関数内の Call 命令を順に列挙することで呼び出し順序を維持して出力する
for i in listing.getInstructions(func.body, True):
print(i)
指定アドレス範囲内で実行される Call 関数が呼び出す関数名を列挙する
以下のスクリプトでは、指定のアドレス範囲内で実行される関数のシンボル名を列挙できます。
以下の問題で使用しました。
参考:Cake CTF 2023 Writeup - nande
from ghidra.program.flatapi import FlatProgramAPI
from ghidra.program.model.address import AddressSet
listing = currentProgram.getListing()
fpapi = FlatProgramAPI(currentProgram)
start_addr = fpapi.toAddr(0x1043c9)
end_addr = fpapi.toAddr(0x104825)
addr_set = AddressSet()
addr_set.add(start_addr, end_addr)
for p in listing.getInstructions(addr_set, True):
code = p.toString()
if "CALL" in code:
func_addr = int(code.split(" ")[1],16)
fpapi.getFunctionContaining(fpapi.toAddr(func_addr)).getName()
ディスアセンブル結果から連続してハードコードされたバイト配列を取得する
以下のコードでは、一連のアセンブリコードのオペランドを連続して抽出できます。
# 特定アドレスから 0x26 バイト分のオペランドを取得する
addr = toAddr(0x109011)
inst = getInstructionAt(addr)
result = []
for i in range(0x26):
result.append(inst.getDefaultOperandRepresentation(1))
inst = inst.getNext()
print(result)
特定のアドレスの構造体情報を識別して値を取得する
以下のコードでは、特定のアドレスの構造体情報を識別して、その値を取得することができます。
以下の問題で使用しました。
参考:AmateursCTF 2023 Writeup - CSCE221-Data Structures and Algorithms
from ghidra.app.script import GhidraScript
# list の取得
start_address = toAddr("0x404000")
data_section = currentProgram.getMemory().getBlock(start_address)
data_address = toAddr("0x404060")
data_object = getDataAt(data_address)
# 自作した list 構造体が解釈される
data_structure = data_object.dataType
data_component = data_structure.getComponent(0x0)
# Get the relative offset, length and data type of the component
offset = data_component.offset
length = data_component.length
data_type = data_component.dataType
# list 構造体から int 分の値を取得
byte_array = getBytes(data_address.add(offset), length)
print(hex(byte_array[0]))
# Get the address of the .data section
start_address = toAddr("0x404000")
data_section = currentProgram.getMemory().getBlock(start_address)
# list のアドレス
data_address = toAddr("0x404060")
data_object = getDataAt(data_address)
特定のアドレスに構造体を割り当て、値を取得する
このスクリプトでは、任意のアドレス範囲のデータを構造体として定義して値を参照することができます。
以下の問題で使用しました。
参考:AmateursCTF 2023 Writeup - CSCE221-Data Structures and Algorithms
# listnode の取得
data_type_manager = currentProgram.getDataTypeManager()
my_structure = data_type_manager.getDataType("main.coredump/listnode")
start_address = toAddr("0x405000")
data_section = currentProgram.getMemory().getBlock(start_address)
flag = ""
listnode_addr = 0x4052a0
# listnode
data_address = toAddr(hex(listnode_addr))
data_object = createData(data_address, my_structure)
# 自作した listnode 構造体が解釈される
data_structure = data_object.dataType
data_component = data_structure.getComponent(0x0)
# Get the relative offset, length and data type of the component
offset = data_component.offset
length = data_component.length
data_type = data_component.dataType
# listnode 構造体から byte 分の値を取得
byte_array = getBytes(data_address.add(offset), length)
flag += chr(byte_array[0])
任意のアドレスレンジのバイトデータをファイルとして保存する
暗号化されているデータが埋め込まれているセクションなど、手動でのコピーが面倒な場合に使用するスクリプトです。
シンプルに任意のアドレス範囲のデータを 1 バイトずつ取得してファイルに書き込みます。
from ghidra.program.model.address import Address
from ghidra.program.model.mem import MemoryAccessException
import struct
def save_bytes_to_file(start_address, end_address, filename):
currentProgram = getCurrentProgram()
memory = currentProgram.getMemory()
start_addr = toAddr(start_address)
end_addr = toAddr(end_address)
with open(filename, "wb") as file:
address = start_addr
while address <= end_addr:
try:
byte = memory.getByte(address)
file.write(struct.pack("B", byte))
address = address.next()
except MemoryAccessException as e:
print("Error reading memory at address:", address, e)
break
start_address = 0x403040
end_address = 0x403000 + 0x1ce00 - 1
filename = "C:\\Users\\Public\\output.bin"
save_bytes_to_file(start_address, end_address, filename)
まとめ
Ghidra Script を使いこなせるようになるともっとできることが増えるような気がします。
ただ、Ghidra のデフォルトの Jython はバージョンが古い上に、サンプルとして公開されているスクリプトは圧倒的に Java が多いので、本格的に使うなら Java を勉強する方がいいかもしれません。。