我想要一个简单的C方法,以便能够在Linux 64位机器上运行十六进制字节码。这是我的C程序:
char code[] = "\x48\x31\xc0";
#include <stdio.h>
int main(int argc, char **argv)
{
int (*func) ();
func = (int (*)()) code;
(int)(*func)();
printf("%s\n","DONE");
}
我试图运行的代码("\x48\x31\xc0"
)我是通过编写这个简单的汇编程序获得的(它不应该真的做任何事情)
.text
.globl _start
_start:
xorq %rax, %rax
然后编译并对其进行objdump以获取字节码。
然而,当我运行我的C程序时,我遇到了分段错误。有什么想法吗?
答案 0 :(得分:16)
您需要包含机器代码的页面才具有执行权限。与传统的386页表不同,x86-64页表有一个独立的执行位,与读取权限不同。
这是一个简单的例子:
#include <stdio.h>
#include <string.h>
#include <sys/mman.h>
int main ()
{
char code[] = {
0x8D, 0x04, 0x37, // lea eax,[rdi+rsi] // retval = a+b;
0xC3 // ret
};
int (*sum) (int, int) = NULL;
// copy code to executable buffer
sum = mmap (0,sizeof(code),PROT_READ|PROT_WRITE|PROT_EXEC,
MAP_PRIVATE|MAP_ANON,-1,0);
memcpy (sum, code, sizeof(code));
// doesn't actually flush cache on x86
// but is still necessary so memcpy isn't optimized away as a dead store.
__builtin___clear_cache(sum, sum + sizeof(sum)); // GNU C
// run code
int a = 2;
int b = 3;
int c = sum (a, b);
printf ("%d + %d = %d\n", a, b, c);
return 0;
}
答案 1 :(得分:4)
你的机器代码可能没问题,但你的CPU对象。
现代CPU分段管理内存。在正常操作中,操作系统将新程序加载到程序文本段中,并在数据段中设置堆栈。操作系统告诉CPU永远不要在数据段中运行代码。您的代码位于code[]
的数据段中。因此,段错误。
答案 2 :(得分:3)
您需要通过特殊的编译器指令在线包含程序集,以便它能够正确地在代码段中结束。请参阅本指南,例如:http://www.ibiblio.org/gferg/ldp/GCC-Inline-Assembly-HOWTO.html
答案 3 :(得分:2)
这需要一些努力。
您的code
变量存储在可执行文件的.data
部分中:
$ readelf -p .data exploit
String dump of section '.data':
[ 10] H1À
H1À
是变量的值。
.data
部分不可执行文件:
$ readelf -S exploit
There are 30 section headers, starting at offset 0x1150:
Section Headers:
[Nr] Name Type Address Offset
Size EntSize Flags Link Info Align
[...]
[24] .data PROGBITS 0000000000601010 00001010
0000000000000014 0000000000000000 WA 0 0 8
我熟悉的所有64位处理器本身都支持页面表中的非可执行页面。大多数较新的32位处理器(支持PAE的处理器)在其页表中提供足够的额外空间,以便操作系统模拟硬件非可执行页面。您需要运行古老的操作系统或古老的处理器才能将.data
部分标记为可执行文件。
因为这些只是可执行文件中的标志,所以你应该能够通过其他机制设置X
标志,但我不知道该怎么做。而且您的操作系统甚至可能不会让您拥有可写和可执行文件的页面。
答案 4 :(得分:0)
您可能需要先设置页面可执行文件,然后才能调用它。 在MS-Windows上,请参阅VirtualProtect -function。
网址:http://msdn.microsoft.com/en-us/library/windows/desktop/aa366898%28v=vs.85%29.aspx
答案 5 :(得分:0)
两个问题:
.data
部分中使用的数组。ret
指令结尾,因此,即使它确实运行了,执行也将落入内存中的下一个内容,而不是返回。顺便说一句,REX前缀完全是多余的。 "\x31\xc0"
xor eax,eax
has exactly the same effect as xor rax,rax
。
您需要包含机器代码的页面具有执行权限。与旧版386页表不同,x86-64页表具有一个单独的位,用于与读取权限分开执行。
使静态数组位于read + exec内存中的最简单方法是使用 gcc -z execstack
进行编译。 (使堆栈和其他部分可执行)。
直到最近(2018年或2019年),标准工具链(binutils ld
)都将.rodata
节与.text
放在相同的ELF段中,因此它们都读为+执行权限。因此,使用 const char code[] = "...";
足以执行手动指定的字节作为数据。
但是在我的带有GNU ld (GNU Binutils) 2.31.1
的Arch Linux系统上,情况不再如此。 readelf -a
显示.rodata
部分进入.eh_frame_hdr
和.eh_frame
的ELF段,并且仅具有读取权限。 .text
进入具有Read + Exec的段,而.data
进入具有Read + Write的段(以及.got
和.got.plt
)。 (What's the difference of section and segment in ELF file format)
#include <stdio.h>
// can be non-const if you use gcc -z execstack. static is also optional
static const char code[] = {
0x8D, 0x04, 0x37, // lea eax,[rdi+rsi] // retval = a+b;
0xC3 // ret
};
static const char ret0_code[] = "\x31\xc0\xc3"; // xor eax,eax ; ret
// the compiler will append a 0 byte to terminate the C string,
// but that's fine. It's after the ret.
int main () {
// void* cast is easier to type than a cast to function pointer,
// and in C can be assigned to any other pointer type. (not C++)
int (*sum) (int, int) = (void*)code;
int (*ret0)(void) = (void*)ret0_code;
// run code
int c = sum (2, 3);
return ret0();
}
在较旧的Linux系统上:gcc -O3 shellcode.c && ./a.out
(之所以有效,是因为{/ {1}}在全局/静态阵列上)
在旧的和当前的Linux上:const
(由于gcc -O3 -z execstack shellcode.c && ./a.out
而起作用,无论您的计算机代码存储在哪里)。 还可以与-zexecstack
一起使用。 gcc允许clang -z execstack
没有空格,但是clang不允许。
这些也可以在Windows上使用,在Windows中,只读数据进入-zexecstack
而不是.rdata
。
由编译器生成的.rodata
看起来像这样(来自main
)。 您可以在objdump -drwC -Mintel
内运行它,并在gdb
和code
上设置断点
ret0_code
您可以使用(I actually used gcc -no-pie -O3 -zexecstack shellcode.c hence the addresses near 401000
0000000000401020 <main>:
401020: 48 83 ec 08 sub rsp,0x8 # stack aligned by 16 before a call
401024: be 03 00 00 00 mov esi,0x3
401029: bf 02 00 00 00 mov edi,0x2 # 2 args
40102e: e8 d5 0f 00 00 call 402008 <code> # note the target address in the next page
401033: 48 83 c4 08 add rsp,0x8
401037: e9 c8 0f 00 00 jmp 402004 <ret0_code> # optimized tailcall
来分配新的可执行页面,或者使用gcc -zexecstack
来将现有页面更改为可执行文件,而不用mmap(PROT_EXEC)
进行编译。 (包括保存静态数据的页面。)当然,您通常通常也希望至少mprotect(PROT_EXEC)
,有时甚至是PROT_READ
。
在静态数组上使用PROT_WRITE
意味着您仍在从已知位置执行代码,也许可以更轻松地在其上设置断点。
在Windows上,您可以使用VirtualAlloc或VirtualProtect。
在GNU C中,在将机器代码字节写入缓冲区之后,您还需要使用__builtin___clear_cache(buf, buf + len)
,因为优化器不会将取消引用函数指针视为从该地址读取字节。如果编译器证明没有任何东西将存储的机器字节读取为数据,则消除死区存储可以将机器代码字节的存储移至缓冲区中。 https://codegolf.stackexchange.com/questions/160100/the-repetitive-byte-counter/160236#160236和https://godbolt.org/g/pGXn3B有一个示例,其中gcc确实做了此优化,因为gcc“知道” mprotect
。
(并且在I缓存与D缓存不相干的非x86架构上,它实际上将执行任何必要的缓存同步。在x86上,它纯粹是编译时优化阻止程序。)
我对@AntoineMathys答案的编辑添加了此内容。 gcc目前尚不了解malloc
,因此确实没有将存储优化到mmap
返回的指针中。
但是在包含只读C变量的页面上的mmap
之后,您不需要它。
mprotect
在此示例中,我使用了#include <stdio.h>
#include <sys/mman.h>
#include <stdint.h>
// can be non-const if you want, we're using mprotect
static const char code[] = {
0x8D, 0x04, 0x37, // lea eax,[rdi+rsi] // retval = a+b;
0xC3 // ret
};
static const char ret0_code[] = "\x31\xc0\xc3";
int main () {
// void* cast is easier to type than a cast to function pointer,
// and in C can be assigned to any other pointer type. (not C++)
int (*sum) (int, int) = (void*)code;
int (*ret0)(void) = (void*)ret0_code;
// hard-coding x86's 4k page size for simplicity.
// also assume that `code` doesn't span a page boundary and that ret0_code is in the same page.
uintptr_t page = (uintptr_t)code & -4095ULL; // round down
mprotect((void*)page, 4096, PROT_READ|PROT_EXEC|PROT_WRITE); // +write in case the page holds any writeable C vars that would crash later code.
// run code
int c = sum (2, 3);
return ret0();
}
,所以不管您的变量在哪里,它都能工作。如果它是堆栈中的本地对象,而您遗漏了PROT_READ|PROT_EXEC|PROT_WRITE
,则PROT_WRITE
在使堆栈仅尝试推送返回地址时将其变为只读后会失败。
此外,call
还允许您测试可自行修改的shellcode,例如将零编辑为自己的机器代码或避免的其他字节。
PROT_WRITE
如果我将$ gcc -O3 shellcode.c # without -z execstack
$ ./a.out
$ echo $?
0
$ strace ./a.out
...
mprotect(0x55605aa3f000, 4096, PROT_READ|PROT_WRITE|PROT_EXEC) = 0
exit_group(0) = ?
+++ exited with 0 +++
注释掉,它会执行段错误。
如果我做了类似mprotect
的操作,那之后我需要ret0_code[2] = 0xc3;
来确保没有对存储进行优化,但是如果我不修改静态数组,那么之后就不需要了__builtin___clear_cache(ret0_code+2, ret0_code+2)
。在mprotect
+ mmap
或手动存储之后,它是必需的,因为我们要执行用C(用memcpy
编写的字节)。
答案 6 :(得分:-1)
抱歉,我无法按照上面复杂的示例进行操作。 因此,我创建了一个优雅的解决方案,用于从 C 执行十六进制代码。 基本上,您可以使用 asm 和 .word 关键字以十六进制格式放置指令。 见下例:
asm volatile(".rept 1024\n"
CNOP
".endr\n");
其中 CNOP 的定义如下: #define ".word 0x00010001 \n"
基本上,我当前的汇编程序不支持 c.nop
指令。因此,我使用正确的语法将 CNOP
定义为 c.nop
的十六进制等价物,并在 asm 中使用,这是我所知道的。
.rept <NUM> .endr
基本上会重复该指令 NUM 次。
此解决方案有效且经过验证。