在一些比较复杂的出程序中,特别是被混淆过的程序,要对这种程序进行静态分析是很困难的,这时候黑盒测试就成了分析函数的不二之选,本分将会讲述Windows如何利用Dll注入进行黑盒测试。
目录
方法
- 编写注入dll、so
- 进行注入测试
举例
源程序下载,文章最后会给出源码:http://file.eonew.cn/pe/crackme_dll.exe 。
用IDA
查看源程序。
main
int __cdecl main(int argc, const char **argv, const char **envp)
{
size_t v3; // eax
char v5[32]; // [esp+0h] [ebp-38h]
char v6[4]; // [esp+20h] [ebp-18h]
unsigned int i; // [esp+34h] [ebp-4h]
strcpy(v6, "yougettheflag");
puts(aInputYourFlag);
scanf(a30s, v5);
if ( sub_40179E(v5) == 1 )
{
for ( i = 0; ; ++i )
{
v3 = strlen(v6);
if ( i >= v3 )
break;
if ( sub_401000((unsigned __int8)v5[i]) != v6[i] - 97 )
{
puts(aFailed);
return 0;
}
}
puts(aSuccess);
}
else
{
puts(aFailed_0);
}
return 0;
}
这里先说明一下sub_40179E
函数的功能是将输入的字符串转换成十六进制。而sub_401000
函数是经过了混淆,其原本的功能很难分析出来,但是我编写的时候就是将它写成了一个对应的输入输出表,所以我们只需获得这张表,即可以用这张表来还原输入。
sub_401000
signed int __usercall sub_401000@<eax>(int a1@<ebx>, int a2@<ebp>, signed int a3@<edi>, signed int a4)
{
signed int v4; // edx
int v5; // ecx
signed int v6; // esi
signed int v7; // edi
signed int v8; // ebx
signed int v9; // ebp
int v10; // edx
signed int v11; // ecx
int v12; // eax
bool v13; // sf
unsigned __int8 v14; // of
signed int v15; // eax
bool v16; // al
signed int v17; // edi
int v18; // esi
int v19; // esi
signed int v20; // eax
bool v21; // sf
unsigned __int8 v22; // of
int v23; // edi
bool v25; // [esp+3h] [ebp-47Dh]
int v26; // [esp+4h] [ebp-47Ch]
int *v27; // [esp+8h] [ebp-478h]
int v28; // [esp+Ch] [ebp-474h]
int v29; // [esp+10h] [ebp-470h]
int v30; // [esp+14h] [ebp-46Ch]
int v31; // [esp+18h] [ebp-468h]
int v32; // [esp+1Ch] [ebp-464h]
int v33; // [esp+20h] [ebp-460h]
signed int v34; // [esp+24h] [ebp-45Ch]
signed int v35; // [esp+24h] [ebp-45Ch]
int v36; // [esp+28h] [ebp-458h]
int *v37; // [esp+2Ch] [ebp-454h]
int v38; // [esp+30h] [ebp-450h]
int v39; // [esp+34h] [ebp-44Ch]
int *v40; // [esp+3Ch] [ebp-444h]
int v41; // [esp+40h] [ebp-440h]
int v42; // [esp+44h] [ebp-43Ch]
int v43; // [esp+48h] [ebp-438h]
int v44; // [esp+4Ch] [ebp-434h]
int v45; // [esp+50h] [ebp-430h]
int v46; // [esp+54h] [ebp-42Ch]
signed int v47; // [esp+5Ch] [ebp-424h]
int v48; // [esp+60h] [ebp-420h]
int v49; // [esp+64h] [ebp-41Ch]
int v50; // [esp+68h] [ebp-418h]
int v51; // [esp+6Ch] [ebp-414h]
int v52[260]; // [esp+70h] [ebp-410h]
v4 = a4;
v5 = 548813401;
v6 = 1881953091;
if ( a4 > 255 )
v5 = -1415659758;
v33 = a4 - 1;
v32 = a4 - 1;
v31 = a4 - 1;
v30 = a4 - 1;
v28 = a4 - 1;
v47 = v5;
v29 = a4 - 1;
while ( 1 )
{
while ( 1 )
{
while ( 1 )
{
while ( 1 )
{
LABEL_6:
while ( v6 <= 514714396 )
{
if ( v6 > 86368825 )
{
if ( v6 == 86368826 )
{
v5 = v41;
v6 = -994522485;
v29 = v52[v41];
goto LABEL_69;
}
if ( v6 == 190114385 )
{
v6 = 1319777738;
v32 = v52[v4];
goto LABEL_6;
}
if ( v6 != 400573796 )
goto LABEL_5;
LABEL_4:
v6 = -1534202243;
a3 = -1;
goto LABEL_5;
}
if ( v6 == -444507636 )
{
a2 = v52[v4];
v6 = 1660167534;
goto LABEL_6;
}
if ( v6 != -395968525 )
{
if ( v6 == 6163799 )
{
v6 = -1798225969;
v31 = v52[v44];
goto LABEL_69;
}
goto LABEL_5;
}
v44 = a4 - 1;
v31 = 0x1000000;
v6 = (((a4 - 1) >> 31) & 0x94733278) + 6163799;
if ( v6 <= -444507637 )
goto LABEL_69;
}
if ( v6 > 1319777737 )
break;
if ( v6 != 514714397 )
{
if ( v6 == 548813401 )
{
v6 = -2109195768;
a1 = 1;
v40 = v52;
v52[0] = 0;
goto LABEL_69;
}
if ( v6 != 783469431 )
goto LABEL_5;
v48 = a2;
v34 = a3;
v7 = 1176118954;
v8 = -2049448533;
v9 = -236476695;
v10 = v30 + 1;
if ( v49 > v50 )
v7 = -811602148;
if ( v50 > v10 )
v8 = -1594435630;
v11 = 414635068;
v12 = -305545985;
if ( v49 > v10 )
v11 = -305545985;
while ( 1 )
{
do
{
LABEL_25:
while ( v9 > 414635067 )
{
if ( v9 == 414635068 )
{
v12 = v49;
v9 = -1822626534;
goto LABEL_35;
}
if ( v9 != 1176118954 )
goto LABEL_24;
v9 = v11;
if ( v11 <= -305545986 )
goto LABEL_35;
}
if ( v9 == -305545985 )
goto LABEL_23;
if ( v9 != -236476695 )
goto LABEL_24;
v9 = v7;
}
while ( v7 > -305545986 );
do
{
while ( 1 )
{
LABEL_35:
while ( v9 <= -1594435631 )
{
if ( v9 != -2049448533 )
{
if ( v9 == -1822626534 )
{
v5 = v26;
v4 = a4;
v6 = -2109195768;
v52[v26] = v12;
a3 = v34;
a2 = v48;
a1 = v26 + 1;
goto LABEL_69;
}
goto LABEL_24;
}
v9 = -1822626534;
v12 = v50;
}
if ( v9 == -1594435630 )
{
LABEL_23:
v9 = -1822626534;
v12 = v30 + 1;
goto LABEL_24;
}
if ( v9 == -811602148 )
break;
LABEL_24:
if ( v9 > -305545986 )
goto LABEL_25;
}
v9 = v8;
}
while ( v8 <= -305545986 );
}
}
v50 = v28 + 1;
v5 = v26 - 7;
v43 = v26 - 7;
v30 = 0x1000000;
v6 = (((v26 - 7) >> 31) & 0x96B0986C) - 1744686325;
if ( v6 <= -444507637 )
goto LABEL_69;
}
if ( v6 == 1319777738 )
break;
if ( v6 == 1660167534 )
{
v14 = __OFSUB__(a2, 0x1000000);
v13 = a2 - 0x1000000 < 0;
v6 = 400573796;
v15 = -395968525;
LABEL_66:
if ( v13 ^ v14 )
v6 = v15;
if ( v6 <= -444507637 )
goto LABEL_69;
}
else
{
if ( v6 != 1881953091 )
goto LABEL_5;
v6 = v47;
if ( v47 <= -444507637 )
goto LABEL_69;
}
}
v6 = 400573796;
if ( v51 != v32 )
v6 = -751818089;
if ( v6 > -444507637 )
goto LABEL_6;
LABEL_69:
if ( v6 > -1534202244 )
break;
if ( v6 <= -1744686326 )
{
if ( v6 == -2109195768 )
{
v26 = a1;
v6 = -1882325015;
v15 = -1735363298;
v5 = a1;
v14 = __OFSUB__(a1, 256);
v13 = a1 - 256 < 0;
goto LABEL_66;
}
if ( v6 != -1882325015 )
{
if ( v6 == -1798225969 )
{
v6 = 190114385;
v5 = 1319777738;
v51 = v31;
v16 = v25;
v32 = 0x1000000;
goto LABEL_119;
}
goto LABEL_5;
}
v36 = a1;
v35 = a3;
v17 = 1227597061;
v18 = 0;
LABEL_91:
while ( v17 <= 1227597060 )
{
if ( v17 != 307379757 )
{
if ( v17 == 385223167 )
{
v17 = -1437864421;
a1 = a2;
v5 = v39 + 1;
goto LABEL_103;
}
goto LABEL_90;
}
a2 = v46;
v17 = 385223167;
}
if ( v17 == 2045173338 )
{
v17 = -1437864421;
v37 = &v40[v38];
v45 = v38 + 1;
a1 = (int)&v40[v38];
v5 = v38 + 1;
goto LABEL_103;
}
if ( v17 == 1227597061 )
{
v38 = v18;
v20 = 2045173338;
v22 = __OFSUB__(v18, 256);
v21 = v18 - 256 < 0;
v17 = -2078856237;
goto LABEL_100;
}
while ( 1 )
{
LABEL_90:
if ( v17 > 307379756 )
goto LABEL_91;
LABEL_103:
while ( v17 > -1437864422 )
{
if ( v17 != -1437864421 )
{
if ( v17 == -166592410 )
{
v19 = *v37;
*v37 = *v27;
v17 = 1227597061;
*v27 = v19;
v18 = v45;
}
goto LABEL_90;
}
v27 = (int *)a1;
v39 = v5;
v20 = -1616260743;
v22 = __OFSUB__(v5, 256);
v21 = v5 - 256 < 0;
v17 = -166592410;
LABEL_100:
if ( v21 ^ v22 )
v17 = v20;
if ( v17 > 307379756 )
goto LABEL_91;
}
if ( v17 == -1616260743 )
break;
if ( v17 == -2078856237 )
{
v4 = a4;
v6 = -444507636;
v5 = 1660167534;
a2 = 0x1000000;
v25 = a4 < 0;
v16 = a4 < 0;
a3 = v35;
a1 = v36;
LABEL_119:
if ( v16 )
v6 = v5;
if ( v6 > -444507637 )
goto LABEL_6;
goto LABEL_69;
}
}
v20 = 307379757;
v46 = (int)&v40[v39];
v23 = v40[v39];
v22 = __OFSUB__(v23, *v27);
v21 = v23 - *v27 < 0;
a2 = (int)v27;
v17 = 385223167;
goto LABEL_100;
}
switch ( v6 )
{
case -1744686325:
v5 = v43;
v6 = 783469431;
v30 = v52[v43];
break;
case -1735363298:
v5 = v26 - 2;
v41 = v26 - 2;
v29 = 0x1000000;
v6 = (((v26 - 2) >> 31) & 0xBF92E851) + 86368826;
if ( v6 <= -444507637 )
goto LABEL_69;
break;
case -1538876337:
v5 = v42;
v6 = 514714397;
v28 = v52[v42];
goto LABEL_6;
default:
LABEL_5:
if ( v6 <= -444507637 )
goto LABEL_69;
break;
}
}
if ( v6 <= -1382112004 )
break;
if ( v6 == -1382112003 )
{
a3 = v33;
v6 = -1534202243;
goto LABEL_69;
}
if ( v6 != -994522485 )
{
if ( v6 == -751818089 )
{
v16 = v25;
v6 = -1514475565;
v5 = -1382112003;
v33 = 0x1000000;
goto LABEL_119;
}
goto LABEL_5;
}
v49 = v29 + 1;
v5 = v26 - 5;
v42 = v26 - 5;
v28 = 0x1000000;
v6 = (((v26 - 5) >> 31) & 0x7A674ECE) - 1538876337;
if ( v6 <= -444507637 )
goto LABEL_69;
}
if ( v6 == -1514475565 )
{
v6 = -1382112003;
v33 = v52[v4];
goto LABEL_69;
}
if ( v6 == -1415659758 )
goto LABEL_4;
if ( v6 != -1534202243 )
goto LABEL_5;
return a3;
}
这种程序用黑盒测试就在好不过了。
注入程序 hook.c
#include <windows.h>
#include <stdio.h>
typedef int (*FUNC)(int);
void hook()
{
FUNC ptr_func;
int i;
// 获得基地址
uintptr_t image_base = (uintptr_t)GetModuleHandle(NULL);
ptr_func = (FUNC)(image_base + 0x1000);
for (i = 0; i < 256; i++)
{
printf("[%d, %d],\n", i, ptr_func(i));
}
}
BOOL WINAPI DllMain(
_In_ HINSTANCE hinstDLL, // 指向自身的句柄
_In_ DWORD fdwReason, // 调用原因
_In_ LPVOID lpvReserved // 隐式加载和显式加载
)
{
puts("hook start");
hook();
return TRUE;
}
DllMain
跟exe有个main或者WinMain入口函数一样,DLL也有一个入口函数,就是就是DllMain。在载入Dll的时候,会先调用DllMain
进行初始化。
之后用msvc
的编译器进行编译,命令如下:
cl /LD hook.c
注入
可以到网上去下载注入工具进行dll注入,下面就是注入后的结果。
D:\test>crackme_dll.exe
Input your flag:
hook start
[0, 0],
[1, 1],
[2, -1],
[3, -1],
[4, 2],
[5, -1],
[6, -1],
[7, -1],
[8, -1],
[9, 3],
[10, -1],
[11, -1],
[12, -1],
[13, -1],
[14, -1],
[15, -1],
[16, 4],
[17, -1],
[18, -1],
[19, -1],
[20, -1],
[21, -1],
[22, -1],
[23, -1],
[24, -1],
[25, 5],
[26, -1],
[27, -1],
[28, -1],
[29, -1],
[30, -1],
[31, -1],
[32, 6],
[33, -1],
[34, -1],
[35, -1],
[36, -1],
[37, -1],
[38, -1],
[39, 7],
[40, -1],
[41, -1],
[42, -1],
[43, -1],
[44, -1],
[45, -1],
[46, 8],
[47, -1],
[48, -1],
[49, -1],
[50, -1],
[51, -1],
[52, -1],
[53, 9],
[54, -1],
[55, -1],
[56, -1],
[57, -1],
[58, -1],
[59, -1],
[60, 10],
[61, -1],
[62, -1],
[63, -1],
[64, -1],
[65, -1],
[66, -1],
[67, 11],
[68, -1],
[69, -1],
[70, -1],
[71, -1],
[72, -1],
[73, -1],
[74, 12],
[75, -1],
[76, -1],
[77, -1],
[78, -1],
[79, -1],
[80, -1],
[81, 13],
[82, -1],
[83, -1],
[84, -1],
[85, -1],
[86, -1],
[87, -1],
[88, 14],
[89, -1],
[90, -1],
[91, -1],
[92, -1],
[93, -1],
[94, -1],
[95, 15],
[96, -1],
[97, -1],
[98, -1],
[99, -1],
[100, -1],
[101, -1],
[102, 16],
[103, -1],
[104, -1],
[105, -1],
[106, -1],
[107, -1],
[108, -1],
[109, 17],
[110, -1],
[111, -1],
[112, -1],
[113, -1],
[114, -1],
[115, -1],
[116, 18],
[117, -1],
[118, -1],
[119, -1],
[120, -1],
[121, -1],
[122, -1],
[123, 19],
[124, -1],
[125, -1],
[126, -1],
[127, -1],
[128, -1],
[129, -1],
[130, 20],
[131, -1],
[132, -1],
[133, -1],
[134, -1],
[135, -1],
[136, -1],
[137, 21],
[138, -1],
[139, -1],
[140, -1],
[141, -1],
[142, -1],
[143, -1],
[144, 22],
[145, -1],
[146, -1],
[147, -1],
[148, -1],
[149, -1],
[150, -1],
[151, 23],
[152, -1],
[153, -1],
[154, -1],
[155, -1],
[156, -1],
[157, -1],
[158, 24],
[159, -1],
[160, -1],
[161, -1],
[162, -1],
[163, -1],
[164, -1],
[165, 25],
[166, -1],
[167, -1],
[168, -1],
[169, -1],
[170, -1],
[171, -1],
[172, 26],
[173, -1],
[174, -1],
[175, -1],
[176, -1],
[177, -1],
[178, -1],
[179, 27],
[180, -1],
[181, -1],
[182, -1],
[183, -1],
[184, -1],
[185, -1],
[186, 28],
[187, -1],
[188, -1],
[189, -1],
[190, -1],
[191, -1],
[192, -1],
[193, 29],
[194, -1],
[195, -1],
[196, -1],
[197, -1],
[198, -1],
[199, -1],
[200, 30],
[201, -1],
[202, -1],
[203, -1],
[204, -1],
[205, -1],
[206, -1],
[207, 31],
[208, -1],
[209, -1],
[210, -1],
[211, -1],
[212, -1],
[213, -1],
[214, 32],
[215, -1],
[216, -1],
[217, -1],
[218, -1],
[219, -1],
[220, -1],
[221, 33],
[222, -1],
[223, -1],
[224, -1],
[225, -1],
[226, -1],
[227, -1],
[228, 34],
[229, -1],
[230, -1],
[231, -1],
[232, -1],
[233, -1],
[234, -1],
[235, 35],
[236, -1],
[237, -1],
[238, -1],
[239, -1],
[240, -1],
[241, -1],
[242, 36],
[243, -1],
[244, -1],
[245, -1],
[246, -1],
[247, -1],
[248, -1],
[249, 37],
[250, -1],
[251, -1],
[252, -1],
[253, -1],
[254, -1],
[255, -1],
然后我们获得了这张表,在用这张表进行逆推,即可得到正确的输入9e588220107b7b271019430020
。
运行实例
D:\test>crackme_dll.exe
Input your flag:
9e588220107b7b271019430020
Success
Linux 动态库劫持
_init
和Windows的WinMain入口函数一样,Linux的动态库也有一个入口函数,就是_init
。在载入so文件的时候,会先调用_init
进行初始化。
用gcc
编译器进行编译,命令如下:
gcc -fPIC -shared hook2.c -c -o hook2.o
ld -shared -ldl hook2.o -o hook2.so
例子 hook2.c
#include <stdio.h>
#include <dlfcn.h>
void _init()
{
void *image_base = *(void **)dlopen(NULL, 1);
printf("Image base: %p\n", image_base);
}
下面是运行的实例:
ex@Ex:~/test$gcc -fPIC -shared hook2.c -c -o hook2.o
ex@Ex:~/test$ ld -shared -ldl hook2.o -o hook2.so
ex@Ex:~/test$ cat main.c
#include <stdio.h>
int main()
{
puts("hello world");
return 0;
}
ex@Ex:~/test$ gcc main.c -o main
ex@Ex:~/test$ ./main
hello world
ex@Ex:~/test$ LD_PRELOAD=./hook2.so ./main
Image base: 0x55f1063cb000
hello world
ex@Ex:~/test$
源码
obfuscator.c
// clang -mllvm -fla -O3
#define MAX 0x1000000
#define ARRAY(array, index) ((index) < 0 ? MAX : array[(index)])
inline static int get_min(int a, int b, int c)
{
if (a > b)
{
if (b > c)
{
return c;
}
else
{
return b;
}
}
else
{
if (a > c)
{
return c;
}
else
{
return a;
}
}
}
inline static void sort(int *array, int n)
{
int i, ii, *min, temp;
for (i = 0; i < n; i++)
{
min = &array[i];
for (ii = i + 1; ii < n; ii++)
{
if (array[ii] < *min)
{
min = &array[ii];
}
}
temp = array[i];
array[i] = *min;
*min = temp;
}
}
int table(int n)
{
int array[256], i;
if (n >= 256)
{
return -1;
}
array[0] = 0;
for (i = 1; i < 256; i++)
{
array[i] = get_min(ARRAY(array, i - 2) + 1, ARRAY(array, i - 5) + 1, ARRAY(array, i - 7) + 1);
}
sort(array, 256);
if (ARRAY(array, n) < MAX && (ARRAY(array, n - 1) != ARRAY(array, n)))
{
return ARRAY(array, n);
}
else
{
return -1;
}
}
这是一个动态规划算法的改动版本,如果不知道具体用途的话是很难猜测其功能的。
main.c
#include <stdio.h>
#include <string.h>
extern int table(int n);
int str_to_hex(char *str)
{
char high = 0, low = 0, i;
if (strlen(str) % 2 != 0)
{
return 0;
}
for (i = 0; str[i]; i += 2)
{
if (str[i] >= '0' && str[i] <= '9')
{
high = str[i] - '0';
}
else if (str[i] >= 'a' && str[i] <= 'f')
{
high = str[i] - 'a' + 10;
}
else
{
return 0;
}
if (str[i + 1] >= '0' && str[i + 1] <= '9')
{
low = str[i + 1] - '0';
}
else if (str[i + 1] >= 'a' && str[i + 1] <= 'f')
{
low = str[i + 1] - 'a' + 10;
}
else
{
return 0;
}
str[i / 2] = ((high & 0x0f) << 4 | (low & 0x0f));
}
str[i / 2] = '\0';
return 1;
}
int main()
{
unsigned char str[32];
char key[] = "yougettheflag";
int i;
puts("Input your flag:");
scanf("%30s", str);
if (str_to_hex(str) == 1)
{
for (i = 0; i < strlen(key); i++)
{
if (table(str[i]) != key[i] - 'a')
{
puts("Failed");
return 0;
}
}
puts("Success");
}
else
{
puts("Failed");
}
return 0;
}
总结
方法虽然简单,却很实用。