【2026春节解题红包】WriteUP(2、3、4、5、6、7、9)

admin 2026-03-27 13:10:58 网络安全文章 来源:ZONE.CI 全球网 0 阅读模式

文章总结: 本文是2026年春节解题红包活动中部分题目的解题思路分享。作者使用IDA、JEB等工具,通过动态调试、静态分析及AI辅助等方式,详细解析了第二题(Windows程序)、第三题(Android应用)和第四题(Python程序)的加密逻辑并给出了解密代码。其中,第二题通过分析核心函数发现数据异或加密;第三题在逆向分析后找到关键的flag字符串;第四题则通过反编译和AI翻译字节码来还原加密算法。 综合评分: 85 文章分类: CTF,WEB安全,二进制安全,移动安全,安全工具


AES S-Box

S_BOX = [     0x63, 0x7C, 0x77, 0x7B, 0xF2, 0x6B, 0x6F, 0xC5, 0x30, 0x01, 0x67, 0x2B,     0xFE, 0xD7, 0xAB, 0x76, 0xCA, 0x82, 0xC9, 0x7D, 0xFA, 0x59, 0x47, 0xF0,     0xAD, 0xD4, 0xA2, 0xAF, 0x9C, 0xA4, 0x72, 0xC0, 0xB7, 0xFD, 0x93, 0x26,     0x36, 0x3F, 0xF7, 0xCC, 0x34, 0xA5, 0xE5, 0xF1, 0x71, 0xD8, 0x31, 0x15,     0x04, 0xC7, 0x23, 0xC3, 0x18, 0x96, 0x05, 0x9A, 0x07, 0x12, 0x80, 0xE2,     0xEB, 0x27, 0xB2, 0x75, 0x09, 0x83, 0x2C, 0x1A, 0x1B, 0x6E, 0x5A, 0xA0,     0x52, 0x3B, 0xD6, 0xB3, 0x29, 0xE3, 0x2F, 0x84, 0x53, 0xD1, 0x00, 0xED,     0x20, 0xFC, 0xB1, 0x5B, 0x6A, 0xCB, 0xBE, 0x39, 0x4A, 0x4C, 0x58, 0xCF,     0xD0, 0xEF, 0xAA, 0xFB, 0x43, 0x4D, 0x33, 0x85, 0x45, 0xF9, 0x02, 0x7F,     0x50, 0x3C, 0x9F, 0xA8, 0x51, 0xA3, 0x40, 0x8F, 0x92, 0x9D, 0x38, 0xF5,     0xBC, 0xB6, 0xDA, 0x21, 0x10, 0xFF, 0xF3, 0xD2, 0xCD, 0x0C, 0x13, 0xEC,     0x5F, 0x97, 0x44, 0x17, 0xC4, 0xA7, 0x7E, 0x3D, 0x64, 0x5D, 0x19, 0x73,     0x60, 0x81, 0x4F, 0xDC, 0x22, 0x2A, 0x90, 0x88, 0x46, 0xEE, 0xB8, 0x14,     0xDE, 0x5E, 0x0B, 0xDB, 0xE0, 0x32, 0x3A, 0x0A, 0x49, 0x06, 0x24, 0x5C,     0xC2, 0xD3, 0xAC, 0x62, 0x91, 0x95, 0xE4, 0x79, 0xE7, 0xC8, 0x37, 0x6D,     0x8D, 0xD5, 0x4E, 0xA9, 0x6C, 0x56, 0xF4, 0xEA, 0x65, 0x7A, 0xAE, 0x08,     0xBA, 0x78, 0x25, 0x2E, 0x1C, 0xA6, 0xB4, 0xC6, 0xE8, 0xDD, 0x74, 0x1F,     0x4B, 0xBD, 0x8B, 0x8A, 0x70, 0x3E, 0xB5, 0x66, 0x48, 0x03, 0xF6, 0x0E,     0x61, 0x35, 0x57, 0xB9, 0x86, 0xC1, 0x1D, 0x9E, 0xE1, 0xF8, 0x98, 0x11,     0x69, 0xD9, 0x8E, 0x94, 0x9B, 0x1E, 0x87, 0xE9, 0xCE, 0x55, 0x28, 0xDF,     0x8C, 0xA1, 0x89, 0x0D, 0xBF, 0xE6, 0x42, 0x68, 0x41, 0x99, 0x2D, 0x0F,     0xB0, 0x54, 0xBB, 0x16 ]

