当前位置: 首页 > news >正文

某国赛CTF逆向题目Writeup:re2

目录

  • 脱壳
  • 第一层EXE
  • 第二层EXE
  • FLAG

脱壳

010打开

UPX改了特征码,版本5.1.0

直接拖到ida分析不出来

这里我们选择动态调试脱壳,先找OEP,f9运行到入口

触发TLS回调函数断点,停在了ret上

64位UPX壳中不会用一个明显的跨段jmp指令跳向OEP,而是把OEP的地址压入堆栈,然后执行一条ret指令,这样CPU就会把堆栈顶部的OEP地址当作返回地址弹出来,并跳转过去

我们直接f8过去

此时进入了系统DLL地址,不用管,执行到用户代码

标准的UPX壳保存环境的方式,步过4次

此时当前的栈顶已经被修改了,内存中转到RSP,在栈顶位置下硬件断点,然后一直f9到触发硬件断点


此时正在恢复保存的寄存器状态,最后的jmp即是尾部跳转,直接f4过去,然后步过一下

可以看到已经解密完成,0x4014E0就是OEP

打开Scylla插件搜索IAT

找到两个IAT大小,因为这个exe只有几十kb,528字节的输入表正好,选择否,然后获取输入表

dump之后修复得到最终脱壳后的程序

第一层EXE

通过ida逆向

可以看到这里是获取了用户的输入并和Str2进行比较,Str 为 NGeQwv8eCRpINEcO


标准的windows可执行文件头

如果正确就会通过sub_401660函数进行base64解码并向缓存路径内写入一个随机名称的exe,直接执行原文件输入密码就能得到

第二层EXE

运行发现需要输入key

继续通过ida逆向这个文件,通过main函数找到处理逻辑(sub_401550函数)

分析可知,这里的逻辑也是接收用户输入的key,然后通过nullsub_3函数判断key是否正确

中间的do-while步骤是优化后的strlen函数实现,输入长度不超过240

现在我们的思路就是分析nullsub_3函数,不过nullsub在ida中代表为只有一个ret的空函数,说明针对反编译器做了修改,我们需要看看到底是不是真的空函数

跟进后发现是一个奇怪的hello节,应该就是题目提示了

这里我们选择通过x64dbg进行动态调试,首先加载后找到主逻辑函数地址

查找Enter the key关键字引用

ida中找到地址


输出Faild和Success中间的call就是,直接跳转然后下断点0x401623

然后一直f9运行到等待用户输入,返回窗口随便输入一串key:12345678

跟进


这里可以看到明显是有内容的,分析逻辑如下:

1、逐字节读取刚刚输入的key
2、计算离16字节的填充并补齐,经典的PKCS#7填充
3、为404CB0函数准备参数(rcx、r9、r8)

基本可以判断是AES或者SM4

运行到404F86(调用404CB0函数之前)拿到三个参数

Key: C2 30 12 AB 39 10 18 33 F8 ED 4E 46 8D A1 5D 8D 8C FB F0 72 68 99 DC 7C 84 6E 7E CF 32 BB DA F8 IV: AE BA 0D BB CA 26 7F 99 06 ED 7C 70 E3 8D 8B 11 CipherText: 9B 5E 1E 8F D7 C3 43 62 A2 37 86 C0 CE 3D 3C F4 C3 B6 88 FF 3C 9C 13 D2 BB 6F 49 CE FF 59 A2 5C 36 E4 61 9E 60 61 C3 BB 3F 63 AF 00 3B 3D 8D A7

然而AES和SM4解密都失败了,说明题目对标准加密流程进行了魔改

跟进404CB0函数内部:

这里往栈上压入了一个参数E(十进制14),而加密轮数14的分组密码只有AES,我们上一步解密失败了,最大的可能就是魔改了标准的S盒

往下看,压栈后调用了404B60函数,直接跟进

这里就是AES的核心算法,先把内存地址 4071E0 装载到了 r14 寄存器里,然后把明文字节当做索引 rdx,去 r14 也就是 4071E0 这个地址里查表替换,这就是 AES 加密中最核心的字节替换操作,那么4071E0地址就是魔改S盒的地址

然而进去后发现就是标准的S盒

接下来就该排查行移位ShiftRows和列混淆MixColumns步骤有没有问题

这里调用了2个函数,分别跟进看一下

404070:

0, 5, 10, 15、13, 4, 9, 14、8, 13, 2, 7

