[原创]全国******2012功能破解

发布者:仙果
发布于:2015-07-31 22:25

  • 题记:

  • 本来接到这个活,我是拒绝的,原因一是跟自己的工作实在是不相干,之二是编程基础差功能实现差。奈何实在是受不了人情,就答应下来把这个活干完,
    加之本身工作忙,只能偷周末时间来完成,断断续续弄了几个月的时间来完成,也算是刷新了自己拖延症的记录。
    文章之中的口水话太多,各位请轻拍。最近受打击比较大,还请大家饶恕!

  • 测试环境&工具使用

  • 操作系统:
    Windows XP SP3 _Cn 虚拟机
    工具:
    IDA 、dede、010editor、OD、Windbg、notepad++、PEID

  • 查壳&脱壳

  • PEID查壳,发现是最简单的UPX壳,随便有点基础的就能够破解之。使用方法是堆栈ESP跳转法

    00701C60 >pushad
    00701C61  mov esi,568881.00603000    ;执行到此步,寄存器窗口,esp指针数据窗口跟随,接着下dword硬件访问断点
    00701C66  lea edi,dword ptr ds:[esi-0x202000]
    00701C6C  push edi                                 ; 568881.005B7C80
    00701C6D  mov ebp,esp
    00701C6F  lea ebx,dword ptr ss:[esp-0x3E80]


    断点之后F9运行,就会触发硬件断点,如下:
    007027EC  lea eax,dword ptr ss:[esp-0x80]    ;断在此处
    007027F0  push 0x0
    007027F2  cmp esp,eax
    007027F4  jnz short 568881.007027F0
    007027F6  sub esp,-0x80
    007027F9  jmp 568881.005E905C                   ;远跳即为OEP
    007027FE  add byte ptr ds:[eax],al
    00702800  sbb byte ptr ds:[eax],ch
    00702802  jo short 568881.00702804
    00702804  pop eax                                  ; kernel32.7C817067


    跳转之后就可以使用OD自带的脱壳插件进行脱壳,脱壳之后发现使用的是delphi编写
    Borland Delphi 6.0 - 7.0

    完成程序脱壳,接下来就需要进行实际数据的获取

  • 功能性限制破解


    • 1. 会员试用过期破解

    电脑第一次注册可以有30天的免费试用时间,过期之后,存在用户服务到期的提示:
    ---------------------------
    警告
    ---------------------------
    登录失败
    用户服务到期.
    ---------------------------
    确定   
    ---------------------------

    用户到期猜测是在服务器端进行判断的,所以选择重新注册一个账户,点击注册时会有如下提示:
    ---------------------------
    提示
    ---------------------------
    此电脑已经注册过会员号,请与当地服务商联系!总部客服电话:(86)0755-83485277 83485279
    ---------------------------
    确定   
    ---------------------------

    已经注册过会员号,肯定是本地提交判断的,OD载入程序,跟踪下断点:
    0045F894    FF93 10010000   call dword ptr ds:[ebx+0x110] ; 11.0058AB08 此函数中弹出警告框
    0045F89A    5B              pop ebx                       ; 00CFBD00

    F7跟入这个函数:
    0058ABEB   je short 11.0058ABF2
    0058ABED   sub eax,0x4
    0058ABF0   mov eax,dword ptr ds:[eax]
    0058ABF2   test eax,eax            ;判断是否注册过会员账号
    0058ABF4   jle short 11.0058AC10   ;小于则跳转,必须使其跳转才能跳过验证,直接暴力改为 jne jle->jne
    0058ABF6   lea edx,dword ptr ss:[ebp-0xC]
    0058ABF9   mov eax,11.0058ACE4
    0058ABFE   call 11.004993F8
    0058AC03   mov eax,dword ptr ss:[ebp-0xC]
    0058AC06   call 11.00496CE4
    0058AC0B   jmp 11.0058ACB8
    0058AC10   xor ecx,ecx
    0058AC12   mov dl,0x1
    0058AC14   mov eax,dword ptr ds:[0x58A02C]
    0058AC19   call 11.004E2EA0


    0058ABF2处代码修改完成之后,就能够点击注册,可以写入账号密码、昵称之类,点击注册,接着会有提示:
    ---------------------------
    提示
    ---------------------------
    对不起,系统检测到本电脑已有用户注册过!A47014B09DEC2C3C6FCCF840B5A89840
    ---------------------------
    确定   
    ---------------------------

    经过一番调试后发现 A47014B09DEC2C3C6FCCF840B5A89840是通过一个值计算出来的,具体计算过程未分析,约束条件是由一个值来决定:
    005891F6  mov eax,dword ptr ss:[ebp-0xC4]
    005891FC  lea edx,dword ptr ss:[ebp-0xC0]
    00589202  call 11.0049CC98
    00589207  mov ecx,dword ptr ss:[ebp-0xC0]
    0058920D  lea eax,dword ptr ss:[ebp-0xBC]          ; CPU_ID
    EAX 01277878 ASCII "00000000000000000001"
    ECX 01277848
    EDX 01277120
    EBX 012A9CC0
    ESP 0012EFA8
    EBP 0012F108
    ESI 00D24600
    EDI 0012F204
    EIP 005891FC 11.005891FC

    eax的值为一个比较重要的计算因子,值不同,最后计算出来的CUP_ID也不同,程序依此与服务器上存储的值进行判断,是否重复注册!修改eax的值,为任意值如:00000000000000000003,即可完成注册:
    ---------------------------
    提示
    ---------------------------
    注册成功,请与当地服务商联系.
    天津: 深圳:0755-83485277 83485279
    ---------------------------
    确定   
    ---------------------------


    • 2. 破解服务到期提示


    新注册的一个账号会有30天的试用期,剩余几天的说话会有如下的提示:

    0D中采用F12断点法很容易就定位到了弹框点:
    0047C562  push ebp
    0047C563  push 11.0047C5E4
    0047C568  push dword ptr fs:[ecx]
    0047C56B  mov dword ptr fs:[ecx],esp
    0047C56E  push esi
    0047C56F  mov eax,dword ptr ss:[ebp-0x8]           ; 11.00496D3C
    0047C572  push eax                                 ; 11.00496D3C
    0047C573  push edi
    0047C574  push ebx
    0047C575  call <jmp.&user32.MessageBoxA>    ;在此处弹出服务提示

    堆栈回溯如下:
    0012F8BC   001F019C  |hOwner = 001F019C ('全国物流信息网大件通2012(1128)',class='jHi1bbnVbbbbFRWcQxwaXj46ltV')
    0012F8C0   00DBE778  |Text = "您的服务还有3天到期,请尽快续费!"
    0012F8C4   00496D3C  |Title = "提示"
    0012F8C8   00000040  \Style = MB_OK|MB_ICONASTERISK|MB_APPLMODAL
    0012F8CC   0012F948  指向下一个 SEH 记录的指针
    0012F8D0   0047C5E4  SE处理程序

    找到对应的汇编代码,前后查找发现,无法绕过弹框提示,干脆暴力一点,直接暴力修改汇编代码,然后保存:
    0047C56F  mov eax,dword ptr ss:[ebp-0x8]           ; 11.00496D3C
    0047C572  xor eax,eax                              ; 11.00496D3C
    0047C574  mov eax,0x1
    0047C579  nop
    0047C57A  mov dword ptr ss:[ebp-0xC],eax           ; 11.00496D3C
    0047C57D  xor eax,eax  


  • 分析过程记录


    • 1. 基本分析

    首先考虑到了需求,目的是获取到货源信息,这个信息包括:货源信息、电话、货源信息的发布时间,

    示例如下:
    货源:
    抚顺 天水 一台50装载机
    电话:
    189 3217 7126
    时间:
    10:55

    都是能够通过页面来得到,在每条信息上双击能够查看详细信息,如图:


    • 2. 查找数据处理过程

    分析过程中遇到了难点就是如何枚举获取到的数据,从程序的数据处理逻辑来说,所有数据都是从网络中获取并展示到页面中,
    通过wireshark抓包,没有得到比较有用的数据,推断是数据是经过加密处理,后面分析也验证了推断。
    使用Delphi的反编译工具dede对脱壳后的程序进行反编译操作,发现程序使用了web引擎来实现货源数据的获取和更新。
    而web方面正是直接的弱项,实在是没有什么比较的方法,只能硬啃!大悲剧!!!


    • 1. 查找货源信息的突破口


    OD在数据搜索方面的确不如windbg好使,此后的分析过程全部使用的是windbg
    既然是货源信息,就以一条信息作为一个突破口,进而得到全部信息。
    以页面的显示的货源进行搜索,可以得到以下结果:
    0:002> s -u 00000000 L7ffffff "家到衡阳220"
    001b049c  5bb6 5230 8861 9633 0032 0032 0030 6316  .[0Ra.3.2.2.0..c
    001b25b4  5bb6 5230 8861 9633 0032 0032 0030 6316  .[0Ra.3.2.2.0..c
    001d52c4  5bb6 5230 8861 9633 0032 0032 0030 6316  .[0Ra.3.2.2.0..c

    能够在内存中搜索到相应的数据,
    下内存读写断点:
    ba w1 001c6eac

    继续运行,断点会断在:
    770f4b7a 57              push    edi
    770f4b7b 8bcb            mov     ecx,ebx
    770f4b7d 8bd1            mov     edx,ecx
    770f4b7f c1e902          shr     ecx,2
    770f4b82 8bf8            mov     edi,eax
    770f4b84 f3a5            rep movs dword ptr es:[edi],dword ptr [esi]    ;断在此处,进行的是memcpy 操作
    770f4b86 8bca            mov     ecx,edx
    770f4b88 83e103          and     ecx,3
    770f4b8b f3a4            rep movs byte ptr es:[edi],byte ptr [esi]
    770f4b8d 5f              pop     edi
    770f4b8e 6683240300      and     word ptr [ebx+eax],0
    770f4b93 5b              pop     ebx
    770f4b94 5e              pop     esi
    770f4b95 5d              pop     eb

    程序在进行
    memcpy
    操作,查找源数据如下:
    0:000> du esi
    0012e840  "衡阳220挖机马上装 绍兴到福州3吨挖机 昆明到郑州75挖机 黄"
    0012e880  "山到福清2翻斗 上海到勐腊200挖机 包头到集宁50铲矒"

    确认是在处理货源信息,就这样一点一点的向上追溯,最终能够跟踪到数据解密函数
    DLS0:00488FB4 ; 数据包-货源的解密函数
    DLS0:00488FB4
    DLS0:00488FB4 sub_488FB4      proc near               ; CODE XREF: sub_489678+1CEp
    DLS0:00488FB4
    DLS0:00488FB4 var_18          = word ptr -18h
    DLS0:00488FB4 var_16          = byte ptr -16h
    DLS0:00488FB4 var_14          = dword ptr -14h
    DLS0:00488FB4
    DLS0:00488FB4                 push    ebx
    DLS0:00488FB5                 push    esi
    DLS0:00488FB6                 push    edi
    DLS0:00488FB7                 push    ebp
    DLS0:00488FB8                 add     esp, 0FFFFFFF8h
    DLS0:00488FBB                 mov     ebp, edx
    DLS0:00488FBD                 mov     eax, [ebp+0]
    DLS0:00488FC0                 mov     [esp+18h+var_14], eax
    DLS0:00488FC4                 mov     eax, [esp+18h+var_14]

    解密完成的数据是以类似GB2312格式存放,直接使用windbg的da命令是看不到真实内容,保存到硬盘上之后才能看到,使用windbg命令:
    .writemem c:\22.log 00ca29d8  L 100

    之后通过MultiByteToWideCharAPI转换成unicode格式的数据,显示到程序的主页面中。
    货源的信息找到了,接下来是电话和时间,这两个通过观察堆栈中数据指针,发现多数都会指向特定的数据,
    其中就有感兴趣的数据。如下:
    00012f83c  00000001
    0012f840  00000001
    0012f844  0012f8a4                ;指向 货源信息指针
    0012f848  001d7b7c                ;unicode 格式的货源信息
                                ;001d7b7c  "大家好,本人专业做导航,河北河南省,进山东,进山西,进湖北,进安"
                                ;001d7bbc  "徽,进北京,进天津,有需要联系电话 强子"
    0012f84c  ffffffff
    ...
    0012f8a4  001e4f3c        ; unicode 格式的货源电话号码    "Tel:13673343431"
    0012f8a8  001be994        ; unicode 格式的货源时间         "[21:24]"
    0012f8ac  01357838        ; accii 格式的货源时间        "[21:24]"
    0012f8b0  00d552f8        ; accii 格式的货源电话号码    "Tel:13673343431"
    0012f8b4  013581c8
    ...
    0012f8c8 00c929e8 
    0012f8cc 013567b8         ;accii 格式的全国信息 "全国(0755)"
    0012f8d0 001b7754         ;unicode 格式的全国信息 "全国(0755)"
    0012f8d4 00000000 
    0012f8d8 00000000 


    • 2. 编程获取货源信息

    之前的分析过程中,获取到了足够多的信息,但是对如何编程获取货源相关的信息只能提供基础的帮助,
    如何来获取成为摆在面前的难题。
    考虑了多种方法,包括写一个调试器加载主程序,在解密函数运行后下断,获取寄存器信息,读取内存,
    数据就能够读出来,直接输出到一个txt文档中即可。此方法确实可行,直接也尝试了论坛中开源的几个调试器,
    关键问题来了,自己编程实在太差。调试器代码编译通过没有任何问题,能够把主程序调试起来,但是断点设置之后,
    主程序每次运行的时候,断点的位置非常不固定,排错无力啊,找了几天也没有弄出一个所以然。最终果断放弃!
    最终采用了HOOK的方法,InLine HOOK掉某一个指令,然后读取esp寄存器,通过寄存器偏移来获取到货源
    相关信息。
    HOOK那条指令却是一个问题,找啊找,找啊找,最终找到一个非常简单的函数,如下:
    DLS0:0052A588 sub_52A588      proc near               ; CODE XREF: sub_52B3A8+D3p
    DLS0:0052A588                 mov     eax, [eax+1F4h]
    DLS0:0052A58E                 retn
    DLS0:0052A58E sub_52A588      endp

    取值后返回,足够简单而且也在程序的执行流程中,能够通过堆栈偏移来获取信息。
    Hook代码来自【原创】RING3代码HOOK的原理实现 (学习笔记1),在此非常感谢,
    主要代码如下:
    http://bbs.pediy.com/showthread.php?t=78418
    //首先通过FindWindow API获取到窗口句柄,进而获取到进程句柄,接着采用进程注入的方式,把hook的dll注入到主程序中。
    void InjectDLL()
    {
    DWORD pid = 0;
    HANDLE hProcess = NULL;
    DWORD Process_ID;
    LPDWORD AddressDw= NULL;
    DWORD dyWriteNumber = NULL;
    HANDLE ThreadHandle = NULL;
     //获取窗口句柄
    char *dllpath;
    //char *DllPath;
    dllpath = new char[MAX_PATH];
    GetCurrentDirectoryA(MAX_PATH,dllpath);
    strcat(dllpath,"\\ReadMemory.dll");
    HWND DjtH = FindWindow(NULL,_T("全国物流信息网大件通2012(1128)"));
    if (DjtH !=0)
    {
        //获取窗口进程的Pid
        GetWindowThreadProcessId(DjtH,&Process_ID);
        if (Process_ID !=0)
        {
            printf("获取到了窗口进程的PID\r\n");
        }
    }
    //根据窗口句柄获取进程pid
    //GetWindowThreadProcessId(DjtH,&pid);
    //printf ("获取窗口句柄报错: %d\r\n",GetLastError() ); //能够获取到
    //if(pid = NULL)
    //{
    //    printf ("获取程序句柄出错 %s, %d\r\n",pid,GetLastError());
    //    return;
    //}
    //通过进程pid,获取进程句柄
    hProcess = OpenProcess(PROCESS_ALL_ACCESS,false,Process_ID);
    if(hProcess ==NULL)
    {
        printf ("获取进程句柄出错 %d\r\n",GetLastError());
        return;
    }
    //分配内存空间
    AddressDw=(LPDWORD)VirtualAllocEx(hProcess,NULL,256,MEM_COMMIT,PAGE_READWRITE);
    if(AddressDw == NULL)
    {
        printf ("分配进程空间出错 %d\r\n",GetLastError());
        return;
    }
    //写入DLL全路径
    WriteProcessMemory(hProcess,AddressDw,dllpath,strlen(dllpath)+1,&dyWriteNumber);
    if(dyWriteNumber < strlen(dllpath))
    {
        printf ("写入DLL全路径出错 %s,%d\r\n",hProcess,GetLastError());
        return;
    }
    ThreadHandle = CreateRemoteThread(hProcess,NULL,NULL,(LPTHREAD_START_ROUTINE)LoadLibraryA,AddressDw,NULL,NULL);
    WaitForSingleObject(ThreadHandle,0xfffffff);//等待进程执行成功
    CloseHandle(ThreadHandle);
    VirtualFreeEx(hProcess,AddressDw,256,MEM_COMMIT);
    CloseHandle(hProcess);
    }

    注入代码之后,接着就会进行代码hook,获取货源信息并保存!
    #define oldfunction 0x0052A588;    //hook函数地址
    HANDLE hfile;    //货源记录文件句柄
    //两个hook函数
    bool AfxHookCode(void* TargetProc, void* NewProc,void ** l_OldProc, int bytescopy);
    bool AfxUnHookCode(void* TargetAddress, void * l_SavedCode, unsigned int len);
    unsigned int * OldProc;
    char oldinfo[0x200]={0};//上一条货物信息
     int myHookFunction()
    {
    //OldProc = (unsigned int *)0x0052A588;
    char *GodInfo;//货源信息
    char *GodTime;//货源时间
    char *GodTel;//货源电话
    DWORD dwWrite = 0;
    char *buf;
    //MessageBox(NULL,"I am in my Function","warnning2",SW_SHOW);
    __asm
    {
        //int 3
        pushad
        mov eax,DWORD ptr ds:[esp+2e4h]
        mov GodInfo,eax
        mov eax,DWORD ptr [esp + 2c8h]
        mov GodTime,eax
        mov eax,DWORD ptr [esp+2cch]
        mov GodTel,eax
        //mov p_eax,eax 
    }
    //for()
    /**
    打开货源记录文件
    **/
    hfile = CreateFile("godinfo.log",GENERIC_WRITE|GENERIC_READ,FILE_SHARE_WRITE|FILE_SHARE_READ,NULL,OPEN_ALWAYS,NULL,NULL);
    if (hfile == INVALID_HANDLE_VALUE)
    {
        MessageBox(NULL,"create log file error","warnning1",SW_SHOW);
        return FALSE;
    }
    //设置文件末尾并追加
    DWORD dwSize = GetFileSize (hfile, NULL) ; 
    SetFilePointer (hfile, 0, NULL, FILE_END);
    int godinfo_len = strlen(GodInfo);
    int godtime_len = strlen(GodTime);
    int GodTel_len = strlen(GodTel);
    buf = new char [godinfo_len+godtime_len+GodTel_len+5];
    ZeroMemory(buf,godinfo_len+godtime_len+GodTel_len+5);
    //存储格式  货源信息 货源电话 发布时间
    strcat(buf,GodInfo);
    strcat(buf," ");
    strcat(buf,GodTel);
    strcat(buf," ");
    strcat(buf,GodTime);
    strcat(buf,"\r\n");
    //strcat()
    //如果与上一条相同,则不进行追加
    if (strcmp(buf,oldinfo) != 0)
    {
    WriteFile(hfile,buf,strlen(buf),&dwWrite,NULL);
    memcpy(oldinfo,buf,strlen(buf));
    }
    delete buf;
    CloseHandle(hfile);
    //恢复堆栈并返回执行原来的函数
    __asm {
        popad
        add esp,20h
        lea ebp,DWORD ptr [esp+20ch]
        mov     eax, [eax+1F4h]        ;此处完全替代了hook函数的功能,然后返回,不用直接像inline hook 需要跳转到原始函数处进行执行。
        ret
    }
    }


    • 2. 遗留问题

    1 货源信息的更新必须要在页面中动几下鼠标,滚轮几下才会记录到文档中
    此问题的原因是hook函数的地方,必须要有操作才会执行,换一个hook函数就能解决
    数据去重
    2 获取到的数据是从堆栈上获取,这导致了会获取到很多重复的数据,如何进行数据去重,确实是一个难题。
    3 是否能够通过截取网络包来进行货源信息的获取
    4 找到了数据包的解密函数,把算法逆向出来之后,就可以直接通过截取网络数据包来解析货源数据,比目前采用的方式要友好的多。

  • 总结

  • 前前后后花费了几个月的时间来做这个事情,最终的采用了非常笨的方法来实现既定功能。虽说解决了实际问题,但是毕竟是非常不完美的解决方案,
    于己来说,心理那道坎实在是过不去。在此记录一下,以后能力提高了一定要找到更好的解决方案。
    上传的附件:

    声明:该文观点仅代表作者本人,转载请注明来自看雪