标准PNG文件头

PNG_HEADER = [0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A]

defbytestouint64le(array):     returnint.frombytes(array, byteorder=”little”)

defuint64tobytesle(value):     value &= 0xFFFFFFFFFFFFFFFF     array = value.tobytes(8, byteorder=”little”, signed=False)     return array

defrol64(value, shift):     return ((value << shift) & 0xFFFFFFFFFFFFFFFF) | (value >> (64 – shift))

defror64(value, shift):     return (value >> shift) | ((value << (64 – shift)) & 0xFFFFFFFFFFFFFFFF)

反推key

defreversekey(lastkey):     invSBox = [0] * len(SBOX)     for i inrange(len(SBOX)):         invSBox[S_BOX[i]] = i

    key = lastkey     for  inrange(8):         low = key & 0xFF         hi = invS_Box[low]         key = (key >> 8) | ((hi << 56) & 0xFFFFFFFFFFFFFFFF)

    return ror64(key, 3)

数据非线性混合

defmix_qword(buf, mixdata, offset):

    v5 = bytestouint64le(buf[0:8])     v7 = rol64(v5, 3)     for  inrange(8):         hi = (v7 >> 56) & 0xFF         low = SBOX[hi]         v7 = ((v7 << 8) & 0xFFFFFFFFFFFFFFFF) | low     buf[0:8] = uint64tobytesle(v7)     tmp = mixdata[offset : offset + 8]

    for i inrange(8):         mixdata[offset + i] = buf[i] ^ buf[i + 16] ^ mixdata[offset + i]

    buf[16:24] = tmp

逆向求解

definverse():     premix = PNGHEADER[0:8]     curmix = FILEDATA[8:16]     nexmix = FILEDATA[16:24]     curkey = [premix[i] ^ curmix[i] ^ nexmix[i] for i inrange(8)]     prekey = bytestouint64le(curkey)     return reversekey(pre_key)

解密整个文件,并提取flag

defdecryptfile(key):     buf = [0] * 32     buf[0:8] = list(uint64tobytesle(key))     buf[16:24] = FILEDATA[8:16]     filelen = len(FILEDATA) – 16     filelen &= 0xFFF8     filedata = FILEDATA[16 : 16 + filelen]     for i inrange(0, filelen, 8):         mixqword(buf, filedata, i)

    withopen(“flag_decrypted.png”, “wb”) as f:         f.write(bytes(filedata))

    str = bytes(filedata).decode(“utf-8″, errors=”ignore”)

    result = re.findall(r”flag{\S+?}”, str)     print(result)

defmain():     checknum = inverse()     print(“checknum=” + hex(checknum))     decryptfile(checknum)

if name == “main“:     main()

* 最后展示一下王铁锤的logo,奇怪好像在哪里见过呢

### 第九题(Web中级)

* “如呼吸一般轻松,是产品的设计目标。”,这才是本题的精华

 复制代码 隐藏代码 // 如此之校验,让我不能呼吸,心疼我电脑 asyncfunctioncheckCode(code, expectedHash) {     const enc = newTextEncoder()     let current = enc.encode(code)

    for (let i = 0; i < 0x2026; i++) {         current = await crypto.subtle.digest(‘SHA-256’, current)     }

    const hashArray = Array.from(newUint8Array(current))     const hashHex = hashArray.map(b => b.toString(16).padStart(2, ‘0’)).join(”)

    return hashHex === expectedHash }

* 此函数用来重新产生flag

 复制代码 隐藏代码

    checkboxText.addEventListener(‘click’, async () => {         const uidInput = document.getElementById(‘uid’)

        if (!uidInput.value) {             uidInput.focus()             return         }

        const uid = parseInt(uidInput.value) || 0             const voice = document.getElementById(‘voice’).value

            try {                 const challenge = wasm_bindgen.gen(uid, voice) // 此函数是产生语音的地方                 currentHash = challenge.h

                audio.src = URL.createObjectURL(newBlob([challenge.a], { type: ‘audio/wav’ }))

                challengeView.style.display = ‘block’

                checkboxText.classList.remove(‘btn-important’)                 document.getElementById(‘verifyBtn’).classList.add(‘btn-important’)

                while (n = w.nextNode()) n.data.includes) && (n.remove(), i = 0x2026)

                audio.play().catch(e =>console.warn(“Auto-play blocked:”, e))

                document.getElementById(‘verifyInput’).focus()

                checkboxText.innerText = “重新生成语音验证码”

            } catch (e) {                 console.error(e)             }         })