正确对应行移位后的矩阵排列

404190:

0x1B 是 AES 有限域乘法的标准多项式常数,2 和 3 也是标准的乘法矩阵

到这里我们总结一下:

1、标准S盒
2、标准行移位
3、标准列混淆

还剩最后一个密钥扩展算法,这里我们想要重新分析算法就比较复杂了,但是最终是要生成好放进内存,直接在密钥生成器之后在内存中获取即可

重新运行跟进到404B60内部:

在字节替换操作之前,调用了一个404940函数,多半就是轮密钥的生成逻辑,运行到此处步过一次,而生成的轮密钥随后就被存放在了rbx寄存器指向的地址,内存窗口中找到

正好240字节

C2 30 12 AB 39 10 18 33 F8 ED 4E 46 8D A1 5D 8D 8C FB F0 72 68 99 DC 7C 84 6E 7E CF 32 BB DA F8 B4 67 53 88 8D 77 4B BB 75 9A 05 FD F8 3B 58 70 CD 19 9A 23 A5 80 46 5F 21 EE 38 90 13 55 E2 68 58 FF 16 F5 D5 88 5D 4E A0 12 58 B3 58 29 00 C3 A7 BC F9 0D 02 3C BF 52 23 D2 87 C2 30 87 65 AA 5C B2 BA F1 89 3A E7 BF 29 28 BF 0C 71 01 BF CF 04 C0 F1 87 06 FC 4E D5 25 2E C9 17 15 A9 AC BD 9A 23 C0 A8 13 19 27 17 3A 31 98 1B 4B 30 27 D4 B7 C4 3D CF B1 38 73 1A 94 16 BA 0D 81 BF 16 B0 8B 64 27 A4 98 7D 00 B3 A2 4C 98 A8 E9 7C BF 7C A9 D4 35 DF 18 EC 46 C5 8C FA FC C8 0D 45 EA 78 E4 E3 9B 73 7C 9E 9B C0 DE D2 03 68 37 AE BC 14 33 30 50 25 2B DC 16 E0 A7 26 EA 28 AA 63 00 50 2E 80 C8 DF 52 1E 53 1F 8C CC 50 77 BB 62 EC 63

编写自定义轮密钥的解密脚本:

# 自定义的轮密钥expanded_key_hex="C2 30 12 AB 39 10 18 33 F8 ED 4E 46 8D A1 5D 8D 8C FB F0 72 68 99 DC 7C 84 6E 7E CF 32 BB DA F8 B4 67 53 88 8D 77 4B BB 75 9A 05 FD F8 3B 58 70 CD 19 9A 23 A5 80 46 5F 21 EE 38 90 13 55 E2 68 58 FF 16 F5 D5 88 5D 4E A0 12 58 B3 58 29 00 C3 A7 BC F9 0D 02 3C BF 52 23 D2 87 C2 30 87 65 AA 5C B2 BA F1 89 3A E7 BF 29 28 BF 0C 71 01 BF CF 04 C0 F1 87 06 FC 4E D5 25 2E C9 17 15 A9 AC BD 9A 23 C0 A8 13 19 27 17 3A 31 98 1B 4B 30 27 D4 B7 C4 3D CF B1 38 73 1A 94 16 BA 0D 81 BF 16 B0 8B 64 27 A4 98 7D 00 B3 A2 4C 98 A8 E9 7C BF 7C A9 D4 35 DF 18 EC 46 C5 8C FA FC C8 0D 45 EA 78 E4 E3 9B 73 7C 9E 9B C0 DE D2 03 68 37 AE BC 14 33 30 50 25 2B DC 16 E0 A7 26 EA 28 AA 63 00 50 2E 80 C8 DF 52 1E 53 1F 8C CC 50 77 BB 62 EC 63".replace(" ","")iv=bytes.fromhex("AEBA0DBBCA267F9906ED7C70E38D8B11")ct=bytes.fromhex("9B5E1E8FD7C34362A23786C0CE3D3CF4C3B688FF3C9C13D2BB6F49CEFF59A25C36E4619E6061C3BB3F63AF003B3D8DA7")expanded_key=bytes.fromhex(expanded_key_hex)round_keys=[list(expanded_key[i:i+16])foriinrange(0,240,16)]# 标准S盒、逆S盒SBOX=[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]INV_SBOX=[0]*256foriinrange(256):INV_SBOX[SBOX[i]]=i# 逆向移位definv_shift_rows(s):return[s[0],s[13],s[10],s[7],s[4],s[1],s[14],s[11],s[8],s[5],s[2],s[15],s[12],s[9],s[6],s[3]]defgf_mult(a,b):p=0for_inrange(8):ifb&1:p^=a hi_bit_set=a&0x80a=(a<<1)&0xFFifhi_bit_set:a^=0x1bb>>=1returnpdefinv_mix_columns(s):out=[0]*16foriinrange(0,16,4):c=s[i:i+4]out[i]=gf_mult(0x0e,c[0])^gf_mult(0x0b,c[1])^gf_mult(0x0d,c[2])^gf_mult(0x09,c[3])out[i+1]=gf_mult(0x09,c[0])^gf_mult(0x0e,c[1])^gf_mult(0x0b,c[2])^gf_mult(0x0d,c[3])out[i+2]=gf_mult(0x0d,c[0])^gf_mult(0x09,c[1])^gf_mult(0x0e,c[2])^gf_mult(0x0b,c[3])out[i+3]=gf_mult(0x0b,c[0])^gf_mult(0x0d,c[1])^gf_mult(0x09,c[2])^gf_mult(0x0e,c[3])returnout# AES解密defdecrypt_block(block,round_keys):state=list(block)# 第一步:异或最后一轮密钥 (Round 14)state=[a^bfora,binzip(state,round_keys[14])]# 中间 13 轮逆向推导forround_idxinrange(13,0,-1):state=inv_shift_rows(state)state=[INV_SBOX[b]forbinstate]state=[a^bfora,binzip(state,round_keys[round_idx])]state=inv_mix_columns(state)# 最后一轮 (Round 0)state=inv_shift_rows(state)state=[INV_SBOX[b]forbinstate]state=[a^bfora,binzip(state,round_keys[0])]returnstate# CBC 解密链pt=bytearray()prev=list(iv)foriinrange(0,len(ct),16):block=ct[i:i+16]dec_block=decrypt_block(block,round_keys)pt_block=[a^bfora,binzip(dec_block,prev)]pt.extend(pt_block)prev=block# 去除PKCS#7填充pad_len=pt[-1]flag=pt[:-pad_len].decode('utf-8',errors='ignore')print(flag)

