Nos*_*tap 9 c x86 assembly x86-64 shellcode
我想要一个简单的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");
}
Run Code Online (Sandbox Code Playgroud)
我试图运行的代码("\x48\x31\xc0")我通过编写这个简单的汇编程序获得(它不应该真的做任何事情)
.text
.globl _start
_start:
xorq %rax, %rax
Run Code Online (Sandbox Code Playgroud)
然后编译并objdump它以获取字节码.
但是,当我运行我的C程序时,我得到了一个分段错误.有任何想法吗?
Ant*_*hys 16
这是一个简单的例子:
#include <stdio.h>
#include <string.h>
#include <sys/mman.h>
int main ()
{
char code[] = {
0x8D, 0x04, 0x37, // lea eax,[rdi+rsi]
0xC3 // ret
};
int (*sum) (int, int) = NULL;
// allocate executable buffer
sum = mmap (0, sizeof(code), PROT_READ|PROT_WRITE|PROT_EXEC,
MAP_PRIVATE|MAP_ANONYMOUS, -1, 0);
// copy code to buffer
memcpy (sum, code, sizeof(code));
// doesn't actually flush cache on x86, but ensure 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);
}
Run Code Online (Sandbox Code Playgroud)
直到最近的 Linux 内核版本(5.4 之后的某个时候),您可以简单地编译gcc -z execstack- 这将使所有页面都可以执行,包括只读数据 ( .rodata) 和读写数据 ( .data) char code[] = "..."。
现在-z execstack仅适用于实际堆栈,因此它仍然仅适用于非常量本地数组。 即移动char code[] = ...到main。
有关内核更改,请参阅Linux 默认针对 `.data` 部分的行为,以及当项目中包含程序集文件时,来自 mmap 的意外 exec 权限,以了解旧行为:READ_IMPLIES_EXEC为该程序启用 Linux进程。
另一种选择是在运行时进行系统调用以复制到可执行页面,或更改其所在页面的权限。这比使用本地数组让 GCC 将代码复制到可执行堆栈内存中要复杂得多。
(我不知道READ_IMPLIES_EXEC在现代内核下是否有一种简单的方法可以启用。在 ELF 二进制文件中根本没有 GNU-stack 属性对 32 位代码来说是这样,但不是 64 位。)
另一种选择是__attribute__((section(".text"))) const char code[] = ...;
工作示例:https : //godbolt.org/z/draGeh。
如果您需要数组是可写的,例如对于在字符串中插入一些零的 shellcode,您可以使用ld -N. 但可能最好使用 -z execstack 和本地数组。
问题中的两个问题:
.data部分的数组。ret指令结尾,因此即使它确实运行了,执行也会落入内存中的下一个内容而不是返回。顺便说一句,REX 前缀完全是多余的。 "\x31\xc0" xor eax,eax 具有完全相同的效果xor rax,rax。
您需要包含机器代码的页面具有执行权限。与传统的 386 页表不同,x86-64 页表有一个单独的位用于执行与读取权限分开。
将静态数组放入 read+exec 内存的最简单方法是使用gcc -z execstack. (用于使堆栈和其他部分可执行,现在只有堆栈)。
直到最近(2018 年或 2019 年),标准工具链 (binutils ld) 会将 section.rodata放入与 相同的 ELF 段中.text,因此它们都具有 read+exec 权限。因此 usingconst char code[] = "...";足以将手动指定的字节作为数据执行,而无需 execstack。
但是在我的 Arch Linux 系统上GNU ld (GNU Binutils) 2.31.1,情况不再如此。 readelf -a显示该.rodata部分使用.eh_frame_hdr和进入 ELF 段.eh_frame,并且它只有读取权限。 .text使用 Read + Exec.data进入一个段,并使用 Read + Write 进入一个段(以及.got和.got.plt)。(ELF文件格式中section和segment的区别是什么)
我认为此更改是通过在可执行页面中没有只读数据来使 ROP 和 Spectre 攻击更加困难,在这些页面中,有用的字节序列可以用作以 aret或jmp reg指令的字节结尾的“小工具” 。
// TODO: use char code[] = {...} inside main, with -z execstack, for current Linux
// Broken on recent Linux, used to work without execstack.
#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();
}
Run Code Online (Sandbox Code Playgroud)
在较旧的 Linux 系统上:(gcc -O3 shellcode.c && ./a.out由于const在全局/静态数组上工作)
在 5.5 之前的 Linux 上(左右)gcc -O3 -z execstack shellcode.c && ./a.out(因为-zexecstack无论您的机器代码存储在哪里都可以工作)。有趣的事实:gcc 允许-zexecstack没有空格,但 clang 只接受clang -z execstack.
这些也适用于 Windows,其中只读数据.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
Run Code Online (Sandbox Code Playgroud)
gcc -zexecstack您可以使用mmap(PROT_EXEC)分配新的可执行页面,或mprotect(PROT_EXEC)将现有页面更改为可执行页面,而不是使用进行编译。(包括保存静态数据的页面。)当然,您通常至少需要PROT_READ,有时也需要PROT_WRITE。
使用mprotect上你仍然从已知位置执行代码的静态数组手段,也许更容易设置一个断点就可以了。
在 Windows 上,您可以使用 VirtualAlloc 或 VirtualProtect。
通常像 GCC 这样的编译器假设数据和代码是分开的。这类似于基于类型的严格别名,但即使使用 usingchar*也不能很好地定义将其存储到缓冲区中,然后将该缓冲区作为函数指针调用。
在 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 “知道” malloc。
(并且在 I-cache 与 D-cache 不一致的非 x86 架构上,它实际上会执行任何必要的缓存同步。在 x86 上,它纯粹是一个编译时优化阻止程序,不会扩展到任何指令本身。)
回复:怪异的名字有三个下划线:这是通常的__builtin_name模式,但是name是__clear_cache。
我对@AntoineMathys 的回答的编辑添加了这一点。
实际上,GCC/clang 并不mmap(MAP_ANONYMOUS)像他们了解malloc. 因此,在实践中,优化器会假设,即使没有__builtin___clear_cache(). (除非您将函数类型声明为__attribute__((const))。)
在 x86 上,I-cache 与数据缓存一致,在调用之前让存储发生在 asm 中就足以保证正确性。在其他 ISA 上,__builtin___clear_cache()实际上会发出特殊指令并确保正确的编译时排序。
在将代码复制到缓冲区时包含它是一种很好的做法,因为它不会降低性能,并且可以阻止假设的未来编译器破坏您的代码。(例如,如果他们确实理解这mmap(MAP_ANONYMOUS)会提供新分配的匿名内存,而其他任何东西都没有指向的指针,就像 malloc 一样。)
使用当前的 GCC,我能够通过使用__attribute__((const))告诉优化器sum()是一个纯函数(只读取其 args,而不是全局内存)来激发 GCC 真正进行我们不想要的优化。GCC 然后知道sum()无法读取memcpyas 数据的结果。
与其他memcpy进入通话后相同缓冲,GCC不排除死者店逼到第二店后呼叫。这导致在第一次调用之前没有存储,因此它执行00 00 add [rax], al字节,段错误。
// demo of a problem on x86 when not using __builtin___clear_cache
#include <stdio.h>
#include <string.h>
#include <sys/mman.h>
int main ()
{
char code[] = {
0x8D, 0x04, 0x37, // lea eax,[rdi+rsi]
0xC3 // ret
};
__attribute__((const)) 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));
//__builtin___clear_cache(sum, sum + sizeof(code));
int c = sum (2, 3);
//printf ("%d + %d = %d\n", a, b, c);
memcpy(sum, (char[]){0x31, 0xc0, 0xc3, 0}, 4); // xor-zero eax, ret, padding for a dword store
//__builtin___clear_cache(sum, sum + 4);
return sum(2,3);
}
Run Code Online (Sandbox Code Playgroud)
编上Godbolt编译探险与GCC9.2 -O3
main:
push rbx
xor r9d, r9d
mov r8d, -1
mov ecx, 34
mov edx, 7
mov esi, 4
xor edi, edi
sub rsp, 16
call mmap
mov esi, 3
mov edi, 2
mov rbx, rax
call rax # call before store
mov DWORD PTR [rbx], 12828721 # 0xC3C031 = xor-zero eax, ret
add rsp, 16
pop rbx
ret # no 2nd call, CSEd away because const and same args
Run Code Online (Sandbox Code Playgroud)
传递不同的 args 会得到另一个call reg,但即使有__builtin___clear_cache两个sum(2,3)调用也可以 CSE。 __attribute__((const))不考虑对函数机器代码的更改。不要这样做。不过,如果您要对函数进行一次 JIT,然后多次调用,则是安全的。
取消注释第一个__clear_cache结果
mov DWORD PTR [rax], -1019804531 # lea; ret
call rax
mov DWORD PTR [rbx], 12828721 # xor-zero; ret
... still CSE and use the RAX return value
Run Code Online (Sandbox Code Playgroud)
第一家商店在那里是因为__clear_cache和sum(2,3)电话。(删除第一个sum(2,3)调用确实可以在整个__clear_cache. 中发生死存储消除。)
第二个存储在那里是因为mmap假设返回的缓冲区的副作用很重要,这就是最终值的main离开。
Godbolt./a.out运行程序的选项似乎仍然总是失败(退出状态为 255);也许它沙箱JITing?它在我的桌面上工作__clear_cache,没有崩溃。
mprotect 在保存现有 C 变量的页面上。您还可以授予单个现有页面 read+write+exec 权限。这是编译的替代方法-z execstack
您不需要__clear_cache在包含只读 C 变量的页面上,因为没有要优化的存储。您仍然需要它来初始化本地缓冲区(在堆栈上)。否则 GCC 将优化掉这个私有缓冲区的初始化程序,非内联函数调用肯定没有指向的指针。(逃逸分析)。它不考虑缓冲区可能保存函数的机器代码的可能性,除非您通过__builtin___clear_cache.
#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();
}
Run Code Online (Sandbox Code Playgroud)
我PROT_READ|PROT_EXEC|PROT_WRITE在这个例子中使用了它,所以不管你的变量在哪里它都能工作。如果它是堆栈上的一个局部变量并且您遗漏了PROT_WRITE,call则在尝试推送返回地址时将堆栈设为只读后将失败。
此外,PROT_WRITE让您测试自我修改的 shellcode,例如将零编辑为它自己的机器代码,或它避免的其他字节。
$ 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 +++
Run Code Online (Sandbox Code Playgroud)
如果我注释掉mprotect,它会在最新版本的 GNU Binutils 中出现段错误ld,后者不再将只读常量数据放入与该.text部分相同的 ELF 段中。
如果我做了类似的事情ret0_code[2] = 0xc3;,我需要__builtin___clear_cache(ret0_code+2, ret0_code+2)在那之后确保商店没有被优化掉,但是如果我不修改静态数组,那么在mprotect. 在mmap+memcpy或手动存储之后需要它,因为我们要执行已用 C 编写的字节(使用memcpy)。
您需要通过特殊的编译器指令内联包含程序集,以便它正确地结束在代码段中。请参阅本指南,例如:http : //www.ibiblio.org/gferg/ldp/GCC-Inline-Assembly-HOWTO.html
你的机器码可能没问题,但你的 CPU 对象。
现代 CPU 以段的形式管理内存。在正常操作中,操作系统将新程序加载到程序文本段中,并在数据段中设置堆栈。操作系统告诉 CPU 永远不要在数据段中运行代码。您的代码位于code[], 数据段中。因此段错误。
| 归档时间: |
|
| 查看次数: |
10095 次 |
| 最近记录: |