嘛,打ctf还是需要一定量的练习的,不管是练手还是学习新知都是极好的。
然后要稍微总结一下做题过程中的小问题,就从简单的题目开始好了,难的题目,正在学啦orz
Bugku new
pwn2
直接nc上去就好了。。。然后cat flag
pwn3
最基本的栈溢出,啥保护都没开,直接溢出到返回地址控制返回到get_shell_()
函数即可
pwn4
啥保护都没开,程序有system函数,没有"/bin/sh"
命令,漏洞也是栈溢出,那么就利用栈溢出输入命令以后返回到system函数中执行,但是注意程序是64位的,所以参数的传递需要依靠RDI
寄存器。
1 | checksec pwn4/pwn4 |
看看主函数内容
1 | __int64 __fastcall main(__int64 a1, char **a2, char **a3) |
可以发现溢出的只有0x18个字节,需要完成的布置有
- 把
"/bin/sh"
输入到一个地址不变的位置(比如.data段) - 程序再次运行,将
"/bin/sh"
的地址赋给RDI
,然后返回到system
函数处执行
实际操作的时候发现
1 | .text:000000000040072A lea rax, [rbp+s] ; s = -0x10 |
发现read
的buf是通过rbp-0x10
来计算的,这样我们只需要把rbp
设置成tar_addr + 0x10
就行了,这个可以通过栈溢出直接设置rbp
得到,然后返回地址设置成main
就可以重新运行。
然后寻找一个合适的gadget,设置rdi
,然后返回到system函数就能执行
1 | Gadgets information |
这里选择0x00000000004007d3处的gadget,payload设置为padding(0x18) + gadget + data_addr + system_addr
但是在写好exp之后发现还有点问题,那就是由于延迟绑定,我们之前修改rbp
的时候返回到read时,程序还会再次返回,这样回修改rsp
的值,这时栈的位置就被修改到data段了,但是在延迟绑定的时候,会用到栈的一个比较大的空间,就会导致地址溢出,程序崩溃返回。怎么解决呢?那就是让它先绑定,我们再调用。
在第一次读"/bin/sh"
的时候,还剩下8字节没有利用,恰好可以调用一次system
,然后返回到read
。这样就搞定啦。
exp如下
1 | #!/usr/bin/python |
pwn5
先查看一下保护
1 | checksec pwn5/human |
只开了部分写保护和栈不可执行。
看一下程序
1 | int __cdecl main(int argc, const char **argv, const char **envp) |
程序有一次格式化字符串漏洞和一次栈溢出,溢出大小为0x18。同时注意到格式化字符串参数只有8个字节。目前的思路为:
- 通过格式化字符串漏洞,泄露libc地址。
- 寻找one gadget,通过修改返回地址get shell
首先通过调试,查看栈上是否有什么有趣的东西,我们知道由于调用,栈上应该保存了__libc_start_main
相关的地址。
1 | ──────[ DISASM ]─────── |
可以发现,栈上0x7fffffffde18
存着__libc_start_main+240
的值,这样就能通过计算偏移,然后就能得到__libc_start_main
的地址。通过计算,我们可以得到用来得到地址的字符串应该是%11$llx
。
exp如下
1 | #!/usr/bin/python |
pwn6
这是个堆题,先查看一下保护
1 | checksec pwn6/heap1 |
观察一下程序,实现了一个记事本的功能,有新建、修改、删除、查看功能。
发现在edit
函数功能中有个off by one漏洞
1 | unsigned __int64 edit() |
程序限制最多有10个记录,在新建记录的时候,会malloc(0x10)
作为索引,前8字节存储大小,后8字节存储malloc(size)
指针,然后内容存储在分配的chunk中。删除的时候会清空指针,所以没有uaf。
通过off by one,得到大致利用思路为
- 通过修改堆块的size域,然后free后再malloc,造成堆块重叠,覆盖掉某一个索引,这样可以实现任意地址写。
- 通过任意地址写,先泄露libc的地址,然后再通过任意地址写,把
free
函数的got表改写为system
函数的地址 - 预先设置一个记录内容为
"/bin/sh"
,改写free
的got表后释放该记录,就能get shell
堆块重叠情况示意
这样块2的索引我们就能控制了
exp如下
1 | #!/usr/bin/python |
pwn8
pwn8看起来是一个基础的ret2libc的题,事实上直接按照ret2libc的方式写本地就能get shell,但是远程不知道为啥不可以,自己试了试感觉应该是远程的缓冲区设置有一点点问题,远程需要先输入才有“ret2libc play”的欢迎语,就有点奇怪orz
至于对返回地址的判断,由于使用的是int类型的,所以过大的地址,比如system
的地址,会被认为是负数orz,相当于这个判断没有起作用。
exp如下
1 | #!/usr/bin/python |
pwn9
这就是一个典型的格式化字符串的题,而且程序也给了get shell的函数,也没开PIE,就只需要把需要修改的位置放置到栈上,然后通过格式化字符串漏洞的利用方式修改就行。
但是,问题就是,不知道栈地址就不能修改到返回地址,那咋整呢。
那么,看到程序开了canary保护,这样,我们可以修改__stack_chk_fail
函数的got表呀,把其修改成目标函数的地址,然后我们触发canary,这样程序就会执行__stack_chk_fail
函数,这样,就能执行我们希望的函数了。
exp如下
1 | #!/usr/bin/python |
pwn11
同样,先查看一下保护
1 | checksec f4n_pwn |
程序本身有点复杂,但是在最开始的函数中就有栈溢出的漏洞,然后程序也没有开canary保护。
1 | int read_name() |
可以发现,虽然v2是unsigned int
类型,但是在比较的时候,是转换成signed int
类型。这样,如果输入-1,那么就可以输入4294967295个字符,这样,我们就能直接返回到目标函数了。
exp如下
1 | #!/usr/bin/python |
小结
题目还没写完啊,主要是有些知识点还没弄懂,基础一点有ret2dlresolve,以及IO_file的操作,我IO_file的利用连例子都没成功orz。。。
还是太菜了。。。