首页
社区
课程
招聘
[原创]gdb的那些奇淫技巧
发表于: 2020-9-13 19:35 11657

[原创]gdb的那些奇淫技巧

2020-9-13 19:35
11657

gdb也用了好几年了,虽然算不上骨灰级玩家,但也有一些自己的经验,因此和大家分享交流下,顺便也作为一个存档记录。

最近在调试一个漏洞的exploit时遇到一个问题。目标漏洞程序是一个 CGI 程序,由主进程调起,而且运行只有一瞬的时间;我的需求是想要在在该程序中下断点,在内存布局之后可以调试我的 shellcode,该如何实现?当然目标程序是没有符号的,而且我希望下的断点是一个动态地址。在 lldb 中有--wait-for,gdb 里却没有对应的命令,经过多次摸索,终于总结出一个比较完美的解决方案。

这里构建一个简单的示例来进行实际演示。首先是父进程:

子进程很简单:

这里编译子进程时候指定-no-pie,并且strip掉符号。我们的调试目标是断点在子进程的strcpy中,拓展来说是希望能断点在子进程的任意地址上。

通过搜索可以找到一个 stackoverflow 的回答: gdb break when entering child process。根据其说法,使用 set follow-fork-mode child即可。这是一个 gdb 命令,其目的是告诉 gdb 在目标应用调用fork之后接着调试子进程而不是父进程,因为在 Linux 中fork系统调用成功会返回两次,一次在父进程,一次在子进程。我们来试一下,直接断点在 strcpy 符号中:

噢,断点都打不上,理由很简单,因为不同进程之间的虚拟地址空间都不一样。

另外一个回答中说了,虽然不能断在指定地址,但我们可以break main,告诉 gdb 把断点设置在 main 函数。不过我们的子进程是没有符号的,所以break main并没有卵用。

现在已经有了让 gdb 跟着子进程的方法,只不过问题是无法把断点打到子进程上,因为子进程还没有启动,那么用硬件断点可不可以?

可以是可以,但是断点压根没有触发,子进程直接拷贝溢出崩溃了都没有停下来!所以硬件断点在这里并没有用。

那么把断点设置在一些起始函数的上呢?根据之前对 ELF 以及动态链接的学习,我们可以断在比如_start或者__libc_start_main上面:

实际上该断点也不会触发,因为这个地址是是父进程的地址空间。

不过到现在答案已经呼之欲出了,总结一下,gdb 支持:

所以,就有了一个最终方案。

我的最终方案如下:

首先告诉 gdb 跟踪子进程;然后设置set breakpoint pending on是为了在设置断点时让 gdb 不强制在对符号下断点时就需要固定地址,这样在b _start时就会 pending 而不是报错;最后再连接到父进程以及加载子进程的符号。

detach-on-fork on是为了在 fork 之后断开父进程,避免 gdb 退出时把父进程杀死,并不是这节的重点。

其中的时序非常重要。如果先 attach 父进程再下断点,那么断点会直接下到父进程空间从而不会触发;如果先读取了子进程的符号再下断点,可能会下在一个错误的虚拟地址上。

这也是我用了很久的一个方法,不过后来我知道了有更官方的解决方式:

囧,……

Catch Point真是个好东西,支持很多有用的事件:

看来文档搜索能力还有待提高啊。……

在调试大型程序的时候,经常会遇到这么一个问题,即涉及到的线程很多,少则十几个多则上百个线程。在这些线程之间穿梭也是一个常见的困难。

首先最基本的是线程的切换命令:

对于进程也有类似的命令info inferiors/inferior n,在调试多进程交互的程序时会经常用到。

其次,在对某个线程进行单步调试时,会遇到 CPU 的迷之调度,突然一个next或者nexti就跑到其他线程去了,这个时候有个特殊的参数scheduler-locking可以解决这个问题:

通常设置为step模式可解决单步调试的问题。

我经常用到的一个功能是需要使用 gdb 执行某个程序,并且能精确控制程序的参数,包括命令行、标准输入和环境变量等。gdb 的 run 命令就是用来执行程序的。

这里还是先写个示例测试程序:

最基本的,通过 run 命令控制命令行参数:

或者在运行前设置args参数:

在漏洞挖掘或者 CTF 比赛中经常遇到的情况是某些输入触发了进程崩溃,因此要挂 gdb 进行分析,这时候就需要gdb 挂载的程序能够以指定的标准输入运行。如果标准输入是文件,那很简单:

但更多时候为了方便调试,希望能以其他程序的输出来运行,比如:

可惜 gdb 不支持这种管道,不过可以通过下面的方法实现:

或者:

后者实际上是 shell 命令 here string的一种形式。这两种方式是有区别的,注意示例程序中 read 调用会提前返回,所以如果我们想要第一次读取3个字符,第二次读取4个字符的话,就不能一次性全部输入。比如下面这样就不符合预期了:

正确的方式应该是这样:

值得注意的是,这种情况下,使用here string是没用的,因为该字符串是计算完再一次性传给命令:

而且这里是8字节,因为末尾还带了个回车。 所以我更偏向于使用第一种方式。

对于运行程序而言,还有个重要的参数来源是环境变量,比如在调试 CGI 程序的时候。这在 gdb 中可以使用environment参数,不过需要注意的是该参数的设置是以空格为切分而不是传统的以=对环境变量赋值。

还有要注意的是这个参数要求变量是uninterpreted strings,也就是说只能指定可打印字符。如果我们要传输一个的 payload 或者 shellcode 还要用 gdb 调试怎么办呢?我一般使用的方式是在调用 gdb 时指定,比如:

对于二进制研究人员来说,gdb 是一个锋利的好工具,支持X86、ARM、MIPS、RISCV、Xtensa等各种常用和不常用的系统架构,对其熟练使用有时候可以达到事半功倍的效果,在文末的附录中我也列举了一些比较常用的命令。由于 gdb 本身支持 python 接口,因此现实中使用通常结合一些拓展使用,比如:

这几个我都用过,各有千秋。现在工作中使用更多的是gef,因为安装太方便了,一个文件搞定。

上面这几个拓展可能大家可能都不陌生,但还有另外一个我比较常用的是 gdb-dashboard,其功能更为简单,而且使用的是 gdb 原本的信息,所以支持的指令集更多。比如下面的截图就是我曾经用 gdb + OpenOCD 来调试 ESP32固件的示例:

esp32

ESP32是比较少见的Xtensa指令集架构,上面的拓展都不支持,不过 gdb 本身支持,因此配合使用的效果绝佳。

gdb 还有其他一些小技巧,可以参考awesome-cheatsheets/tools/gdb.txt中的列表。该列表最初由韦神创建,我时不时也会添加一些上去。当然为了方便大家的查阅,这里直接给出汇总表格附录:

欢迎交流:
blog: https://evilpan.com
公众号: 有价值炮灰
有价值炮灰

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
#include <stdio.h>
#include <unistd.h>
#include <sys/wait.h>

int main(int argc, char **argv, char **env) {
    printf("parent started, pid=%d\n", getpid());

    char *line = NULL;
    size_t len = 0;
    ssize_t read;
    while ((read = getline(&line, &len, stdin)) != -1) {
        pid_t pid = fork();
        if (pid == -1) {
            perror("fork");
            break;
        }

        if (pid == 0) {
            printf("1 fork return in child, pid=%d\n", getpid());
            char *const av[] = {"child", line, NULL};
            if (-1 == execve("./child", av, env)) {
                perror("execve");
                break;
            }
        } else {
            printf("2 fork return in parent, child pid=%d\n", pid);
            int status = 0;
            wait(&status);
        }
    }

    return 0;
}
#include <stdio.h>
#include <string.h>

void vuln(char *str) {
    char buf[4];
    strcpy(buf, str);
    printf("child buf: %s", buf);
}

int main(int argc, char **argv) {
    puts("child started");
    vuln(argv[1]);
    return 0;
}
gdb child --pid $parent_pid
(gdb) set follow-fork-mode child
(gdb) b strcpy
Breakpoint 1 at 0x4004c0
(gdb) c
Continuing.
Warning:
Cannot insert breakpoint 1.
Cannot access memory at address 0x4004c0

Command aborted.
gdb child --pid $parent_pid
(gdb) set follow-fork-mode child
(gdb) hb *0x4004c0
Hardware assisted breakpoint 1 at 0x4004c0
(gdb) c
Continuing.
[New process 309]
process 309 is executing new program: /pwn/child

Thread 2.1 "child" received signal SIGABRT, Aborted.
[Switching to process 309]
gdb child --pid $parent_pid
(gdb) set follow-fork-mode child
(gdb) b _start
Breakpoint 1 at 0x7fbfb4c30090
set detach-on-fork on
set follow-fork-mode child
set breakpoint pending on
b _start
attach $parent_pid
file child
continue
set follow-fork-mode child
catch exec
(gdb) help set scheduler-locking
Set mode for locking scheduler during execution.
off    == no locking (threads may preempt at any time)
on     == full locking (no thread except the current thread may run)
          This applies to both normal execution and replay mode.
step   == scheduler locked during stepping commands (step, next, stepi, nexti).
          In this mode, other threads may run during other commands.
          This applies to both normal execution and replay mode.
replay == scheduler locked in replay mode and unlocked during normal execution.

[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!

收藏
免费 13
支持
分享
打赏 + 5.00雪花
打赏次数 1 雪花 + 5.00
 
赞赏  菱志漪   +5.00 2022/06/01 学到很多!
最新回复 (8)
雪    币: 14517
活跃值: (17538)
能力值: ( LV12,RANK:290 )
在线值:
发帖
回帖
粉丝
2
mark,感谢分享
2020-9-13 19:46
0
雪    币: 0
活跃值: (268)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
3
正在找这个方法,受教了,谢谢楼主分享
2020-9-14 16:25
0
雪    币: 3738
活跃值: (3872)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
4
感谢分享!
2020-9-16 11:18
0
雪    币: 7002
活跃值: (3312)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
5
感谢分享
2020-9-19 19:26
0
雪    币: 436
活跃值: (2668)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
6
感谢分享
2020-10-21 14:58
0
雪    币: 186
活跃值: (357)
能力值: ( LV6,RANK:97 )
在线值:
发帖
回帖
粉丝
7
感谢分享~
2020-10-21 15:26
0
雪    币: 4168
活跃值: (15932)
能力值: ( LV9,RANK:710 )
在线值:
发帖
回帖
粉丝
8
感谢分享
2021-7-17 09:34
0
雪    币: 138
活跃值: (497)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
9
大佬,遇到多线程的时候,gdb能否只针对某些线程挂起吗?因为遇到反调试的时候,一般都是新开线程去进行检测,然后检测到了问题就退出app。
2022-6-1 15:37
0
游客
登录 | 注册 方可回帖
返回
//