-
-
[原创]逆向分析(二)--首次针对苹果M1芯片的恶意软件GoSearch22
-
发表于: 2024-1-5 12:08 7753
-
即使绕过了反调试的检测,当恶意软件在调试器中继续执行时,它仍然会终止运行。
事实证明,恶意的GoSearch22二进制文件包含更多的反调试逻辑。这个额外的反调试逻辑是通过sysctl API实现的,换句话说,通过这个API,恶意软件可以查询它自己是否正在被调试。
在恶意软件的核心逻辑中,我们发现了对sysctl API的调用:
由于恶意软件广泛使用混淆,通过静态分析,不容易看出此API调用将导致恶意软件过早终止。然而,在调试器中,如果我们允许调用sysctl,恶意软件很快就会退出。这时候就要想,该怎么样才能让恶意软件愉快地继续运行下去呢?
我们先来看看sysctl函数的声明:
可以调用该函数来检索各种信息,包括有关当前流程状态的详细信息。这些细节包括在程序调试时将设置的一些东西。下面的C代码说明了这一点:
这段C代码首先声明了一个kinfo_proc结构体,并为这个结构体的大小设置了一个变量。然后,它声明并初始化一个带有值(CTL_KERN等)的数组,这些值将指示sysctl函数检索有关正在运行的进程的信息。
然后调用sysctl函数并填充传入的kinfo_proc结构。这包括设置一个p_flag成员,该成员可以针对p_tracked常量进行测试,以确定正在运行的进程是否正在调试(跟踪)。
如下所示,检查恶意软件的反汇编显示,恶意软件也试图以同样的方式检测它是否正在被调试。在反汇编中,我们发现前面提到的sysctl API的调用位于0x0000000100054ff0。这个调用是通过BL指令进行的,它简化了函数调用。
调用前的两个指令通过MOVZ指令将第五个和第六个参数(寄存器x4和x5)初始化为零。
继续往回看,在地址0x0000000100054fe4,第二个参数设置为4。
由于该参数是一个32位整数,因此使用w1寄存器(x1寄存器的32位部分)。将32位零寄存器(WZR)与4按位或运算将寄存器也设置为4。从函数声明中,我们知道第二个参数是name数组的大小,即4。
第一个、第三个和第四个参数(寄存器x0、x2、x3)都是通过LDUR指令初始化的。
第一个参数(X0)用指向数组的指针初始化。在调试器中,我们可以输出它的值(通过x/4wx命令)。
对应于CTL_KERN (0x1)、KERN_PROC (0xe)、KERN_PROC_PID (0x1)和当前恶意软件的进程标识符(pid),这些值将指示sysctl函数检索有关恶意软件运行进程的信息。
最后,第四个参数(x3)是用kinfo_proc结构的大小(0x288)初始化的。这个初始化需要四条指令:
首先,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):
首先,(通过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位。
以下是arm64指令,从恶意软件的反汇编中提取,用于提取p_tracked位:
在前面的指令中,恶意软件首先通过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位没有设置。
如果设置了p_tracked位,则恶意软件将提前退出,因为这表明恶意软件正在调试中。
为了绕过第二个反调试检查,以便我们的调试会话可以不受阻碍地继续,我们可以(再一次)跳过有问题的调用。具体来说,一旦恶意软件要执行分支指令来调用sysctl,我们就可以将程序计数器更改为下一条指令。由于没有进行sysctl调用,kinfo_proc结构保持未初始化(带有零),这意味着对p_tracked标志的任何检查都将返回0 (false)。
在这一点上,我们已经确定并挫败了恶意软件的anti-debugging logic。这意味着我们的调试会话可以不受限制地继续进行,这一点很重要,因为其他anti-analysis logic仍然潜伏着。
(lldb)
continue
Process 667 resuming
Process 667 exited with status = 0 (0x00000000)
(lldb)
continue
Process 667 resuming
Process 667 exited with status = 0 (0x00000000)
...
0x0000000100054fe8 movz x4,
#0x0
0x0000000100054fec movz x5,
#0x0
0x0000000100054ff0 bl sysctl
...
0x0000000100054fe8 movz x4,
#0x0
0x0000000100054fec movz x5,
#0x0
0x0000000100054ff0 bl sysctl
int
sysctl(
int
*name, u_int namelen,
void
*oldp,
size_t
*oldlenp,
void
*newp,
size_t
newlen);
int
sysctl(
int
*name, u_int namelen,
void
*oldp,
size_t
*oldlenp,
void
*newp,
size_t
newlen);
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
}
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))
{
[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)