2011年9月16日星期五

[轉載]簡單的函數逆向(還原)


作 者: 天高 
時 間: 2011-09-02,01:15:29
鏈接: http://bbs.pediy.com/showthread.php?t=139643

看過論壇的一些關於逆向的貼子
說說我的看法:逆向工程並不是對代碼進行分析就算了,實際上要對工程進行一定的還原(源代碼形式),當然難度挺大,要對算法非常熟悉。要係統的數據結構非常熟悉,才有能力推還原工程的數據結構。

這裡僅對一個簡短的函數進行還原,當然並不可能還原為原作者源代碼,是還原為在函數的邏輯思想上最接近的形式。

代碼:
ntdll!memcpy_s:
00000000`77abee8c 48895c2408 mov qword ptr [rsp+8],rbx
00000000`77abee91 4889742410 mov qword ptr [rsp+10h],rsi
00000000`77abee96 57 push rdi
00000000`77abee97 4883ec30 sub rsp,30h
00000000`77abee9b 498bd9 mov rbx,r9
00000000`77abee9e 498bf0 mov rsi,r8
00000000`77abeea1 488bfa mov rdi,rdx
00000000`77abeea4 4d85c9 test r9,r9
00000000`77abeea7 7504 jne ntdll!memcpy_s+0x21 (00000000`77abeead)

ntdll!memcpy_s+0x1d:
00000000`77abeea9 33c0 xor eax,eax
00000000`77abeeab eb1c jmp ntdll!memcpy_s+0x3d (00000000`77abeec9)

ntdll!memcpy_s+0x21:
00000000`77abeead 4885c9 test rcx,rcx
00000000`77abeeb0 7527 jne ntdll!memcpy_s+0x4d (00000000`77abeed9)

ntdll!memcpy_s+0x26:
00000000`77abeeb2 48214c2420 and qword ptr [rsp+20h],rcx
00000000`77abeeb7 4533c9 xor r9d,r9d
00000000`77abeeba 4533c0 xor r8d,r8d
00000000`77abeebd 33d2 xor edx,edx
00000000`77abeebf e85c95ffff call ntdll!invalid_parameter (00000000`77ab8420)

ntdll!memcpy_s+0x38:
00000000`77abeec4 b816000000 mov eax,16h

ntdll!memcpy_s+0x3d:
00000000`77abeec9 488b5c2440 mov rbx,qword ptr [rsp+40h]
00000000`77abeece 488b742448 mov rsi,qword ptr [rsp+48h]
00000000`77abeed3 4883c430 add rsp,30h
00000000`77abeed7 5f pop rdi
00000000`77abeed8 c3 ret

ntdll!memcpy_s+0x4d:
00000000`77abeed9 4d85c0 test r8,r8
00000000`77abeedc 7412 je ntdll!memcpy_s+0x64 (00000000`77abeef0)

ntdll!memcpy_s+0x52:
00000000`77abeede 483bd3 cmp rdx,rbx
00000000`77abeee1 720d jb ntdll!memcpy_s+0x64 (00000000`77abeef0)

ntdll!memcpy_s+0x57:
00000000`77abeee3 4c8bc3 mov r8,rbx
00000000`77abeee6 488bd6 mov rdx,rsi
00000000`77abeee9 e8e2f7fbff call ntdll!memcpy (00000000`77a7e6d0)
00000000`77abeeee ebb9 jmp ntdll!memcpy_s+0x1d (00000000`77abeea9)

ntdll!memcpy_s+0x64:
00000000`77abeef0 4c8bc2 mov r8,rdx
00000000`77abeef3 33d2 xor edx,edx
00000000`77abeef5 e8d63ffcff call ntdll!memset (00000000`77a82ed0)
00000000`77abeefa 4885f6 test rsi,rsi
00000000`77abeefd 7505 jne ntdll!memcpy_s+0x78 (00000000`77abef04)

ntdll!memcpy_s+0x73:
00000000`77abeeff 8d5e16 lea ebx,[rsi+16h]
00000000`77abef02 eb0a jmp ntdll!memcpy_s+0x82 (00000000`77abef0e)

