国科大软件安全漏洞分析与发现第一次作业key2和key3

来源:互联网 发布:egress软件 编辑:程序博客网 时间:2024/06/08 00:05

题目和源代码

 

在找key2和key3之前存在几个困惑

  1. 在IDA中Strings window出现的“?decode@@YAPADPADH@Z”字符串到底表示什么含义,是不是我们要去破解的加密密码?
  2. 第一个密码输入之后生成的DllU.dll文件和Dll2.dll文件是什么关系,是否还需要使用Dll2.dll文件?
  3. 在homework1.exe中的main函数是如何调用dll文件的?
     

针对以上几个问题,开始进行下一步的探索,进而一个个得到了解决

1、首先是第一个问题,根据该标识查到了叫做调用约定的内容,根据带你玩转Visual Studio——调用约定与(动态)库这篇文章,进而得出该字符串的含义为表示一个函数,并不是要破解的加密密码,而表示char __cdecl * decode(char *tem1 , int tem2)这样的一个函数声明,具体原因如下图

decode函数分析

 
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拥有了一定得基础,然后需要思考另外两个问题

  1. decode函数声明为char __cdecl * decode(char *tem1 , int tem2),调用格式知道了,但是如何实现的key2和key3加密?
  2. 在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获取了一个地址,该地址存储的就是解码后的key2v23 = ((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 0
原创粉丝点击