首页 / 资讯中心 / 文章详情

Qiling框架的使用、capstone框架、STL函数识别、Angr 符号执行原理

Qiling框架的使用、capstone框架、STL函数识别、Angr 符号执行原理 ★ FEATURED ARTICLE
Qiling框架的使用1.Qiling 框架Qiling 框架是高级二进制模拟执行框架Qiling 框架基于 Unicorn 模拟执行框架为其添加了系统运行时以及可执行文件装载器同时还实现了大部分外围 IO 例如文件系统。使用 Qiling 可以快速解决无环境调试的问题。Qiling 框架主要的特性如下:支持多种系统: Windows, MacOS, Linux, BSD, UEFI, DOS, MBR, Ethereum Virtual Machine支持多种指令集: X86, X86_64, Arm, Arm64, MIPS, 8086支持多种执行文件格式: Support multiple file formats: PE, MachO, ELF, COM, MBR支持多种插桩方法: instruction/basic-block/memory-access/exception/syscall/IOQiling 框架基于 Unicorn 框架Unicorn 框架是基于 qemu 的模拟执行框架Unicorn 可以实现对被执行代码不同粒度的控制亦可控制中断、系统调用、IO 等资源并且支持主流的指令集。capstone框架1.简介Capstone 的特性支持多架构Arm, Arm64 (Armv8), BPF, Ethereum Virtual Machine, M68K, M680X, Mips, MOS65XX, PowerPC, RISC-V, Sparc, SystemZ, TMS320C64X, Web Assembly, XCore X86 (include X86_64)简单易用的 APIC/C、Python、Rust、……支持指令细节反汇编支持提供指令的语义信息例如隐式读写的寄存器信息Capstone 的 Hello Worldfrom capstone import * CODE b\x55\x48\x8b\x05\xb8\x13\x00\x00 md Cs(CS_ARCH_X86, CS_MODE_64) for i in md.disasm(CODE, 0x1000): print(0x%x:\t%s\t%s % (i.address, i.mnemonic, i.op_str))CS_ARCH_X86告诉 Capstone 要反汇编 X86 指令集可通过代码补全查看其他 CS_ARCH_ 开头的常量。CS_MODE_64告诉 Capstone 要反汇编 64 位的指令集。调用 disasm 对 CODE 数据反汇编0x1000 是数据对应的基地址disasm 函数返回一个由指令对象CsInsn构成的迭代器。设置 detail True 开启细节分析Capstone 的 detail 信息是架构独立的每个架构都不同开启细节分析后可以获得指令更多细节信息指令读取的寄存器列表指令修改的寄存器列表指令所属的指令组指令操作码信息STL函数识别1.stl问题缺少符号无法判断 STL 容器类型主要困难STL 内部数据结构复杂难以提取数据O3 优化使用大量 stl 函数被 inline常用的 stl 容器std::string 字符串std::vector 动态数组std::map 关联数组2.常用 STL 容器内存布局调试时观察数据的内存布局推断容器类型以 64 位 gnu c 的 stl 为例std::string 结构大致如下struct basic_string { char *begin_; // actual string data size_t size_; // actual size union { size_t capacity_; // used if larger than 15 bytes char sso_buffer[16]; // used if smaller than 16 bytes }; };结构体固定长度为 32 字节4 个 dq如果字符串长度小于 16数据就存储在当前结构否则重新分配内存存放数据std::map 内存布局stl 内部使用红黑树实现 std::map要获得所有键值对只需要遍历二叉树struct std::map { juizixue.com void* allocator; // ignore color color; // ignore node * root; node * leftmost; // ignore node * rightmost; // ignore size_t node_count; } ​ struct node { color color; node* parent; node* left; node* right; ​ TypeKey key; // data area TypeValue value; }parent 字段可以用来在调试时确认数据类型C逆向1.内存布局class MyClass{ public: MyClass() { var1 1; var2 2; var3 3; }; int get_var1() { return var1; } private: int var1; int var2; int var3; };//// 0 | var1 (4) //// 4 | var2 (4) //// 8 | var3 (4)每个int类型占 4 字节常见系统下所以 var1 从地址 0 开始var2 从 4 开始var3 从 8 开始含有虚函数的内存布局class MyClassVirtual { int var1; public: virtual int get_num(int x, int y); virtual void reset_values() { var1 7; }; };内存布局说明//// //// 0 | [vtable ptr] //// 4 | var1 (4)含虚函数的类内存布局首地址会放一个虚表指针vtable ptr指向虚函数表vtable虚表是一个函数地址表包含该类所有虚函数的地址29 const MyClassVirtual::vftable DD FLAT:const MyClassVirtual::RTTI C 30 DD FLAT:virtual int MyClassVirtual::get_num(int,int) 31 DD FLAT:virtual void MyClassVirtual::reset_values(void)虚表指针在构造函数中初始化右侧构造函数代码里的mov DWORD PTR [eax], OFFSETFLAT:MyClassVirtual::vftable 就是初始化虚表指针的操作单继承内存布局class MyClassVirtual { public: int var1; virtual int get_num(int x, int y); virtual void reset_values() { var1 7; }; }; ​ class MyClassVirtual2 : public MyClassVirtual { public: int var1; int get_values() { return var1; } };对应的内存布局//// //// | [vtable ptr] //// | var1 (4) // 基类MyClassVirtual的成员 //// | var1 (4) // 派生类MyClassVirtual2的成员单继承时派生类的内存布局会先包含基类的完整内存结构包括基类的虚表指针、基类成员变量然后再存放派生类自己的成员变量。这里基类和派生类都有var1会各自占用内存空间不会重叠。多继承内存布局class A { int var1; public: virtual int calc(); }; ​ class B : public A { int var2; public: virtual int B_1() { return 1; }; virtual int calc() { return var2; }; }; ​ class C : public B, MyClassVirtual2 { int var3; public: virtual int C_1() { return 222; }; virtual int calc() { return var3; }; };内存布局//// //// 0 | [vtable ptr] B // 基类B对应的虚表指针 //// 4 | A::var1 // 基类A的成员B继承自A //// 8 | B::var2 // 基类B的成员 //// //// | [vtable ptr] MyClassVirtual2 // 基类MyClassVirtual2对应的虚表指针 //// 10 | MyClassVirtual2::var1 // 基类MyClassVirtual2的成员注这里地址“10”对应十六进制0xC //// 14 | MyClassVirtual2::var1 // 基类MyClassVirtual2的另一个成员需结合之前的MyClassVirtual2定义 //// 18 | C::var3 // 派生类C自己的成员多继承时派生类C的内存布局会按继承顺序依次包含每个基类的完整内存结构每个基类都带自己的虚表指针 成员最后放派生类自己的成员。因为 C 继承了B和MyClassVirtual2所以内存中先放B的结构包含 B 的虚表指针、B 继承自 A 的成员再放MyClassVirtual2的结构包含其虚表指针 成员最后放 C 的成员var3。每个基类对应的虚表会由编译器重新构造以适配派生类的虚函数重写比如 C 重写了calc。Angr 符号执行原理1.栈溢出ret2textRet2text: 利用栈溢出将函数的返回地址覆盖为后门函数地址该后门函数代码位于 .text 代码段。对字符串输入建模字符串输入可能会产生溢出题目部署在题目根目录下运行: docker-compose up -d ctf 即可完成部署题目访问方式部署命令执行成功后直接用 nc 访问本地 127.0.0.1:1447 端口就能访问题目题目返回内容访问题目后题目发送一段经过 base64 编码的 elf 文件每次连上服务发回的文件都不同。程序共性简单逆向分析后这些程序都有共性程序有 128 条路径只有一条路径有栈溢出并且每个程序都有后门函数。需要[将输入符号化编写一个符号管理函数用于分配符号并把符号存入程序 statedef getBVV(state, sizeInBytes, type str): global pathConditions name s_ str(state.globals[symbols_count]) bvs claripy.BVS(name, sizeInBytes * 8) state.globals[symbols_count] 1 state.globals[name] (bvs, type) return bvs对于整数输入通过 getBVV 分配一个 4 字节符号对于字符串输入用 getBVV 分配字符串输入长度字节数的符号。getBVV 函数会将新分配的符号保存到 state 中的 globals 变量以便于在之后求解的时候能找到所有的符号。2.解题思路个自动化点程序的漏洞溢出点在哪个路径什么样的输入才能到达溢出点其它问题如何对输入建自动生成的程序有两种输入字符串输入整数输入提示内容对字符串输入建模字符串输入可能会产生溢出符号化说明将输入符号化编写一个符号管理函数用于分配符号并把符号存入程序 state函数代码def getBVV(state, sizeInBytes, type str): global pathConditions name s_ str(state.globals[symbols_count]) bvs claripy.BVS(name, sizeInBytes * 8) state.globals[symbols_count] 1 state.globals[name] (bvs, type) return bvs输入符号分配说明对于整数输入通过 getBVV 分配一个 4 字节符号对于字符串输入用 getBVV 分配字符串输入长度字节数的符号getBVV 函数会将新分配的符号保存到 state 中的 globals 变量以便于之后求解的时候能找到所有的符号使用 Hook 技术对输入函数进行处理类定义代码class ReplacementCheckInput(angr.SimProcedure): def run(self, buf, len): jiuzixue.com len self.state.solver.eval(len) self.state.memory.store(buf, getBVV(self.state, len)) ​ class ReplacementInputVal(angr.SimProcedure): def run(self): self.state.regs.rax getBVV(self.state, 4, int)Hook 调用代码p.hook_symbol(__Z10input_linePc, ReplacementCheckInput()) p.hook_symbol(__Z9input_valv, ReplacementInputVal())还需要处理字符串比较函数该函数可能产生路径爆炸字符串比较处理类代码class ReplacementCheckEquals(angr.SimProcedure): def run(self, str1, str2): jiuzixue.com cmp1 angr_load_str(self.state, str2).encode(ascii) cmp0 self.state.memory.load(str1, len(cmp1)) self.state.regs.rax claripy.If(cmp1 cmp0, claripy.BVV(0, 32), claripy.BVV(1, 32))该类是 Angr 的自定义模拟过程作用是替换原字符串比较函数通过加载并编码目标字符串 str2 再加载 str1 对应长度的内容最后用 claripy.If 判断两者是否相等并将结果存入 rax 寄存器0 表示相等1 表示不相等Hook 调用代码python运行p.hook_symbol(__Z5fkstHPKCSo_, ReplacementCheckEquals())通过 hook_symbol 将目标符号原字符串比较函数挂钩到自定义的 ReplacementCheckEquals 类实现对原函数的替换处理。核心问题程序的漏洞溢出点在哪个路径解决思路说明可以利用 angr 的 explore 引擎遍历所有分支寻找满足能使得 eip 寄存器 unconstrained 的分支。eip 寄存器 unconstrained 意味着 eip 寄存器的值是一个符号值Angr 的符号执行引擎无法确定下一步该执行什么命令。只有当 eip 被输入符号覆盖时才会产生这种情况simgr p.factory.simgr(enter, save_unconstrainedTrue) d simgr.explore()指定 save_unconstrained 为 Truesimgr 在遍历分支过程中遇到 eip 被符号化时会将此时的 state 放入的。d.unconstrained 数组中。simgr p.factory.simgr(enter, save_unconstrainedTrue) d simgr.explore() for state in d.unconstrained: bindata b rsp state.regs.rsp next_stack state.memory.load(rsp, 8, endnessp.arch.memory_endness) state.add_constraints(state.regs.rip ret_rop) state.add_constraints(next_stack backdoor) for i in range(state.globals[symbols_count]):simgr p.factory.simgr(enter, save_unconstrainedTrue) d simgr.explore() for state in d.unconstrained: bindata b rsp state.regs.rsp next_stack state.memory.load(rsp, 8, endnessp.arch.memory_endness) state.add_constraints(state.regs.rip ret_rop) state.add_constraints(next_stack backdoor) for i in range(state.globals[symbols_count]):解题思路说明从该 d.unconstrained 数组中取出的 state 就是溢出点对应的 state利用该 state 中积累的符号信息求解到该 state 对应的输入。还可以从栈中取出想控制位置的符号进行限制用于构造 payloadfor state in d.unconstrained: bindata b rsp state.regs.rsp next_stack state.memory.load(rsp, 8, endnessp.arch.memory_endness) state.add_constraints(state.regs.rip ret_rop) state.add_constraints(next_stack backdoor) for i in range(state.globals[symbols_count]): s, s_type state.globals[s_ str(i)] if s_type str: bb state.solver.eval(s, cast_tobytes) if bb.count(b\x00) len(bb): bb b\x00 bindata bb elif s_type int: bindata str(state.solver.eval(s, cast_toint)).encode(ASCII) b r.send(bindata) r.interactive()使用 Hook 技术对输入函数进行处理输入函数处理类代码class ReplacementCheckInput(angr.SimProcedure): def run(self, buf, len): jiuzixue.com len self.state.solver.eval(len) self.state.memory.store(buf, getBVV(self.state, len)) ​ class ReplacementInputVal(angr.SimProcedure): def run(self): self.state.regs.rax getBVV(self.state, 4, int)Hook 调用代码p.hook_symbol(__Z10input_linePc, ReplacementCheckInput()) p.hook_symbol(__Z9input_valv, ReplacementInputVal())
阅读完成 · 觉得有帮助?
咨询建站