上一期文章说的关于PWN入门常见的问题

24 篇文章 8 订阅
5 篇文章 2 订阅

我要是有地方说错了一定要指出来啊嘤嘤嘤
主要是因为有些地方感觉讲错了

虽然是从我自己的角度来理解的,但是估计应该大概还是有帮助的(吧)。
自己想的和群友提出来我自己收集的,涉及的问题有:

  1. 宝贝,怎么入门pwn啊
  2. 那个canary保护机制,为什么说是覆盖低字节来得到canary,没理解到。反正就只知道是大概这种方式来得到canary
  3. plt和got是什么关系啊,为什么写payload的时候有时候在前,有时候在后,有时候又重复出现这样的,没搞懂
  4. 为什么要用puts而不用printf(他意思是ret2libc的题)
  5. 格式化字符串里的$是啥意义啊
  6. 为什么有时候本地通远程不通 有时候本地不通远程通

1.怎么入门pwn
别问,问就是我自己都没入门…
都是刚开始学的,为什么就来问我了呜呜呜。
还是回答一下,我反正是把C给学了,王爽的汇编语言看了前9章,虽然已经完全记不住,但是我觉得还是要看看积累点基础知识。
然后千万千万不要直接去刷题看WP刷题看WP。这又不是misc一来就刷题刷🐴呢。首先是跟着笔记一起学还有实操,不懂的就搜,实在不行的就放一边。推荐一点学习的网站,比较少,但是应该够用吧。

https://ctf-wiki.org/ wiki是肯定排榜首的
https://www.yuque.com/hxfqg9/bin yichen的二进制安全,有入门、wiki的内容、buuctf的WP及其他的,挺不错的。
https://www.yuque.com/cyberangel Cyberangel的PWN博客,也不错。

然后就是靶场:
https://buuoj.cn/challenges 首先肯定是放buu
https://ctf.show/challenges ctfshow将来应该会出入门
https://pwnable.tw/challenge/
http://pwnable.kr/play.php 这两个都是国外的
https://bamboofox.cs.nctu.edu.tw/courses bamboofox的题也还可以,wiki都有用的。

还有就是,在学wiki上的内容的时候,不要只局限于看上面的博客,遇到不懂的地方就去百度。

2.为啥是覆盖低字节canary
emmm,低高字节首先懂伐?比如A7,A就是高字节,7就是低字节。
然后上面都写了:Canary当初的的设计就是以\x00结尾
x86的canary就是4字节,x64就是8字节
这里用格式化字符串来举例,为什么要把canary最低设置为\x00
比如printf("%s",str);,%s就是将一串字符串打印出来,而\x00是字符串结尾的标志,如果输入的内容正好和canary连在了一起,如果canary最低位不是\x00,printf就会一起把canary给打印出来。所以我们为了泄漏出canary,就需要把最低位覆盖掉(或者说改掉)

那么现在可能会存在一个问题,那就是修改了canary,函数不应该检查到错误进入__stack_chk_fail然后退出么?
答:不会的,因为那是在函数返回的时候,才会与其进行比较,再此之前咱们修改回去就行了。

那么再用wiki里讲的canary来作为例子,我这里编译成了64位

gcc --no-pie Canary1.c -o Canary2
在这里插入图片描述

然后运行来测试一下
在这里插入图片描述

因为aaa覆盖了canary,返回时检查到不同,就进入__stack_chk_fail,结束程序。
下面用gdb来调试,在read处下断点

b *0x4007EB
r

然后查看栈的状态

stack 40

在这里插入图片描述
高亮的地方就是canary,可以看到最后那个字节是00,就也是\x00
然后输入可以溢出的很长的数据

ni
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa

再次查看栈的情况
在这里插入图片描述
可以看见,canary已经被覆盖掉,而继续输入“ni”还是可以正常运行,因为函数还并没有返回
然后一直输入“ni”,直到下图
在这里插入图片描述
可以看到,此时才是将存入在fs:0x28的canary的值取出来作比较
当执行到0x40081b <vuln+97>: call 0x400600 __stack_chk_fail@plt的时候,再来两次“ni”
在这里插入图片描述
程序崩溃,自动结束
然后写个脚本来看看canary

