- 学习pwn这个抽象到一定程度的东西,前期的坐牢是一定的,一个题目延申出的新知识也是超多的。所以写一个这个板块记录一下自己学习的东西,或许会有(2),(3)....
- checksec
拿buuctf的test_your_nc为例子
可以看到checksec后出现了很多东西
Arch: amd64-64-little(程序架构信息,这是一个64位的程序,就是说可拖进64位IDA)
Relro:Partial Relro(Relocation Read-Only技术部分开启,这是一项针对于GOT表改写攻击的技术)
Stack:No Canary found(能栈溢出)
NX: NX enable(Non-Executable Memory,不可执行内存)
PIE: PIE enable(Address space layout randomization技术,地址空间布局随机化,通过将数据随机放置来防止攻击的技术)
有时候做栈溢出的题目会有Canary,这个时候怎么办呢?
得先懂canary的机制吧。
- What is the canary
canary直译过来就是金丝雀,来源于英国矿井工人用来探查井下气体是否有毒的金丝雀笼子。工人们每次下井都会带上一只金丝雀。如果井下的气体有毒,金丝雀由于对毒性敏感 就会停止鸣叫甚至死亡,从而使工人们得到预警。
那这个放在代码里就是,在栈底处放入了一串特殊的字符,一旦你利用栈溢出把这段字符覆盖,那系统将会狠狠的拒绝你的访问,使得你的栈溢出失败。因为在代码跑起来之后,他会去验证canary(其实在linux是一个cookie信息)是否没被修改,一旦canary没了那就game over了。
canary在栈中的结构大概是下面这样的
High
Address | |
+-----------------+
| args |
+-----------------+
| return address |
+-----------------+
rbp => | old ebp |
+-----------------+
rbp-8 => | canary value |
+-----------------+
| 局部变量 |
Low | |
Address
而这个值并不是凭空捏造的,Canary的值是从fs寄存器中取0x28的值,然后放到栈底。每次运行后,会将Canary和fs寄存器0x28处进行异或,通过这种手段把这个代码保护起来。
这样我们就能产生两个大方向对这个问题进行解决:
(1) 获取Canary的值
(2) 覆盖Canary的时候把fs寄存器的_stack_guard给一同覆盖
(具体过程得写出来在单独整理一下)
- got表与plt表
啥时候得用呢?
当二进制文件中不存在\bin\sh或者后门函数的时候就得想到这个方式,
此时需要我们狠狠的利用libc里面的\bin\sh,知道了libc中的一个函数的地址就可以确定该程序利用的libc版本,从而知道其他函数的地址。
获得libc的某个函数的地址通常采用的方法是:
通过 got 表泄露,但是由于libc的延迟绑定,需要泄露的是已经执行过的函数的地址。
为什么是已经执行过的函数的地址呢,此处就要介绍plt表和got表的内容了。
got表:globle offset table 全局偏移量表,位于数据段,是一个每个条目是8字节地址的数组,用来存储外部函数在内存的确切地址。我们的最终目标就是拿到system函数的got表地址,同时知道libc的基地址的话即可找到system函数的真实地址。
plt表:procedure link table 程序链接表,位于代码段,是一个每个条目是16字节内容的数组,使得代码能够方便的访问共享的函数或者变量。可以理解为函数的入口地址,通过劫持返回地址为puts函数的plt表地址,即可执行puts函数。
很绕的概念,听不懂了。o.O
看个图帮帮
可执行的二进制文件里面保存的是PLT表的地址,对应PLT地址指向的是GOT的地址,GOT表指向的就是glibc中的地址那我们可以发现,在这里面想要通过plt表获取函数的地址,首先要保证got表已经获取了正确的地址(即最靠右的两个箭头已经建立),但是在一开始(尚未发生函数调用时)就进行所有函数的重定位是比较麻烦的,为此,linux引入了延迟绑定机制。(超级大白话就是,想要从左往右成功推导,起码得需要结果处得路径存在)
那延迟绑定是啥东西啊?!o.O
简单来说就是当在二进制文件中调用a函数时,会先去plt表中找到a在got表中的位置,这个时候,got表还没完全更新(也就是上图中最后面的箭头没建立起来),那就不会直接的调用a函数,而是跑到公用的plt表中把这个联系建立起来,然后第二次调用got表,再把a函数成功调用出来。
上述过程可以很明显的看出来是由两个大步骤构成的调用函数,这不就给了我们启发,我们先调用一次函数,让他自己把最后的路径搭建好,然后再次调用一遍,那地址不就自己跑出来了。这就是再linux下的got表与plt表。
最后水一题(和上面没有关系的题目,纯粹再练习我的rec和send)
LitCTF 2023 口算题卡
可以看到就是见到的100以内加减法,幸好这题没有设置时间限制,所以我们甚至能爆算100题获取flag
但是我们要学会用pwntools嘛,所以直接接受这个题目信息让他自己算就好了(绝对不是算的太慢还错的原因
exp如下
from pwn import *
io = remote('node4.anna.nssctf.cn',28353)
context(arch='amd64', os='linux',log_level='debug')
while 1:
try:
io.recvuntil(b'What is ')
line = io.recvline().strip(b'\n').strip(b'?').decode()#接受\n到?之间的内容,并将其变为string格式
expression = line
result = eval(expression)#eval可以直接计算string
io.sendline(str(result))#将结果send上去
except:
io.close()
continue
io.interactive()
使用debug将每一次获取到的结果显示出来
这题就是这样考察一些接受的基本操作,难度不大