buuctf刷题记录
rip
入门题,防护全关
1 | int __cdecl main(int argc, const char **argv, const char **envp) |
exp
1 | from pwn import* |
吃的大亏,现在才知道有些题目虽然有输出信息,但远程recv()是收不到东西会卡住的
①
在一些比较新的环境下,如果覆盖返回地址的开头的操作为
1 | push rbp |
程序就会崩溃
至于原因,就是调用system时,栈没有对齐,如果不push rbp则对齐,故选择跳过
因此视情况跳过这两句代码
warmup_csaw_2016
和上一题基本没有区别
只不过最后不是给shell权限而是直接cat flag罢了
1 | from pwn import* |
ciscn_2019_n_1
保护只开了nx
ida查看
1 | int func() |
两个思路
- 直接溢出返回地址到cat /flag指令处
- 溢出修改v2,这个比较值是字面值可以在.rodata中找到
exp1
1 | from pwn import* |
exp2
1 | from pwn import |
pwn1_sctf_2016
保护只开nx
ida查看
发现是c++代码,我这半桶水读起来有点吃力
直接运行看看,发现输入I会被替换为you,这样一个I能填充三个字节就可以做到溢出的效果了
exp
1 | from pwn import* |
读一读源码
fgets(s, 32, edata) ,edata其实也就是stdin了
使用
std::string::operator=
将s
中的内容赋值给名为input
的std::string
对象。- 使用
std::allocator<char>::allocator
创建了一个std::allocator<char>
对象,并将其地址传递给v5
变量。 - 使用
std::string::string
构造了一个std::string
对象v4
,其中包含字符串 “you”。 - 使用
std::allocator<char>::allocator
创建了另一个std::allocator<char>
对象,并将其地址传递给v7
变量。 - 使用
std::string::string
构造了另一个std::string
对象v6
,其中包含字符串 “I”。 - 调用
replace()
函数,将input
对象中的子字符串 “I” 替换为字符串 “you”. - 使用
std::string::operator=
将v3
变量中的字符串内容赋值给input
对象,并在v6
和v4
的帮助下构造了一个新的字符串。 - 调用
std::string
和std::allocator
的析构函数来释放已分配的内存。 - 将
input
中的字符串复制到s
变量中。 - 使用
printf()
函数打印最终结果
jarvisoj_level0
没什么好说的
1 | from pwn import* |
[第五空间2019 决赛]PWN5
保护查一查,开启了nx与canary
ida看一看,确定为格式化字符串漏洞
第一想法是dword_804c044有出现在参数中,那么栈中能找到能找到它,可以泄露其中的数据,再输入以通过
不过gdb调试发现read后的栈中它的地址已经被覆盖了,
那就只能选择任意地址写了
格式化字符串是第一个参数,那么输入内容的相对偏移是10
exp
1 | from pwn import* |
ciscn_2019_c_1
检查保护,只开了nx
ida反汇编,程序为一个菜单式程序
没有system函数和/bin/sh字符串
基本确定为libc泄露类题
危险函数如上
这个循环会修改我们的输入但是有可以跳过的办法,即v0一定是一个大于零的数,则只要payload开头为\x00就行了
exp如下
1 | from pwn import* |
pwntools中ELF获取plt
即ELF.symbol,ELF.plt,ELF.got的使用区分
ELF.plt得到的是plt的地址,ELF.plt的内容首项是跳转到ELF.got中存储地址,ELF.got的内容是函数的真实加载地址
当需要访问函数的真实加载地址就需要访问ELF.got内容,但动态链接下,初始ELF.got项必然不是函数真实地址,且访问ELF.got又需要访问ELF.plt
另外ELF.plt一定能进入函数,ELF.got则不一定(未初始化)
使用哪个,则要看需要访问的内容,要实现什么目的
再来看symbols和plt的使用场合
这两个很多时候返回是相同的(差不多可以当作一个用了,像这题,puts_plt=e.symbols[‘puts’也是可以的])
1 | ELF.symbols适用场景: |
因为不严谨导致问题的细节
其实就是一些小细节
- send和sendline的使用,大多数时候二者没有差别,但是诸如遇到了getchar(),gets()这些函数,就只能用sendline(或者send自己加\n)了,因为这两个函数不接收到\n就不会继续执行,导致程序的执行卡住,不能往下执行
- puts函数输出时会自带\n,接收时要注意
栈对齐
距离shell临门一脚的坑
ubuntu18(glibc2.27)后64位下
system函数执行过程中会有这么一条指令
movaps xmmword ptr [rsp + 0x??], xmm0
故而就要求在运行到该处时.rsp要是16的整数倍,
又由于程序指令的相对不变性,所以需要对system函数地址在栈中的存放地址有要求
一般来说都是要使得system函数地址在栈中的存放地址要是16的整数倍(不一定),即能达到上述目的
除system外,printf等函数也会有这种指令,也就是上面提到的跳过栈帧开辟
ciscn_2019_n_8
ida一看,就是对一个数组进行输入,第14个元素如果等于17则拿到shell
唯一值得注意的就是
if ( *(_QWORD *)&var[13] )
将dword指针变为了qword指针,所以第15个元素得留空,不过以防之前栈中存留了一些数据,也可以用p64打包直接覆盖
exp
1 | from pwn import* |
好几次遇到了同一个问题,进入shell模式后,第一条指令永远没有输出,虽然无伤大雅,且并不是每题都这样,但强迫症很难受啊
jarvisoj_level2
保护只开了nx
ida查看有system函数,而且能找到到binsh字符串
exp
1 | from pwn import* |
bjdctf_2020_babystack
常规入门题
只不过read字符数由自己输入
exp
1 | from pwn import* |
get_started_3dsctf_2016
标准流程就不重复了,这题单看题不难,但坑是一个接着一个
第一个坑,ida显示的v4距返回地址计算出来应该是60,但是实际去gdb调试会发现应该是56,之前做了那么多题都是直接用ida给的数据,这次突然跳出来一个不准的确实很搞人(主要这题栈帧不是常见的类型),也算得到了一个教训,最好还是gdb实操计算偏移,当然直接去读汇编代码也能得出正确结果
第二个大坑,就是这题没有设置setbuf(stdout,0),所以本题的输出是缓存在服务器本地的,换句话说:如果程序不正常退出,本地是不会有输出的,所以必须要正常退出,其实这也应该是第一题我没能接收信息卡住的原因
更多可见基础杂烩篇
exp
1 | from pwn import* |
[OGeek2019]babyrop
保护开了got表不可写以及nx
ida查看
主体是
向buf中读入了一个随机数
下面两个函数依次是
这里有两个不大熟悉的函数,原型及功能分别是
1 | strncmp(const char *str1,const cahr *str2,size_t n) |
可以看到最后一个函数的读入字节数由第二个函数的返回值决定,
细看第二个函数,将s与buf比较,s中存的是随机数,肯定猜不到,这里也没有办法能够泄露或改写它,那么只能令v1等于0,即比较0个字节是否相同,那必然是相等的,要使v1等于0,只要buf开头是\0就行了,最后返回的是buf第8个字节
之后这题没有后门函数也没有binsh,那就是libc泄露类题目
最终exp
1 | from pwn import* |
离大谱,自己泄露出来的libc怎么也打不通,最后发现题目给了libc🤡
细心的可能发现了
main_addr=e.symbols[‘__libc_start_main’]
这一句根本没用上
因为找不到main的symbols
所以我本来是打算用__libc_start_main作第一次rop的返回地址,但可以发现后面并没有使用它,因为这么做是打不通的,至于为什么
__libc_start_main是需要参数的
脑子短路了
因为这个硬生生被卡住了半个小时
也算吃了个教训,以后返回地址不能用__libc_start_main
(之所以我会这么做,是因为我记岔了,__libc_start_main是可以用来做被泄露的函数,但我记成可以做返回地址了)
jarvisoj_level2_x64
与jarvisoj_level2一样只不过变成了64位,注意参数传递方式即可
exp
1 | from pwn import* |
[HarekazeCTF2019]baby_rop
和上一题一模一样
exp
1 | from pwn import* |