Giới thiệu
Qiling là một nền tảng cho phép giả lập nhiều môi trường (Linux, macOS, Windows, FreeBSD, DOS,…) và cấu trúc nhân khác nhau (hỗ trợ x86 - 16bit, 32bit, x64, ARM, ARM64 và MIPS) để debug cũng như fuzzing các file thực thi nhằm tìm kiếm lỗ hổng bảo mật. Thông tin chi tiết về Qiling các bạn có thể tham khảo tại địa chỉ này. Nhân dịp thực hiện kiểm tra, đánh giá bảo mật trong một dự án cần đến sự hỗ trợ của nền tảng này, tôi muốn chia sẻ với mọi người về cách giả lập bằng Qiling và tìm ra lỗ hổng bảo mật.
Chi tiết
Việc đầu tiên là tìm các chân và scan port của thiết bị, phần này khá đơn giản vì mạch này vẫn để UART debug.
Sử dụng serial để kết nối vào và kiểm tra xem thiết bị sử dụng kiến trúc nào. Trên thiết bị không có lệnh “file” nên tôi lấy binary về và check.
Thiết bị sử dụng kiến trúc ARM, little endian. Kiểm tra các port đang mở với lệnh netstat.
Vì mục đích bài này để giới thiệu cũng như hướng dẫn sử dụng Qiling nên tôi sẽ chỉ tập trung vào 1 port là 5060 udp. Các port còn lại, có thể tôi sẽ viết tiếp vào một thời điểm thích hợp khác.
Tải file binary về và sử dụng Ghidra để phân tích:
Để tìm đoạn mã xử lí của port 5060 thì tôi sẽ tìm những chỗ gọi đến hàm bind và recv, recvfrom. Các hàm này sẽ liên quan đến việc tạo socket và nhận dữ liệu được gửi đến thiết bị.
Sau một thời gian phân tích, ta có thể tìm được phần xử lí dữ liệu nhận được của port 5060
Đọc các dòng log của chương trình có thể thấy chương trình sử dụng thư viện eXosip, về cơ bản được xây dựng dựa trên thư viện osip, phục vụ cho việc lập trình VoIP (Voice over Internet Protocol).
Tôi có thể lấy source của thư viện về và thực hiện fuzzing,
tuy nhiên source hiện tại của thư viện chưa chắc đã có lỗi và phiên bản thiết bị
sử dụng chưa chắc đã là phiên bản mới nhất. Thậm chí luồng thực thi của thư viện
và chương trình khác nhau khá nhiều. Và điều cuối cùng là chúng
tôi muốn có PoC cụ thể đối
với mỗi sản phẩm được chúng tôi đánh giá.
Do đó, tôi quyết định fuzzing trên chính file executable này bằng cách sử dụng Qiling, cũng là một phần muốn áp dụng Qiling vào công việc thực tế để đánh giá hiệu quả của nó. Hướng dẫn cài đặt và sử dụng Qiling mọi người có thể tham khảo tại đây.
Về cài đặt, tôi khuyến nghị nên thực hiện manual install vì vài lần tôi cài thẳng bằng pip thì bị lỗi khá nhiều. Mục tiêu là chọn ra một hàm xử lí dữ liệu được gửi tới, setup các param cần thiết, emulate hàm này và thực hiện fuzzing.
Như trong hình trên có thể thấy được hàm osip_parse là target thích hợp. Hàm nhận hai tham số: buf – dữ liệu nhận được, len – độ dài dữ liệu nhận được. Để có thể sử dụng Qiling emulate được chương trình thì cần hai thứ: script và môi trường. Tôi sẽ tạo một thư mục rootfs đơn giản bằng cách copy thư mục mẫu của Qiling.
Script:
```
import sys
from qiling import
*
def run_sandbox(path,
rootfs, output):
ql = Qiling(path, rootfs, output = output)
ql.run()
if __name__ ==
"__main__":
run_sandbox(["./arm_linux/bin/main"], "./arm_linux",
"none")
```
$ python3
myLoad.py
```
import sys
from qiling import
*
def
run_sandbox(path, rootfs, output, env):
ql = Qiling(path, rootfs, output=output,
env=env)
input("env: " +
env["LD_LIBRARY_PATH"])
ql.run()
if __name__ ==
"__main__":
run_sandbox(["./arm_linux/bin/main"], "./arm_linux",
"none",\
{"LD_LIBRARY_PATH":"/lib:"+\
"/env/config/wifi:"+\
"/env/customer/tslib/lib:"+\
"/env/customer/libminigui:"+\
"/env/customer/lib:"+\
"/env/customer/morfeicore/libs"})
```
Cùng với đó là
setup các tham số của osip_parse là buf và len:
```
#!/usr/bin/env
python3
import sys
from qiling import
*
BEGIN_ADDRESS =
0x207568
END_ADDRESS =
0x20756c
myMap = 0
size = 3000
payload =
b"this is payload" # we will replace that with real one later
def setupReg(ql):
global myMap
myMap = ql.mem.map_anywhere(size)
ql.mem.write(myMap, payload)
ql.reg.write("r0", myMap)
ql.reg.write("r1",len(payload))
def
run_sandbox(path, rootfs, output):
ql = Qiling(path, rootfs, output = output)
ql.hook_address(setupReg, BEGIN_ADDRESS)
# here we hook BEGIN_ADDRESS with setReg
function
ql.run(begin=BEGIN_ADDRESS,
end=END_ADDRESS)
print("[.] Done")
if __name__ ==
"__main__":
run_sandbox(["./arm_linux/bin/main"], "./arm_linux",
"none")
```
```
#!/usr/bin/env
python3
import sys
from qiling import
*
BEGIN_ADDRESS =
0x207568
END_ADDRESS =
0x20756c
myMap = 0
size = 3000
payload =
b"this is payload"
def
_startHook(ql):
global myMap
# malloc memory for payload
myMap = ql.mem.map_anywhere(size)
ql.mem.write(myMap, payload)
# replace main addr with target addr
ql.reg.write("r0", BEGIN_ADDRESS)
def setupReg(ql):
# osip_parse(buf, len)
ql.reg.write("r0", myMap) # buf
ql.reg.write("r1",len(payload)) #
len
def
run_sandbox(path, rootfs, output):
ql = Qiling(path, rootfs, output=output)
ql.hook_address(_startHook, 0x000A6144) #
call target function instead of main
ql.hook_address(setupReg, BEGIN_ADDRESS) #
setup arguments for target function
ql.run(end=END_ADDRESS)
#ql.run()
print("[.] Done")
if __name__ ==
"__main__":
run_sandbox(["./arm_linux/bin/main"], "./arm_linux",
"debug")
```
```
#!/usr/bin/env
python3
import sys
from qiling import
*
import unicornafl
unicornafl.monkeypatch()
BEGIN_ADDRESS =
0x207568
END_ADDRESS =
0x20756c
myMap = 0
size = 3000
def
_startHook(ql):
global myMap
# map memory for payload
myMap = ql.mem.map_anywhere(size)
# replace main addr with target addr
ql.reg.write("r0", BEGIN_ADDRESS)
def setupReg(ql):
# osip_parse(buf, len)
ql.reg.write("r0", myMap) # buf
ql.reg.write("r1",len(payload)) #
len
def
run_sandbox(path, rootfs, output, input_file):
ql = Qiling(path, rootfs,console=True,
output=output)
### AFL go from here
# callback where we write content from
input to memory
def place_input_callback(uc, payload, _,
data):
#
write payload into our memory
ql.mem.write(myMap, payload)
# osip_parse(buf, len)
ql.reg.write("r0", myMap) #
buf
ql.reg.write("r1",len(payload)) # len
# afl hook at target function
def start_afl(_ql: Qiling):
"""
Callback from inside
"""
# We start our AFL forkserver or run
once if AFL is not available.
# This will only return after the
fuzzing stopped.
try:
#print("Starting
afl_fuzz().")
# input_file: this is where we feed
corpus
# place_input_callback: we will
implement how input is handle in this callback
if not
_ql.uc.afl_fuzz(input_file=input_file,
place_input_callback=place_input_callback,
exits=[ql.os.exit_point]):
print("Ran once without
AFL attached.")
print(input_file)
os._exit(0) # that's a looot faster than tidying up.
except unicornafl.UcAflError as ex:
print("[.] Error: " + ex)
ql.hook_address(_startHook, 0x000A6144) #
call target function instead of main
ql.hook_address(start_afl, BEGIN_ADDRESS) #
setup afl
ql.run(end=END_ADDRESS)
#ql.run()
if __name__ ==
"__main__":
if len(sys.argv) != 2:
print("Require 2 arguments")
else:
run_sandbox(["./arm_linux/bin/main"], "./arm_linux",
"debug", sys.argv[1])
```
```
#!/usr/bin/env
python3
import sys
from qiling import
*
import unicornafl
unicornafl.monkeypatch()
start_osipParse =
0x207568
end_osipParse =
0x20756c
start_parserInit =
0x0022AF8C
end_parserInit =
0x0022AF90
myMap = 0
size = 3000
#payload =
b"a"*1000
def
_startHook(ql):
global myMap
# map memory for payload
myMap = ql.mem.map_anywhere(0x10000)
# replace main addr with target addr
ql.reg.write("r0",
start_parserInit)
def
directToTarget(ql):
ql.reg.write("pc",
start_osipParse)
def setupReg(ql):
ql.mem.write(myMap, payload)
# osip_parse(buf, len)
ql.reg.write("r0", myMap) # buf
ql.reg.write("r1",len(payload)) #
len
def
run_sandbox(path, rootfs, output, input_file):
ql = Qiling(path, rootfs,console=True,
output=output)
### AFL go from here
# callback where we write content from
input to memory
def place_input_callback(uc, payload, _,
data):
#
write payload into our memory
ql.mem.write(myMap, payload)
# osip_parse(buf, len)
ql.reg.write("r0", myMap) #
buf
ql.reg.write("r1",len(payload)) # len
# afl hook at target function
def start_afl(_ql: Qiling):
"""
Callback from inside
"""
# We start our AFL forkserver or run
once if AFL is not available.
# This will only return after the
fuzzing stopped.
try:
#print("Starting
afl_fuzz().")
# input_file: this is where we feed
corpus
# place_input_callback: we will
implement how input is handle in this callback
if not
_ql.uc.afl_fuzz(input_file=input_file,
place_input_callback=place_input_callback,
exits=[ql.os.exit_point]):
print("Ran once without
AFL attached.")
os._exit(0) # that's a looot faster than tidying up.
except unicornafl.UcAflError as ex:
print("[.] Error: " + ex)
ql.hook_address(_startHook, 0x000A6144) #
call target function instead of main
ql.hook_address(directToTarget,
end_parserInit) # direct to osip_parse
ql.hook_address(start_afl, start_osipParse)
# setup afl
ql.run(end=end_osipParse)
#ql.run()
if __name__ ==
"__main__":
if len(sys.argv) != 2:
print("Require 2 arguments")
else:
run_sandbox(["./arm_linux/bin/main"], "./arm_linux",
"debug", sys.argv[1])
```
bạn cho hỏi
ReplyDeletetôi muốn nghiên cứu về lĩnh vực này tôi cần học những kiến thức gì
cảm ơn bạn