[原創]全國******2012功能破解

仙果發表於2015-07-31

  • 題記:

  • 本來接到這個活,我是拒絕的,原因一是跟自己的工作實在是不相干,之二是程式設計基礎差功能實現差。奈何實在是受不了人情,就答應下來把這個活幹完,
    加之本身工作忙,只能偷週末時間來完成,斷斷續續弄了幾個月的時間來完成,也算是重新整理了自己拖延症的記錄。
    文章之中的口水話太多,各位請輕拍。最近受打擊比較大,還請大家饒恕!

  • 測試環境&工具使用

  • 作業系統:
    Windows XP SP3 _Cn 虛擬機器
    工具:
    IDA 、dede、010editor、OD、Windbg、notepad++、PEID

  • 查殼&脫殼

  • PEID查殼,發現是最簡單的UPX殼,隨便有點基礎的就能夠破解之。使用方法是堆疊ESP跳轉法
    [原創]全國******2012功能破解
    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天的試用期,剩餘幾天的說話會有如下的提示:
    [原創]全國******2012功能破解
    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

    都是能夠透過頁面來得到,在每條資訊上雙擊能夠檢視詳細資訊,如圖:
    [原創]全國******2012功能破解

    • 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 找到了資料包的解密函式,把演算法逆向出來之後,就可以直接透過擷取網路資料包來解析貨源資料,比目前採用的方式要友好的多。

  • 總結

  • 前前後後花費了幾個月的時間來做這個事情,最終的採用了非常笨的方法來實現既定功能。雖說解決了實際問題,但是畢竟是非常不完美的解決方案,
    於己來說,心理那道坎實在是過不去。在此記錄一下,以後能力提高了一定要找到更好的解決方案。
    上傳的附件:

    相關文章