看过论坛的一些关于逆向的贴子
说说我的看法:逆向工程并不是对代码进行分析就算了,实际上要对工程进行一定的还原(源代码形式),当然难度挺大,要对算法非常熟悉。要系统的数据结构非常熟悉,才有能力推还原工程的数据结构。
这里仅对一个简短的函数进行还原,当然并不可能还原为原作者源代码,是还原为在函数的逻辑思想上最接近的形式。
代码:
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)
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() 调用用先将 rdx, 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)
于是,我们在这里可以得出:
代码:
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; } }
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)
那么,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; }