前言:
一直以来使用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/urandom
的0x20
字节相同且与开始的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()