网鼎杯2022 青龙组 Fakeshell

2k words

Analyse

文件分析

exe程序,可以通过exeinfo对其架构进行查看

可以看到程序为64位,检测出存在UPX加壳

解题

1. 脱壳

存在UPX加壳,尝试使用UPX脱壳工具,但是均未果

这道题采用了一些技巧防止直接脱壳,也是一个可以学习的知识点,具体可以看参考链接

使用010editor打开Re2.exe,观察发现

这里需要对UPX加壳后的数据特征有一定的了解。UPX在加壳时,会修改文件的结构,以便在运行时动态地解压原始的代码和数据。为实现这一点,UPX会在加壳后的文件中添加一些新的段。

  • UPX0:这个段包含了被压缩的原始数据,在文件中,这个段的大小通常是0,因为它只是一个占位符,当程序运行时,这个段会被解压到内存中

  • UPX1:这个段包含了被压缩的原始代码。和UPX0一样,它在文件中的大小也可能是0,但在程序运行时,这个段会被压到内存中

  • UPX2:(如果存在的话)这个段包含了UPX的解压缩代码。当加了壳的程序启动时,这段代码首先会被执行,以解压UPX0,UPX1的数据和代码

所以这里UPX脱壳失败的原因应该是将区段名UPX0UPX1修改成了FUK0FUK1

通过010editor或者winhex修改这两个段名,保存

就可以对程序进行正常的UPX脱壳了,比如我们可以使用kali在终端upx -d进行脱壳

2. 逆向分析

拖进IDA分析

main_0()

看到程序会先要求用户输入flag,并最多获取200个字符。接着进入sub_1400112235()这个函数,并且传入刚才用户输入的字符,如果返回为0,则输出"Wrong."并且程序退出了。那么sub_1400112235()这个函数应该会对用户输入进行判断

跟进sub_1400112235

可以看到v7 = j___intrinsic_setjmp(Buf, v6);,这个j__intrinsic_setjmp是什么作用呢,我们来看看setjumplongjmp

setjmplongjmp是C语言中实现非本地跳转的两个函数,它们允许程序从一个函数直接跳转到另一个函数,而不需要正常的函数调用和返回机制。这种跳转机制通常用于处理错误和异常的情况。(现代编程相对较少,可能会导致代码难以维护)

  • setjmp

    • setjmp 用于保存当前的执行环境(包括程序计数器,堆栈指针,和其他重要的寄存器)

    • 首次调用setjmp时返回0

    • 后续使用longjmp跳转到这个setjmp的位置,setjmp将返回一个非零值,这个值是longjmp提供

  • longjmp

    • longjmp主要用于恢复之前由setjmp保存的执行环境,这导致程序从setjmp的位置重新开始执行,就好像setjmp刚刚返回的一样
    • longjmp需要两个参数,一个是jmp_buf(保存的环境变量)和一个整数值,这个值将成为setjmp的返回值(不为01)

那么这里首次setjmp,返回值(v7)为0,则会进入sub_140011230()

绕了半天, 终于发现这里会对用户输入的字符串进行长度判断,要求长度等于20。接着对字符串每一位和0x66进行异或,异或完跳转到setjmp处,并返回20

好的,sub_1400112235这条线跟完了,接着回到main_0()

程序又进行了setjmp v7 = j__intrinsic_setjmp(stru_14001D330)

并且进入sub_1400111E5()

for ( j = 0i64; j < v8; ++j )
    sub_140011276((unsigned int)j, (*(char *)(j + a1) + 10) ^ 0x50u);

注意这里,v8也就是a2,也就是20。程序将字符串的逐个+10后和0x50异或,再传递给函数sub_140011276()

这个函数将异或的值与dword_14001D000进行比较,如果存在不同,则会跳转到上一个setjmp的位置,并且返回1

查看dword_14001D000

4Bh, 48h, 79h, 13h, 45h, 30h, 5Ch, 49h, 5Ah, 79h, 13h, 70h, 6Dh, 78h, 13h, 6Fh, 48h, 5Dh, 2 dup(64h)

综合一系列的过程,总结一下,该程序实现了对字符串的长度判断(20),接着与0x66进行了异或,每位加上10再与0x50进行了异或,最后与dword_14001D000进行比较

3. Reverse script
key = [0x4B, 0x48, 0x79, 0x13, 0x45, 0x30, 0x5C, 0x49, 0x5A, 0x79, 0x13, 0x70, 0x6D, 0x78, 0x13, 0x6F, 0x48, 0x5D, 0x64, 0x64]
flag = []
for i in range(20):
    flag.append(((key[i] ^ 0x50) - 10) ^ 0x66)
for i in flag:
    print(chr(i), end="")
#why_m0dify_pUx_SheLL