写在前面:逆向工程小白,仅供参考,如有错误,欢迎指正。
实验室有台K3C路由器,趁老师不在,逆向玩玩。听老师说这个固件是官改,就去网上查查有没有相关固件。
固件地址:https://download.csdn.net/download/fjh1997/15566880
备份:https://www.right.com.cn/forum/thread-318971-1-1.html http://iytc.net/tools/k3c_v121_fs.bin
wget http://iytc.net/tools/k3c_v121_fs.bin
binwalk -M -d -e k3c_v121_fs.bin
#注意要加-M表示Recursively 递归提取,比较彻底,
在提取出来的squashfs-root可以找到www,这个里面放的是我们网页根目录的东西。注意到里面有个cgi-bin打开一看是个lua脚本:
#!/usr/bin/lua
require "luci.cacheloader"
require "luci.sgi.cgi"
luci.dispatcher.indexcache = "/tmp/luci-indexcache"
luci.dispatcher.dataindexcache = "/tmp/luci-dataindexcache"
luci.sgi.cgi.run()
这对应于我们路由器首页的的cgi-bin,但我们首页进去明显是一个登录页面,不太对。我们就可以用以下命令搜索这个登录页面存在固件的哪个文件里:
grep -rna "请输入密码" .
grep -rna "zh-cn.js" . #(在macos下最好用ggrep,使用brew install grep即可)
我们看一下谁引用了这个资源文件:
那么谁引用了这个文件呢?查一下,没查到/
grep -rna "luci-mod-base.list" .
扩大范围试试:
grep -rna "luci-mod-base" .
去这个文件里看看,发现了登录验证内容:
发现这个登录引用了名为"luci.data.guide"的包,来验证当前输入的密码是否正确。既然当前的dispacher.lua脚本位置在/usr/lib/lua/luci
目录下,那么我们就去/usr/lib/lua/luci/data
目录下找:
找到guide.lua发现引用了guide_plt.lua
去guide_plt.lua里面看看发现引用了luci.adapter.libphi_cgi的get_conf函数来获得密码:
引用了set_conf函数来修改密码:
于是去目录/usr/lib/lua/luci/adapter/
下面寻找,发现了一个.so文件,去这个这个文件里面找get_conf和set_conf函数,使用ghidra,搜索字符串"get_conf"和"set_conf",
继续跟,
发现了lua_register注册函数。
去(lua5.1官方手册上看了一下https://www.lua.org/manual/5.1/manual.html)
发现第三个参数是一个数组:
void luaL_register (lua_State *L,
const char *libname,
const luaL_Reg *l);
luaL_Reg他的结构体是:
typedef struct luaL_Reg {
const char *name;
lua_CFunction func;
} luaL_Reg;
也就是一个是函数名一个是函数的地址,这个组成一个luaL_Reg结构体,多个这样的结构体组成一个数组,我们把数组开头的元素的地址作为我们的第三个参数也就是PTR_DAT_0002406c + 0x4010,其中PTR_DAT_0002406c =0x20000相加得到0x24010。
luaL_Reg 数组必须以一对name与 func 皆为 NULL 结束。
这与我们逆向的结果吻合。
可以参考这篇博客:https://www.jianshu.com/p/6f5ab6d67ffc
比较详细,讲怎么注册函数才能给lua作为动态链接库用。
换言之里面的FUN_0012fd4就是我们get_conf的函数位置,也就是说get_conf对应结构体中的const char *name;
,FUN_0012fd4对应结构体中的lua_CFunction func;
我们跟进去看看:
很明显,这个函数里面要调用PTR_00024078 + 0x2ce8这个函数,继续追踪,首先看PTR_00024078 结果发现:
这个指针指向的是0x000000地址,如果相加的话,也就是PTR_00024078 + 0x2ce8=0x2ce8,但显然不对,我们没有找到这个地址但函数。
注意到整个动态链接库的起始段地址是以0x10000开头:
那么我们不妨猜一下PTR_00024078的指针指向的就是这个0x10000地址,那么PTR_00024078 + 0x2ce8=0x12ce8,
而之前函数 luaL_register 的第二个参数指向的地方就是PTR_00024078 + 0x3e18=0x13e18,去那个地方看看:
发现果然第二个参数指的是一个字符串叫做libphi-cgi,那么这个get-conf指向的就是函数0x12ce8
去这个函数看看,发现这个函数调用了cal_getvalue这个外部函数。
去打开外部链接库看看,发现有个库libcal.so可疑,去看看,
在cal_getvalue里面发现capi_getvalue调用
很显然capi_getvalue与libcapi.so 有关,再去看看:
结果发现,capi_getvalue调用了外部函数help_sendMsgToServer,显然是与help相关的库导入的,问题是目前有两个,libhelper.so和 libugwhelper.so 至于该用哪个,不太确定,我们在root目录下用命令
grep -rna "help_sendMsgToServer" .
可以看到
可以看到在libugwhelper.so里面,再去找找:
发现这个函数里面主要在与ubus做交互,ubus是啥,网上查了一下,发现是openwrt用于进程间通讯的程序。说明本质上这个程序获取账号密码或者修改账号密码的本质都是向ubus发送数据,再由ubus来执行。
之后去github上面查了help_sendMsgToServer这个函数,居然查到了源码(https://github.com/paldier/k3c_code/blob/master/ugw/feeds_ugw/framework/helper/libugwhelper/src/ugw_framework.c),这下就基本不用怎么逆向了。
这里面的注释也基本符合我的猜测,这个函数就是给ubus发送信息的。其中第三第四个参数IN const char *pcServerName, 和 IN const char *pcOper引发了我的兴趣,这应该是发送给ubus表示自己要调用的服务名,servername和操作operation,也就是,puvar6和7我看了看,和前面的那个思路一样,一个是0x1207c一个是0x120a8
说明服务名是csd操作名是get。
去固件里查了下这个程序发现确实存在,逆向这个试试
打开字符串的列表,发现里面有很多文件的路径,会不会就是密码的存放路径呢?这些文件名字中大都有“run”,待会就用这个来过滤系统调用。
我们登到路由器里面查看进程,发现确实有这个进程,进程id为13340:
然后编写了个改账号密码的脚本来测试下系统调用:
local plt = require("luci.data.guide_plt")
local errcode, result
errcode, result = plt.modify_account_plt("admin", "admin")
保存为test.lua文件然后使用命令lua test.lua
来执行。这样是手动调用来修改密码,同时在此之前,在另一个地方打开终端使用strace来追踪系统调用:
strace -p 13340 2>&1 |grep run
我们注意到csd这个程序有访问/tmp目录内的文件,但我们知道/tmp目录中的文件是临时的,一般不会拿来保存密码,同时还注意到有对/opt/lantiq/config/.run-data.xml的access的请求,但却没有写入操作,这说明该程序可能唤起了一个子进程来写文件。
我们给strace加上-f参数来试试:
strace -f -p 13340 2>&1 |grep run
结果神奇的事情发生了,我们还看到了程序使用execve唤起了openssl的加密,密码是HALLELUJAH,加密后的文件写入了/opt/lantiq/config/.run-data.xml这个与我们对csd程序中的函数进行逆向的结果吻合(可以查找字符串/opt/lantiq/config/.run-data.xml找到):
既然这样的话,那么我们使用同样的命令对该文件进行解密不就行了么?于是,我使用以下命令,果然执行了成功的解密(源码文件也证实了我这点:https://github.com/paldier/k3c_code/blob/master/ugw/feeds_ugw/framework/libscapi/src/scapi_crypt.c):
openssl aes-256-cbc -base64 -d -in /opt/lantiq/config/.run-data.xml -out result.xml -pass pass:HALLELUJAH
解密出来的是一个xml文件,在里面可以看到密码:
总结:openwrt嵌入式系统修改密码的方式是使用ubus调用csd服务,而csd服务并不会专门给修改密码定义函数,csd只接收传进来的对象然后把他们添加或者修改成为临时的xml文件再对临时的xml文件进行加密,最终存储在flash里面。