例题下载地址:点此下载
压缩包中共有四个文件
run.sh是启动程序的shell脚本
LD_PRELOAD=./un.so.1 ./babyunic func
从这个脚本中我们可以知道babyunic是可执行程序,放入IDA分析下
查看main函数,我们大概可以得知程序的主要逻辑是把用户输入的flag给sub_CBA这个函数,进行一顿运算赋值给s1,然后拿s1的值和&unk202020程序硬编码的数据进行比较。
分析一下sub_CBA这个函数的三个参数,v4是用户输入的字符串,s1是一个指针,迪桑参数是运行程序时命令行跟的参数func,参数分析完毕,进入sub_CBA函数。
看到这几个uc_开头的函数,让我想到了unicorn-engine,Unicorn是一个轻量级, 多平台, 多架构的CPU模拟器框架。分析这个函数得知这个函数的大概逻辑就是读取func文件然后使用unicorn-engine模拟执行,可以通过uc_open这个函数的参数得到字节码文件func的系统架构,参数的具体意思需要去github上看unicorn-engine源代码的注释。
这里只需要分析第一个和第二个参数就可以了
uc_open(3LL, 0x40000004LL, &v9);
uc_open的定义:
uc_err uc_open(uc_arch arch, uc_mode mode, uc_engine **uc);
uc_arch的定义:
得知这个字节码是MIPS架构的程序
typedef enum uc_arch {
UC_ARCH_ARM = 1, // ARM architecture (including Thumb, Thumb-2)
UC_ARCH_ARM64, // ARM-64, also called AArch64
UC_ARCH_MIPS, // Mips architecture
UC_ARCH_X86, // X86 architecture (including x86 & x86-64)
UC_ARCH_PPC, // PowerPC architecture (currently unsupported)
UC_ARCH_SPARC, // Sparc architecture
UC_ARCH_M68K, // M68K architecture
UC_ARCH_MAX,
} uc_arch;
uc_mode的定义:
得知这个字节码是MIPS架构32bit的大端序的程序
typedef enum uc_mode {
UC_MODE_LITTLE_ENDIAN = 0, // little-endian mode (default mode)
UC_MODE_BIG_ENDIAN = 1 << 30, // big-endian mode
// arm / arm64
UC_MODE_ARM = 0, // ARM mode
UC_MODE_THUMB = 1 << 4, // THUMB mode (including Thumb-2)
UC_MODE_MCLASS = 1 << 5, // ARM's Cortex-M series (currently unsupported)
UC_MODE_V8 = 1 << 6, // ARMv8 A32 encodings for ARM (currently unsupported)
// arm (32bit) cpu types
UC_MODE_ARM926 = 1 << 7, // ARM926 CPU type
UC_MODE_ARM946 = 1 << 8, // ARM946 CPU type
UC_MODE_ARM1176 = 1 << 9, // ARM1176 CPU type
// mips
UC_MODE_MICRO = 1 << 4, // MicroMips mode (currently unsupported)
UC_MODE_MIPS3 = 1 << 5, // Mips III ISA (currently unsupported)
UC_MODE_MIPS32R6 = 1 << 6, // Mips32r6 ISA (currently unsupported)
UC_MODE_MIPS32 = 1 << 2, // Mips32 ISA
UC_MODE_MIPS64 = 1 << 3, // Mips64 ISA
// x86 / x64
UC_MODE_16 = 1 << 1, // 16-bit mode
UC_MODE_32 = 1 << 2, // 32-bit mode
UC_MODE_64 = 1 << 3, // 64-bit mode
// ppc
UC_MODE_PPC32 = 1 << 2, // 32-bit mode (currently unsupported)
UC_MODE_PPC64 = 1 << 3, // 64-bit mode (currently unsupported)
UC_MODE_QPX = 1 << 4, // Quad Processing eXtensions mode (currently unsupported)
// sparc
UC_MODE_SPARC32 = 1 << 2, // 32-bit mode
UC_MODE_SPARC64 = 1 << 3, // 64-bit mode
UC_MODE_V9 = 1 << 4, // SparcV9 mode (currently unsupported)
// m68k
} uc_mode;
根据分析得知func这段字节码是MIPS架构32bit的大端序的程序
由于IDA正常不转插件的情况下MIPS没办法看伪代码,所以我们上另一个神器Ghidra,这个程序可以分析MIPS架构的伪代码。
新建一个项目,然后导入func文件,接着选择架构
打开后默认应该是这样
在a区域按d会反汇编成汇编代码,然后单击b按钮,会出现c区域的伪代码,接着就可以愉快的分析伪代码了。
这两个参数就是func这个函数的参数,a1是用户输入,a2就相当于返回的值
然后上一层函数使用a2和程序硬编码数据做比较
func这个函数逻辑还是比较清楚的,只是这个42元的方程有点烦,看这一串括号都瘆得慌。这里就要用z3这个解方程神器了。
写有一个IDAPython脚本把程序中硬编码的数据dump出来,这里有一点需要注意,func函数是大端序,ida分析的硬编码数据如果以dword形式显示,是小端序,所以要注意处理这部分的数据,所以我的IDAPython脚本使用get_bytes按顺序取数据。
import binascii
start = 0x202020
tmp = []
for i in range(42):
tmp.append(binascii.hexlify(get_bytes(start,4)))
start += 4
print tmp
['ffffff94', 'ffffff38', '00000126', 'ffffff28', 'fffffc10', '00000294', 'fffffc9e', '000006ea', '000000dc', '00000006', 'ffffff0c', 'fffffdf6', 'fffffa82', 'fffffcd0', '00000182', '000003de', '0000014e', '000002b2', 'fffff8d8', '00000174', 'fffffaa6', 'fffff9d4', '000001c2', 'fffff97c', '0000035a', '00000146', 'ffffff3c', 'fffffa14', '000001ce', '000007dc', 'fffffd48', '00000098', '0000085e', 'fffffdb0', 'ffffffbc', '0000036e', 'ffffff4e', 'fffff836', '000005c0', '000006ae', '00000694', '00000022'