国科大软件安全漏洞分析与发现第一次作业key2和key3
题目和源代码在找key2和key3之前存在几个困惑在IDA中Strings window出现的“?decode@@YAPADPADH@Z”字符串到底表示什么含义,是不是我们要去破解的加密密码?第一个密码输入之后生成的DllU.dll文件和Dll2.dll文件是什么关系,是否还需要使用Dll2.dll文件?在homework1.exe中的main函数是如何调用dll文件的?针对以上几个问题,开
题目和源代码
在找key2和key3之前存在几个困惑
- 在IDA中Strings window出现的“?decode@@YAPADPADH@Z”字符串到底表示什么含义,是不是我们要去破解的加密密码?
- 第一个密码输入之后生成的DllU.dll文件和Dll2.dll文件是什么关系,是否还需要使用Dll2.dll文件?
- 在homework1.exe中的main函数是如何调用dll文件的?
针对以上几个问题,开始进行下一步的探索,进而一个个得到了解决
1、首先是第一个问题,根据该标识查到了叫做调用约定的内容,根据带你玩转Visual Studio——调用约定与(动态)库这篇文章,进而得出该字符串的含义为表示一个函数,并不是要破解的加密密码,而表示char __cdecl * decode(char *tem1 , int tem2)这样的一个函数声明,具体原因如下图
2、然后是第二个问题,第二个问题根据从homework1.exe反编译出来的main函数代码中的一段可以看出来是Dll2.dll生成了DllU.dll并且之后在解密过程中使用的是DllU.dll,如下代码,所以之后并不需要使用Dll2.dll,该代码在反编译函数代码文件夹下的homework.c。题目和源代码
v16 = fopen("Dll2.dll", "rb");//读取Dll2.dll文件
fopen("DllU.dll", "wb");//写DllU.dll文件
if ( v16 )
sub_401050();
/*sub_401050()的两个参数是文件指针,
在这个函数里实现了将Dll2.dll解密成
DllU.dll的过程,也就是说之后的key2
和key3解密只需要DllU.dll就可以了*/
v17 = LoadLibraryA("DllU.dll");//加载dll
3、针对第三个问题,如何调用dll,其实在反编译的代码中可以看出来主要使用了LoadLibrary和GetProcAddress函数,但是反编译的代码并不能使用,所以我就实践了一下,自己生成test1.dll并且将其放到test2的main.c文件目录中,在程序中尝试调用,最终了解了整个的过程,该工程在test1文件夹下面,题目和源代码,主要代码如下
test1的myAPI.h
#ifndef _DLL_API
#define _DLL_API _declspec(dllexport)
#else
#define _DLL_API _declspec(dllimport)
#endif
_DLL_API int printMax(int&a,int&b);
test1的main.cpp
#include "myAPI.h"
#include <iostream>
using namespace std;
int printMax(int&a,int&b)
{
cout<<"Among ("<<a<<","<<b<<"), the Max Number is "<<(a>b?a:b)<<endl;
return a;
}
test2的main.cpp
#include <iostream>
#include <windows.h>
#include <strsafe.h>
using namespace std;
typedef void(*FUNA)(int&,int&);//定义指向和DLL中相同的函数原型指针
void ErrorExit(LPTSTR lpszFunction)
{
// Retrieve the system error message for the last-error code
LPVOID lpMsgBuf;
LPVOID lpDisplayBuf;
DWORD dw = GetLastError();
FormatMessage(
FORMAT_MESSAGE_ALLOCATE_BUFFER |
FORMAT_MESSAGE_FROM_SYSTEM |
FORMAT_MESSAGE_IGNORE_INSERTS,
NULL,
dw,
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
(LPTSTR) &lpMsgBuf,
0, NULL );
// Display the error message and exit the process
lpDisplayBuf = (LPVOID)LocalAlloc(LMEM_ZEROINIT,
(lstrlen((LPCTSTR)lpMsgBuf) + lstrlen((LPCTSTR)lpszFunction) + 40) * sizeof(TCHAR));
StringCchPrintf((LPTSTR)lpDisplayBuf,
LocalSize(lpDisplayBuf) / sizeof(TCHAR),
TEXT("%s failed with error %d: %s"),
lpszFunction, dw, lpMsgBuf);
MessageBox(NULL, (LPCTSTR)lpDisplayBuf, TEXT("Error"), MB_OK);
LocalFree(lpMsgBuf);
LocalFree(lpDisplayBuf);
ExitProcess(dw);
}
int main() {
typedef int (*printMax)(int&a,int&b);
const char* dllName = "test1.dll";
const char* funName = "printMax";
int x(100),y(100);
HMODULE hDLL = LoadLibrary(L"test1.dll");//加载dll,将dll放在该工程根路径即可
if(hDLL != NULL)
{
//FUNA fp = FUNA(GetProcAddress(hDLL,"?printMax@@YAXAAH0@Z"));//获取导入到应用程序中的函数指针,根据方法名取得
//测试不好用啊。
printMax fp = printMax(GetProcAddress(hDLL,"?printMax@@YAHAAH0@Z"));
// FUNA fp = FUNA(GetProcAddress(hDLL,MAKEINTRESOURCE(1)));//根据直接使用DLL中函数出现的顺序号//测试ok
if(fp != NULL)
{
cout<<"Input 2 Numbers:";
cin>>x>>y;
cout<<fp(x,y)<<endl;
}
else
{
//ErrorExit(TEXT(""));
cout<<"Cannot Find Function : " <<funName<<endl;
}
FreeLibrary(hDLL);
}
else
{
cout<<"Cannot Find dll : "<<dllName<<endl;
}
return 1;
}
其中函数名为?printMax@@YAHAAH0@Z是由于调用约定导致的,不是简单地printMax,如果这里写
printMax fp = printMax(GetProcAddress(hDLL,"printMax"));`
fp就只是一个空指针,而如何获取printMax函数的调用约定名字呢?要通过VS自带工具dumpbin来获取,具体方法在VS自带工具:dumpbin的使用,使用过后就看到了如下的输出
发现了调用约定名字为?printMax@@YAHAAH0@Z的函数,在查看动态库包含哪些接口函数之前还遇到了一个坑,就是生成dll文件的时候,如果没有我加到文件里的myAPI.h这个头,只有main.cpp的话,dll文件里是不会包含所写的函数的,也就是只会列出来一些summary信息,我之前参考的 C++调用DLL方法文章后面的显式调用代码是有问题的,如果按照它的来写是不可能的出来结果的,这个坑请注意,也可以看这个解释dll的输出函数使用__stdcall调用约定后,客户端用GetProcAddress出现的问题!
在解决了以上三个问题之后,距离找到key2和key3拥有了一定得基础,然后需要思考另外两个问题
- decode函数声明为char __cdecl * decode(char *tem1 , int tem2),调用格式知道了,但是如何实现的key2和key3加密?
- 在key2和key3分别调用decode函数的时候tem1和tem2的值是多少?
如果解决了这两个问题那么也就能得到最终答案了
1、针对第一个问题,我们现在已知decode的反编译代码,同时知道了如何调用dll文件,那么就可以写一个程序去掉用DllU.dll中的decode函数输出返回值,并且阅读decode的反编译代码重写逻辑构造可以运行的c程序,将二者的输出结果进行比对,如果一致说明对于decode程序的理解是正确的,该工程在useDllU文件夹下面,题目和源代码
#include <iostream>
#include <windows.h>
#include <strsafe.h>
#include <stdlib.h>
using namespace std;
//typedef void(*FUNA)(int&,int&);//定义指向和DLL中相同的函数原型指针
char *__cdecl mydecode(char *a1, int a2);/*根据IDA中反编译出来的decode函数,自行理解并且写出的mydecode函数,实际调用与dll调用值基本一致*/
int main()
{
typedef char* (*pDecode)(char *a, int b);
const wchar_t* dllName = L"DllU.dll";
const char* funName = "?decode@@YAPADPADH@Z";
int y;
char *x;
x=(char *)malloc(11);
HMODULE hDLL = LoadLibrary(dllName);//加载dll,将dll放在该工程根路径即可
if(hDLL != NULL)
{
//FUNA fp = FUNA(GetProcAddress(hDLL,"?decode@@YAPADPADH@Z"));//获取导入到应用程序中的函数指针,根据方法名取得
//FUNA fp = FUNA(GetProcAddress(hDLL,MAKEINTRESOURCE(1)));//根据直接使用DLL中函数出现的顺序号//测试ok
pDecode fp = pDecode(GetProcAddress(hDLL,funName));//获取dll中函数指针
if(fp != NULL)
{
/*调用dll的函数*/
cout<<"Input 1 string(11bit) and 1 number:";
cin>>x>>y;
cout<<"dll中函数计算值为:"<<fp(x,y)<<endl;
/*自己恢复的decode函数*/
char *tem =(char *)malloc(11);
tem=mydecode(x,y);
cout<<"自己函数计算值为:"<<tem<<endl;
}
else
{
cout<<"Cannot Find Function : " <<funName<<endl;
}
FreeLibrary(hDLL);
}
else
{
cout<<"Cannot Find dll : "<<dllName<<endl;
}
return 1;
}
/*根据IDA中反编译出来的decode函数,
自行理解并且写出的mydecode函数,
实际调用与dll调用值基本一致*/
char *__cdecl mydecode(char *a1, int a2)
{
int i; // [sp+50h] [bp-Ch]@1
int v7=11; // [sp+58h] [bp-4h]@1
char *tem=(char *)malloc(11);
i = 0;
if ( a2 )
{
if ( a2 == 1 )
{
for ( i = 0; i < v7; ++i )
tem[i] = a1[i] ^ 0x77;
}
else if ( a2 == 2 )
{
for ( i = 0; i < v7; ++i )
{
tem[i] = (((a1[i] & 0xF0) >> 4) & 0xF) + 16 * (a1[i] & 0xF);
}
}
}
else
{
for ( i = 0; i < (signed int)v7; ++i )
a1[i] = a1[i] - 1;
}
return tem;
}
经过验证,key2的加密算法为
string = string ^ 0x77;
key3的加密算法为
string = (((string & 0xF0) >> 4) & 0xF) + 16 * (string & 0xF);
2、根据上述代码分析char __cdecl * decode(char *tem1 , int tem2)中tem2=1时是对key2加密,tem2=2时是对key3加密,那么接下来就是tem1的取值了,对于tem1的取值在homework1.exe反编译出来的main函数可以看到
v21 = ((int (__stdcall *)(_UNKNOWN *, signed int))v19)(&unk_40FA64, 1);
//&unk_40FA64是一个地址存储字符串,1是key2的加密方式,调用decode函数
v22 = (const char *)sub_401000(v21);
//该函数是根据int类型的v21获取了一个地址,该地址存储的就是解码后的key2
v23 = ((int (__stdcall *)(_UNKNOWN *, signed int))v20)(&unk_40FA70, 2);
//&unk_40FA64是一个地址存储字符串,2是key3的加密方式,调用decode函数
v30 = (const char *)sub_401000(v23);
//该函数是根据int类型的v21获取了一个地址,该地址存储的就是解码后的key3
所以值就存在了&unk_40FA64和&unk_40FA70中,但是这两个地址在IDA动态调试中会发生变化,而且其中也并没有存值,这个是让我非常不解的地方,没有办法只好进行动态调试,在调试到这两个地址的时候,忽然发现eax出现了有规律的句子,于是异常惊喜的记录了下来,如下图
在输入命令行中发现是正确答案,忽然觉得自己上面一大堆分析好像并没有什么用处,直接动态调试就好了嘛!!!!!
最后得到了正确的结果,但是那两个地址的值得变化是如何跳到这里来的还不是很明确,希望有了解的同学能告诉我原因~~,最后的最后,把所有的反编译的代码粘出来,供大家参考,是不能运行的哟~
/*homework1.exe中使用IDA反编译出来的main函数,无法调用*/
int __cdecl main(int argc, const char **argv, const char **envp)
{
void *v3; // eax@1
void *v4; // esi@1
char *v5; // ecx@1
signed int v6; // edi@1
const char *v7; // edi@3
signed int v8; // ecx@3
int v9; // edx@3
int v10; // esi@4
char v11; // bl@4
const char *v12; // eax@5
signed int v13; // ecx@5
char *v14; // ebx@5
const char *v15; // esi@5
FILE *v16; // edi@9
HMODULE v17; // eax@11
HMODULE v18; // edi@11
FARPROC v19; // eax@12
FARPROC v20; // esi@12
int v21; // eax@13
const char *v22; // edi@13
int v23; // eax@13
int v24; // esi@13
const char *v25; // ebx@13
int v26; // esi@13
const char *v27; // eax@13
DWORD v28; // eax@17
const char *v30; // [sp+Ch] [bp-2Ch]@13
HMODULE hLibModule; // [sp+10h] [bp-28h]@11
char v32[32]; // [sp+14h] [bp-24h]@5
v3 = malloc(0xBu);
v4 = v3;
*(_DWORD *)v3 = 0;
*((_DWORD *)v3 + 1) = 0;
*((_WORD *)v3 + 4) = 0;
*((_BYTE *)v3 + 10) = 0;
v5 = (char *)(&unk_40FA58 - (_UNKNOWN *)v3);
v6 = 11;
do
{
*(_BYTE *)v3 = *((_BYTE *)v3 + (_DWORD)v5) - 1;
v3 = (char *)v3 + 1;
--v6;
}
while ( v6 );
v7 = (const char *)malloc(0xCu);
v8 = 0;
v9 = (_BYTE *)v4 - v7;
do
{
v10 = (int)&v7[v8];
v11 = v8 * v8 + *(&v7[v8] + v9) * *(&v7[v8] + v9);
++v8;
*(_BYTE *)v10 = v11;
}
while ( v8 < 11 );
v7[v8] = 0;
printf("please input key1: ");
scanf("%30s", v32);
v12 = (const char *)malloc(0xCu);
v13 = 0;
v14 = (char *)(v32 - v12);
v15 = v12;
while ( 1 )
{
*v15 = v13 * v13;
*v15 = v13 * v13 + v15[(_DWORD)v14] * v15[(_DWORD)v14];
++v13;
++v15;
if ( v13 >= 11 )
break;
v14 = (char *)(v32 - v12);
}
v12[v13] = 0;
if ( !strcmp(v7, v12) )
{
v16 = fopen("Dll2.dll", "rb");//读取Dll2.dll文件
fopen("DllU.dll", "wb");//写DllU.dll文件
if ( v16 )
sub_401050();
/*sub_401050()的两个参数是文件指针,
在这个函数里实现了将Dll2.dll解密成
DllU.dll的过程,也就是说之后的key2
和key3解密只需要DllU.dll就可以了*/
v17 = LoadLibraryA("DllU.dll");//加载dll
v18 = v17;
hLibModule = v17;
if ( v17 )
{
v19 = GetProcAddress(v17, "?decode@@YAPADPADH@Z");
/*获取了类型为char* __cdecl decode(char *a, int b)的函数指针
decode函数里面有两个分支,分别是1和2,1是复杂计算,2是移位计算*/
v20 = v19;
if ( v19 )
{
v21 = ((int (__stdcall *)(_UNKNOWN *, signed int))v19)(&unk_40FA64, 1);
//&unk_40FA64是一个地址存储字符串,1是key2的加密方式,调用decode函数
v22 = (const char *)sub_401000(v21);
//该函数是根据int类型的v21获取了一个地址,该地址存储的就是解码后的key2
v23 = ((int (__stdcall *)(_UNKNOWN *, signed int))v20)(&unk_40FA70, 2);
//&unk_40FA64是一个地址存储字符串,2是key3的加密方式,调用decode函数
v30 = (const char *)sub_401000(v23);
//该函数是根据int类型的v21获取了一个地址,该地址存储的就是解码后的key3
printf("please input key2: ");
v24 = (int)malloc(0xBu);
scanf("%11s", v24);
v25 = (const char *)sub_401000(v24);
printf("please input key3: ");
v26 = (int)malloc(0xBu);
scanf("%11s", v26);
v27 = (const char *)sub_401000(v26);
if ( !strcmp(v22, v25) && !strcmp(v30, v27) )
{
printf("You Win!\n");
v18 = hLibModule;
}
else
{
printf("You Failed!\n");
v18 = hLibModule;
}
}
else
{
v28 = GetLastError();
printf("GetProcAddr failed %d 0x%x!\n", v28, 0);
system("pause");
}
FreeLibrary(v18);
remove("DllU.dll");
}
else
{
printf("Load dll failed!\n");
}
}
else
{
printf("key error!\n");
}
system("pause");
return 0;
}
/*DllU.dll中使用IDA反编译出来的decode函数,无法调用*/
char *__cdecl decode(char *a1, int a2)
{
char v3; // [sp+Ch] [bp-50h]@1
int v4; // [sp+4Ch] [bp-10h]@1
int i; // [sp+50h] [bp-Ch]@1
void *v6; // [sp+54h] [bp-8h]@1
size_t v7; // [sp+58h] [bp-4h]@1
memset(&v3, 0xCCu, 0x50u);
v7 = 11;
v6 = malloc(0xBu);
memset(v6, 0, v7);
i = 0;
v4 = a2;
if ( a2 )
{
if ( v4 == 1 )
{
for ( i = 0; i < (signed int)v7; ++i )
*((BYTE *)v6 + i) = a1[i] ^ 0x77;
}
else if ( v4 == 2 )
{
for ( i = 0; i < (signed int)v7; ++i )
*((BYTE *)v6 + i) = (((a1[i] & 0xF0) >> 4) & 0xF) + 16 * (a1[i] & 0xF);
}
}
else
{
for ( i = 0; i < (signed int)v7; ++i )
*((BYTE *)v6 + i) = a1[i] - 1;
}
return (char *)v6;
}
/*homework1.exe中使用IDA反编译的文件解密函数,无法调用*/
void __usercall sub_401050(FILE *a1@<ebx>, FILE *a2@<edi>)
{
size_t v2; // esi@2
signed int i; // eax@2
char v4[1024]; // [sp+0h] [bp-404h]@2
if ( a1 )
{
do
{
v2 = fread(v4, 1u, 0x400u, a2);
for ( i = 0; i < (signed int)v2; ++i )
--v4[i];
fwrite(v4, 1u, v2, a1);
}
while ( (signed int)v2 > 0 );
fclose(a2);
fclose(a1);
}
}
/*homework1.exe中使用IDA反编译的根据int获取字符串函数,无法调用*/
void *__cdecl sub_401000(int a1)
{
void *v1; // edi@1
signed int v2; // ecx@1
int v3; // esi@2
char v4; // bl@2
v1 = malloc(0xCu);
v2 = 0;
do
{
v3 = (int)((char *)v1 + v2);
v4 = v2*v2 + *((BYTE *)v1 + v2 + a1 - (DWORD)v1) * *((BYTE *)v1 + v2 + a1 - (DWORD)v1);
++v2;
*(BYTE *)v3 = v4;
}
while ( v2 < 11 );
*((BYTE *)v1 + v2) = 0;
return v1;
}
更多推荐
所有评论(0)