其他
用麒麟框架深入分析实模式二进制文件
本文为看雪论坛优秀文章
看雪论坛作者ID:澪同学
前言
Unicorn
平台独立。
易于使用和理解的 API。
动态 hook。
编译更快。
麒麟
模拟
|-------| |---------|
|Program| <-------> | Binary |
| OS | <-------> | Qilin |
| CPU | <-------> | Unicorn |
|-------| |---------|
原生应用 模拟应用
|--------| ---
| os | |
| loader | Qilin
| arch | |
|--------| ---
arch 层进行一些和 CPU 架构相关的设置,比如大小端,寄存器等等。
loader 层像真正的系统二进制加载器一样,解析目标二进制文件,设置内存布局,把代码和数据加载到内存里。
os 层是最重要的部分,提供了系统调用的实现。
HI.COM 例子
mov ah,9
mov dx, 10d
int 21
mov ax, 4c00
int 21
nop
db "Hello world!$"
def int21(self): # Handler for INT 21
ah = self.ql.reg.ah
if ah == 0x4C:
self.ql.uc.emu_stop() # Stop emulation
# other interrupts...
elif ah == 0x9:
s = self.read_dos_string_from_ds_dx() # Read string
self.ql.nprint(s) # Print the string to console
# other interrupts...
Petya 例子
def int10(self):
# BIOS video support
# https://en.wikipedia.org/wiki/INT_10H
# https://stanislavs.org/helppc/idx_interrupt.html
# implemented by curses
ah = self.ql.reg.ah
al = self.ql.reg.al
if ah==0:
# time to set up curses
# copied from curses.wrapper
self.stdscr = curses.initscr()
curses.noecho()
curses.cbreak()
self.stdscr.keypad(1)
try:
curses.start_color()
except:
pass
if al == 0 or al == 1:
curses.resizeterm(25, 40)
elif al == 2 or al == 3:
curses.resizeterm(25, 80)
elif al == 4 or al == 5 or al == 9 or al == 0xD or al == 0x13:
curses.resizeterm(200, 320)
elif al == 6:
curses.resizeterm(200, 640)
elif al == 8:
curses.resizeterm(200, 160)
elif al == 0xA or al == 0xE:
curses.resizeterm(200, 640)
elif al == 0xF:
curses.resizeterm(350, 640)
elif al == 0x10:
curses.resizeterm(350, 640)
elif al == 0x11 or al == 0x12:
curses.resizeterm(480, 640)
else:
self.ql.nprint("Exception: int 10h syscall Not Found, al: %s" % hex(al))
raise NotImplementedError()
# Quoted from https://linux.die.net/man/3/resizeterm
#
# If ncurses is configured to supply its own SIGWINCH handler,
# the resizeterm function ungetch's a KEY_RESIZE which will be
# read on the next call to getch.
ch = self._get_ch_non_blocking()
if ch == curses.KEY_RESIZE:
self.ql.nprint(f"[!] You term has been resized!")
elif ch != -1:
curses.ungetch(ch)
self.stdscr.scrollok(True)
if not curses.has_colors():
self.ql.nprint(f"[!] Warning: your terminal doesn't support colors, content might not be displayed correctly.")
# https://en.wikipedia.org/wiki/BIOS_color_attributes
# blink support?
if curses.has_colors():
for fg in range(16):
for bg in range(16):
color_pair_index = 16*fg + bg + 1
if fg not in self.color_pairs:
self.color_pairs[fg] = {}
curses.init_pair(color_pair_index, COLORS_MAPPING[fg], COLORS_MAPPING[bg])
color_pair = curses.color_pair(color_pair_index)
self.color_pairs[fg][bg] = color_pair
self.revese_color_pairs[color_pair] = (fg, bg)
模拟侧重点的区别
分析
fs_mapper
#!/usr/bin/env python3
# Source: https://github.com/qilinframework/qilin/blob/dev/examples/hello_x86_linux_fake_urandom.py
#
# Cross Platform and Multi Architecture Advanced Binary Emulation Framework
# Built on top of Unicorn emulator (www.unicorn-engine.org)
from qilin import *
from qilin.os.mapper import QlFsMappedObject
class Fake_urandom(QlFsMappedObject):
def read(self, size):
return b"\x01" # fixed value for reading /dev/urandom
def fstat(self): # syscall fstat will ignore it if return -1
return -1
def close(self):
return 0
if __name__ == "__main__":
ql = Qilin(["rootfs/x86_linux/bin/x86_fetch_urandom"], "rootfs/x86_linux")
ql.add_fs_mapper("/dev/urandom", Fake_urandom())
ql.run()
// Souce: https://github.com/qilingframework/qiling/blob/dev/examples/src/linux/fetch_urandom.c
#include <stdio.h>
int main(void) {
FILE *fp;
int randno;
if ((fp = fopen("/dev/urandom", "r")) == NULL) {
fprintf(stderr, "Error! Could not open /dev/urandom for read\n");
return -1;
}
randno = fgetc(fp);
printf("randno: %d\n", randno);
fclose(fp);
return 0;
}
#!/usr/bin/env python3
# Source: https://github.com/qilinframework/qilin/blob/dev/examples/petya_8086_mbr.py
#
# Cross Platform and Multi Architecture Advanced Binary Emulation Framework
# Built on top of Unicorn emulator (www.unicorn-engine.org)
import sys
sys.path.append("..")
from qilin import *
from qilin.os.disk import QlDisk
if __name__ == "__main__":
ql = Qilin(["rootfs/8086/petya/mbr.bin"],
"rootfs/8086",
console=False,
output="debug",
log_dir=".")
# Note:
# This image is only intended for PoC since the core petya code resides in the
# specific sectors of a harddisk. It doesn't contain any data, either encryted
# or unencrypted.
ql.add_fs_mapper(0x80, QlDisk("rootfs/8086/petya/out_1M.raw", 0x80))
ql.run()
系统调用劫持
from qilin import *
def my_puts(ql):
addr = ql.os.function_arg[0]
print("puts(%s)" % ql.mem.string(addr))
if __name__ == "__main__":
ql = Qilin(["rootfs/x8664_linux/bin/x8664_hello"], "rootfs/x8664_linux", output="debug")
ql.set_api('puts', my_puts)
ql.run()
快照 & 部分执行
def one_round(ql: Qilin, key: bytes, key_address):
gkeys = generate_key(key)
ql.mem.write(key_address, gkeys)
# Partial executaion
ql.run(begin=verfication_start_ip, end=verfication_start_ip+6)
lba37 = ql.mem.read(ql.reg.sp + 0x220, 0x200)
for ch in lba37:
if ch != 0x37:
return False
return True
# In this stage, we will crack for the password.
def second_stage(ql: Qilin):
disk = QlDisk("rootfs/8086/petya/out_1M.raw", 0x80)
#nonce = get_nonce(disk)
# Prepare stack
verfication_data = disk.read_sectors(0x37, 1)
nonce_data = disk.read_sectors(0x36, 1)
ql.reg.sp -= 0x200
verification_data_address = ql.reg.sp
ql.reg.sp -= 0x200
nonce_address = ql.reg.sp + 0x21
ql.reg.sp -= 0x20
key_address = ql.reg.sp
ql.mem.write(verification_data_address, verfication_data)
ql.mem.write(nonce_address - 0x21, nonce_data)
ql.arch.stack_push(0x200)
ql.arch.stack_push(verification_data_address)
ql.arch.stack_push(0)
ql.arch.stack_push(nonce_address)
ql.arch.stack_push(key_address)
for x in product(list(accepted_chars), repeat=2):
ctx = ql.save()
# 3xMxjxXxLxoxmxAx
key = b"3xMxjxXxLxoxmx" + ("".join(x)).encode("utf-8")
print(f"Trying: {key}")
if one_round(ql, key, key_address):
print(f"Key: {key}")
return key
else:
ql.restore(ctx)
return None
调试
扩展
总结
后记
看雪ID:澪同学
https://bbs.pediy.com/user-home-751602.htm
*本文由看雪论坛 澪同学 原创,转载请注明来自看雪社区。
推荐文章++++
求分享
求点赞
求在看