debug-server 自动化调试工具

admin 2024年5月21日20:12:12评论5 views字数 11816阅读39分23秒阅读模式
还在因为远程环境或者定制环境没有调试工具而苦恼吗?星盟安全团队给您带来福音啦。

星盟安全团队推出了一款定制化调试工具:https://github.com/Ex-Origin/debug-server。

debug-server 自动化调试工具
该工具不需要用户在远程环境或者定制环境中安装一大堆调试工具。利用gdbserver程序,自动对目标进行Attach。

除此以外该工具还支持一键启动strace程序观察目前的系统调用情况,以及或者目前程序的内存映射地址。

主程序使用C语言编写,可以方便的在Linux系列的系统上进行编译使用,帮您解决跨架构的烦恼。

其使用方法如下所示:

Usage: debug-server [-hmsvn] [-e CMD] [-p PID] [-o CMD]General:  -e CMD   service argv  -p PID   attach to PID  -o CMD   get pid by popen  -h       print help message  -m       enable multi-service  -s       halt at entry point  -v       show debug information  -n       disable address space randomization  -u       do not limit memory
其中 debug-server 提供了几个简单的接口:

🔹attach(script=''):attach目标进程,script为传入的gdb预执行脚本。

🔹strace():strace目标进程,strace信息将由debug-server的日志中输出。

🔹address(search:str):从/proc/pid/maps中获得目标进程对应lib库的地址,比如 address('libc.so.6') 就是获得 libc 库的地址。

🔹run_service():运行服务,该API常用于apache和nginx等网络服务。

依赖

远端环境需要安装 gdbserver, strace 来保证服务正常。

本地环境需要安装 gdb-multiarch, pwntools 来保证服务正常。

使用举例

远端环境和本地环境可以是同一个环境,但是为了凸显 debug-server 对于嵌入式的便利性,这里的远端环境使用的是 aarch64 架构的系统。

远程环境使用无桌面环境的 Debian GNU/Linux 12(bookworm) aarch64 架构。

本地环境使用带桌面环境的 Ubuntu 24.04 LTS x86_64 架构。

远程测试的例子如下:

// aarch64-linux-gnu-gcc -g echo.c -o echo#include <unistd.h>int main(){    while(1)    {        char buf[0x100] = {0};        int result = 0;        write(STDOUT_FILENO, "Input: ", 7);        result = read(STDIN_FILENO, buf, sizeof(buf)-1);        write(STDOUT_FILENO, "Output: ", 8);        write(STDOUT_FILENO, buf, result);    }    return 0;}

其对应的依赖如下:

~ # ldd ./echo   linux-vdso.so.1 (0x0000ffffbd14a000)  libc.so.6 => /lib/libc.so.6 (0x0000ffffbcf30000)  /lib/ld-linux-aarch64.so.1 (0x0000ffffbd10d000)

远程环境输入如下命令对目标程序进行调试服务:

~ # ./debug-server -e ./echo2024-05-19 18:16:25 | INFO    | Start debugging service, pid=160, version=1.3.3

随后本地使用 gdbpwn.py 连接到对应的远程 IP:

$ gdbpwn.py 192.168.1.82024-05-19 18:17:28,277 : INFO : Connecting to 192.168.1.8:95452024-05-19 18:17:28,282 : INFO : It has connected successfully2024-05-19 18:17:28,282 : INFO : Start gdb client

随后即可在 exp.py 中插入所需要的调试代码即可:

#!/usr/bin/env python3# -*- coding:utf-8 -*-from pwn import *context.clear(arch='amd64', os='linux', log_level='debug')attach_host = '192.168.1.8'attach_port = 9545def attach(script=''):    tmp_sock = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM, socket.IPPROTO_UDP)    gdb_script = re.sub(r'#.*', '', f'''define pr    x/16gx $rebase(0x0)endb *$rebase(0x0)''' + 'n' + script)    gdbinit = '/tmp/gdb_script_' + attach_host    script_f = open(gdbinit, 'w')    script_f.write(gdb_script)    script_f.close()    _attach_host = attach_host    if attach_host.find(':') == -1: _attach_host = '::ffff:' + attach_host    tmp_sock.sendto(struct.pack('BB', 0x02, len(gdbinit.encode())) + gdbinit.encode(), (_attach_host, attach_port))    tmp_sock.recvfrom(4096)    tmp_sock.close()    print('attach successfully')def strace():    tmp_sock = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM, socket.IPPROTO_UDP) # UDP    _attach_host = attach_host    if attach_host.find(':') == -1: _attach_host = '::ffff:' + attach_host    tmp_sock.sendto(struct.pack('B', 0x03), (_attach_host, attach_port))    tmp_sock.recvfrom(4096)    tmp_sock.close()    print('strace successfully')def address(search:str)->int:    tmp_sock = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM, socket.IPPROTO_UDP)    _attach_host = attach_host    if attach_host.find(':') == -1: _attach_host = '::ffff:' + attach_host    tmp_sock.sendto(struct.pack('BB', 0x04, len(search.encode())) + search.encode(), (_attach_host, attach_port))    tmp_recv = tmp_sock.recvfrom(4096)[0]    tmp_sock.close()    return struct.unpack('Q', tmp_recv[2:10])[0]def run_service():    tmp_sock = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM, socket.IPPROTO_UDP) # UDP    _attach_host = attach_host    if attach_host.find(':') == -1: _attach_host = '::ffff:' + attach_host    tmp_sock.sendto(struct.pack('B', 0x06), (_attach_host, attach_port))    tmp_sock.recvfrom(4096)    tmp_sock.close()    print('run_service successfully')'''Your Code'''
其中 Your Code 指的是我们要写的调试代码。

简单的调试例子

下面我举个调试代码的例子,只需要把下面的代码替换成 Your Code 对应的部分即可,该例子演示了如何简单的调试一个程序。

sh = remote(attach_host, 9541)sh.recvuntil(b'Input: ')attach()sh.sendline(b'Hello world')sh.interactive()

其本地 exp.py 输出信息如下所示:

$ python3 exp.py [+] Opening connection to 192.168.1.8 on port 9541: Done[DEBUG] Received 0x7 bytes:    b'Input: 'attach successfully[DEBUG] Sent 0xc bytes:    b'Hello worldn'[*] Switching to interactive mode$ 

本地 gdbpwn.py 输出信息如下所示:

$ gdbpwn.py 192.168.1.82024-05-19 18:17:28,277 : INFO : Connecting to 192.168.1.8:95452024-05-19 18:17:28,282 : INFO : It has connected successfully2024-05-19 18:17:28,282 : INFO : Start gdb client2024-05-19 18:21:49,450 : INFO : Receive COMMAND_GDBSERVER_ATTACHpwndbg: loaded 157 pwndbg commands and 46 shell commands. Type pwndbg [--shell | --all] [filter] for a list.pwndbg: created $rebase, $base, $ida GDB functions (can be used with print/break)Remote debugging using ::ffff:192.168.1.8:9549...Breakpoint 1 at 0xaaaab8580000...─────────────────────[ DISASM / aarch64 / set emulate on ]────────────────────── ► 0xffff9a219e64 <read+36>    svc    #0 <SYS_read>        fd: 0 (socket:[3088])        buf: 0xffffd4b72538 ◂— 0        nbytes: 0xff   0xffff9a219e68 <read+40>    mov    x19, x0   0xffff9a219e6c <read+44>    cmn    x0, #1, lsl #12   0xffff9a219e70 <read+48>    b.hi   #read+148                   <read+148>   0xffff9a219e74 <read+52>    mov    x0, x19   0xffff9a219e78 <read+56>    ldp    x19, x20, [sp, #0x10]   0xffff9a219e7c <read+60>    ldp    x29, x30, [sp], #0x30   0xffff9a219e80 <read+64>    ret       0xffff9a219e84 <read+68>    mov    x20, x2   0xffff9a219e88 <read+72>    str    x21, [sp, #0x20]   0xffff9a219e8c <read+76>    mov    x21, x1───────────────────────────────────[ STACK ]────────────────────────────────────...pwndbg> 
自动下断点

下面演示一个自动下断点的例子。首先查看一下 echo 程序的汇编代码:

$ aarch64-linux-gnu-objdump -d ./echo ...0000000000000814 <main>: 814:  d10483ff   sub  sp, sp, #0x120 818:  a9117bfd   stp  x29, x30, [sp, #272] 81c:  910443fd   add  x29, sp, #0x110 820:  f00000e0   adrp  x0, 1f000 <__FRAME_END__+0x1e62c> 824:  f947f400   ldr  x0, [x0, #4072] 828:  f9400001   ldr  x1, [x0] 82c:  f90087e1   str  x1, [sp, #264] 830:  d2800001   mov  x1, #0x0                     // #0 834:  910023e0   add  x0, sp, #0x8 838:  4f000400   movi  v0.4s, #0x0 83c:  ad000000   stp  q0, q0, [x0] 840:  ad010000   stp  q0, q0, [x0, #32] 844:  ad020000   stp  q0, q0, [x0, #64] 848:  ad030000   stp  q0, q0, [x0, #96] 84c:  ad040000   stp  q0, q0, [x0, #128] 850:  ad050000   stp  q0, q0, [x0, #160] 854:  ad060000   stp  q0, q0, [x0, #192] 858:  ad070000   stp  q0, q0, [x0, #224] 85c:  b90007ff   str  wzr, [sp, #4] 860:  d28000e2   mov  x2, #0x7                     // #7 864:  90000000   adrp  x0, 0 <__abi_tag-0x278> 868:  91238001   add  x1, x0, #0x8e0 86c:  52800020   mov  w0, #0x1                     // #1 870:  97ffff98   bl  6d0 <write@plt> 874:  910023e0   add  x0, sp, #0x8 878:  d2801fe2   mov  x2, #0xff                    // #255 87c:  aa0003e1   mov  x1, x0 880:  52800000   mov  w0, #0x0                     // #0 884:  97ffff9b   bl  6f0 <read@plt> 888:  b90007e0   str  w0, [sp, #4] 88c:  d2800102   mov  x2, #0x8                     // #8 890:  90000000   adrp  x0, 0 <__abi_tag-0x278> 894:  9123a001   add  x1, x0, #0x8e8 898:  52800020   mov  w0, #0x1                     // #1 89c:  97ffff8d   bl  6d0 <write@plt> 8a0:  b98007e1   ldrsw  x1, [sp, #4] 8a4:  910023e0   add  x0, sp, #0x8 8a8:  aa0103e2   mov  x2, x1 8ac:  aa0003e1   mov  x1, x0 8b0:  52800020   mov  w0, #0x1                     // #1 8b4:  97ffff87   bl  6d0 <write@plt> 8b8:  d503201f   nop 8bc:  17ffffde   b  834 <main+0x20>

这次我们的目标是在 89c 处下断点,那么其对应的调试代码如下:

sh = remote(attach_host, 9541)sh.recvuntil(b'Input: ')attach(f'''b *{address("echo")+0x89c}c''')sh.sendline(b'Hello world')sh.interactive()
其在 89c处下断点后,立刻执行了 c (continue)指令,最终结果是其可以一步执行到 89c 处,而不用每次手动调节,这极大加快了调试进度。

本地 gdbpwn.py 输出信息如下所示:

Breakpoint 1 at 0xaaaae31d0000Breakpoint 2 at 0xaaaae31d089c: file echo.c, line 12.Breakpoint 2, 0x0000aaaae31d089c in main () at echo.c:1212          write(STDOUT_FILENO, "Output: ", 8);...─────────────────────[ DISASM / aarch64 / set emulate on ]──────────────────────0xaaaae31d089c <main+136>    bl     #write@plt                  <write@plt>        fd: 1 (socket:[3150])        buf: 0xaaaae31d08e8 ◂— adr x15, #0xaaaae32b9793 /* 'Output: ' */        n: 8   0xaaaae31d08a0 <main+140>    ldrsw  x1, [sp, #4]   0xaaaae31d08a4 <main+144>    add    x0, sp, #8   0xaaaae31d08a8 <main+148>    mov    x2, x1   0xaaaae31d08ac <main+152>    mov    x1, x0   0xaaaae31d08b0 <main+156>    mov    w0, #1   0xaaaae31d08b4 <main+160>    bl     #write@plt                  <write@plt>   0xaaaae31d08b8 <main+164>    nop       0xaaaae31d08bc <main+168>    b      #main+32                    <main+32>   0xaaaae31d08c0 <_fini>       nop       0xaaaae31d08c4 <_fini+4>     stp    x29, x30, [sp, #-0x10]!───────────────────────────────[ SOURCE (CODE) ]────────────────────────────────
lib库下断点