* 继续跟进,会跟到wasm中的gen函数,函数非常的长,这里就不贴了
* 核心的流程大概是把uid与一些特定的随机数,然后进行HMAC计算hash,最后再进行base64产生字符
* base64码表在内存中的地址是固定的1295903,可以在内存中直接查看a-zA-Z0-9?!
* 最后算出来的这个base64值就是语音播报的内容,后面应该是合成语言的过程,没有分析
* 所以,只需要将这个base64的值给dump出来就可以了

 复制代码 隐藏代码

i32.load8_u offset=1295903 // 这个值是base64码表的位置 local.set $var8 local.get $var3 i32.load offset=416 local.get $var0 i32.eq if   local.get $var3   i32.const 416   i32.add   call $func39 end local.get $var3 //var3中保存了内存的基址,抓包发现是定值1047920 i32.load offset=420 //这个是地址偏移量 local.get $var6 // var6是索引后的偏移,地址的计算法方是[[var3+420] + var6] i32.add local.get $var8 //此处是查完了base64表的值 i32.store // 此处保存回内存 local.get $var3 local.get $var0 i32.const 1 i32.add local.tee $var0 i32.store offset=424 local.get $var2 i32.const 6


 复制代码 隐藏代码       local.get $var0       i32.const 1       i32.add       local.tee $var0       i32.store offset=424       br $label15     end     i32.const 4     i32.const 200     call $func65     unreachable   end   i32.const 1   i32.const 37   call $func65   unreachable end $label1 local.get $var3 // 在此处下断比较合适,这地方刚好计算完50个字符 i32.load offset=420 local.set $var6 local.get $var0 if (result i32)

