NepCTF2023-九龙拉棺wp

本文最后更新于:几秒前

综合难度: easy
考点:多线程,父子进程,共享内存,硬件断点检测,int3断点反调试,xtea加密算法.

出题思路/解题思路/杂记:

本题使用了8个线程+1个新的进程,实现了flag字符串的验证。

看似是 8 个线程,实则8个线程均通过一个变量控制,所以使用的是一个线程。只要跟随变量(nCurLevel)的变化,走到相应的线程,即可了解程序逻辑。

  • 4个线程用来解密子进程exe,.分别使用 Rc4 -> Base32 -> Base58 -> Base64. 解密。因为使用的是线程,只能通过
  • 1个线程用于内存写入exe文件,并执行子进程。
  • 1个线程用于反调试
    • 反调试动态读取.text(第一个段)并通过累加的方式,不断读取对比的方式,来实现反调试。如果程序一开始就存在断点,则该反调试无效,若在调试中突然增加int3断点,则该程序直接退出。
  • 1个线程用于给输入的字符串加密,(部分字符, 使用了 xtea算法。若对比失败,则直接退出经常,不经过最后的输出验证结果。
  • 1个线程用于获取最终的对比结果。用来判断是否验证成功。

检测硬件断点

大部分的线程中穿插着检查硬件断点的检测。

读取当前线程的Context, 查看Dr7 是否为一个正常值。若不为正常的值,则判断该线程设置了硬件断点。会尝试使用SetThreadContext 来设置Dr7 取消硬件断点。若无法取消,则直接退出进程。

Main 函数:

首先提权,需要一些权限。

然后启动8个线程。

紧接着接收用户输入字符串

将用户输入字符串放入 myShareMemory 中。

初始化当前应执行线程ID.使用Wait等待线程执行完毕。

用户输入字符的存储形式解释

myShareMemory 作为保存用户输入数据的地方,为了实现双进程间的数据交换,我直接使用的CreateFileMapping。并且通过 随机数来取一个随机偏移存放用户输入字符串。

并且初始化操作先于 main 函数执行。

导出子进程

了解完子进程的加密算法,可以找到原始数据导出,即可快速获得子进程

子进程解析

子进程使用纯汇编编写。

获取Kernel32dll的基地址

获取Kernel32dll的基本信息

遍历Kernel32.dll的导出函数名称表

找到最终的函数地址:

子进程验证部分

子进程主要 取MapViewOfFile, 获取父进程的共享内存。 Check2 函数去检查输入是否正确。

Check2 同样使用 XTEA算法进行加密后比较,注意长度,0x40. 部分长度与 Check1主进程的检查线程数据重复,需要筛选一下。(故意设置的)

如果验证成功,会遍历主进程的内存,将结果写回到主进程。(为了增加难度).

最后清理变量。

编写解密函数(XTEA):

取Check2的 0x40 + Check1 的 最后16位即为 最后的flag。
NepCTF{c9cdnwdi3iu41m0pv3x7kllzu8pdq6mt9n2nwjdp6kat8ent4dhn5r158iz2f0cmr0u7yxyq}

总结:

因为主进程使用的Release 模式,导致主进程代码被大量优化,可能对新手不太友好。不过我相信认真逆向下来的话,一定能学到不少东西。


本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!