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服务相关的东西写了写,后续漏洞挖掘请师傅们自行探索了。