qiling_lab_aarch64

前言:

一直以来使用qemu来仿真路由器固件,有时候针对不同固件需要做大量配置文件的修改,并且有时侯我只需要针对某一个服务进行分析
由此萌发了学习qiling框架的念头,一直以来对该框架的跨平台跨架构有所耳闻,劫持系统调用,hook函数也十分方便
本文qilinglab主要就是为熟悉qiling框架而写,程序分别有11个challenge,每完成一个challenge后都会显示solved

challenge1:

这部分我们需要将0x1337地址处的值改为1337
该地址所在内存未被映射
首先我们需要映射一块内存,且需要满足页对齐
然后写入映射内存

from qiling import *
from qiling.const import *
import sys

def challenge1(ql):  
    ql.mem.map(0x1000,0x1000,info='challenge1') #注意内存页对齐
    ql.mem.write(0x1337,ql.pack64(1337))

if __name__ == "__main__":
    path=['qilinglab-aarch64']
	rootfs="../../rootfs/arm64_linux"
    ql=Qiling(path,rootfs,verbose=QL_VERBOSE.OFF)
    
    challenge1(ql)
    
    ql.run()
    

challenge2:

该部分挑战调用uname函数,将相关信息存入一个utsname的结构体中
我们需要在结构体对应部分(sysname && version)写入指定值绕过比较,最后返回正常值来完成挑战
系统调用返回时,结构体相关数据成员的地址可由sp寄存器值加上偏移得出
在系统调用完之后执行hook函数,QL_INTERCEPT.EXIT
使用 ql.arch.regs.sp得到sp中存储的数据成员地址即可

def my_uname_on_exit_hook(ql,*args):
#     struct utsname {
#     char sysname[65];
#     char nodename[65];
#     char release[65];   
#     char version[65];
#     char machine[65];
#     char domainname[65];
# };

    # 00100d88 e1 03 01 91     add        x1,sp,#0x40
    # 00100d8c 21 68 60 38     ldrb       w1,[x1, x0, LSL ]
    # 00100d90 e0 3b 80 b9     ldrsw      x0,[sp, #local_1b8]
    # 00100d94 e2 23 07 91     add        x2,sp,#0x1c8
    # 00100d98 40 68 60 38     ldrb       w0,[x2, x0, LSL ]
    # 00100d9c 3f 00 00 6b     cmp        w1,w0

    sys_addr = ql.arch.regs.sp+0x40
    
    # print("sys_addr: ",hex(sys_addr))
    
    ql.mem.write(sys_addr,b"QilingOS")

    # 00100dd8 e1 0f 04 91     add        x1,sp,#0x103
    # 00100ddc 21 68 60 38     ldrb       w1,[x1, x0, LSL ]
    # 00100de0 e0 3f 80 b9     ldrsw      x0,[sp, #local_1b4]
    # 00100de4 e2 63 07 91     add        x2,sp,#0x1d8
    # 00100de8 40 68 60 38     ldrb       w0,[x2, x0, LSL ]
    # 00100dec 3f 00 00 6b     cmp        w1,w0

    version_addr = ql.arch.regs.sp+0x103
    
    # print("version_addr: ",hex(version_addr))
    
    ql.mem.write(version_addr,b"ChallengeStart")
    
def challenge2(ql):
    ql.os.set_syscall("uname",my_uname_on_exit_hook,QL_INTERCEPT.EXIT)
    

challenge3:

该部分挑战从/dev/urandom中先读入0x20个字节,再读1字节
然后调用getrandom获取0x20字节
这里需要让getrandom获取的0x20字节与从/dev/urandom0x20字节相同且与开始的1字节不同
我们这里通过自定义文件对象和自己hook一个getrandom来实现
hook函数对照原函数实现

自定义文件对象是通过QlFsMappedObject实现的,其中至少需要一个close(),其他系统调用根据需求,这里我们需要从/dev/urandom中读入数据到栈上,我们可以自定义一个read的系统调用,区分单独读入1字节的情况

class Fake_urandom(QlFsMappedObject):
    def read(self,size):
        if size == 1:
            return b"\x32"
        else:
            return b"\x00"*size
        
    def close(self):
        return 0
    
def getrandom_hook(ql,buf,size,flags):
    ql.mem.write(buf,b"\x00"*size)
    return 0
    
def challenge3(ql):
    ql.add_fs_mapper('/dev/urandom',Fake_urandom())
    ql.os.set_syscall("getrandom",getrandom_hook)

challenge4:

如图,该循环是不成立的,为了完成challenge4我们需要进入循环中
我们通过hook cmp指令处,使w0>0即可
在执行cmp这条指令之前我们会调用hook函数,所以最后w0>0,我们也得以进入该循环

def hook_cmp_address(ql):
    ql.arch.regs.w0=1

