前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >PWN入门(从零开始)

PWN入门(从零开始)

作者头像
鸿鹄实验室
发布2021-04-15 11:12:33
1.3K0
发布2021-04-15 11:12:33
举报
文章被收录于专栏:鸿鹄实验室鸿鹄实验室

栈溢出:栈溢出是指在栈内写入超出长度限制的数据,从而破坏程序运行甚至获得系统控制权的攻击手段。

满足栈溢出的条件:

1. 程序要有向栈内写入数据的行为,利用函数,如gets,writes

2. 程序不限制写入数据的长度

如果想用栈溢出来执行攻击命令,就要在溢出数据内包含攻击指令的内容或者地址,并且将程序的控制权交给该指令(system/shellcode),攻击指令可以是自定义的指令片段,也可以利用系统内已有的函数和指令

保护机制:

一、Canary保护机制

简单来说,就是在ebp的前面放一个值,这个值被称为金丝雀值,函数开始执行的时候,会先往栈里面插入这个值,当函数真正返回时会先验证这个值,如果这个值与原来的不对,那么就会停止函数的运行

gcc -fno-stack-protector -o test test.c //禁用栈保护

gcc -fstack-protector -o test test.c //启用堆栈保护,不过只为局部变量中含有 char 数组的函数插入保护代码

gcc -fstack-protector-all -o test test.c //启用堆栈保护,为所有函数插入保护代码

二、FORTIFY保护机制

通过对数组大小的判断替换strcpy, memcpy, memset等函数名,达到防止缓冲区溢出的作用

三、NX(DEP)保护机制

将数据所在内存页标识为不可执行,就是没有数据执行的权限

gcc -z execstack -o test test.c

四、PIE(ASLR)

地址随机分配

五、RELRO

设置符号重定向表格为只读或在程序启动时就解析并绑定所有动态符号,从而减少对GOT(Global Offset Table)攻击。RELRO为” Partial RELRO”,说明我们对GOT表具有写权限

栈溢出攻击

当函数正在执行内部指令的过程中我们是无法拿到程序的控制权的,只有在发生函数调用或者结束函数调用的时候,程序的控制权会在函数状态之间发生跳转,这时才可以通过修改函数的状态实现攻击。而控制程序执行指令的最关键的寄存器就是eip,所以我们的目标就是让eip载入攻击指令的地址

函数调用结束的时候,如果让eip指向攻击命令的准备

首先,在退栈的过程中,返回地址会被传给eip,所以我们可以让溢出数据覆盖函数的返回地址

其次,我们可以在溢出数据内包含一段攻击指令也可以在内存其他位置寻找可用的攻击指令

函数调用发生的时候,如果让eip指向攻击命令的准备

这个时候,eip会指向原程序中某个指定的函数,我们没法通过改写地址来控制,不过可以“偷梁换柱”—将原本指定的函数在调用时替换为其他函数。

Shellcode

--修改返回地址,让其指向溢出数据中的一段指令

原理:在溢出数据内包含一段攻击指令,用攻击指令的起始地址覆盖掉返回地址。

攻击指令一般是用来打开shell的,从而获得当前进程的控制权,所以这类指令片段也被称为“shellcode”

Ret2libc

--修改返回地址,让其指向内存中已有的某个函数

利用system(‘/bin/sh’)

Payload:

32位:padding1+addr_system + ‘dead7 + addr_binsh

64位:padding + pop_rdi +addr_binsh + addr_system

ROP(Return Oriented Programming)

--修改返回地址,让其指向内存中已有的一段指令

payload : padding + address of gadget

ROP常见的拼凑效果是实现一次系统调用,

Linux系统下对应的汇编指令是int 0x80,执行这条指令的时候,

被调用函数的编号->eax

调用参数依次->ebx,ecx,edx,esi,edi

Hijack GOT

--修改某个被调用函数的地址,让其指向另一个函数

程序对外部函数的调用需要在生成可执行文件时将外部函数链接到程序中,链接的方式分为静态链接和动态链接。

静态链接得到的可执行文件包含外部函数的全部代码,动态链接得到的可执行文件并不包含外部函数的代码而是在运行时将动态链接库加载到内存的某个位置,再在发生调用的时候去链接库所需函数。

GOT表单,全局偏移量表,用来储存外部函数在内存中的确切地址,GOT表单存储在数据段内,可在程序运行的过程中被修改。

PLT表单,程序链接表,用来存储外部函数的入口点,换言之程序总会到PLT中寻找外部函数的地址,PLT表单存储在代码段内,在运行之前就已经确定并且不会被修改

PLT并不知道程序运行的时候动态链接库被加载的确切位置,PLT表内存储的入口点就是GOT表中对应条目的地址

ret2__libc_csu_init

原理:在64位程序中,函数的前六个参数是通过寄存器传递的,但是大多时候,我们很难找到每一个寄存器对应的gadgets,这时候,我们可以利用x64下的__libc_csu_init中的gadgets。这个函数是用来对libc进行初始化操作,而一般的程序都会调用libc函数,所以这个函数一定会存在。

X64中的前六个参数依次保存在rdi,rsi,rdx,rcx,r8,r9中

r13=rdx=arg3

r14=rsi=arg2

r15d=edi=arg1

r12= call address

