破防了QAQ,刚刚写完了,但是没保存上。又要重新写一遍了,悲

Qiling

qilingframework/qiling: A True Instrumentable Binary Emulation Framework

Qiling Framework Documentation

打印和过滤

使用这个进行日志输出

1
ql.log.info('Hello from Qiling Framework!')

设定日志详细级别verbose:

1
2
from qiling.const import QL_VERBOSE
ql = Qiling([r'/bin/ls'], r'examples/rootfs/x86_linux', verbose=QL_VERBOSE.DEBUG)
详细级别 描述
QL_VERBOSE.DISABLED 完全关闭
QL_VERBOSE.OFF 仅记录warnings, errors和critical entries
QL_VERBOSE.DEFAULT 除了上一级还记录info
QL_VERBOSE.DEBUG 除了上一级还记录debug
QL_VERBOSE.DISASM 除了上一级还记录每条指令的反汇编
QL_VERBOSE.DUMP 除了上一级还记录CPU上下文

快照

部分执行

1
2
3
4
5
6
ql = Qiling(["../examples/rootfs/x8664_linux/bin/sleep_hello"], "../examples/rootfs/x8664_linux", verbose=QL_VERBOSE.DEBUG)
X64BASE = int(ql.profile.get("OS64", "load_address"), 16)
ql.restore(snapshot="/tmp/snapshot.bin")
begin_point = X64BASE + 0x109e
end_point = X64BASE + 0x10bc
ql.run(begin = begin_point, end = begin_point)

run时可以指定begin和end

状态恢复与保存

全局状态

1
2
ql_all = ql.save()
ql.restore(ql_all)

文件描述符

1
2
all_fd = ql.fd.save()
ql.fd.restore(all_fd)

CPU

1
2
all_registers_context = ql.arch.regs.context_save()
ql.arch.regs.context_restore(all_registers_context)

内存

1
2
all_mem = ql.mem.save()
ql.mem.restore(all_mem)

寄存器

1
2
3
all_registers = ql.arch.regs.save()
all_registers["eip"] = 0xaabbccdd
ql.arch.regs.restore(all_registers)

也可以直接修改

寄存器

读取和写入

1
2
3
4
5
6
7
ql.arch.regs.read("EAX") # 使用字符串
ql.arch.regs.read(UC_X86_REG_EAX) # 使用UC常量
eax = ql.arch.regs.eax # 使用结构

ql.arch.regs.write("EAX", 0xFF)
ql.arch.regs.write(UC_X86_REG_EAX, 0xFF)
ql.arch.regs.eax = 0xFF

跨平台寄存器使用,仅支持pc和sp

1
2
3
4
5
ql.arch.regs.arch_pc
ql.arch.regs.arch_sp

ql.arch.regs.arch_pc = 0xFF
ql.arch.regs.arch_sp = 0xFF

获取寄存器列表

1
ql.arch.regs.register_mapping()

按位获取寄存器:从64位获取32位

1
ql.arch.reg_bits("eax")

内存

栈操作

1
2
3
4
value = ql.arch.stack_pop() # pop
ql.arch.stack_push(value) # push
value = ql.arch.stack_read(offset) # peek
ql.arch.stack_write(offset, value) # 直接写入

内存操作

创建和销毁

1
2
3
4
ql.mem.map(addr: int, size: int, perms: int = UC_PROT_ALL, info: Optional[str] = None) -> None
ql.mem.map_anywhere(size: int) # 不关心地址

ql.mem.unmap(addr: int, size: int) -> None

perms为权限,可以是如下:

1
2
3
4
5
UC_PROT_NONE = 0
UC_PROT_READ = 1
UC_PROT_WRITE = 2
UC_PROT_EXEC = 4
UC_PROT_ALL = 7

读写

1
2
3
4
5
6
7
ql.mem.read(addr: int, size: int) -> bytearray
ql.mem.read_ptr(addr: int, size: int = 0, *, signed = False) -> int

ql.mem.write(addr: int, data: bytes) -> None
ql.mem.write_ptr(addr: int, value: int, size: int = 0, *, signed = False) -> None

string(self, addr: int, value=None, encoding='utf-8') -> Optional[str]

read/write_ptr的size表示指针大小,可选1,2,4,8。read/write的size指读取长度。string无value则为读,否则为写,写则无返回。

搜索

1
ql.mem.search(needle: Union[bytes, Pattern[bytes]], begin: Optional[int] = None, end: Optional[int] = None) -> List[int]

needle为内存中想要搜索的字节组,begin和end为可选,表示搜索范围。

内存管理