def challenge4(ql):
    base_addr=ql.mem.get_lib_base(ql.path)
    cmp_addr=base_addr+0xfe0
    ql.hook_address(hook_cmp_address,cmp_addr)

challenge5:

第一个循环赋值
需要绕过第二个循环,最简单方法使rand()返回值为0即可
之前我们的challenge3是劫持系统调用,该部分我们劫持外部函数,使用ql.os.set_api()来劫持

def rand_hook(ql):
    ql.arch.regs.w0=0

def challenge5(ql):
    ql.os.set_api("rand",rand_hook)

challenge6:

跳出死循环,跟challenge4一样在cmp之前hook修改寄存器值即可跳出

def hook_cmp1_address(ql):
    ql.arch.regs.w0=0

def challenge6(ql):
    base_addr=ql.mem.get_lib_base(ql.path)
    cmp_addr=base_addr+0x1118
    ql.hook_address(hook_cmp1_address,cmp_addr)

challenge7:

跳出sleep(0xffffffff)
跟challenge5一样,该部分我们只需劫持sleep函数调用即可

def sleep_hook(ql,*args):
    ql.arch.regs.w0=0
    
def challenge7(ql):
    ql.os.set_api("sleep",sleep_hook)

challenge8:

该部分需要我们向check上写值,确定该地址的方法是通过magic_num去寻找
使用ql.mem.search,如果找到多个地址,则根据本题距离magic_num_addr-0x8处的字符串是否为"Random data"来确定,大致结构如下:

"Random data"
magic_num
check
def magic_hook(ql):
    magic=0x3dfcd6ea00000539
    magic_addrs=ql.mem.search(ql.pack64(magic))
    for i in magic_addrs:
        malloc_addr=i-0x8
        malloc_data=ql.mem.read(malloc_addr,24)
        string_addr,_,check_addr=struct.unpack('QQQ',malloc_data)
        ### 这一行将 malloc_data 分成 3个64位8字节无符号整数 传给前面的几个变量
        if ql.mem.string(string_addr) == 'Random data':
            ql.mem.write(check_addr,b"\x01")
            break
        
def challnege8(ql):
    base_addr=ql.mem.get_lib_base(ql.path)
    hook_addr=base_addr+0x11e0
    ql.hook_address(magic_hook,hook_addr)    

challenge9:

使用ql.os.set_api来hook函数调用,让tolower失效即可

def tolower_hook(ql,*args):
    return
    
def challenge9(ql):
    ql.os.set_api("tolower",tolower_hook)

challenge10:

该部分从/proc/self/cmdline读入数据并与qilinglab进行比较
劫持文件系统即可,注意返回值为bytes

class Fake_cmdline(QlFsMappedObject):
    def read(self,size):
        return b"qilinglab"
    
    def close(self):
        return 0
    
def challenge10(ql):
    ql.add_fs_mapper('/proc/self/cmdline',Fake_cmdline())

challenge11:

写了两种方法,一种是在指定地址处hook

def hook_lsr_address(ql):
    ql.arch.regs.x0=0x1337<<0x10

def challenge11(ql):
    base_addr=ql.mem.get_lib_base(ql.path)
    lsr_addr=base_addr+0x13f8
    ql.hook_address(hook_lsr_address,lsr_addr)

还有一种是hook指令,当运行到该条指令时(通过机器码识别指令),会直接劫持该条指令

def hook_mrs_code(ql,address,size):
    if ql.mem.read(address,size) == b"\x00\x00\x38\xd5" :
        ql.arch.regs.x0=0x1337<<0x10
        ql.arch.regs.pc+=4
        

def challenge11_1(ql):
    ql.hook_code(hook_mrs_code)

由于所有该指令都将被劫持,带来的性能开销比较大,个人比较青睐第一种方法,即hook地址

sum:

from qiling import *
from qiling.const import *
from qiling.os.mapper import QlFsMappedObject
import struct
import sys
from udbserver import udbserver

def challenge1(ql):  
    ql.mem.map(0x1000,0x1000,info='challenge1') #注意内存页对齐
    ql.mem.write(0x1337,ql.pack64(1337))