ntdll!memcpy_s+0x78:
00000000`77abef04 483bfb cmp rdi,rbx
00000000`77abef07 73bb jae ntdll!memcpy_s+0x38 (00000000`77abeec4)

ntdll!memcpy_s+0x7d:
00000000`77abef09 bb22000000 mov ebx,22h

ntdll!memcpy_s+0x82:
00000000`77abef0e 488364242000 and qword ptr [rsp+20h],0
00000000`77abef14 4533c9 xor r9d,r9d
00000000`77abef17 4533c0 xor r8d,r8d
00000000`77abef1a 33d2 xor edx,edx
00000000`77abef1c 33c9 xor ecx,ecx
00000000`77abef1e e8fd94ffff call ntdll!invalid_parameter (00000000`77ab8420)
00000000`77abef23 8bc3 mov eax,ebx
00000000`77abef25 eba2 jmp ntdll!memcpy_s+0x3d (00000000`77abeec9)
這是windows 7 64 位系統上ntdll 模塊的一個非常簡短的函數memcpy_s() ,結果並不重要,重要的是過程

1. 確定函數的參數個數

在64 位windows 系統上,函數的傳遞方式相對簡單,統一使用寄存器進行傳遞參數,分別使用:rcx, rdx, r8 以及r9 寄存器來傳遞前4 個參數,多餘的參數依舊使用stack 來傳遞。
在這個函數中,我們看到使用到了r9 寄存器,因此,我們可以判斷這個函數共有4 個參數,下面是memcpy_s() 函數的原型初形:

memcpy_s(arg1, arg2, arg3, arg4)

分別用arg1 - arg4 來表示,函數的返回值先暫時放一邊,隨著分析過程的展開進行填補。


2. 第 4 個參數的處理

下面看看代碼:

00000000`77abeea4 4d85c9 test r9,r9 ; arg4
00000000`77abeea7 7504 jne ntdll!memcpy_s+0x21 (00000000`77abeead)

ntdll!memcpy_s+0x1d:
00000000`77abeea9 33c0 xor eax,eax ; 返回值
00000000`77abeeab eb1c jmp ntdll!memcpy_s+0x3d (00000000`77abeec9)

... ...

ntdll!memcpy_s+0x3d:
00000000`77abeec9 488b5c2440 mov rbx,qword ptr [rsp+40h]
00000000`77abeece 488b742448 mov rsi,qword ptr [rsp+48h]
00000000`77abeed3 4883c430 add rsp,30h
00000000`77abeed7 5f pop rdi
00000000`77abeed8 c3 ret

從上面可以看到,這裡先判斷arg4 參數,如果為0 的話,它最終將會函數返回。
於是,我們可以得到下面的邏輯:

代碼:
if (arg4 == 0)
        return 0;

3. 第 1 個參數的處理

下面看代碼:

ntdll!memcpy_s+0x21:
00000000`77abeead 4885c9 test rcx,rcx
00000000`77abeeb0 7527 jne ntdll!memcpy_s+0x4d (00000000`77abeed9)

ntdll!memcpy_s+0x26:
00000000`77abeeb2 48214c2420 and qword ptr [rsp+20h],rcx
00000000`77abeeb7 4533c9 xor r9d,r9d
00000000`77abeeba 4533c0 xor r8d,r8d
00000000`77abeebd 33d2 xor edx,edx
00000000`77abeebf e85c95ffff call ntdll!invalid_parameter (00000000`77ab8420)

ntdll!memcpy_s+0x38:
00000000`77abeec4 b816000000 mov eax,16h

ntdll!memcpy_s+0x3d:
00000000`77abeec9 488b5c2440 mov rbx,qword ptr [rsp+40h]
00000000`77abeece 488b742448 mov rsi,qword ptr [rsp+48h]
00000000`77abeed3 4883c430 add rsp,30h
00000000`77abeed7 5f pop rdi
00000000`77abeed8 c3 ret

