dll调用爆破exe函数

湖湘杯中有一道highwayhash64的题目,通过github找到highwayhash的源码,赛后看到题解,大佬们都是改了源码中的异或值,直接用其源码进行爆破的,但是安全客的一篇题解中的通过dll调用其内置的函数思路从来没见多,于是赶紧复现一波,学到好多知识,也佩服大佬操作。

dll简介


DLL,(Dynamic Link Library)是一个可以被其他程序调用的程序模块,其中封装了可以被调用的资源或函数。dll的文件格式也是PE文件格式,不过DLL不能自己独立运行,可以通过exe来调用其来运行。DLL和exe都符合PE格式,那么如何区分其格式是dll还是exe我们可以通过PE文件头中的IMAGE_FILE_DLL 是否为1还是0来区分其是否为dll,同样的我们可以将exe中的该值改为1来将exe文件改为dll,通过指定函数地址,调用其内置的函数。

dll调用


对dll程序的调用一般有两种方法:

  1. 静态调用,需要在编写调用程序的时候通过 #pragma comment(lib,”DLLName”) 来告诉链接器在DLLNAME文件中找到DLL的到处函数信息。通过链接器将导出函数写入可执行文件。
  2. 动态调用,这种调用不是在链接时完成的,而是在运行时完成的。我们需要通过LoadLibrary函数载入DLL文件,通过GetProcAddress(hMoule,”FunctionName”)或者FunctionAddress来调用函数。

题目解析


程序一共进行了两次的Hash运算

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/*经过IDA后main函数中的部分*/
v6 = -1i64;
v7 = -1i64;
do
++v7;
while ( Dst[v7] );
v13 = v7;//V7中存储的是字符串的长度
if ( sub_1400017A0((__int64)&v13, 4ui64) != -3236539321542973756i64 )//传入的是字符串的长度的地址,每次传入的地址的值是一样的
exit(1);
v8 = (unsigned int)(v13 - 1);
if ( (unsigned int)v8 >= 0x104 )
{
_report_rangecheckfailure();
JUMPOUT(*(_QWORD *)&byte_1400019E1);
}
Dst[v8] = 0;
do
++v6;
while ( v15[v6] );
if ( sub_1400017A0((__int64)v15, (unsigned int)v6) != 2074793449951618155i64 )//这里传入的是字符串的地址
exit(1);
sub_140001880((__int64)"successful!\nplease entry any key exit...", v9, v10, v11);
fgetchar();
return 0;

第一次要首先对字符串的地址进行hash,爆破函数如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
void len(){
for (int i =0;i<50;i++ ){
unsigned long long res;
res = func(&i,4);//这里传入的是地址,但是函数内有对地址的值进行操作的程序
if(res == 0xD31580A28DD8E6C4){
printf("the len is ",i-9);
break;
}
}
}
int main(int argc, char *argv[]) {
printf("HEllo world\n");
HINSTANCE hDll = LoadLibrary(TEXT("reverse.dll"));
if(hDll == NULL){
MessageBox(NULL,"FirstDll.dll 不存在","Dll failed to load",MB_OK);
return 1;
}
MessageBox(NULL,"Hi I'm here","Hello",MB_OK);
printf("Dll base is %llxn",hDll);
printf("success\n");
func = ((f)((char*)hDll + 0x17A0));//这里的func指向的是dll中我们要爆破的hash函数
printf("Dll base is %llxn",hDll);
printf("success\n");
len();
}

通过上一步对于输入字符串的长度的爆破我们知道字符串我们要爆破的是九位的数字,继续爆破。从IDA中看到的函数如下

1
2
3
4
5
do
++v6;
while ( v15[v6] );
if ( sub_1400017A0((__int64)v15, (unsigned int)v6) != 2074793449951618155i64 )
exit(1);

但是无论如何没有在伪C代码中找到V15在哪里赋值的,查看汇编后,分析,在rsp+30处是输入的字符串,[rsp+158h+var_120]为 v15字符串的起始部分,即我们要爆破的数字的起始位置处。通过++v6,统计的是我们的digit的位数。

1
2
3
4
5
6
.text:000000014000196C                 mov     [rsp+rax+158h+Dst], 0 ; rsp+30
.text:0000000140001971 lea rax, [rsp+158h+var_120] ; rsp+38
.text:0000000140001976 ; 34: ++v6;
.text:0000000140001976
.text:0000000140001976 loc_140001976: ; CODE XREF: main+9D↓j
.text:0000000140001976 inc rbx

已知是九位数字的字符串,爆破函数如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
void flag(){
char buffer[100];
unsigned long long i;
for ( i =1000000000;i<=10000000004;i++){
//sprintf(buffer,"%d",i);
sprintf(buffer, "%0.10llu", i);
unsigned long long res =10;
res = func((long long)buffer,10);
if (i % 100000 == 0)
{
printf("%0.10llun\n", i);
}
if (res == 0x1CCB25A666AC646B){
printf("flag is %lld \n",i);
break;
}
//printf("%s\n",buffer);
}
}

说说踩得那些坑


题目很好玩,大佬的思路很涨姿势,但是也踩了不少的坑

坑一

当改了Image_File_dll为1时,写好程序各种运行,均为调用成功,最后通过depends.exe查看发现是系统位数不匹配,下dev c++ 64版本。

坑二

好不容易成功调用了,自以为离成功不远了,可惜太天真了,LoadLibrary后直接进入reverse源程序的main函数执行程序了。各种尝试,都想patch掉了,各种无果,询问作者,只要改一下entry point为0就可以了。坑啊…

坑三

第二个爆破为什么是对输入的数组的爆破,why!!!,去看源码v15对应的是rsp+158h-120h,而Dst对应的是rsp+158h-128h,但是在数据中却长这样,不应是是var_120比Dst大么,苦苦思索,还是被大佬点醒,前面有负号。

1
2
3
4
5
6
7
8
9
-0000000000000128 Dst             db ?                    ; rsp+30
-0000000000000127 db ? ; undefined
-0000000000000126 db ? ; undefined
-0000000000000125 db ? ; undefined
-0000000000000124 db ? ; undefined
-0000000000000123 db ? ; undefined
-0000000000000122 db ? ; undefined
-0000000000000121 db ? ; undefined
-0000000000000120 var_120 db ? ; rsp+38

参考链接

  1. https://www.anquanke.com/post/id/164604