FLAG


得到flag:dart{c3d4f5cc-8aab-46ce-a188-2fc453f3b288}

http://www.jsqmd.com/news/494795/

相关文章:

  • 用ip命令替代过时的ifconfig和route命令
  • python-flask的公司企业产品检测报告管理系统 _00o61
  • 拆分管理化技术中的拆分计划拆分实施拆分验证
  • C/C++: 栈包含哪些数据信息
  • 免费查AI率网站对比:哪个检测结果最准确
  • 生成式AI在内容创作领域的技术实现与伦理思考
  • 组织技术矩阵式团队与功能式团队的管理效率对比
  • 读2025世界前沿技术发展报告153D打印技术(下)
  • AI代码工具采纳率:量化研发效能提升的核心方法与实现策略
  • L4级自动驾驶规模化商用前夕,为何“数字化主激光雷达+全固态补盲激光雷达”成为黄金组合?
  • 【BBF系列协议】TR181-1 TR069的设备数据模型
  • Java的java.lang.foreign.MemorySegment内存访问与对齐要求在不同平台
  • 安全测试入门:OWASP Top 10
  • 加解密篇 - 非对称加密算法 (RSA、DSA、ECC、DH)
  • 33.华为 OD-C 卷 200 分题目 5 - 项目排期(Java 实现)
  • 【安装】TortoiseGit 可视化界面 小乌龟 汉化
  • 电商行业的数据智能化趋势
  • 【BBF系列协议】TR181-2 TR369的设备数据模型
  • Python的继承与多态
  • CDial-GPT 开源项目使用教程
  • 嵌入式系统优化
  • 易通成稿www.no1paper.cn在代码中插入此成稿内网
  • 主板调速风扇电路设计
  • Redis 缓存穿透与防御方案实现
  • 2.7通用串行总线 USB Universal Serial Bus
  • 【RK3588开发记录】RK3588之opencv安装(解决libjasper-dev和mipi-csi2接口)
  • 用 RollCode 重构营销 H5 的开发协作逻辑
  • 程序设计-股票最大收益问题(Java)
  • ESXI主机安装Zabbix 6.2
  • 三相并网逆变器外环PI+内环模型预测MPC电流矢量控制仿真(带简要文档)