如果,第1 個參數arg1 為0 的話,它將調用invalid_parameter() 函數,返回一個代碼值(返回狀態!)
invalid_parameter() 調用用先將r​​dx, r8 以及r9 寄存清0,那麼這裡我姑且認為它也是4 個參數(注意:這裡使用了edx, r8d 和r9d 寄存器,說明這些參數是32 位值)並且我們知道memcpy_s() 函數應該是返回一個狀態值!
現在,我們又可以得出它的邏輯(結果起來):


代碼:
STATUS memcpy_s(arg1, arg2, arg3, arg4)
{
        if (arg4 == 0)
                return 0;

        if (arg1 == 0)
        {
                 invalid_parameters(arg1, 0, 0, 0);
                 return 0x16; // 狀態值
         }
}

3. 第 3 個參數的處理

假如,第1 個參數arg1 不為0 的時候呢?

ntdll!memcpy_s+0x21:
00000000`77abeead 4885c9 test rcx,rcx
00000000`77abeeb0 7527 jne ntdll!memcpy_s+0x4d (00000000`77abeed9)

... ...

ntdll!memcpy_s+0x4d:
00000000`77abeed9 4d85c0 test r8,r8 ; 第3 個參數
00000000`77abeedc 7412 je ntdll!memcpy_s+0x64 (00000000`77abeef0)

... ...

ntdll!memcpy_s+0x64:
00000000`77abeef0 4c8bc2 mov r8,rdx ; rdx 寄存器的值為arg2
00000000`77abeef3 33d2 xor edx,edx
00000000`77abeef5 e8d63ffcff call ntdll!memset (00000000`77a82ed0)
00000000`77abeefa 4885f6 test rsi,rsi
00000000`77abeefd 7505 jne ntdll!memcpy_s+0x78 (00000000`77abef04)

它將接下來判斷第3 個參數arg3,如果arg3 也為0 的時候,它將調用memset()

我們知道memset() 是置memory buffer 為某一值的作用,上面所示,它的參數有3 個,它的邏輯為:

代碼:
memset(char *dest, char c, unsigned int count)
在這個函數的調用中,我們可以知道rdx 寄存器將是傳遞給memset() 函數作為第3 個參數,而rcx 寄存器正是目標地址值,於是,我們知道memcpy_s() 函數的第1 個參數是目標地址值!
於是,我們在這裡可以得出:

代碼:
NT_STATUS memcpy_s(char *dest, arg2, arg3, arg4)
{
  if (arg4 == 0) return 0;
  
  if (arg1 == 0) {
    invalid_argeter(arg1, 0, 0, 0)
    return 0x16;
  }

  if (arg3 == 0) {
    memset(dest,0, arg2);
    invalid_argeter(arg1, 0, 0, 0)
    return 0x16;
  }
}
在這一步,我們得出了memcpy_s() 函數的第1 個參數,紅色標註的。


5. 第 2 個參數與第 4 個參數處理

當第3 個參數不為0 的時候,將會繼續判斷第2 個和第3 個參數:

ntdll!memcpy_s+0x52:
00000000`77abeede 483bd3 cmp rdx,rbx ; arg2 與arg4 之間的比較
00000000`77abeee1 720d jb ntdll!memcpy_s+0x64 (00000000`77abeef0)

... ...

ntdll!memcpy_s+0x64:
00000000`77abeef0 4c8bc2 mov r8,rdx
00000000`77abeef3 33d2 xor edx,edx
00000000`77abeef5 e8d63ffcff call ntdll!memset (00000000`77a82ed0)
00000000`77abeefa 4885f6 test rsi,rsi ; 關鍵一步, rsi 的值就是r8 也就是arg1
00000000`77abeefd 7505 jne ntdll!memcpy_s+0x78 (00000000`77abef04)

... ...

ntdll!memcpy_s+0x78:
00000000`77abef04 483bfb cmp rdi,rbx
00000000`77abef07 73bb jae ntdll!memcpy_s+0x38 (00000000`77abeec4)

