一丶第一种方式
在实际工作中会遇到很多strlen. 这里针对strlen函数做一下代码还原.
并且讲解其原理
高级代码如下:
#include <iostream>
int main()
{
char szBuffer[] = "HelloWorld";
scanf_s("%s", szBuffer);
long StrSize = strlen(szBuffer);
scanf_s("%d", &StrSize);
return 0;
}
请不要关注scanf_s 为了编译器能够正确编译加上自己懒得去掉SDL检查.所以加了.这里的scanf_s 只是为了防止编译器直接全部优化掉.
对应汇编代码如下:
.text:00401050 ; int __cdecl main(int argc, const char **argv, const char **envp)
.text:00401050 main proc near ; CODE XREF: __scrt_common_main_seh+F5↓p
.text:00401050
.text:00401050 var_14 = dword ptr -14h
.text:00401050 var_10 = qword ptr -10h
.text:00401050 var_8 = word ptr -8
.text:00401050 var_6 = byte ptr -6
.text:00401050 var_4 = dword ptr -4
.text:00401050 argc = dword ptr 8
.text:00401050 argv = dword ptr 0Ch
.text:00401050 envp = dword ptr 10h
.text:00401050
.text:00401050 push ebp
.text:00401051 mov ebp, esp
.text:00401053 sub esp, 14h
.text:00401056 mov eax, __security_cookie
.text:0040105B xor eax, ebp
.text:0040105D mov [ebp+var_4], eax
.text:00401060 mov ax, word ptr ds:aHelloworld+8 ; "ld"
.text:00401066 movq xmm0, qword ptr ds:aHelloworld ; "HelloWorld"
.text:0040106E mov [ebp+var_8], ax
.text:00401072 mov al, byte ptr ds:aHelloworld+0Ah ; ""
.text:00401077 mov [ebp+var_6], al
.text:0040107A lea eax, [ebp+var_10]
.text:0040107D push eax
.text:0040107E push offset aS ; "%s"
.text:00401083 movq [ebp+var_10], xmm0
.text:00401088 call scanf_s
.text:00401090 add esp, 8
.text:00401093 lea edx, [eax+1]
.text:00401096
.text:0040108D lea eax, [ebp+var_10]
.text:00401096 loc_401096: ;
.text:00401096 mov cl, [eax]
.text:00401098 inc eax
.text:00401099 test cl, cl
.text:0040109B jnz short loc_401096
.text:0040109D sub eax, edx
.text:0040109F mov [ebp+var_14], eax
.text:004010A2 lea eax, [ebp+var_14]
.text:004010A5 push eax
.text:004010A6 push offset aD ; "%d"
.text:004010AB call scanf_s
.text:004010B0 mov ecx, [ebp+var_4]
.text:004010B3 add esp, 8
.text:004010B6 xor ecx, ebp
.text:004010B8 xor eax, eax
.text:004010BA call __security_check_cookie
.text:004010BF mov esp, ebp
.text:004010C1 pop ebp
.text:004010C2 retn
.text:004010C2 main endp
提取出的strlen的汇编代码. 去掉流水线优化后如下
.text:00401093 lea edx, [eax+1]
.text:0040108D lea eax, [ebp+var_10]
.text:00401096 loc_401096: ;
.text:00401096 mov cl, [eax]
.text:00401098 inc eax
.text:00401099 test cl, cl
.text:0040109B jnz short loc_401096
.text:0040109D sub eax, edx
不要关注前边的地址. 因为去掉流水线优化了.不知道啥叫流水线优化.去查看 <C++反汇编与逆向技术解密>
汇编解析与原理:
1. lea eax,[ebp + var_10] 获取字符的首地址
2. lea edx, [eax+1] 获取字符串首地址 + 1的值.
3. mov cl,[eax] 获取单个字符给cl
4. inc eax 地址递增
5. test cl,cl 判断单个字符是否为0也就是结尾.不是就上跳,上跳是循环.所以地址不断递增
6. sub eax,edx 循环过后.eax = 字符串的高地址. 此时高地址-低地址 就是字符串的长度.
因为strlen的特点就是遇到0结尾推出.所以汇编 sub eax,edx
eax = 字符串0结尾的地址
edx = 首地址字符串+1的地址
两者相减就是字符串长度.
因为strlen的特点遇到0结尾.所以需要多减掉1. 所以这就是为什么 edx = 字符串首地址 + 1了.
其实也可以换成下方汇编
如下:
lea eax,字符串首地址
lea edx,字符串首地址
labale:
mov cl,[eax] 取出单个字符
inc eax eax相加也就是地址做增量
test cl,cl 判断是否结尾
jnz lable 不是结尾上跳,是的话不跳转
sub eax,edx 求出 字符串最后0结尾的地址 - 字符串首地址
sbu eax,1 strlen的特殊性.所以eax - 1 (也就是去掉0) 就是字符串的长度
上面edx是进行优化了. 所以虽然是获取了字符串 + 1的地址 但是最后相减就是字符串长度了.
ShellCode表现形式, 需要修复的位置为: 0x????
8D 05 ?? ?? ?? ?? lea eax,dword ptr ds:[77A11000]
8D 50 01 lea edx,dword ptr ds:[eax+1]
8A 08 mov cl,byte ptr ds:[eax]
40 inc eax
84 C9 test cl,cl
75 F9 jne -7
2B C2 sub eax,edx
90 nop
UCHAR StrlenShellCode{
0x8D, 0x05, 0x??, 0x??, 0x??, 0x??,
0x8D, 0x50, 0x01,
0x8A, 0x08,
0x40,
0x84, 0xC9,
0x75, 0xF9,
0x2B, 0xC2,
0x90
};
这是优化后的. 当前前边有说 使用传操作指令也可以实现.就看怎么实现了
二丶第二种实现方式
这种属于实战中遇到的,特此记录一下。
mov edi,[ebp - xxx]
mov ecx,0FFFFFFFFh
sub eax,eax
repne scasb
not ecx
此方法进行解析
- mov edi,[ebp -xxx] 是获取局部变量地址,局部变量中是以0结尾的字符串
- mov ecx,-1 设置计数器为-1
- sub eax,eax 两个数相减就是0 也可以换成 xor eax,eax
- repne scasb rep重复指令ne判断指令,合起来就是判断 ecx计数器是否 == 0
伪代码
if (ecx != 0)
{
rep scasb
}
else
{
}
5.not ecx, 当rep指令执行时,计数器会递减,比较的内存地址会增加
ecx-- 而 比较di跟al的 di会增加。 最终当di == al的值的时候就会退出
此时not ecx 则会得出长度。
可利用ShellCode
BF XX XX XX XX mov edi,字符串内存地址
B9 FF FF FF FF mov ecx,-1
33 C0 xor eax,eax
F2 AE repne scasb
F7 D1 not ecx
83 E9 01 sub ecx,0x1
我自己加了一个 sub ecx,1 上面not ecx之后,得出的长度会带 结尾也就是 也在计算。 加了一个之后则可以不带0结尾。
ShellCode
C/C++
unsigned char szShellCode[]={0XBF,0X??,0X?? ,0X??,0X??,0XB9,0XFF,0XFF,0XFF,0XFF,0X33,0XC0,0XF2,0XAE,0XF7,0xD1,0x83 ,0xE9,0x01};
E语言/vb/VBA
{191,?,?,?,?,185,255,255,255,255,51,192,242,174,247,209,131,233,1}
其中第二种形式也可以进行变幻.如下:
lea edi, [esp+60h+By14UserInputBuffer] ; 获取输入
or ecx, 0FFFFFFFFh
xor eax, eax
repne scasb ; 串扫描指令. stos是设置指令
not ecx
dec ecx
16进制
unsigned char ida_chars[] =
{
0x8D, 0x7C, 0x24, 0x4C, 0x83, 0xC9, 0xFF, 0x33, 0xC0, 0xF2,
0xAE, 0xF7, 0xD1, 0x49
};
10进制
unsigned char ida_chars[] =
{
141, 124, 36, 76, 131, 201, 255, 51, 192, 242,
174, 247, 209, 73
};