0%

【置顶】技巧misc

LD_PRELOAD: hook动态链接库函数

LD_PRELOADLinux系统中的一个环境变量,利用它可以指定在程序运行前优先加载的动态链接库,实现在主程序和其他动态链接库的中间加载自定义的动态链接库,甚至覆盖正常的函数。一般而言,程序启动后会按一定顺序加载动态库:

  1. 加载LD_PRELOAD指定的动态库;
  2. 加载文件/etc/ld.so.preload指定的动态库;
  3. 搜索LD_LIBRARY_PATH指定的动态库路径;
  4. 搜索路径/lib64下的动态库文件。

在对嵌入式设备进行仿真时,经常需要进行环境修复,比如劫持与NVRAM相关的函数、hook某些函数使得程序继续运行不崩溃等。以qemu user mode为例,通过-E选项指定LD_PRELOAD环境变量,从而达到上述目的。

1
$sudo chroot . ./qemu-arm-static -E LD_PRELOAD='<custom_lib.so>' <binary_path> arg0 arg1

有时,使用LD_PRELOAD环境变量可能会不起作用,可以采用另一种方式:修改/etc/ld.so.preload配置文件,指定需要加载的自定义动态链接库。

通常,有2种常见的方式可以让LD_PRELOAD失效(上面提到的情况不属于这2种):

  • 静态链接
  • 设置文件的setgid/setuid标志:有SUID权限的程序,系统会忽略LD_PRELOAD环境变量

最后,推荐两个常用的用于hook的第三方库,代码及实现比较优雅,可以直接拿来使用或者参考借鉴:

  • libnvram:固件仿真框架Firmadyne中提供的用于模拟NVRAM行为的动态库,支持很多常见的api,同时还会解析固件中自带的一些默认键值对;
  • preeny:支持很多常见的api,包括socket相关、fork()alarm()rand()等。

相关链接

  • libnvram
  • preeny

gdb命中断点后继续运行

在使用gdb进行调试时,有时侯想让程序命中断点执行一些操作后继续运行,比如dump指定内存地址处的内容、记录执行过的基本块地址等。在gdb中,让程序命中断点执行一些操作后继续运行,常见的方式有三种:

  • define hook-stop方式

    1
    2
    3
    4
    5
    6
    # gdb
    > b *0x12345678 # set breakpoint
    > define hook-stop
    x/4wx $esp # custom gdb command
    continue
    end
  • commands命令 + gdb.events事件

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    # gdb_event.py
    # !!! call gdb.execute('continue') in event functions will cause recursive call
    def handle_stop_event(event):
    if not isinstance(event, gdb.BreakpointEvent):
    # do what you want

    gdb.events.stop.connect(handle_stop_event)

    # gdb
    > b *0x12345678
    > source gdb_event.py # run python script in gdb
    > commands 1 # breakpoint num
    continue # custom gdb command
    end
  • 自定义gdb.Breakpoint

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    # custom_gdb.py
    class MyBreakpoint(gdb.Breakpoint):
    def stop(self):
    # do what you want
    return False # continue automatically

    MyBreakpoint("*{:#x}".format(0x12345678))

    # gdb
    > source custom_gdb.py

其中,在hook-stop中运行continue命令似乎仅在第一次有效,后续命中断点后还是会停下来;而采用commands + gdb.events方式有时在多线程中会报异常;采用自定义gdb.Breakpoint方式是比较推荐的。

另外,如果只是想在命中断点后,打印指定内存地址处的内容,一种更好地方式是直接使用dprtinf命令,其原理是设置断点(dprintf类型),然后调用printf输出,之后再继续运行行。

1
dprintf location,template,expression[,expression…]

相关链接

  • How to continue the exection after hitting breakpoints in gdb?
  • User-defined Command Hooks
  • Events In Python
  • Manipulating breakpoints using Python
  • Dynamic Printf
  • gdb events example

IDA命令行运行idapython脚本

IDA GUI中可以通过执行idapython脚本来完成一些特定的工作,如果需要对多个程序执行相同的操作,一种方式是在IDA GUI中逐个程序执行对应的脚本,另一种更优雅的方式则是通过IDA命令行进行自动化批量分析。

Windows平台为例,针对单个程序,通过命令行自动执行idapython脚本的步骤如下:

  1. 调用idat.exe/idat64.exe对程序进行初始分析,生成对应的idb文件

    1
    2
    3
    4
    5
    6
    # processor type
    # x86/x86_64: metapc
    # arm: arm/armb
    # mips: mipsl/mipsb
    # PowerPC: ppcl/ppc
    $ "<ida_path>" -A -B -p<processor_type> -o<idb_path> <binary_path>
  2. 基于生成的idb文件,运行对应的自动化脚本

    1
    $ "<ida_path>" -A -S"<script_path> <arg1> <arg2>" <idb_path>

其中,idapython脚本中通过ARGV[i]来获取传递的参数,同时最后通过调用idc.Exit(0)退出。

1
2
3
4
arg1 = ARGV[1]
arg2 = ARGV[2]
# ... # do what you want
idc.Exit(0)

Linux平台与Windows平台类似,但存在细微差别:1) ida可执行程序变为idal/idal64;2) 在命令行参数最开始加上TVHEADLESS=1,最后可加上 > /dev/null

1
$ TVHEADLESS=1 "<ida_path>" -B -p"<processor_type>" -o"<idb_path>" "<binary_path>" > /dev/null

附件下载

示例脚本

相关链接

  • IDA Help: Command line switches