-
-
[原创]逆向分析(二)--首次针对苹果M1芯片的恶意软件GoSearch22
-
2024-1-5 12:08 4803
-
anti-debugging Logic (via sysctl)
即使绕过了反调试的检测,当恶意软件在调试器中继续执行时,它仍然会终止运行。
1 2 3 | (lldb) continue Process 667 resuming Process 667 exited with status = 0 (0x00000000) |
事实证明,恶意的GoSearch22二进制文件包含更多的反调试逻辑。这个额外的反调试逻辑是通过sysctl API实现的,换句话说,通过这个API,恶意软件可以查询它自己是否正在被调试。
在恶意软件的核心逻辑中,我们发现了对sysctl API的调用:
1 2 3 4 | ... 0x0000000100054fe8 movz x4, #0x0 0x0000000100054fec movz x5, #0x0 0x0000000100054ff0 bl sysctl |
由于恶意软件广泛使用混淆,通过静态分析,不容易看出此API调用将导致恶意软件过早终止。然而,在调试器中,如果我们允许调用sysctl,恶意软件很快就会退出。这时候就要想,该怎么样才能让恶意软件愉快地继续运行下去呢?
我们先来看看sysctl函数的声明:
1 2 | int sysctl( int *name, u_int namelen, void *oldp, size_t *oldlenp, void *newp, size_t newlen); |
可以调用该函数来检索各种信息,包括有关当前流程状态的详细信息。这些细节包括在程序调试时将设置的一些东西。下面的C代码说明了这一点:
1 2 3 4 5 6 7 8 9 10 11 12 | struct kinfo_proc processInfo = {0}; size_t size = sizeof ( struct kinfo_proc); int name[4] = {0} name[0] = CTL_KERN; name[1] = KERN_PROC; name[2] = KERN_PROC_PID; name[3] = getpid(); sysctl(name, 4, &processInfo, &size, NULL, 0); if (0 != (processInfo.kp_proc.p_flag & P_TRACED)) { //debugger detected } |
这段C代码首先声明了一个kinfo_proc结构体,并为这个结构体的大小设置了一个变量。然后,它声明并初始化一个带有值(CTL_KERN等)的数组,这些值将指示sysctl函数检索有关正在运行的进程的信息。
然后调用sysctl函数并填充传入的kinfo_proc结构。这包括设置一个p_flag成员,该成员可以针对p_tracked常量进行测试,以确定正在运行的进程是否正在调试(跟踪)。
如下所示,检查恶意软件的反汇编显示,恶意软件也试图以同样的方式检测它是否正在被调试。在反汇编中,我们发现前面提到的sysctl API的调用位于0x0000000100054ff0。这个调用是通过BL指令进行的,它简化了函数调用。
0x0000000100054fcc ldur x8, [x29, var_B8] 0x0000000100054fd0 movz w9, #0x288 0x0000000100054fd4 str x9, [x8] 0x0000000100054fd8 ldur x0, [x29, var_C8] 0x0000000100054fdc ldur x3, [x29, var_B8] 0x0000000100054fe0 ldur x2, [x29, var_A8] 0x0000000100054fe4 orr w1, wzr, #0x4 0x0000000100054fe8 movz x4, #0x0 0x0000000100054fec movz x5, #0x0 0x0000000100054ff0 bl sysctl
调用前的两个指令通过MOVZ指令将第五个和第六个参数(寄存器x4和x5)初始化为零。
1 2 | 0x0000000100054fe8 movz x4, #0x0 0x0000000100054fec movz x5, #0x0 |
继续往回看,在地址0x0000000100054fe4,第二个参数设置为4。
1 | 0x0000000100054fe4 orr w1, wzr, #0x4 |
由于该参数是一个32位整数,因此使用w1寄存器(x1寄存器的32位部分)。将32位零寄存器(WZR)与4按位或运算将寄存器也设置为4。从函数声明中,我们知道第二个参数是name数组的大小,即4。
第一个、第三个和第四个参数(寄存器x0、x2、x3)都是通过LDUR指令初始化的。
1 2 3 | 0x0000000100054fd8 ldur x0, [x29, var_C8] 0x0000000100054fdc ldur x3, [x29, var_B8] 0x0000000100054fe0 ldur x2, [x29, var_A8] |
第一个参数(X0)用指向数组的指针初始化。在调试器中,我们可以输出它的值(通过x/4wx命令)。
1 2 | (lldb) x /4wx $x0 0x16fe86de0: 0x00000001 0x0000000e 0x00000001 0x00000475 |
对应于CTL_KERN (0x1)、KERN_PROC (0xe)、KERN_PROC_PID (0x1)和当前恶意软件的进程标识符(pid),这些值将指示sysctl函数检索有关恶意软件运行进程的信息。
最后,第四个参数(x3)是用kinfo_proc结构的大小(0x288)初始化的。这个初始化需要四条指令:
1 2 3 4 5 6 | 0x0000000100054fcc ldur x8, [x29, var_B8] 0x0000000100054fd0 movz w9, #0x288 0x0000000100054fd4 str x9, [x8] 0x0000000100054fd8 ldur x0, [x29, var_C8] ... 0x0000000100054fdc ldur x3, [x29, var_B8] |
首先,LDUR指令将变量(var_B8)的地址加载到X8寄存器中。然后通过MOVZ指令将kinfo_proc结构(0x288)的值移到W9寄存器中。STR (store)指令然后将这个值(用X9)存储在X8寄存器的地址中。最后,通过LDUR指令将该值加载到X3寄存器中,以完成参数初始化。
发出sysctl调用后,恶意软件检查现在填充的kinfo_proc结构。具体来说,它检查p_flag标志是否设置了p_tracked位。如果设置了这个位,恶意软件就知道它正在被调试,并将提前终止运行。
下面的指令从已填充的kinfo_proc结构体中提取p_flag成员(其地址存储在堆栈中的专用位置,反汇编程序标记为var_90):
1 2 3 | 0x000000010005478c ldur x8, [x29, var_90] 0x0000000100054790 ldr w8, [x8, #0x20] 0x0000000100054794 stur w8, [x29, var_88] |
首先,(通过LDUR指令)将kinfo_proc结构的地址加载到X8寄存器中。然后(通过LDR指令)32位p_flag成员(位于结构中的偏移量0x20处)被加载到W8寄存器中。然后通过STUR命令将该值存储在var_88变量中。
稍后,恶意软件检查p_flags标志是否设置了p_tracked位(p_tracked是常量0x00000800,这意味着它的第11位设置为0x1)。在调试会话中,我们可以确认,正如预期的那样,p_flags标志确实设置了p_tracked位。
1 | (lldb) p /t $w8 0b00000000000000000101100000000110 |
以下是arm64指令,从恶意软件的反汇编中提取,用于提取p_tracked位:
1 2 3 | 0x0000000100055428 ldur w8, [x29, var_88] 0x000000010005542c ubfx w8, w8, #0xb, #0x1 0x0000000100055430 sturb w8, [x29, var_81] |
在前面的指令中,恶意软件首先通过LDUR指令将保存的p_flag值(var_88)加载到W8寄存器中。然后执行UBFX指令来提取p_tracked位。UBFX指令接受一个目标寄存器(W8)、一个源寄存器(W8)、位域索引(0xb或11d)和宽度(1,表示单个位)。换句话说,它从p_flag中获取偏移量为11的位,这是p_tracked位。然后通过STURB指令保存提取的p_tracked位。稍后,它检查(比较)以确保P_TRACE位没有设置。
1 2 | 0x00000001000550ac ldurb w8, [x29, var_81] 0x00000001000550b0 cmp w8, #0x0 |
如果设置了p_tracked位,则恶意软件将提前退出,因为这表明恶意软件正在调试中。
为了绕过第二个反调试检查,以便我们的调试会话可以不受阻碍地继续,我们可以(再一次)跳过有问题的调用。具体来说,一旦恶意软件要执行分支指令来调用sysctl,我们就可以将程序计数器更改为下一条指令。由于没有进行sysctl调用,kinfo_proc结构保持未初始化(带有零),这意味着对p_tracked标志的任何检查都将返回0 (false)。
在这一点上,我们已经确定并挫败了恶意软件的anti-debugging logic。这意味着我们的调试会话可以不受限制地继续进行,这一点很重要,因为其他anti-analysis logic仍然潜伏着。
[培训]《安卓高级研修班(网课)》月薪三万计划,掌握调试、分析还原ollvm、vmp的方法,定制art虚拟机自动化脱壳的方法