def my_uname_on_exit_hook(ql,*args):
#     struct utsname {
#     char sysname[65];
#     char nodename[65];
#     char release[65];   
#     char version[65];
#     char machine[65];
#     char domainname[65];
# };

    # 00100d88 e1 03 01 91     add        x1,sp,#0x40
    # 00100d8c 21 68 60 38     ldrb       w1,[x1, x0, LSL ]
    # 00100d90 e0 3b 80 b9     ldrsw      x0,[sp, #local_1b8]
    # 00100d94 e2 23 07 91     add        x2,sp,#0x1c8
    # 00100d98 40 68 60 38     ldrb       w0,[x2, x0, LSL ]
    # 00100d9c 3f 00 00 6b     cmp        w1,w0

    sys_addr = ql.arch.regs.sp+0x40
    
    # print("sys_addr: ",hex(sys_addr))
    
    ql.mem.write(sys_addr,b"QilingOS")

    # 00100dd8 e1 0f 04 91     add        x1,sp,#0x103
    # 00100ddc 21 68 60 38     ldrb       w1,[x1, x0, LSL ]
    # 00100de0 e0 3f 80 b9     ldrsw      x0,[sp, #local_1b4]
    # 00100de4 e2 63 07 91     add        x2,sp,#0x1d8
    # 00100de8 40 68 60 38     ldrb       w0,[x2, x0, LSL ]
    # 00100dec 3f 00 00 6b     cmp        w1,w0

    version_addr = ql.arch.regs.sp+0x103
    
    # print("version_addr: ",hex(version_addr))
    
    ql.mem.write(version_addr,b"ChallengeStart")
    
def challenge2(ql):
    ql.os.set_syscall("uname",my_uname_on_exit_hook,QL_INTERCEPT.EXIT)
    

class Fake_urandom(QlFsMappedObject):
    def read(self,size):
        if size == 1:
            return b"\x32"
        else:
            return b"\x00"*size
        
    def close(self):
        return 0
    
def getrandom_hook(ql,buf,size,flags):
    ql.mem.write(buf,b"\x00"*size)
    return 0
    
def challenge3(ql):
    ql.add_fs_mapper('/dev/urandom',Fake_urandom())
    ql.os.set_syscall("getrandom",getrandom_hook)

def hook_cmp_address(ql):
    ql.arch.regs.w0=1

def challenge4(ql):
    base_addr=ql.mem.get_lib_base(ql.path)
    cmp_addr=base_addr+0xfe0
    ql.hook_address(hook_cmp_address,cmp_addr)

def rand_hook(ql):
    ql.arch.regs.w0=0

def challenge5(ql):
    ql.os.set_api("rand",rand_hook)

def hook_cmp1_address(ql):
    ql.arch.regs.w0=0

def challenge6(ql):
    base_addr=ql.mem.get_lib_base(ql.path)
    cmp_addr=base_addr+0x1118
    ql.hook_address(hook_cmp1_address,cmp_addr)

def sleep_hook(ql,*args):
    ql.arch.regs.w0=0
    
def challenge7(ql):
    ql.os.set_api("sleep",sleep_hook)

def magic_hook(ql):
    magic=0x3dfcd6ea00000539
    magic_addrs=ql.mem.search(ql.pack64(magic))
    for i in magic_addrs:
        malloc_addr=i-0x8
        malloc_data=ql.mem.read(malloc_addr,24)
        string_addr,_,check_addr=struct.unpack('QQQ',malloc_data)
        ### 这一行将 malloc_data 分成 3个64位8字节无符号整数 传给前面的几个变量
        if ql.mem.string(string_addr) == 'Random data':
            ql.mem.write(check_addr,b"\x01")
            break
        
def challnege8(ql):
    base_addr=ql.mem.get_lib_base(ql.path)
    hook_addr=base_addr+0x11e0
    ql.hook_address(magic_hook,hook_addr)            

def tolower_hook(ql,*args):
    return
    
def challenge9(ql):
    ql.os.set_api("tolower",tolower_hook)

class Fake_cmdline(QlFsMappedObject):
    def read(self,size):
        return b"qilinglab"
    
    def close(self):
        return 0
    
def challenge10(ql):
    ql.add_fs_mapper('/proc/self/cmdline',Fake_cmdline())

def hook_lsr_address(ql):
    ql.arch.regs.x0=0x1337<<0x10

def challenge11(ql):
    base_addr=ql.mem.get_lib_base(ql.path)
    lsr_addr=base_addr+0x13f8
    ql.hook_address(hook_lsr_address,lsr_addr)
    
def hook_mrs_code(ql,address,size):
    if ql.mem.read(address,size) == b"\x00\x00\x38\xd5" :
        ql.arch.regs.x0=0x1337<<0x10
        ql.arch.regs.pc+=4
        

def challenge11_1(ql):
    ql.hook_code(hook_mrs_code)


if __name__ == "__main__":
    path=['qilinglab-aarch64']
    rootfs="../../rootfs/arm64_linux"
    ql=Qiling(path,rootfs,verbose=QL_VERBOSE.OFF)

    # udbserver(ql.uc,1234,0)
    
    challenge1(ql)
    challenge2(ql)
    challenge3(ql)
    challenge4(ql)
    challenge5(ql)
    challenge6(ql)
    challenge7(ql)
    challnege8(ql)
    challenge9(ql)
    challenge10(ql)
    challenge11(ql)
    # challenge11_1(ql)
    
    ql.run()