2021祥云杯部分逆向题解

robots

 

Rev_Dizzy

因为main函数太大,ida默认反编译函数的大小只有64K,所以这里会反编译会失败。

这个问题可以通过修改反编译插件的配置文件\cfg\hexrays.cfg中MAX_FUNCSIZE,改为1024就好了。

然后观察反编译后的伪代码,对输入进行了5000多行代码的运算且不是线性运算。

首先想到的是用z3来解,但刚复制完代码准备跑脚本的时候发现运算其实只有加,减,异或,那这就很好办了,直接把密文用程序中的运算加法改成减法,减法改成加法,然后倒着跑一遍就解密了,。

用python处理运算表达式:

fp = open("1.py", "rb")
fp1 = open("ans.txt", "w")
data = fp.read()
data = data.split(b'\n')
for i in data[::-1]:
    tmp = i.decode()
    tmp = tmp.replace('\r', '')
    if '+' in tmp:
        tmp = tmp.replace('+', '-')
    elif '-' in tmp:
        tmp = tmp.replace('-', '+')
    fp1.write(tmp+'\n')
fp1.close()
fp.close()

最后在在头部补上密文,运行得到flag:

#include <stdio.h>
#include <stdlib.h> 

int main(void)
{
    char flag[] = {0x27, 0x3c, 0xe3, 0xfc, 0x2e, 0x41, 0x7, 0x5e, 0x62, 0xcf, 0xe8, 0xf2, 0x92, 0x80, 0xe2, 0x36, 0xb4, 0xb2, 0x67, 0x77, 0xf, 0xf6, 0xd, 0xb6, 0xed, 0x1c, 0x65, 0x8a, 0x7, 0x53, 0xa6, 0x66, 0};

    flag[16] -= flag[20];
    flag[0] -= flag[5];
    flag[21] += 54;
    flag[22] += flag[31];
    flag[29] -= flag[25];
    flag[18] ^= flag[14];
    flag[1] -= 33;
    ...
    ...
    ...
       flag[14] -= flag[3];
    flag[10] -= flag[6];
    flag[10] += flag[27];
    flag[6] -= flag[3];

    puts(flag);         
}

 

勒索解密

程序加密了一个bmp图片,让我们逆向程序得到加密算法进而解密还原图片得到flag。

开始我通过自己创建文件加密后看密文与明文的关系,发现16字节一组加密,每次加密结果都不一样,且明文的最后一组会被填充到32字节,接着会在密文后填充128字节数据加末尾的0x80。

接着分析程序,来到main函数,代码有点繁琐,调试辅助分析,开始就是去取C:\XX_CTF_XX\目录下的文件,得到文件内容然后对其加密。

定位到main函数中加密开始的逻辑:

注意到它是用的wincrypt.h库中的加密函数,官方文档

来看关键的加密函数,看到加密前的初始化工作:

因为使用的wincrypt,通过alg_id来区分使用的加密算法,查看文档:https://docs.microsoft.com/en-us/windows/win32/seccrypto/alg-id

所以说就是先用得到pdata进行了sha256然后作为aes_128的初始化密钥。

调试得到pdata是通过一些算出的固定值和时间戳组成的16字节:

然后直接在最后加密的函数下断点,看加密数据是否是我们的输入,确定输入在之前没有变化操作。

自己用数据测试了本地aes解密经过程序加密的数据正确后开始解密工作。

先得到pdata进而得到key:

1.通过文件最后修改的时间,然后在线转换一下得到对应的时间戳。

2.通过bmp文件的魔术字段爆破出时间戳。

这里我2个方法都试了下,得到同样的结果:

爆破pdata,进而得到key:

#coding:utf-8
import base64
from hashlib import *
from Crypto.Cipher import AES

def decrypt(data, key):
    cryptos = AES.new(key, AES.MODE_ECB)
    decrpytBytes = list(base64.b64decode(data))
    decrpytBytes = bytes(decrpytBytes) 
    meg = cryptos.decrypt(decrpytBytes)
    return meg