通常 gdb 对于 php 和 nginx 等需要导入 lib 库的方式难以下断点进行调试,但是本调试模式可以弥补该缺点。

举个例子,现在需要在 write 函数入口处第 8 字节的偏移处下个断点,其 libc.so.6 的汇编如下所示:

$ $ aarch64-linux-gnu-objdump -d libc.so.6...00000000000d9f10 <__write@@GLIBC_2.17>:   d9f10:  a9bd7bfd   stp  x29, x30, [sp, #-48]!   d9f14:  d0000663   adrp  x3, 1a7000 <getdate_err@@GLIBC_2.17+0x338>   d9f18:  910003fd   mov  x29, sp   d9f1c:  3967a063   ldrb  w3, [x3, #2536]

那么该调试框架只需要进行如下编写即可:

sh = remote(attach_host, 9541)sh.recvuntil(b'Input: ')attach(f'''b *{address("libc.so.6")+0xd9f18}c''')sh.sendline(b'Hello world')sh.interactive()
其首先获得libc.so.6的地址,随后向对应的偏移下断点,整个过程自动实现,方便了对于 lib 库的调试,尤其是对于没有符号函数的 lib 库,其便利效果更突出。

本地 gdbpwn.py 输出信息如下所示:

Breakpoint 1 at 0xaaaab94f0000Breakpoint 2 at 0xffffb6b09f18Breakpoint 2, 0x0000ffffb6b09f18 in write () from target:/lib/libc.so.6...─────────────────────[ DISASM / aarch64 / set emulate on ]────────────────────── ► 0xffffb6b09f18 <write+8>     mov    x29, sp                   FP => 0xffffc4e3bd90   0xffffb6b09f1c <write+12>    ldrb   w3, [x3, #0x9e8]   0xffffb6b09f20 <write+16>    stp    x19, x20, [sp, #0x10]   0xffffb6b09f24 <write+20>    sxtw   x19, w0   0xffffb6b09f28 <write+24>    cbz    w3, #write+68               <write+68>   0xffffb6b09f2c <write+28>    mov    x0, x19                   X0 => 1   0xffffb6b09f30 <write+32>    mov    x8, #0x40                 X8 => 0x40   0xffffb6b09f34 <write+36>    svc    #0   0xffffb6b09f38 <write+40>    mov    x19, x0   0xffffb6b09f3c <write+44>    cmn    x0, #1, lsl #12   0xffffb6b09f40 <write+48>    b.hi   #write+148                  <write+148>───────────────────────────────────[ STACK ]────────────────────────────────────
调试网络应用程序

针对 nginx 和 sshd 等网络应用程序,由于其不是通过 标准输入输出流 进行交互,而是通过 socket 交互,这类网络应用程序的调试过程往往十分繁琐,并且出现问题时还需要重启服务。

这里我们演示使用 debug-server 来调试这类网络程序。

远程环境输入如下命令对目标程序进行调试:

./debug-server -e /usr/sbin/sshd -o 'pidof sshd'

对应的调试的代码如下:

run_service()time.sleep(1)attach()sh = remote(attach_host, 22)sh.send(b'aaaa')sh.interactive()
在启动该调试前,需要确保当前系统没有已经启动的 sshd 服务。

其中 run_service() 函数会执行 /usr/sbin/sshd,随后 time.sleep(1) 让调试脚本暂停 1 秒以确保 sshd 服务启动成功,随后 attach() 函数对目标进程进行 attach,其进程pid的定位方式是通过 popen 函数执行 pidofsshd 而得到。对应了 -o 'pidof sshd' 参数。

通过该种调试方式,可以极大简化网络应用调试流程。

禁用随机化

远程调试服务启动时,加上 -n 参数即可关闭目标程序的随机化。

./debug-server -n -e ./echo

该操作仅对目标程序有效,并不会影响系统的随机化规则,因此其具有更高的安全性。

strace举例

对于 strace 支持,只需要将 attach() 函数替换成 strace() 函数即可。

其对应的调试代码如下:

sh = remote(attach_host, 9541)sh.recvuntil(b'Input: ')strace()sh.sendline(b'Hello world')sh.interactive()

其对应的远程输出日志如下:

2024-05-19 21:02:46 | INFO    | Strace start, pid=389strace: Process 388 attachedread(0, "Hello worldn", 255)           = 12write(1, "Output: ", 8)                 = 8write(1, "Hello worldn", 12)           = 12write(1, "Input: ", 7)                  = 7read(0, 

通过该种方式,可以观察指定代码的系统调用情况,方便研究人员理解程序。

入口处暂停

针对某些无 IO 的程序,或者是需要在入口函数之前进行修改的程序,使用 pwntools 进行调试时会十分麻烦。

本 debug-server 可以使用 -s 参数使得程序在入口处暂停,这样可以便利的对程序进行特异性初始化,尤其对于逆向某些无IO的程序很有帮助。

其远程调试服务启动的命令如下:

./debug-server -s -e ./echo

对应的调试脚本如下:

sh = remote(attach_host, 9541)attach(f'''b maincc''')sh.recvuntil(b'Input: ')sh.sendline(b'Hello world')sh.interactive()
由于 debug-server 采用发送 SIGSTOP 信号的方式暂停程序,因此第一个 c(continue)命令是处理 SIGSTOP 信号的,第二个 c 命令才会让程序继续执行下去。

本地 gdbpwn.py 输出信息如下所示:

Breakpoint 1 at 0xaaaaac560000Breakpoint 2 at 0xaaaaac560820: file echo.c, line 5.Program received signal SIGCONT, Continued.0x0000ffff8a2d2980 in ?? () from target:/lib/ld-linux-aarch64.so.1...Breakpoint 2, main () at echo.c:5...─────────────────────[ DISASM / aarch64 / set emulate on ]──────────────────────0xaaaaac560820 <main+12>    adrp   x0, #0xaaaaac57f000     X0 => 0xaaaaac57f000 ◂— 0   0xaaaaac560824 <main+16>    ldr    x0, [x0, #0xfe8]        X0 => 0xffff8a2f7b88 (__stack_chk_guard) ◂— 0x657551da6d76f000   0xaaaaac560828 <main+20>    ldr    x1, [x0]                X1 => 0x657551da6d76f000   0xaaaaac56082c <main+24>    str    x1, [sp, #0x108]   0xaaaaac560830 <main+28>    mov    x1, #0                  X1 => 0   0xaaaaac560834 <main+32>    add    x0, sp, #8              X0 => 0xffffdf9036d8 —▸ 0xffff8a2f1f60 ◂— 0   0xaaaaac560838 <main+36>    movi   v0.4s, #0   0xaaaaac56083c <main+40>    stp    q0, q0, [x0]   0xaaaaac560840 <main+44>    stp    q0, q0, [x0, #0x20]   0xaaaaac560844 <main+48>    stp    q0, q0, [x0, #0x40]   0xaaaaac560848 <main+52>    stp    q0, q0, [x0, #0x60]───────────────────────────────[ SOURCE (CODE) ]────────────────────────────────

如果不开启入口处暂停功能的话,程序则无法在入口处停下,其原因在与 IO 速度过快,如果不暂停程序,IO的速度始终大于调试的速度,使得无法调试 IO 过程之前的代码。

开源支持

本 debug-server 使用 MIT 开源证书,欢迎各位感兴趣的极客们加入维护。

下载

https://github.com/Ex-Origin/debug-server

原文始发于微信公众号(星盟安全):debug-server 自动化调试工具

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2024年5月21日20:12:12
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   debug-server 自动化调试工具https://cn-sec.com/archives/2763045.html

发表评论

匿名网友 填写信息