from pwn import *
context(log_level = 'debug', arch = 'amd64', os = 'linux')
p = process('./Canary2')
getshell = 0x40076A
p.recv()
p.sendline('a'*(0x70-8))
p.recvuntil('a'*(0x70-8))
Canary = u64(p.recv(7).ljust(8,'\x00'))
print('[+]Canary:' + hex(Canary))
print('[+]Canary:' + hex(Canary-0xa))

记住当时我写的时候说的,p.sendline()最后还发送了一个换行符,所以要减掉0xa,所以后面的是Real Canary,只是为了好看所以还是写的Canary
在这里插入图片描述

总之,上面有一句加粗的话就是为什么要覆盖canary的低位了。
3.plt和got,还有怎么利用来写payload
首先来说plt和got

.GOT(Global Offset Table)全局偏移表
.PLT(Procedure Linkage Table)程序链接表
.GOT.PLT: GOT 专门为 PLT 专门准备的节

这里举个例子

// gcc -no-pie -g -o plt plt.c
#include <stdio.h>
#include <stdlib.h>

int main(int argc, char **argv)
{
char a[10];
puts("Hello world!");
gets(a);
exit(0);
}

然后gdb调试
我是在main下断点,然后r
在这里插入图片描述
主要是方便看到,这里调用了puts@plt、gets@plt、exit@plt
1).为什么后面要加@plt:因为这个为PLT表中的数据的地址
2).为什么反编译中的代码地址为PLT表中的地址:为了节省CPU内存占用,程序编译时会采用两种表进行辅助。一个是GOT,另一个就是PLT。PLT可以称作内部函数表,而GOT称为全局函数表。这两个表是相对应的,也就是PLT上的某一个函数地址就对应GOT上的一个地址
所以现在貌似情况是下图
在这里插入图片描述
PLT表的每一项数据都对应GOT表中的一项的地址是不会变的。
所以这里也可以知道,PLT的地址对应的是GOT地址,并不是函数的真实地址。可以通过PLT跳转GOT表获取真实地址。

emmm,然后我这里小意外,所以后面就用别人的图(其博客地址就是图片右下角水印),是关于反编译puts的,地址和咱们的不同不用去深究。
在这里插入图片描述
一共三行代码
第一行是通过PLT跳转到GOT对应位置
第二行是把0x3压入栈
第三行又是一次跳转
而在第一次运行某一个函数之前,这个函数PLT表对应的GOT表中的数据为@plt函数中第二行指令的地址,针对图中来说步骤如下

  • jmp跳转GOT
  • GOT表中数据为0x400486
  • 跳转到0x400486
  • 执行push 0x3(这个为在GOT中的下标序号)
  • 执行0x400440
  • 0x400400为PLT[0]地址
  • PLT[0]的指令会进入动态链接器的入口
  • 执行一个函数将真正的函数地址覆盖到GOT表中

其中可以衍生出几个问题
1.PLT[0]做了什么,为什么一直没有直接跳转到GOT[0]
2.为什么要push 0x3
3.为什么是0x3不是其他

1.PLT做了什么
首先来看0x400440后面的地址
在这里插入图片描述
可以看到的是,从450开始才是puts@plt,而前面16个字节根本不知道是在干什么,所以这里再单看前面16个字节
在这里插入图片描述

可以发现后面也有一串十六进制数,如果做题做多了一看就可以看出来是GOT的地址,没看出来就用objdump
在这里插入图片描述

plt是我程序名字啊,不要理解错了就行。
GOT的地址都是0x60…,所以之前那个#后面的就是GOT地址。
所以可以确定的是,在存储一个GOT函数地址前,PLT会对其进行处理,并且是从后16个字节开始才是puts@plt的地址,但是前16字节也属于plt,而这就是PLT[0]。所以PLT[0]没有跳转到GOT[0]。
本来按照最开始的思路PLT[1]也是跳转到GOT[1]的,GOT[2]同理,但是这两个数据好像被PLT[0]利用了,同时GOT[0]好像消失了,这里GOT[0]暂且不说它的作用是什么,针对GOT[1]和GOT[2]被PLT[0]利用,所以我们程序中真实情况其实是从PLT[1]到GOT[3],PLT[2]到GOT[4]
在这里插入图片描述