ntdll!memcpy_s+0x7d:
00000000`77abef09 bb22000000 mov ebx,22h

ntdll!memcpy_s+0x82:
00000000`77abef0e 488364242000 and qword ptr [rsp+20h],0
00000000`77abef14 4533c9 xor r9d,r9d
00000000`77abef17 4533c0 xor r8d,r8d
00000000`77abef1a 33d2 xor edx,edx
00000000`77abef1c 33c9 xor ecx,ecx
00000000`77abef1e e8fd94ffff call ntdll!invalid_parameter (00000000`77ab8420)
00000000`77abef23 8bc3 mov eax,ebx
00000000`77abef25 eba2 jmp ntdll!memcpy_s+0x3d (00000000`77abeec9

這里通過比較arg2 與arg4 的大小,當arg2 小於arg4 的時候,同樣調用memset(),然後置狀態值0x22,然後返回。
在這一步,我們得到:

代碼:
NT_STATUS memcpy_s(char *dest, arg2, arg3, arg4)
{
  if (arg4 == 0) return 0;
  
  if (arg1 == 0) {
    invalid_pargeter(dest, 0, 0, 0)
    return 0x16;
  }

  if (arg3 == 0) {
    memset(dest,0, arg2);
    invalid_pargeter(dest, 0, 0, 0)
    return 0x16;
  }

  if (arg2 < arg4)
  {
    memset(dest, 0, arg2);
    invalid_pargeter(dest, 0, 0, 0)
    return 0x22;
  }
}

6. 最後一步,確定 arg2,arg3 以及 arg4

看下面最終的 memcpy() 代碼:

ntdll!memcpy_s+0x57:
00000000`77abeee3 4c8bc3 mov r8,rbx ; arg4 是size
00000000`77abeee6 488bd6 mov rdx,rsi ; r8 是source
00000000`77abeee9 e8e2f7fbff call ntdll!memcpy (00000000`77a7e6d0)
00000000`77abeeee ebb9 jmp ntdll!memcpy_s+0x1d (00000000`77abeea9)

最終將會調用memcpy() 進行複制,我們知道memcpy() 的原型大概是這樣的:

代碼:
memcpy(char *dest, char *source, unsinged int size)
這裡,我們明確的答案了, arg4 將會是size,arg3 將會是source

那麼,arg2 是什麼呢?通過前面的if (arg2 < arg4) 的比較,我們可以斷定,arg2 是buffer size,如果buffer size 小於count size 值時,那會將會出錯。

因此,最後一步,我們得到完全的邏輯:

代碼:
NT_STATUS memcpy_s(char *dest, arg2, arg3, arg4)
{
  if (arg4 == 0) return 0;
  
  if (arg1 == 0) {
    invalid_argeter(dest, 0, 0, 0)
    return 0x16;
  }

  if (arg3 == 0) {
    memset(dest,0, arg2);
    invalid_argeter(dest, 0, 0, 0)
    return 0x16;
  }

  if (arg2 < arg4)
  {
    memset(dest, 0, arg2);
    invalid_argeter(dest, 0, 0, 0)
    return 0x22;
  }
  
  memcpy(dest, arg3, arg4);
  
  return 0;
}

7. 最後,我們整理一下代碼,得出最終的一個結果:

下面是還原的結果,這不是原始源代碼,只是按照函數的邏輯形成的一個功能和邏輯一樣的代碼:



代碼:
STATUS memcpy_s(char *dest, unsigned int buffer_size, char *source, unsigned int count)
{
  STATUS status = STATUS_SUCCESS;

  if (count == 0)
    return status;

  if (dest == NULL)
  {
    status = STATUS_INVALID_ADDRESS;
  }
  else if (source == NULL)
  {
    memset(dest, 0, buffer_size);
    status = STATUS_INVALID_ADDRESS;
  }
  else if (buffer_size < count)
  {
    memset(dest, 0, buffer_size);
    status = STATUS_INVALID_BUFFER_SIZE;
  }
    
  else
    memcpy(dest, source, count);

  
  if (status != STATUS_SUCCESS)
    invalid_parameter(dest, 0, 0, 0);
  
  return status;
}
當然請注意:這裡的status 值是一個表述,在這裡不是真實的常量

没有评论:

发表评论