1
2
3
4
5
ql.mem.get_formatted_mapinfo() # 获取所有申请的地址列表
ql.mem.find_free_space(size) # 找一个未申请的地方
ql.mem.is_available(addr, size) # 判断某处是否可申请
ql.mem.is_mapped(addr, size) # 与上面的相反
ql.mem.find_free_space(size, min_addr=0, max_addr = 0, alignment=0x10000) # 带对齐的找申请

钩子

hook_address

在执行某个地址的指令之前触发hook

1
2
3
4
5
6
7
8
9
from qiling import Qiling

def stop(ql: Qiling) -> None:
ql.log.info('killer switch found, stopping')
ql.emu_stop()
ql = Qiling([r'examples/rootfs/x86_windows/bin/wannacry.bin'], r'examples/rootfs/x86_windows')
# have 'stop' called when execution reaches 0x40819a
ql.hook_address(stop, 0x40819a)
ql.run()

hook_code

每执行一条汇编前触发一次hook

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
from capstone import Cs
from qiling import Qiling
from qiling.const import QL_VERBOSE

def simple_diassembler(ql: Qiling, address: int, size: int, md: Cs) -> None:
buf = ql.mem.read(address, size)

for insn in md.disasm(buf, address):
ql.log.debug(f':: {insn.address:#x} : {insn.mnemonic:24s} {insn.op_str}')

if __name__ == "__main__":
ql = Qiling([r'examples/rootfs/x8664_linux/bin/x8664_hello'], r'examples/rootfs/x8664_linux', verbose=QL_VERBOSE.DEBUG)

# have 'simple_disassembler' called on each instruction, passing a Capstone disassembler instance bound to
# the underlying architecture as an optional argument
ql.hook_code(simple_diassembler, user_data=ql.arch.disassembler)

ql.run()

hook_block

每个基本块进入时执行hook回调

1
2
3
4
def ql_hook_block_disasm(ql, address, size):
ql.log.debug("\n[+] Tracing basic block at 0x%x" % (address))

ql.hook_block(ql_hook_block_disasm)

hook_intno

对中断向量表hook,下面的0x80是中断号

1
ql.hook_intno(hook_syscall, 0x80)

hook_insn

对系统中断hook,比如syscall(UC_X86_INS_SYSCALL)

1
2
3
4
5
6
7
8
9
10
from typing import Tuple
from unicorn.x86_const import UC_X86_INS_IN

def handle_in(ql: Qiling, port: int, size: int) -> Tuple[int, int]:
# call some function to look up the value held in the specified port (not implemented by Qiling)
value = lookup_port_value(port, size)
ql.log.debug(f'reading from port {port:#x}, size {size:d} -> {value:#0{size * 2 + 2}x}')
# return a tuple indicating other hooks may be processed (0) and the read value (value)
return (0, value)
ql.hook_insn(handle_in, UC_X86_INS_IN)

其它hook

可以对内存的访问做hook来模拟硬件断点

1
2
3
4
5
6
7
8
9
ql.hook_int()
ql.hook_mem_unmapped()
ql.hook_mem_read_invalid()
ql.hook_mem_write_invalid()
ql.hook_mem_fetch_invalid()
ql.hook_mem_invalid()
ql.hook_mem_read(callback: Callable, begin: int = 1, end: int = 0)
ql.hook_mem_write(callback: Callable, begin: int = 1, end: int = 0)
ql.hook_mem_fetch()

hook管理

1
2
ql.hook_del()
ql.clear_hooks()

API劫持

标准流

from qiling.extensions import pipe中有模拟流用的对象:SimpleInStream(fd)或SimpleOutStream(fd)替换掉标准输入流后可以直接write

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
from qiling import 
from qiling.extensions import pipe

def force_call_dialog_func(ql: Qiling) -> None:
# get DialogFunc address
lpDialogFunc = ql.mem.read_ptr(ql.arch.regs.esp - 0x8, 4)

# setup stack for DialogFunc
ql.stack_push(0)
ql.stack_push(1001)
ql.stack_push(273)
ql.stack_push(0)
ql.stack_push(0x0401018)

# force EIP to DialogFunc
ql.arch.regs.eip = lpDialogFunc

if __name__ == "__main__":
# expected flag: Ea5yR3versing
ql = Qiling([r'rootfs/x86_windows/bin/Easy_CrackMe.exe'], r'rootfs/x86_windows')

# hijack program's stdin and feed it with the expected flag
ql.os.stdin = pipe.SimpleInStream(0)
ql.os.stdin.write(b'Ea5yR3versing\n')

ql.hook_address(force_call_dialog_func, 0x00401016)
ql.run()

