转:你们猜XP SP2的运行时库里的strlen怎么实现的

大家好,这个论坛真不错,呵呵
我也来写点东西吧

这个礼拜六做了两件事
睡觉+吃饭,我下面说的这件事一般是在睡觉的时候做的,还有一半是吃饭的时候做的,恩。开始了
周末在家很无聊,人太无聊了就会做傻事……
于是我开始逆向XP SP2的ntdll玩,突然有个很想法,闲的没事的时候把里面的函数都用C重写一遍。
本来想找个软柿子捏,我看strlen很不错嘛,又简单,闭着眼就能搞定吧。

程序的结构很简单,移指针找'\0',找到了把指针一减就OK了。
恩,我带着定势思维继续看下去。
完了晕了
.text:7C922ABB                 mov     eax, [ecx]
.text:7C922ABD                 mov     edx, 7EFEFEFFh
.text:7C922AC2                 add     edx, eax
.text:7C922AC4                 xor     eax, 0FFFFFFFFh
.text:7C922AC7                 xor     eax, edx
.text:7C922AC9                 add     ecx, 4
.text:7C922ACC                 test    eax, 81010100h
这罗里巴嗦的忙活什么呢……
屁大点事,找个'\0'还这么麻烦
头晕中……
三小时后
微软的代码果然牛X,这么简单的函数,看看人家考虑的就是周到,大公司就是大公司,大公司就是一定要把简单的事情复杂化,这样才能把复杂的事情简单化……
恩,我们看看他是怎么干的。
//.text:7C922A9D                 mov     ecx, [esp+arg_0]      字符串指针放入EXC
//.text:7C922AA1                 test    ecx, 3                     判断地址是否是4字节对齐的
//.text:7C922AA7                 jz      short loc_7C922ABB
//.text:7C922AA9
//                      不是4字节对齐的地址,一个字节一个字节的比较
//.text:7C922AA9 loc_7C922AA9:                           ; CODE XREF: _strlen+19j  
//.text:7C922AA9                 mov     al, [ecx]   
//.text:7C922AAB                 inc     ecx         
//.text:7C922AAC                 test    al, al                      判断al是不是'\0'
//.text:7C922AAE                 jz      short loc_7C922AEE   是就跳
//.text:7C922AB0                 test    ecx, 3                    是不是对齐地址?
//.text:7C922AB6                 jnz     short loc_7C922AA9   不是就继续
//.text:7C922AB8                 add     eax, 0    eax清0
//.text:7C922ABB 已经是4字节对齐的地址了,这里可以4个字节4个字节的比较了
//.text:7C922ABB loc_7C922ABB:                           ; CODE XREF: _strlen+Aj
//.text:7C922ABB                                                ; _strlen+34j ...
//.text:7C922ABB                 mov     eax, [ecx]             从字符串里读4个字节
//.text:7C922ABD                 mov     edx, 7EFEFEFFh     神奇数字~~!
//.text:7C922AC2                 add     edx, eax               
//.text:7C922AC4                 xor     eax, 0FFFFFFFFh    取反
//.text:7C922AC7                 xor     eax, edx   
//.text:7C922AC9                 add     ecx, 4
//.text:7C922ACC                 test    eax, 81010100h     另一个很神奇的数字
//.text:7C922AD1                 jz      short loc_7C922ABB  检查4字节里有没有0,没有就跳
//.text:7C922AD3                 mov     eax, [ecx-4]          有的话找出哪个是0
//.text:7C922AD6                 test    al, al
//.text:7C922AD8                 jz      short loc_7C922B0C   第一个就是
//.text:7C922ADA                 test    ah, ah
//.text:7C922ADC                 jz      short loc_7C922B02   第二个是就-3
//.text:7C922ADE                 test    eax, 0FF0000h         第三个是就-2
//.text:7C922AE3                 jz      short loc_7C922AF8
//.text:7C922AE5                 test    eax, 0FF000000h      第四个是就-1
//.text:7C922AEA                 jz      short loc_7C922AEE
//.text:7C922AEC                 jmp     short loc_7C922ABB  都不是?有没有搞错……一般来这算法如果没有问题是不会到这里的

它用了一种很神奇的算法,可以找出一个四字节中有没有'\0'。
总之,我仿照着写了一个C函数,感觉不如他们汇编写的那个好。测试了一下是OK的。
就算你知道微软的编译选项也请不要拿编译以后的代码和原来的代码比较,因为我没有那么强悍的逆向水平,呵呵就这么着吧。
我觉得原先的代码就是用汇编手写的。
这么简单的一个函数居然还考虑了字节对齐,很强悍……
[cpp]size_t __cdecl strlen(char* p)
{
char* temp;
temp = p;
long fourbyte = 0;
while (((long)p)&3 != 0) {//如果地址没有对齐
  if (*p != '\0' )
   p++;
  else
   goto exit1;
}
do {
  fourbyte = *(long*)(p);
  p+=4;
} while((((fourbyte^0xffffffff)^(fourbyte+0x7EFEFEFF))&0x81010100) == 0);
//四字节读入一个long型变量的时候是倒着放的,不要忘了
//即char a[4] = {77,78,79,80},放到一个long 里就是80797877
if (((char*)(&fourbyte))[0] == '\0') {
  goto exit4;
}
if (((char*)(&fourbyte))[1] == '\0') {
  goto exit3;
}
if (((char*)(&fourbyte))[2] == '\0') {
  goto exit2;
}
if (((char*)(&fourbyte))[3] == '\0') {
  goto exit1;
}
exit1:
return p-temp-1;
exit2:
return p-temp-2;
exit3:
return p-temp-3;
exit4:
return p-temp-4;
}[/cpp]
关于那个算法,大家来讨论一下原理吧,呵呵就当思考题了
pk8995@163.com
Live in the code

转载自:http://www.0ginr.com/bbs/viewthread.php?tid=1163&highlight=strlen
原文地址:https://www.cnblogs.com/suyang/p/1572400.html