zoukankan      html  css  js  c++  java
  • strlen的另一种实现,可以作为ShellCode

    一丶第一种方式

    在实际工作中会遇到很多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
    

    此方法进行解析

    1. mov edi,[ebp -xxx] 是获取局部变量地址,局部变量中是以0结尾的字符串
    2. mov ecx,-1 设置计数器为-1
    3. sub eax,eax 两个数相减就是0 也可以换成 xor eax,eax
    4. 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
    };
    
  • 相关阅读:
    使用runOnUiThread更新UI
    Leetcode Symmetric Tree
    EBS 开发中如何动态启用和禁止请求(Current Request)的参数
    c 陷阱与缺陷(一)
    钟浩荣战胜病魔,不负众望重踏传智播客!
    【原创】分布式之elk日志架构的演进
    【强烈谴责】博客园园友随意抄袭他人文章并作为自己原创的行为
    【原创】研发应该懂的binlog知识(下)
    【原创】研发应该懂的binlog知识(上)
    【原创】一个线程oom,进程里其他线程还能运行吗?
  • 原文地址:https://www.cnblogs.com/iBinary/p/12178736.html
Copyright © 2011-2022 走看看