为什么在汇编中没有调用 strcpy ?

逆向工程 部件 C 数据库
2021-06-27 18:40:18

我试图更好地理解程序集,目前正在使用一些编译的 c 代码片段。我有以下片段:

#include <stdio.h>
#include <string.h>

int main() {
    char b[] = " a nice nice long string";

    char c[100];
    strcpy(c, b);
    printf("Hello World %s\n", c);
    strcpy(c, " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA");
}

使用gcc hello_world.c -o hello_worldgdb编译并打开后,main 函数的汇编代码如下所示:

   0x00000000000006fa <+0>:     push   rbp
   0x00000000000006fb <+1>:     mov    rbp,rsp
   0x00000000000006fe <+4>:     sub    rsp,0x90
   0x0000000000000705 <+11>:    mov    rax,QWORD PTR fs:0x28
   0x000000000000070e <+20>:    mov    QWORD PTR [rbp-0x8],rax
   0x0000000000000712 <+24>:    xor    eax,eax
   0x0000000000000714 <+26>:    movabs rax,0x206563696e206120
   0x000000000000071e <+36>:    movabs rdx,0x6e6f6c206563696e
   0x0000000000000728 <+46>:    mov    QWORD PTR [rbp-0x90],rax
   0x000000000000072f <+53>:    mov    QWORD PTR [rbp-0x88],rdx
   0x0000000000000736 <+60>:    movabs rax,0x676e697274732067
   0x0000000000000740 <+70>:    mov    QWORD PTR [rbp-0x80],rax
   0x0000000000000744 <+74>:    mov    BYTE PTR [rbp-0x78],0x0
   0x0000000000000748 <+78>:    lea    rdx,[rbp-0x90]
   0x000000000000074f <+85>:    lea    rax,[rbp-0x70]
   0x0000000000000753 <+89>:    mov    rsi,rdx
   0x0000000000000756 <+92>:    mov    rdi,rax
   0x0000000000000759 <+95>:    call   0x5b0 <strcpy@plt>
   0x000000000000075e <+100>:   lea    rax,[rbp-0x70]
   0x0000000000000762 <+104>:   mov    rsi,rax
   0x0000000000000765 <+107>:   lea    rdi,[rip+0xe8]        # 0x854
   0x000000000000076c <+114>:   mov    eax,0x0
   0x0000000000000771 <+119>:   call   0x5d0 <printf@plt>
   0x0000000000000776 <+124>:   lea    rax,[rbp-0x70]
   0x000000000000077a <+128>:   movabs rsi,0x4141414141414120
   0x0000000000000784 <+138>:   movabs rdi,0x4141414141414141
   0x000000000000078e <+148>:   mov    QWORD PTR [rax],rsi
   0x0000000000000791 <+151>:   mov    QWORD PTR [rax+0x8],rdi
   0x0000000000000795 <+155>:   movabs rdx,0x4141414141414141
   0x000000000000079f <+165>:   movabs rcx,0x4141414141414141
   0x00000000000007a9 <+175>:   mov    QWORD PTR [rax+0x10],rdx
   0x00000000000007ad <+179>:   mov    QWORD PTR [rax+0x18],rcx
   0x00000000000007b1 <+183>:   mov    BYTE PTR [rax+0x20],0x0
   0x00000000000007b5 <+187>:   mov    eax,0x0
   0x00000000000007ba <+192>:   mov    rcx,QWORD PTR [rbp-0x8]
   0x00000000000007be <+196>:   xor    rcx,QWORD PTR fs:0x28
   0x00000000000007c7 <+205>:   je     0x7ce <main+212>
   0x00000000000007c9 <+207>:   call   0x5c0 <__stack_chk_fail@plt>
   0x00000000000007ce <+212>:   leave
   0x00000000000007cf <+213>:   ret

