【问题标题】:Re-writing a small execve shellcode重写一个小的 execve shellcode
【发布时间】:2013-05-14 00:21:07
【问题描述】:

通过http://hackoftheday.securitytube.net/2013/04/demystifying-execve-shellcode-stack.html

我了解调用 execve 并试图重写它的 nasm 程序。

一些背景资料:

int execve(const char *filename, char *const argv[], char *const envp[]);

所以,eax = 11execve 的函数调用号),ebx 应该指向 char* filenameecx 应该指向 argv[](这将与 ebx 相同,因为第一个参数是*filename 本身,例如在这种情况下为“/bin/sh”),edx 将指向envp[](在这种情况下为null)。

原始nasm代码:

global _start

section .text
_start:

xor eax, eax
push eax

; PUSH //bin/sh in reverse i.e. hs/nib//

push 0x68732f6e
push 0x69622f2f

mov ebx, esp

push eax
mov edx, esp

push ebx
mov ecx, esp

mov al, 11
int 0x80

堆栈如下:

现在我尝试通过减少一些指令来优化它。我同意在mov ebx, esp 之前代码将保持不变。但是,由于ecx 需要指向ebx,我可以重写代码如下:

global _start

section .text
_start:

xor eax, eax
push eax

; PUSH //bin/sh in reverse i.e. hs/nib//

push 0x68732f6e
push 0x69622f2f
mov ebx, esp

mov ecx,ebx

push eax
mov edx, esp

mov al, 11
int 0x80

但是,当我运行重新编写的代码时,我遇到了分段错误。

我的堆栈如下:

任何想法为什么重新编写的代码不起作用?我也跑过gdb,地址值是根据我的想法,但它不会运行。

【问题讨论】:

  • 注意:这些汇编代码强烈依赖机器,不能保证在每台随机机器上执行。
  • @M.M 但原始代码工作得很好。我已经在同一台机器上测试了它们。

标签: c++ assembly nasm shellcode execve


【解决方案1】:

在这两种情况下,ebx 都指向字符串“//bin/sh”。相当于这样的C代码:

char *EBX = "//bin/sh";  

但在您的第一个示例中,ecx 设置为指向该字符串的指针的地址。相当于这样的C代码:

char *temp = "//bin/sh"; // push ebx
char **ECX = &temp;      // mov ecx, esp

在您的第二个示例中,ecx 只是设置为与 ebx 相同的值。

char *ECX = "//bin/sh";

因此,这两个示例根本不同,ecx 有两个完全不同的类型和值。

更新:

我应该补充一点,从技术上讲,ecx 是一个 char 指针数组(argv 参数),而不仅仅是指向 char 指针的指针。您实际上是在堆栈上构建一个包含两项的数组。

char *argv[2];
argv[1] = NULL;         // push eax, eax being zero
argv[0] = "//bin/sh";   // push ebx
ECX = argv;             // mov ecx,esp

只是该数组的一半也是 envp 参数的两倍。由于 envp 是一个单项数组,并且该单项设置为 NULL,因此您可以考虑使用 C 代码设置的 envp 参数,如下所示:

EDX = envp = &argv[1];           

这是通过将 edx 设置为 esp 来实现的,而 argv 数组只构建了一半。将这两个作业的代码组合在一起,您会得到:

char *argv[2];
argv[1] = NULL;         // push eax, eax being zero
EDX = &argv[1];         // mov edx,esp
argv[0] = "//bin/sh";   // push ebx
ECX = argv;             // mov ecx,esp

这有点令人费解,但我希望这对你有意义。

更新 2

execve 的所有参数都作为寄存器传递,但这些寄存器是指向内存的指针,需要在某处分配 - 在本例中,在堆栈上。由于堆栈在内存中是向下构建的,因此需要以相反的顺序构建内存块。

三个参数的内存如下所示:

char *filename:  2f 2f 62 69 | 6e 2f 73 68 | 00 00 00 00 
char *argv[]:    filename    | 00 00 00 00               
char *envp[]:    00 00 00 00   

文件名是这样构造的:

push eax        // '\0' terminator plus some extra
push 0x68732f6e // 'h','s','/','n'
push 0x69622f2f // 'i','b','/','/'

argv 参数如下:

push eax // NULL pointer
push ebx // filename

envp 参数如下:

push eax // NULL pointer

但正如我所说,原始示例决定在 argv 和 evp 之间共享内存,因此不需要最后一个 push eax

我还应该注意,构造字符串时使用的两个双字中字符的相反顺序是因为机器的字节序,而不是堆栈方向。

【讨论】:

  • 为什么 ECX 是双指针(在 mov ecx,esp 行)?另外,我还观察到一件事。即使我再次推送相同的值,我也会遇到分段错误。只有首先推送 ebx 然后 edx 然后 ecx 的原始代码才有效。我试过这个:mov ebx,esp;推 ebx; mov ecx,esp;推动eax;移动 edx, esp;这也行不通。这让我认为,不知何故,将值压入堆栈的顺序很重要。
  • 您的答案看起来很有希望。我还可以问为什么 //bin/sh 以相反的顺序推送?如果它真的应该是反向的,那么最初的空字节应该在推完 //bin/sh 的反向之后再推。
  • 是的,我正在努力理解您的回答。但是,我看到在第一个代码中推送了两个 NULL。一个在 //bin/sh 之前(通过 xor eax,eax 获得),一个在它之后(push eax)。所以 ebx = //bin/sh + 第一个空值。第二个 null 设置为 edx。
  • 我希望从我的第二次更新中可以清楚地看到这一点。第一个 NULL 只是用作字符串的空终止符。第二个 NULL 既用作 envp 数组中的 NULL 指针,也用作 argv 数组中第二项的 NULL 指针。请注意,edx 没有设置为 NULL - 它指向一块包含 NULL 的内存。
  • 既然 argv = filename 和 filename 已经以 null 结尾,为什么还要在 argv 中添加一个 null?
猜你喜欢
  • 2012-11-26
  • 2014-03-17
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-04-05
  • 2013-11-01
相关资源
最近更新 更多