也有交互式的,类似pwntool:

1
2
3
4
5
6
7
from qiling import Qiling
from qiling.extensions import pipe

if __name__ == "__main__":
ql = Qiling([r'rootfs/x86_linux/bin/crackme_linux'], r'rootfs/x86_linux')
ql.os.stdin = pipe.InteractiveInStream() # you will want to type L1NUX when the program waits for input
ql.run()

文件系统

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
from qiling import Qiling
from qiling.os.mapper import QlFsMappedObject

class FakeUrandom(QlFsMappedObject):

def read(self, size: int) -> bytes:
# return a constant value upon reading
return b"\x04"

def fstat(self) -> int:
# return -1 to let syscall fstat ignore it
return -1

def close(self) -> int:
return 0

if __name__ == "__main__":
ql = Qiling([r'rootfs/x86_linux/bin/x86_fetch_urandom'], r'rootfs/x86_linux')

ql.add_fs_mapper(r'/dev/urandom', FakeUrandom())
ql.run()

自定义文件系统类继承自QlFsMappedObject,自定义读和写以及关闭

同时也可以硬盘模拟,这里没看懂:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
from qiling import Qiling
from qiling.os.disk import QlDisk

if __name__ == "__main__":
ql = Qiling([r'rootfs/8086_dos/petya/mbr.bin'], r'rootfs/8086_dos')

# Note that this image is only intended for PoC purposes since the core petya code
# resides in the sepecific sectors of a hard disk. It doesn't contain any data, either
# encryted or unencrypted.

emu_path = 0x80
emu_disk = QlDisk(r'rootfs/8086_dos/petya/out_1M.raw', emu_path)

ql.add_fs_mapper(emu_path, emu_disk)
ql.run()

系统调用

使用ql.os.set_syscall来实现系统调用劫持

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
40
41
42
from qiling import Qiling
from qiling.const import QL_INTERCEPT

# customized system calls always use the same arguments list as the original
# ones, but with a Qiling instance on front. The Qiling instance may be used
# to interact with various subsystems, such as the memory or registers
def my_syscall_write(ql: Qiling, fd: int, buf: int, count: int) -> int:
try:
# read data from emulated memory
data = ql.mem.read(buf, count)

# select the emulated file object that corresponds to the requested
# file descriptor
fobj = ql.os.fd[fd]

# write the data into the file object, if it supports write operations
if hasattr(fobj, 'write'):
fobj.write(data)
except:
ret = -1
else:
ret = count

ql.log.info(f'my_syscall_write({fd}, {buf:#x}, {count}) = {ret}')

# return a value to the caller
return ret

if __name__ == "__main__":
ql = Qiling([r'rootfs/arm_linux/bin/arm_hello'], r'rootfs/arm_linux')

# the following call to 'set_syscall' sets 'my_syscall_write' to execute whenever
# the 'write' system call is about to be called. that practically replaces the
# existing implementation with the one in 'my_syscall_write'.
ql.os.set_syscall('write', my_syscall_write, QL_INTERCEPT.CALL)

# note that system calls may be referred to either by their name or number.
# an equivalent alternative that replaces the write syscall by refering its number:
#
#ql.os.set_syscall(4, my_syscall_write)

ql.run()

此处可以传:

1
2
3
QL_INTERCEPT.CALL # 完全替代
QL_INTERCEPT.ENTER # 进入前hook
QL_INTERCEPT.EXIT # 结束后hook

对于Windows:

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
from qiling import *
from qiling.os import *
from qiling.const import *
from qiling.os.windows.api import *
from qiling.os.windows.fncc import *

@winsdkapi(cc=STDCALL,params={
'dest':POINTER,
'src':POINTER,
'count':UINT
})
def my_memcpy(ql:Qiling, address, params):
dest = params['dest']
src = params['src']
count = params['count']

ql.mem.write(dest,b'Hello_Qiling')
return dest


if __name__ == '__main__':
path = ['C:/qiling/rootfs/x8664_windows/QiLingAPIHijeckTest.exe']
rootfs = "C:/qiling/rootfs/x8664_windows"
ql = Qiling(path, rootfs)
ql.os.set_api('memcpy', my_memcpy)
ql.verbose = 0
ql.run()

需要在回调函数前使用这个指定传参和调用约定,x64默认MS64。内部传参军用字典取参数

1
2
3
4
5
@winsdkapi(cc=MS64,params={
'dest':POINTER,
'src':POINTER,
'count':UINT
})

调试

指定

1
qiling.debugger = "qdb"

可以开始调试,界面类似gdb