0x00 前言
只是因为某宝上搜路由器跳出来的第一个设备是它,于是就选它了
型号是tplink-ax3000(TL-XDR3010易展版),固件版本是V8.0
搜了搜网上几乎一点分析资料没有,自己就试着写写看。
0x10 解包
0x11 获取固件
固件获取地址:https://resource.tp-link.com.cn/?filterClass=[23]&keyword=xdr3010
固件下载后用binwalk
解开即可
我尝试过本地模拟,但是报错涉及的东西感觉实在太多,于是选择了购买真机,后续测试交互也是通过真机完成的,有师傅模拟成功的也欢迎来交流。
0x20 逆向分析
该路由器的大部分服务由dms
程序提供,因此主要分析该程序。
0x21 服务注册
由于一般最开始想到还是http
服务,且该程序中留有大部分符号,直接对http
进行搜索看是否有相关函数。
可以发现有一个httpdServiceInit
的函数,在函数末尾调用了registerAppModule
函数且参数为uhttpdService
结构体,看上去是用于注册http
服务的。
registerAppModule
函数中,传入的结构体参数先是被复制到ModulerRoot + 216
的位置,然后获取该地址偏移+4
的内容并作为函数去调用。
由此推断结构体的第二个成员为该服务的注册函数。
点进该uhttpdService
结构体,可以看到其中四个函数,其中sub_62a040
即为注册函数
为什么是注册函数而不是初始化函数,因为点进去可以发现上面的函数输出日志的时候写着uhttpdRegister
(笑
而结构体第二个函数是做了一系列的初始化
(你可能会好奇httpServiceInit
函数是在哪里被调用的,对该函数做个交叉引用,可以发现它在一堆函数里面夹着,往上翻翻其实就能发现这个函数表是初始函数表,给libc_csu_init
用的,程序刚开始会把函数表里的函数都运行一遍)
0x22 数据模块注册
先看http
的注册函数
开头调用了不少regOprInterface
函数,并且传了个像路径一样的一参,二参则为函数数组
我这边已经重命名过了,实际上也是比较好分析出来的。
第一个函数在给参数传完值后就调用了第二个函数,而第二个函数很明显是增添一个json
对象,并根据传入的参数向该对象中添加键值对。
第三个函数则是从json
对象中根据键名提取值,并放入传入的a3
缓冲区中
剩下的那些register
函数暂时不用看
registerVerNotify:
二参为 1 时,为一参数据模块注册验证函数,是系统在配置修改前调用的函数,用于判断某个配置值是否有效或允许变更。
二参为 0 时,为一参数据模块注册通知函数,是系统在配置修改后调用的函数,用于通知模块配置已更新,可以执行相应动作。
0x23 http服务
进入到http
的初始化函数,就是上面结构体的第二个函数sub_629F8C
重点看httpParserInit
该函数用httpMethodAdd
注册了两种http
方法的处理函数,具体过程是将如下结构体添加进一个全局数组中,后续通过在数组中的位置来(即传入的一参index
)检索对应结构体
底下的httpKeyParserAdd
就是字面意义上的解析报文header
了。
这边都只是初始化相关的处理函数,那真正从头开始的start
函数在哪里呢?
进入uhttpdService
结构体的第三个函数
日志直接告诉你是start
函数了,我这里直接给出大概的执行流了,只是肉眼观察到的出现的顺序,不是执行的顺序(?
httpRemoteGroupListenPort -> httpHandle -> httpParser
进到httpParser
函数。这个函数做了一堆操作,主要就是解析用户发送的报文,然后把对应的数据放到a1
结构体的对应偏移上去
偏移 含义
+8 请求 URL host 起点
+12 请求来源?(2 表示特定来源)
+20 http错误状态码
+24 请求资源标识(文件后缀对应的序号)
+96 缓冲区总大小 int
+100 缓冲区起始地址? char* / void*(或可能是 offset)
+104 当前写指针/有效数据末尾地址 unsigned int
+112 userAgent
+140 数据备份(保存 a1+100 的值) int
+144 当前有效数据开始地址 _BYTE*
+148 url 存储请求路径
+232 URL 参数键值对
+312 error_code
+316 匹配模块 ID
+320 请求方法(GET=1, POST=2等)
+324 HTTP 版本(1=1.0, 2=1.1)
+356 http_password
+392 解码后的 stok
+524 recvBufCache
大概分析了上面这些,仅供参考,可能有误,就不一一细说了。
这个函数里面也做了一些关键数据的解析,像method
,url
,token
,还调用了各种header
的处理函数
然后在这里,解析完成后,就要根据方法来调用对应的处理函数了,像GET
,POST
之类的
这些之前已经在httpParserInit
函数中看过了,那里用httpMethodAdd
注册了两个method
的处理。如果对该函数进行交叉引用,可以看到更多method
及其处理
可以发现upnp
相关的一些服务也是通过httpd
来进行分配处理的(深入分析的话可以知道端口其实是正常开放的,但是会把报文数据转交给http
进行解析,然后再根据具体方法传给对应处理函数)
这里再写一下会遇到的modelwrite
和modelread
函数,其实就是之前regOprInterface
注册的数据模块的处理函数
modelwrite:调用指定数据模块的func[1]来修改json值
modelread: 调用指定数据模块的func[2]来获取json值
POST 方法
就只简单写一下post
方法了
函数在开始将解析后的url
与一个字符串数组里的字符串作比较,如果有相同url
则 会进入httpCodeHandle
函数,如果没有则会进入底下的httpProcDataSrv
函数
httpCodeHandle
没什么好说的,看一下httpProcDataSrv
再结合实际抓包得到的数据
该函数处理POST
数据时,会解析作为POST data
的json
对象。并且根据该对象中的method
(此method
非彼method
,一个是http
的method
,一个是POST
内部的method
)确定处理函数,并且将上面的数据作为参数传入。
httpDoAuthorize
这里再说说这个验证函数,在各个处理函数中基本都能看到,想要做到部分未授权就需要对这个函数进行绕过。
在部分老版本中,在对GET
方法进行权限认证时,如果url
中含有loginLess
,Login.htm
,即可直接通过验证。所以可以通过在url
的将loginLess
添加在url
的末尾,就像/admin/system/download_confloginLess
,来绕过认证下载配置文件,然后从配置文件中提取管理员密码。
而在新版中,修改了判断方式,使用了strncmp
,所以/loginLess/
就只能放在url
的最开头,而且用,就不太好像上面一样拼接路径了。
httpMGetHandle
再再说一下这个函数,里面有个有意思(存疑),但没啥用的 目录穿越 + 部分文件读。
该函数将处理过后的url
交给 miniFsReadFileDirect
函数,最终是通过fopen
来读本地文件,而没有对../
进行过滤,存在个目录穿越。
图中可以看到我标了个调用链,其中*(a1+24)
需要大于5
才能触发读文件,那么这个成员代表的是什么呢?
如果你还记得我上面有张表写了一长串的结构体成员含义,就知道该到哪个函数里去看了。
回到httpParser
函数
就是在调用不同方法的处理函数之前,程序对url
进行了一个后缀名的匹配,如果url
的末尾是指定的后缀名,则会将a1[6]
赋值,该值为后缀名列表的中每个后缀名后的index
(别问为什么上面是*(a1+24)
,这里是a1[6]
,问就是记得把鼠标放a1
上看看类型)
如.bin
的index
为1,.xml
的index
为5。当这个值大于等于5时,才会跳过验证。
虽然可以任意目录泄露,但是后缀名卡在这了,所以大概是没啥用
0x30 最后
差不多就分析到这,只是把大致架构和http
服务相关的东西写了写,后续漏洞挖掘请师傅们自行探索了。