题目和源代码

 

在找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获取了一个地址,该地址存储的就是解码后的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;
}
Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