enc = 'sgL4CWqLPyWU7eexyfw6pw=='
s = [0xB2, 0x2F, 0xC6, 0x0E, 0x4F, 0xD4, 0x54, 0x4B, 0x4E, 0x31, 0x21, 0x61, 0x21, 0xE7, 0xB1, 0x8E]

for i in range(0xff):
    for j in range(0xff):
        for k in range(0xff):
            s[8:11] = [i, j, k]
            key = sha256(bytes(s)).hexdigest()[:32]
            key = bytes.fromhex(key)
            ans = decrypt(enc, key)
            if ans[:2] == b'BM' and ans[15] == 0 and ans[5] == 0:
                print(key)

然后写脚本解密,但发现只有第一组解密正确。

其实这里是我忽略了上面的CryptSetKeyParam

BOOL CryptSetKeyParam(
  HCRYPTKEY  hKey,
  DWORD      dwParam,
  const BYTE *pbData,
  DWORD      dwFlags
);

其中参数2我们可以从wincrypt.h中找到:

从以上我看可以了解到,程序是使用了PKCS5_PADDING与cbc模式加密。

然后从第一组能解密成功可以推测出使用了默认的iv:0。

最后解密还原bmp图片:

#coding:utf-8
import base64
from hashlib import *
from Crypto.Cipher import AES

def decrypt(data, key):
    cryptos = AES.new(key, AES.MODE_ECB)
    decrpytBytes = list(base64.b64decode(data))
    decrpytBytes = bytes(decrpytBytes) 
    meg = cryptos.decrypt(decrpytBytes)
    return meg

key = "f4b6bb19108b56fc60a61fc967c0afbe71d2d9048ac0ffe931c901e75689eb46"[:32]
key = bytes.fromhex(key)
f = open("1.bmp.ctf_crypter", "rb")
fp = open("1", "wb")
data = f.read()

def xor(a, b):
    res = []
    for i in range(len(a)):
        #print(i)
        res += [a[i]^b[i]]
    return bytes(res)

