-
-
[原创]KCTF 2023 第三题 秘密计划 解题过程
-
2023-9-6 14:00 3523
-
1 2 3 4 | 下载后发现exe文件本身还挺大的,除了一个txt说明文件外还带了一个code.dat数据文件。首先查看exe文件本身的构成,在资源节发现一个 zip 包,dump下来后发现里面是一堆xml和image图片,是程序的主界面相关资源。里面没有代码之类的感兴趣的东西。 直接运行发现需要输入 32 位字符串才有验证的提示,而且输入无法多于 32 位,因此随意输入一组字符串进行测试。 ida打开后发现字符串没有加密,里面可以搜索到code.dat,并定位到sub_40DA90这个函数。加载调试后发现会报异常,根据报异常的线程反溯到sub_405F10函数。该函数调用CreateTimerQueueTimer启动反调试,而调用该函数前需要判断是否成功调用CreateTimerQueue函数,因此修改CreateTimerQueue的返回值就可以实现去掉反调试。 去掉反调试后继续跟踪sub_40DA90,发现调用该函数的代码下方启动了虚拟机并运行了一段arm代码。 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | uc_mem_map(( int )uc, 0 , 0 , ( int )sub_A00000, 7 , ( int )lpAddress); v21 = v41; uc_mem_write(( int )uc, 0x43000i64 , ( int )v40, v41); v22 = Src; if ( v56 > = 0x10 ) v22 = (void * * )Src[ 0 ]; uc_mem_write(( int )uc, 0x4033i64 , ( int )v22, v55); if ( uc_emu_start(a1, v17, ( int )uc, 0x43000 , 0 , v21 + 0x43000 , 0 , 0i64 , 0 ) ) { v9 = v47; v23 = (void (__stdcall * )(HWND, UINT, WPARAM, LPARAM))SendMessageW; } else { memset(wParam, 0 , sizeof(wParam)); uc_mem_read(( int )uc, 0x14390u , 0 , (LPBYTE)wParam, 0x20u ); v9 = v47; v23 = (void (__stdcall * )(HWND, UINT, WPARAM, LPARAM))SendMessageW; v48 = 1 ; / / 虚拟机执行成功 SendMessageW((HWND)v47[ 7 ], 0x47Eu , (WPARAM)wParam, 0 ); / / 保存结果 } sub_44E300(( int )uc, 0 , 0 , ( int )sub_A00000); uc_close((char * )uc); |
1 | 该虚拟机内存分为 3 段,其中 0x43000 地址处为代码,保持不变, 0x4033 地址处为输入,和界面上的输入相关,而且变化很大, 0x14390 地址处为输出,运行后读出 0x20 字节大小并通过 0x47E 消息发送给主程序。继续分析虚拟机的代码。 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 | void __noreturn sub_4363C() { int v0; / / r1 char * v1; / / r0 const char * v2; / / r2 int v3; / / r5 int v4; / / r3 int v5; / / r4 v0 = 12 ; while ( 1 ) { v1 = "e1b85b27d6bcb05846c18e6a48f118e89f0c0587140de9fb3359f8370d0dba08" ; v2 = "4fc82b26aecb47d2868c4efbe3581732a3e7cbcc6c2efb32062c08170a05eeb8" ; v3 = 0 ; - - v0; do { v4 = * (_DWORD * )v1; v5 = * (_DWORD * )v2; if ( + + v3 > = 16 ) { if ( "4fc82b26aecb47d2868c4efbe3581732a3e7cbcc6c2efb32062c08170a05eeb8" = = "6749dae311865d64db83d5ae75bac3c9e36b3aa6f24caba655d9682f7f071023" ) { dword_14390 = 1 ; dword_143A8 = 1 ; } LABEL_9: JUMPOUT( 0x43754 ); } v1 + = 4 ; v2 + = 4 ; } while ( v4 = = v5 ); if ( !v0 ) goto LABEL_9; } } |
1 2 3 4 | 代码内包含了 24 段字符串,并且分成了 12 组,每组两个。虽然貌似将输入和每组的第一个字符串进行了循环比较,但是在结尾处还是确认了输入字符串必须为“ 6749dae311865d64db83d5ae75bac3c9e36b3aa6f24caba655d9682f7f071023 ”才会将输出的dword_14390和dword_143A8置 1 。也就是说这段代码的实际功能是判断输入是否为指定字符串。 于是反推输入字符串的来源,根据输入字符串的硬件断点,将算法聚焦到了sub_436900函数。该函数在对输入进行运算时还使用到了 2 个固定数组,通过分析,可以确定这是一个sha256函数。而arm的输入“e1b85b27d6bcb05846c18e6a48f118e89f0c0587140de9fb3359f8370d0dba08”正是界面输入“ 12345678901234567890123456789012 ”的sha256值。 感觉这个题目从逆向题转向了 HASH 碰撞方向。于是对照sha256的源码和相关文章进行分析,发现 32 字节的sha256基本不可逆,因此很快放弃了这条思路。判断答案只能从exe自身寻找(或者去付费 HASH 网站寻找?)。 重新回到arm代码中提取的 24 个字符串,感觉到目标字符串在字符串数组中的位置有些奇怪,而且分组分得有些刻意。于是对每个字符串分别进行了正向和反向的sha256。 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | 字符串 对应注释 e0bc614e4fd035a488619799853b075143deea596c477b8dc077e309c0fe42e9 sha256(sha256( 1 )) 6b86b273ff34fce19d6b804eff5a3f5747ada4eaa22f1d49c01e52ddb7875b4b sha256( 1 ) d8bdf9a0cb27a193a1127de2924b6e5a9e4c2d3b3fe42e935e160c011f3df1fc sha256(sha256( 2 )) d4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35 sha256( 2 ) 6749dae311865d64db83d5ae75bac3c9e36b3aa6f24caba655d9682f7f071023 目标字符串 ea96b41c1f9365c2c9e6342f5faaeab2a44471efe1e65a2356a974646d2588fd 未知 5b65712d565c1551340998102d418ceccb35db8dbfb45f9041c4cae483d8717b sha256(sha256( 3 )) 4e07408562bedb8b60ce05c1decfe3ad16b72230967de01f640b7e4729b49fce sha256( 3 ) 033c339a7975542785be7423a5b32fa8047813689726214143cdd7939747709c sha256(sha256( 4 )) 4b227777d4dd1fc61c6f884f48641d02b4d121d3fd328cb08b5531fcacdabf8a sha256( 4 ) c81d40dbeed369f1476086cf882dd36bf1c3dc35e07006f0bec588b983055487 sha256(sha256( 5 )) ef2d127de37b942baad06145e54b0c619a1f22327b2ebbcfbec78f5564afe39d sha256( 5 ) 9e259b7f6b4c741937a96a9617b3e6b84e166ff6e925e414e7b72936f5a2a51f sha256(sha256( 6 )) e7f6c011776e8db7cd330b54174fd76f7d0216b612387a5ffcfb81e6f0919683 sha256( 6 ) 1048f03db5d45f654b955eae20d84b72673680fb13b318e7da22e8dce58df21c sha256(sha256( 7 )) 7902699be42c8a8e46fbbb4501726517e86b22c56a189f7625a6da49081b2451 sha256( 7 ) 8f0703d406fdb0ea8011d5de342c3aca62214758a8a2b5b8a4e9f1c8c6c42462 sha256(sha256( 8 )) 2c624232cdd221771294dfbb310aca000a0df6ac8b66b696d90ef06fdefb64a3 sha256( 8 ) 182b32359558eb092511b7166867503ddd83fbe5b42f2545e1903016e721393d sha256(sha256( 9 )) 19581e27de7ced00ff1ce50b2047e7a567c76b1cbaebabe5ef03f7c3017bb5b7 sha256( 9 ) fe5f0fc640cbbc113406f042d08cc60ba784c775f7c3299985665323c5fbcdc4 sha256(sha256( 10 )) 4a44dc15364204a80fe80e9039455cc1608281820fe2b24f1e5233ade6af1dd5 sha256( 10 ) 4fc82b26aecb47d2868c4efbe3581732a3e7cbcc6c2efb32062c08170a05eeb8 sha256( 11 ) 1ba586c0b89202f7307b61f1229330978a843afc98589ffc6a62f209225d3528 sha256(sha256( 11 )) |
1 2 3 | 根据分析结果可以发现每组字符串之间均有一个sha256的关系,因此可以推测目标字符串和未知的字符串字节也存在相应关系,直接计算sha256( "ea96b41c1f9365c2c9e6342f5faaeab2a44471efe1e65a2356a974646d2588fd" )发现结果不符合目标字符串,考虑到输入只有 32 个字节,因此将字符串截断后发现sha256( "ea96b41c1f9365c2c9e6342f5faaeab2" )即为结果。输入程序输入框可得验证成功的提示。 提交flag时发现提交ea96b41c1f9365c2c9e6342f5faaeab2不正确,最后提交完整的字符串ea96b41c1f9365c2c9e6342f5faaeab2a44471efe1e65a2356a974646d2588fd显示成功。 ps:虽然直接把flag放到程序内存中也不是不可以,但是Reverse题最后变得有点像Misc了。全题通篇对flag只有sha256一种运算,是否可以考虑在sha256的算法前后多加点变化呢? |
[CTF入门培训]顶尖高校博士及硕士团队亲授《30小时教你玩转CTF》,视频+靶场+题目!助力进入CTF世界
最后于 2023-9-7 12:20
被kanxue编辑
,原因:
赞赏
他的文章