* 计算内存偏移的核心部分,用AI给翻译了一下,这块注意一下,不要给AI太多的数据,要不AI会直接崩溃

 复制代码 隐藏代码 // 这是一个代码块,用于错误处理或提前退出 {     // 分配内存并检查分配结果     ptr = allocate(37, 1);     if (ptr != NULL) {         // 第一部分:数据准备和初始化

        // 对var0进行字节拆分和异或操作         // 从var3+83到var3+80读取4个字节,分别与var0的4个字节异或         byte0 = var0 ^ memory[var3+80];         byte1 = (var0 >> 8) ^ memory[var3+81];         byte2 = (var0 >> 16) ^ memory[var3+82];         byte3 = (var0 >> 24) ^ memory[var3+83];

        // 将结果存入分配的内存         ptr[0] = byte0;         ptr[1] = byte1;         ptr[2] = byte2;         ptr[3] = byte3;

        // 复制var3+80处的数据到ptr+4         memcpy(ptr+4, var3+80, 8);  // 复制8字节         memcpy(ptr+12, var3+88, 8); // 复制8字节         ptr[20] = memory[var3+96];  // 复制1字节

        // 将异或结果存回var3+100到var3+103         memory[var3+100] = byte3;         memory[var3+101] = byte2;         memory[var3+102] = byte1;         memory[var3+103] = byte0;

        // 分配栈空间         stack = malloc(352);

        // 初始化栈空间为0         memset(stack, 0, 352);

        // 从内存地址1295967复制14字节到栈空间         memcpy(stack, 1295967, 14);

        // 将栈空间的数据复制到var3+416处         memcpy(var3+416, stack, 64);

        // 释放栈空间         free(stack);

        // 对var3+416开始的64字节进行异或操作(常数54)         for(i=0; i<64; i+=4) {             memory[var3+416+i] ^= 54;             memory[var3+416+i+1] ^= 54;             memory[var3+416+i+2] ^= 54;             memory[var3+416+i+3] ^= 54;         }

        // 初始化哈希状态和调用哈希函数         memcpy(var3+480, 1295984, 32);  // 复制32字节         var3+512 = 1;  // 设置计数器         hash_function(var3+480, var3+416, 1);  // 调用哈希函数

        // 再次对var3+416开始的64字节进行异或操作(常数106)         for(i=0; i<64; i+=4) {             memory[var3+416+i] ^= 106;             memory[var3+416+i+1] ^= 106;             memory[var3+416+i+2] ^= 106;             memory[var3+416+i+3] ^= 106;         }

        // 初始化另一个哈希状态并调用哈希函数         memcpy(var3+520, 1295984, 32);  // 复制32字节         var3+552 = 1;  // 设置计数器         hash_function(var3+520, var3+416, 1);  // 调用哈希函数

        // 复制哈希结果         memcpy(var3+560, var3+480, 80);  // 复制80字节         memcpy(var3+600, var3+520, 80);  // 复制80字节

        // 从var3+560复制152字节到var3+256         memcpy(var3+256, var3+560, 152);

        // 初始化var3+336处的65字节为0         memset(var3+336, 0, 65);

        // 从var3+256复制152字节到var3+104         memcpy(var3+104, var3+256, 152);

        // 处理数据块         offset = memory[var3+248];  // 获取当前偏移         if(offset >= 43) {             // 如果偏移>=43,处理剩余数据             remaining = 64 – offset;             if(remaining != 0) {                 memcpy(var3+184+offset, ptr, remaining);             }             var3+136 += 1;  // 增加计数器             hash_function(var3+104, var3+184, 1);  // 调用哈希函数

            if(offset != 43) {                 memcpy(var3+184, ptr+remaining, offset-43);             }             newoffset = 0;         } else {             // 如果偏移<43,直接复制数据             memcpy(var3+184+offset, ptr, 21);             newoffset = offset + 21;         }

        // 更新偏移         memory[var3+248] = new_offset;

        // 从var3+104复制152字节到var3+256         memcpy(var3+256, var3+104, 152);

        // 在缓冲区的适当位置添加0x80         bufferoffset = memory[var3+400];         buffer[var3+336+bufferoffset] = 0x80;

        // 计算消息长度并进行字节序转换         length = bufferoffset * 8;  // 转换为比特数         // 进行复杂的字节序转换操作         lengthbytes = converttobigendian64(length);

        // 处理填充         if(bufferoffset != 63) {             // 如果缓冲区不满,填充0             fillcount = 63 – bufferoffset;             if(fillcount != 0) {                 memset(var3+336+bufferoffset+1, 0, fillcount);             }

            if(bufferoffset <= 56) {                 // 如果长度可以放在当前块                 hashfunction(var3+256, var3+336, 1);                 // 准备最终块                 memset(var3+560, 0, 80);                 var3+616 = lengthbytes;  // 存储长度                 hashfunction(var3+256, var3+560, 1);             } else {                 // 如果长度需要下一个块                 var3+392 = lengthbytes;  // 存储长度                 hashfunction(var3+256, var3+336, 1);             }         } else {             // 缓冲区已满的情况             var3+392 = lengthbytes;  // 存储长度             hashfunction(var3+256, var3+336, 1);         }

        // 重置缓冲区偏移         memory[var3+400] = 32;

        // 对哈希状态进行字节序转换         for(i=0; i<8; i++) {             word = (int)(var3+256+i*4);             // 转换为大端序             bigendianword = ((word << 24) & 0xFF000000) |                               ((word << 8) & 0x00FF0000) |                               ((word >> 8) & 0x0000FF00) |                               ((word >> 24) & 0x000000FF);             *(int)(var3+336+i4) = bigendianword;         }

        // 处理最终的哈希块         length = (longlong)(var3+328) * 8;  // 转换为比特数         lengthbytes = converttobigendian_64(length | 0x100);

        // 准备填充         memset(var3+368, 0, 17);         memory[var3+368] = 0x80;         var3+392 = length_bytes;

        // 调用最终的哈希函数         hash_function(var3+296, var3+336, 1);

        // 获取哈希结果并转换为大端序         hash0 = (int)(var3+296);         hash1 = (int)(var3+300);         hash2 = (int)(var3+304);         hash3 = (int)(var3+308);

        // 将结果存回分配的内存         (int)(ptr+21) = converttobigendian32(hash0);         (int)(ptr+25) = converttobigendian32(hash1);         (int)(ptr+29) = converttobigendian32(hash2);         (int)(ptr+33) = converttobigendian32(hash3);

        // 第二部分:类似Base64编码

        // 分配输出缓冲区         output = allocate(200, 4);         if(output != NULL) {             var3+416 = 50;  // 设置块大小             var3+420 = output;  // 设置输出指针             var3+424 = 0;  // 设置输出索引

            inputindex = 0;             bitbuffer = 0;             bitcount = 0;             inputptr = ptr;

            // 处理输入数据             for(byteindex=0; byteindex<37; byteindex++) {                 // 读取一个字节                 byte = inputptr[byte_index];

                // 添加到位缓冲区                 bitbuffer = (bitbuffer << 8) | byte;                 bit_count += 8;

                // 处理完整的6位组                 while(bitcount >= 6) {                     // 提取6位                     index = (bitbuffer >> (bit_count – 6)) & 0x3F;

                    // 检查是否需要调用函数                     if(var3+416 == inputindex) {                         callfunction(var3+416);                     }

                    // 从表中查找并存储                     tablevalue = lookuptable[1295903 + index];                     output[inputindex] = tablevalue;                     inputindex++;                     var3+424 = inputindex;

                    bit_count -= 6;                 }             }

            // 处理剩余的位             if(bitcount > 0) {                 // 提取剩余的位                 index = (bitbuffer << (6 – bit_count)) & 0x3F;

                // 检查是否需要调用函数                 if(var3+416 == inputindex) {                     callfunction(var3+416);                 }

                // 从表中查找并存储                 tablevalue = lookuptable[1295903 + index];                 output[inputindex] = tablevalue;                 inputindex++;                 var3+424 = inputindex;             }

            // 块结束         } else {             // 内存分配失败             error(4, 200);         }     } else {         // 内存分配失败         error(1, 37);     } }