for i in range(len(data)//16):
    #print(data[16*i:16*(i+1)].hex())
    enc = base64.b64encode(data[16*i:16*(i+1)])
    if i > 0:
        ans = xor(decrypt(enc, key), data[16*(i-1):16*i])
    else:
        ans = decrypt(enc, key)
    fp.write(ans)
f.close()
fp.close()
print('*'*100)

至于最后一块的填充数据,从解密结果来看是10。

 

Rev_APC

首先定位到DriverEntry:

再看sub_140004D3C:

我们知道DriverEntry的第一个参数是驱动对象指针(PDRIVER_OBJECT Driver)。驱动对象用DRIVER_OBJECT Driver数据结构表示,它做为驱动的一个实例被内核加载,并且内核中一个驱动只加载一个实例,也就是一个驱动最多只有一个驱动对象。

驱动程序的关键是要去分析AddDevice函数,而设备对象结构体中的一个成员:struct _DRIVER_OBJECT *DriverObject; 它也是一个驱动对象指针,且它与DriverEntry中的一个参数都是同一个驱动对象指针,依据这个我们就能快速的从DriverEntry中找到设备创建相关关键函数了。其实也就是定位DriverEntry的第一个参数Driver,看那个函数把它作为了第一个参数。(如上图演示,找到了sub_1400015EC函数。

看到sub_1400015EC函数:

其中IoCreateSymbolicLink创建符号链接是为了给设备对象起个别名,为了让用户模式下的程序识别这个设备对象;

Driver->DriverUnload是设置驱动卸载时要调用的回调函数,一般负责删除在DriverEntry中创建的设备对象,并把设备对象所关联的符号链接删除;

Driver->MajorFunction记录的是一个函数指针数组,函数是处理IRP的派遣函数,是用户模式发出请求,然后由用户态与内核态之间的桥梁I/O管理器发出。

再看到里面的sub_1400019D8函数:判断指定的dll是否存在,如果不存在就从编码的数据中异或解密出一个dll写入文件。

判断dll是否存在代码:

找到FileAttributes的枚举值:

接着sub_1400019A4函数进行了lpc通信的初始化,监听端口等。

最后sub_140001B78函数设置了一个进程创建的监控函数,本题是监控新创建的进程,用md5值判断该进程是否是explorer.exe

上面我们对整个创建设备对象的函数整体上梳理了一遍,下面开始提取出要解密的dll。

idapython提取解密dll:

from ida_bytes import *

addr = 0x140007000
fp = open("InjectDLL.dll", "wb")
for i in range(0x3c00):
    fp.write(bytes([get_byte(addr+i)]))
fp.close()
print('*'*100)

从字符串信息定位到dll中的关键函数sub_1800015C0,上半部分:使用sha3-256加密AkiraDDL字符串,将32字节的结果通过DeviceIoControl函数发送到CreateFileW函数创建的驱动对象,让驱动对象相应的设备执行相应的操作(也就是驱动程序中设置的Driver->MajorFunction。

接着就是本题解题的关键了:找到正确的用于后面和flag明文加密的32字节数据。

上面我们知道计算的32字节hash值发送到了驱动对象,看到驱动对象中对应的处理函数:可以看到32字节hash经过的异或的数据并没有传出到dll中,而是直接把编码的数据复制到了*(__m128i **)(a1 + 112),所以说我们的hash值根本没有使用的。

接着是后面的sub_180001350函数:处理从驱动对象发送回的数据,使用了lpc通信向服务方发送报文,请求得到LPC服务。

回到驱动程序中找到lpc通信初始化的地方,用StartRoutine函数处理lpc通信请求。

从StartRoutine函数找到处理从dll发送的数据的地方,这里的if else分支中,一个是累异或:每个字节与它之前的所有字节异或;另外一个是将M@gic字符串添加到本来有的27字节数据后面正好组成32字节数据。

剩下就是最后的加密,32轮加密,每轮加密函数用随机数确定。因为这里没有使用srand()初始化种子,那使用的就是默认的种子:1。

上面也说了在我分析来,有2种用于和flag明文加密的数据,这里我在解密时两种结果都试了一下,从第一种累异或得到正确结果。

加密算法也很好逆,一是加密只有异或,加法,减法及移位。二是要和flag明文加密的数据的变化不受明文的影响。

解密脚本:

#include <stdio.h>
#include <stdlib.h>

unsigned char hash[] = {165, 106, 167, 113, 180, 119, 198, 3, 209, 8, 223, 24, 206, 3, 215, 15, 204, 119, 186, 98, 174, 109, 221, 24, 192, 9, 213, 213, 213, 213, 213, 213};
//unsigned char hash[] = {0xA5, 0xCF, 0xCD, 0xD6, 0xC5, 0xC3, 0xB1, 0xC5, 0xD2, 0xD9, 0xD7, 0xC7, 0xD6, 0xCD, 0xD4, 0xD8, 0xC3, 0xBB, 0xCD, 0xD8, 0xCC, 0xC3, 0xB0, 0xC5, 0xD8, 0xC9, 0xDC, 0, 0, 0, 0, 0};
//unsigned char hash[] = {56, 144, 185, 193, 92, 20, 87, 231, 166, 41, 206, 164, 135, 174, 194, 10, 40, 211, 69, 111, 251, 121, 0, 103, 104, 40, 171, 235, 244, 190, 95, 32};
unsigned char hashed[32][32] = {0};
char order[] = {0, 5, 5, 2, 2, 3, 4, 4, 3, 2, 0, 3, 0, 3, 2, 1, 5, 1, 3, 1, 5, 5, 2, 4, 0, 0, 4, 5, 4, 4, 5, 5};
unsigned char enc[32] = {87, 197, 56, 27, 58, 168, 52, 47, 57, 151, 198, 228, 4, 47, 143, 238, 94, 81, 128, 103, 36, 201, 111, 72, 91, 127, 189, 199, 176, 194, 194, 235};
//unsigned char enc[] = {145, 245, 10, 154, 15, 94, 11, 194, 194, 229, 233, 150, 87, 240, 145, 56, 1, 113, 96, 76, 163, 181, 65, 253, 1, 237, 39, 181, 137, 88, 235, 108};
unsigned char plain[32] = {0};

unsigned char fun(unsigned char a)
{
    return ((a<<4)|(a>>4));
}

void fun1(unsigned char *a, unsigned char *b)
{
    for(int i = 0; i < 32; i++)
    {
        a[i] += 16;
        b[i] ^= a[i];
    }
}

void fun2(unsigned char *a, unsigned char *b)
{
    for(int i = 0; i < 32; i++)
    {
        a[i] -= 80;
        b[i] ^= fun(a[i]);
    }
}

void fun3(unsigned char *a, unsigned char *b)
{
    for(int i = 0; i < 32; i++)
    {
        b[i] ^= a[i];
    }
}

void fun4(unsigned char *a, unsigned char *b)
{
    for(int i = 0; i < 32; i++)
    {
        a[i] -= 80;
    }

    for(int i = 0; i < 32; i += 2)
    {
        b[i] ^= 16*a[i];
        b[i+1] ^= a[i] >> 4;
    }

}

void fun5(unsigned char *a, unsigned char *b)
{
    for(int i = 0; i < 32; i++)
    {
        b[i] ^= a[i];
    }

}

void fun6(unsigned char *a, unsigned char *b)
{

    for(int i = 0; i < 32; i++)
    {
        if((unsigned char)(a[i]-33) > 46)
        {
            if((unsigned char)(a[i]-81) > 46)
            {
                if(a[i]>0x80)
                {
                    a[i] = a[i]-48;
                    b[i] -= a[i];
                }
            }
            else
            {
                a[i] = a[i]-48;
                b[i] ^= a[i] >> 4;
            }
        }
        else
        {
            a[i] = a[i]-80;
            b[i] += a[i];
        }
    }
}

void defun6(unsigned char *a, unsigned char *b)
{
    for(int i = 0; i < 32; i++)
    {
        if((unsigned char)(a[i]-33) > 46)
        {
            if((unsigned char)(a[i]-81) > 46)
            {
                if(a[i]>0x80)
                {
                    a[i] = a[i]-48;
                    b[i] += a[i];
                }
            }
            else
            {
                a[i] = a[i]-48;
                b[i] ^= a[i] >> 4;
            }
        }
        else
        {
            a[i] = a[i]-80;
            b[i] -= a[i];
        }
    }
}

int main(void)
{
    for(int i = 0; i < 32; i++)
    {
        unsigned char tmp = 0;
        for(int j = 0; j < i+1; j++)
            tmp ^= hash[j];
    } 

    for(int i = 0; i < 32; i++)
    {
        for(int j = 0; j < 32; j++)
        {
            hashed[i][j] = hash[j];
            //printf("%d, ", hash[j]);
        }
        //putchar(10);
        switch(rand()%6)
        {
            case 0: fun1(hash, plain);
                break;
            case 1: fun2(hash, plain);
                break;
            case 2: fun3(hash, plain);
                break;
            case 3: fun4(hash, plain);
                break;
            case 4: fun5(hash, plain);
                break;
            case 5: fun6(hash, plain);
                break;
        }

    }

    for(int i = 0; i < 32; i++)
    {
        switch(order[i])
        {
            case 0: fun1(hashed[31-i], enc);
                break;
            case 1: fun2(hashed[31-i], enc);
                break;
            case 2: fun3(hashed[31-i], enc);
                break;
            case 3: fun4(hashed[31-i], enc);
                break;
            case 4: fun5(hashed[31-i], enc);
                break;
            case 5: defun6(hashed[31-i], enc);
                break;
        }

    }

    for(int i = 0; i < 32; i++)
    {
        printf("%c", enc[i]);
    }

    return 0;
}
(完)