这段gadgets用于在X64下进行rop,根本原因是X64使用寄存器传参无法直接用栈进行rop。具体用法如下:

分为两部分使用

代码语言:javascript
复制
1.执行gad1

.text:000000000040089A                 pop     rbx  必须为0
.text:000000000040089B                 pop     rbp  必须为1
.text:000000000040089C                 pop     r12  call!!!!
.text:000000000040089E                 pop     r13  arg3
.text:00000000004008A0                 pop     r14  arg2
.text:00000000004008A2                 pop     r15  arg1
.text:00000000004008A4                 retn  ——> to gad2

 

2.再执行gad2

.text:0000000000400880                 mov     rdx, r13
.text:0000000000400883                 mov     rsi, r14
.text:0000000000400886                 mov     edi, r15d
.text:0000000000400889                 call    qword ptr [r12+rbx*8] call!!!
.text:000000000040088D       add     rbx, 1
.text:0000000000400891                 cmp     rbx, rbp
.text:0000000000400894                 jnz     short loc_400880
.text:0000000000400896                 add     rsp, 8
.text:000000000040089A                 pop     rbx
.text:000000000040089B                 pop     rbp
.text:000000000040089C                 pop     r12
.text:000000000040089E                 pop     r13
.text:00000000004008A0                 pop     r14
.text:00000000004008A2                 pop     r15
.text:00000000004008A4                 retn ——> 构造一些垫板(7*8=56byte)就返回了

DynELF

在没有目标系统libc文件的情况下,我们可以使用pwntools的DynELF模块来泄露地址信息,从而获取shell。

DynELF是pwntools中专门用来应对无libc情况的漏洞利用模块

代码的基本框架如同目录下DynELF.py所示

使用条件:

不管有没有lilbc文件,要想获得目标系统的system函数的地址,首先都要求目标二进制程序中存在一个能够泄露目标系统内存的中libc空间内信息的漏洞,同时,由于我们是在对方的内存中不断地搜索地址信息,故我们需要这样的信息泄露漏洞能够被反复的调用

主要使用条件:

1. 目标程序存在可以泄露的libc空间信息的漏洞,如read@got就指向libc地址空间内;

2. 目标程序中存在的信息泄露漏洞能够反复触发,从而可以不断地泄露libc地址空间内的信息。

Write函数配合DynELF工作时可能遇到的问题:

write函数原型是write(fd, addr, len),即将addr作为起始地址,读取len字节的数据到文件流fd(0表示标准输入流stdin、1表示标准输出流stdout)。write函数的优点是可以读取任意长度的内存信息,即它的打印长度只受len参数控制,缺点是需要传递3个参数,特别是在x64环境下,可能会带来一些困扰。

在x64环境下,函数的参数是通过寄存器传递的,rdi对应第一个参数,rsi对应第二个参数,rdx对应第三个参数,往往凑不出类似“pop rdi; ret”、“pop rsi; ret”、“pop rdx; ret”等3个传参的gadget。此时,可以考虑使用__libc_csu_init函数的通用gadget。简单的说,就是通过__libc_csu_init函数的两段代码来实现3个参数的传递,这两段代码普遍存在于x64二进制程序中,只不过是间接地传递参数,而不像原来,是通过pop指令直接传递参数。

puts函数配合DynELF工作时可能遇到的问题:

puts的原型是puts(addr),即将addr作为起始地址输出字符串,直到遇到“\x00”字符为止。也就是说,puts函数输出的数据长度是不受控的,只要我们输出的信息中包含\x00截断符,输出就会终止,且会自动将“\n”追加到输出字符串的末尾,这是puts函数的缺点,而优点就是需要的参数少,只有1个,无论在x32还是x64环境下,都容易调用。

为了克服输入不受控这一缺点,我们考虑利用puts函数输出的字符串最后一位为“\n“这一特点,分两种;情况来解决。

(1) puts输出完后就没有其他输出 , 在这种情况下的leak函数可以这么写。代码见同目录下putsD.py文件

(2) puts输出完后还有其他输出 , 在这种情况下的leak函数可以这么写。代码见同目录下putsDD.py文件

其他需要注意的地址:

在信息泄露过程中,由于循环制造溢出,故可能会导致栈结构发生不可预料的变化,可以尝试调用目标二进制程序的_start函数来重新开始程序以恢复栈。

DynELF没法查找/bin/sh字符串,这道题可以任意地址写所以写到bss段里然后用system调用。

Jarvis OJ level3_x64

Payload要注意

寄存器rdi,rsi,r15三个寄存器存放write参数

Jarvis OJ level4

这题有点坑,因为本地有libc库可以,但是远程根本没有libc库,所以说获取不了权限

然后就认识了一个新的pwntools –DynELF

使用DynELF的条件是可以实现任意地址的读取,并且每一次读完可以恢复到溢出点

但是DynELF只能找到system的地址,找不到‘/bin/sh’字符串的地址,所以我们可以控制read函数将这个字符串写入到bss段中

重新理清一下思路:

1. 常规方法,泄露libc本地调试成功,但是在远程中没有给libc库,所以说,我们要用DynELF这个模块还有write函数去泄露system的地址

2. DynELF不能泄露‘/bin/sh’的地址,所以利用read函数去向bss段写入字符串然后调用

重点:泄露system地址

<END>

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2019-11-10,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 鸿鹄实验室 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档