关于dll逆向的一些实战技术
WMBa0 发表于 河南 CTF 3449浏览 · 2024-01-25 11:05

在CTF比赛中,或许我们并不经常去逆向一个dll,因为它需要我们加载然后运行。

当用IDA调试dll时,会遇到这样的问题。不像exe那样调试比较容易,我们需要写个exe调用dll,再进行动态调试分析。
这道国外的dll逆向,为我们进行dll逆向提供了一个思路。

初步分析

使用IDA打开,发现了许多challenge函数

因为是dll,我们需要分析它导出了哪些函数
发现导出了52个函数,很可疑

进入最后一个导出函数,导出的函数,正是刚才看到的challenge函数

而其他的函数

返回范围 [1-48] 的值

sub_10001000

分析发现是类似解密的一个函数

猜测跟前面的challenge函数调用顺序有关,因为他们的返回值很可疑

使用IDA的signsrch插件

关于安装方法可以自行搜索,这里使用signsrch来搜索相关的加密算法,帮助我们分析

发现是CAST128加密算法
分析导出函数
经过分析发现调用sub_10001000函数有两个,导出序号51和导出序号50的函数

经过对比,发现解密的秘钥和密文不同

现在总结一下:
导出函数51被导出函数1-48所引用,(导出函数1-48修改了byte_10007014秘钥值建立了引用联系)
导出函数50使用了导出函数49,但是导出函数50,并没有被其他函数引用
到这里,我们应该建立了一个思路,猜测走导出函数51这条路可以得到flag
同时,我们应该注意到导出函数1-48,返回了某一范围的值,或许这是暗示调用顺序。

获得dll导出函数调用顺序

使用ida-python

startaddr=0x10001C70
endaddr=0x10002E10

funcs=idautils.Functions(startaddr,endaddr)
for i in funcs:
    m = re.match(rptn, idc.get_func_name(i))
    if m != None and m.group(1) != "51":
        #通过这样方式,得到返回序号
        nextordinal = int(idc.print_operand(i+0x7,1).replace("h",""),16) ^ int(idc.print_operand(i+0xe,1).replace("h",""),16)
        print(nextordinal)
        ret.append(nextordinal)

ret = list(set(ret))
ret.sort()

for i in range(1, 49):
    if ret[i-1] != i:
        print(i)
        break

得到这样的顺序

48
20
1
21
33
25
22
16
14
40
2
38
26
6
13
29
36
47
35
37
42
4
18
3
51
43
7
32
46
5
39
19
24
28
27
9
34
31
10
23
11
12
17
8
15
44
45
30

发现导出函数序号30为base call
现在,我们已经拥有了编写一个exe程序所需的一切,该程序可以按正确的顺序调用导出函数, 将解密的数据dump到文件中以供进一步分析

编写exe调试dll

#include <Windows.h>
#include <stdio.h>
#include <stdint.h>
typedef uint32_t (*EXP)(VOID);
int main(int argc, char **argv)
{
    HMODULE hLib;
    HANDLE hFile;
    EXP exp;
    uint32_t plaintxt;
    uint32_t nextord;
    hLib = LoadLibraryA((LPCSTR)argv[1]);
    nextord = 48; // our starting point
    // decrypt key
    while (nextord != 51)
    {
        exp = (EXP)GetProcAddress(hLib, MAKEINTRESOURCE(ret));
        nextord = exp();
    }
    exp = (EXP)GetProcAddress(hLib, MAKEINTRESOURCE(51));
    // decrypt plaintxt
    exp();
    // dump plaintext to file
    plaintxt = *(uint32_t *)((uint32_t)exp + 0x2e);
    hFile = CreateFile("out", GENERIC_WRITE, 0, 0, CREATE_ALWAYS,
                       FILE_ATTRIBUTE_NORMAL, 0);
    WriteFile(hFile, (LPCVOID)plaintxt, 0x1A10, 0, 0);
    CloseHandle(hFile);
    return 0;
}

输出flag

#include <Windows.h>
#include <stdio.h>
#include <stdint.h>
typedef VOID (*BEEPBEEP)(
    DWORD dwFreq,
    DWORD dwDuration);
int main(int argc, char **argv)
{
    HMODULE hLib;
    BEEPBEEP beep;
    hLib = LoadLibraryA((LPCSTR)argv[1]);
    beep = (BEEPBEEP)GetProcAddress(hLib, MAKEINTRESOURCE(50));
    beep(440, 500);
    beep(440, 500);
    beep(440, 500);
    beep(349, 350);
    beep(523, 150);
    beep(440, 500);
    beep(349, 350);
    beep(523, 150);
    beep(440, 1000);
    beep(659, 500);
    beep(659, 500);
    beep(659, 500);
    beep(698, 350);
    beep(523, 150);
    beep(415, 500);
    beep(349, 350);
    beep(523, 150);
    beep(440, 1000);
    return 0;
}

或者运行python

from ctypes import *

mydll = cdll.LoadLibrary("./flareon2016challenge.dll")
i = 30
while True:
    i = mydll[i]()
    if i == 51:
        break
mydll[51]()

mydll[50](0x1B8, 0x1F4)
mydll[50](0x1B8, 0x1F4)
mydll[50](0x1B8, 0x1F4)
mydll[50](0x15D, 0x15E)
mydll[50](0x20B, 0x96)
mydll[50](0x1B8, 0x1F4)
mydll[50](0x15D, 0x15E)
mydll[50](0x20B, 0x96)
mydll[50](0x1B8, 0x3E8)
mydll[50](0x293, 0x1F4)
mydll[50](0x293, 0x1F4)
mydll[50](0x293, 0x1F4)
mydll[50](0x2BA, 0x15E)
mydll[50](0x20B, 0x96)
mydll[50](0x19F, 0x1F4)
mydll[50](0x15D, 0x15E)
mydll[50](0x20B, 0x96)
mydll[50](0x1B8, 0x3E8)

拿到flag:

0 条评论
某人
表情
可输入 255