* 直接查内存就可以定位到flag了,顺便写了个dump的函数

 复制代码 隐藏代码 __exports.Dump = function(){     ptr = 1048340;     len = 4;     buffer = getArrayU8FromWasm0(ptr,len);     ptr = 0;     for (let i = 0; i < buffer.length; i++) {         console.log(“buffer[“+i+”]=”+buffer[i]);         ptr = (ptr << 8) | buffer[buffer.length – i – 1];     }     console.log(“Address:”+ptr);     strbuf = getStringFromWasm0(ptr,200);     const codestr = strbuf.replace(/[^a-zA-Z0-9?!]/g, ”);     console.log(“CodeLen:”+codestr.length);     console.log(“CheckCode:”+codestr); } “`

  • 断点断下来后,就可以直接调试窗口中运行此函数,即可得到50字节的flag
  • 总之做这道题有点费劲,不太懂JS,完全现学现卖
  • 也不知道用啥工具来进行分析,此题全作仍赖AI的翻译,解的题的方法也比较笨,欢迎指点

总结

  • 今年中级题比较多,而且题出的也比较有意思,作者们都是有故事的人啊
  • 今年开始学习了python,但是不是很熟,题解也尽量都用python开写的,写的不好,多多指点
  • 个人来讲中级题免强能做,但是做出来还是比较费劲,由其代码中加入一些混淆以后,对我的干扰还是很大的,不知道有没有大佬讲讲如何在IDA中去混淆,让代码可读性更好一些
  • 解第七题的时候,一开始一直想着是不是需要暴力破解,写题解的时候,我才发现,作者有提醒,暴力不可取,哎,作者用心良苦啊,耐何我才看到,呵呵
  • 还有那个Android代码的混淆,对于我来说直的是无从下手,目前也搜不到好的方法可以反混淆,不知道有没有大佬能提点一二
  • 今年中级题遇到的最大的挑战就是,Android中的反调试,一点都不会破,IDA一附加上去程序就崩,我也泪崩
  • 总之,通过跟着论坛的贴子一点一点的积累,从啥也不懂的小白,到今年,自我感觉成绩还不错,明年若有机会,我会继续加油努力
  • 最后,希望吾爱论坛越做越好,吾爱百年,百年吾爱!

-官方论坛

www.52pojie.cn

👆👆👆

公众号设置“星标”,不会错过新的消息通知

开放注册、精华文章和周边活动等公告


免责声明:

本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。

任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。

本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我

本文转载自:吾爱破解论坛 吾爱pojie 吾爱pojie《【2026春节解题红包】WriteUP(2、3、4、5、6、7、9)》

评论:0   参与:  0