而plt[0]代码做的事情则是:由于GOT[2]中存储的是动态链接器的入口地址,所以通过GOT[1]中的数据作为参数,跳转到GOT[2]所对应的函数入口地址,这个动态链接器会将一个函数的真正地址绑定到相应的GOT[x]中

总之,一共有两种方法调用,一种就是调用PLT,另一种就是调用GOT。当然PLT最终也是跳转到GOT,GOT表中的地址才是真实的地址。
在第一次执行的时候,GOT表中的数据为@plt函数中下一条指令的地址,上面提到过,已加粗。

2.为什么push 0x3
GOT的下标就是0x3。压栈后跳转PLT[0],然后PLT[0]会根据压栈的多少确定跳转到GOT的多少

3.为什么是0x3
为什么是0x3,第二条讲了。GOT[0]、GOT[1]、GOT[2]都有其他用处


总之,在第一次调用时,GOT表中数据为@plt函数中下一条指令的地址;GOT表存放的才是函数的真实地址,而PLT就是去GOT中找地址,这个时候其实就是去.got.plt中找。


那么这里在回到做题,我们来看看笔记里每次写的payload

蒸米ROP—地址随机化
payload1 = ‘a’*(0x88+4) + p32(write_plt) + p32(func_add) + p32(1) +p32(write_got) + p32(4)

Basic ROP—ret2libc3
payload1 = ‘a’*112 + p32(puts_plt) + p32(start_add) + p32(puts_got)

Intermediate ROP—BROP
payload = ‘a’*72 + p64(pop_rdi_ret) + p64(puts_got) + p64(puts_plt) + p64(main_add)

或者还有其他的情况,这里不说了吧。
第一个payload的解析是:0x88+4的a覆盖write函数,返回到func_add,后面的是write的参数,write(1,write_got,4)—>原型:write(fd,buf,nbyte),即
1:标准输出
write_got:要输出的内存单元
4:长度

第二个payload的解析是(其实同上):112个a覆盖puts,start_add返回,后面的puts_got是用puts来泄漏的,因为puts函数是int puts(const char *string);,所以这里就只需要填一个puts_plt而已

第三个payload的解析:
pop_rdi_ret是通用gadget,然后泄漏puts_got地址,puts_plt为plt表puts项内容,此处相当于调用puts()

根据上面3个例子,应该能理解到是如何构造payload了吧。

4.为什么用puts而不用printf
是可以用的。但是要看你做的那道题适不适合用,如ctfshow第二届月饼杯-简单的pwn就是用的printf

5.格式化字符串里的$是啥意义啊
来,首先看之前那篇格式化字符串的文章,可以看到以下的payload

payload = p32(scanf_got) + ‘%4$s’

payload = p32(c_addr) + ‘a’*12 + ‘%6$n’

payload += ‘%104x’+’%6$hhn’+’%222x’+’%7$hhn’+’%222x’+’%8$hhn’+’%222x’+’%9$hhn’
p.sendline("%6$p")

啊就这些吧
就只说一点,他不是说%4 $s、%6$n等等
他的意思是%s+4$
明明刚开始也写了

printf("%2$d %2$#x; %1$d %1$#x",16,17)
“17 0x11 16 0x10”

比如第一个就是%d+$2,输出有符号整数+第二个参数
所以才i输出的17
所以上面的所有payload就理解到了
payload1:以字符串的方式输出第4个参数
payload2:把前面的字符个数写入变量并输出第6个参数
payload3:把前面的字符个数写入变量并以单字节的方式输出6 7 8 9个参数
payload4:输出第6个参数的地址

6.为什么有时候本地通远程不通 有时候本地不通远程通
存在的情况有很多种,虽然我也不知道…
下面是我自己理解的,可以参考参考(?
本地通远程不通:

  1. libc的问题,还有建议打本地就libc = elf.libc
  2. ubuntu版本的问题,比如18.04就要进行栈对齐

远程通本地不通:

  1. 还是libc的问题,可能有小的区别
  2. 没写context(可能导致都不通)
  3. 可能有些调用未清空

应该就这些吧

写的不好,都是自己理解的和看的别人博客(然后理解的),如果讲的有问题希望大佬能指出来,这对我来说真的很重要。

  • 11
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值