我想我明白哪个部分在做什么。但有些事情我不清楚。

  1. strcpy 仅被调用一次,并且仅在第二个参数是变量时调用。为什么没有调用第二个 strcopy?
  2. printf 用一个字符串和第二个参数调用。该字符串是从 rip+0xe8 加载的。为什么提供给第二个 strcopy 的字符串没有以相同的方式加载?它在 moveabs 指令中被“硬编码”。

这是由于一些编译器优化吗?有人可以详细说明吗?

1个回答
import binascii
print(binascii.hexlify(b" a nice nice long string"))
print(binascii.hexlify(b"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"))

您可以在列表中找到这两个字符串

b'2061206e696365206e696365206c6f6e6720737472696e67'
b'41414141414141414141414141414141414141414141414141414141414141'

第一个字符串

0x0000000000000714 <+26>:    movabs rax,0x206563696e206120
0x000000000000071e <+36>:    movabs rdx,0x6e6f6c206563696e
0x0000000000000736 <+60>:    movabs rax,0x676e697274732067

第二个字符串

 0x000000000000077a <+128>:   movabs rsi,0x4141414141414120
 0x0000000000000784 <+138>:   movabs rdi,0x4141414141414141
 0x000000000000078e <+148>:   mov    QWORD PTR [rax],rsi
 0x0000000000000791 <+151>:   mov    QWORD PTR [rax+0x8],rdi
 0x0000000000000795 <+155>:   movabs rdx,0x4141414141414141
 0x000000000000079f <+165>:   movabs rcx,0x4141414141414141

第一次给它一个在编译时未知的地址,因此编译器被迫使用该函数(动态参数)

第二次你给一个常量字符串,它可以被拆分并放入 c
(静态参数)

或者,如果您提供一个非常大的字符串,它可以获取地址并执行 repmovsq

strcpy(d, " lshdgfdghsdfghsdfghsdfhgsdfhgsdfhgshsfur4tye36346asdgxzcvgaewt34t sg afeaerwbhtyhswrtwqee5t6e67redhfdxbw45bh hhjljuhlfjsdhlksjdghsdlkjfhgsldkjfghlskjdghlkjsdhggkjsdhgksdjhgskdjhglsdkjhgfskjdhfgsdkjhfgkjsdfhgskdjhfgsdk;ljhg;djkgsjhg;dsljkhf;ljg;dljg;jh");
}

使用 repmovsq

mov     eax, OFFSET FLAT:.LC1
mov     ecx, 31
mov     rdi, rdx
mov     rsi, rax
rep movsq

.LC1:
        .string " lshd

只是为了确认删除 b[] = 行并放入 strcpy(c,const string ); 您将看到编译器也消除了 .plt 条目并将 strcpy 转换为 __builtin_memcpy()

 __builtin_memcpy (&c, " a nice nice long string", 25);
  printf ("Hello World %s\n", &c);
  __builtin_memcpy (&c, " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", 33);

用 -fno-builtin 编译,你会得到两个 strcpy

.LC0:
        .string "Hello World %s\n"
.LC1:
        .string " AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
main:
        push    rbp
        mov     rbp, rsp
        sub     rsp, 144
        movabs  rax, 2334381286331801888
        movabs  rdx, 7957697952982722926
        mov     QWORD PTR [rbp-32], rax
        mov     QWORD PTR [rbp-24], rdx
        movabs  rax, 7453010373645639783
        mov     QWORD PTR [rbp-16], rax
        mov     BYTE PTR [rbp-8], 0
        lea     rdx, [rbp-32]
        lea     rax, [rbp-144]
        mov     rsi, rdx
        mov     rdi, rax
        call    strcpy
        lea     rax, [rbp-144]
        mov     rsi, rax
        mov     edi, OFFSET FLAT:.LC0
        mov     eax, 0
        call    printf
        lea     rax, [rbp-144]
        mov     esi, OFFSET FLAT:.LC1
        mov     rdi, rax
        call    strcpy
        mov     eax, 0
        leave
        ret

您可以在此处阅读其他各种 __builtins