做完这些lab,对qiling的作用,这个framework的理解,更加的深,希望看到这里的读者可以自己去做一遍,不要纯看他人的wp,因为每道题都有着很多种解法,但是目标都是成功的hook或Hijack,Qiling还有着一些fuzz or 仿真的 example,我都会去做一遍的!同时接下来每周都有着很多考试,希望一切顺利~
Qiling是一个先进的二进制仿真框架,具有以下特点:
在qiling framework的github项目上还有几个示例demo,这里注意到了通过Qiling框架仿真模拟并对二进制程序进行hook可以更加方便的fuzz
要求在0x1337地址上写入0x1337
Qiling给出了这些Managing memory的方法
内存在被访问之前必须被映射。 map
方法将连续的内存区域绑定到指定位置,并设置其访问保护位。可以提供字符串标签以便在映射信息表上轻松识别
参数:-addr
- 请求的映射基地址,应该在页面粒度上;- size
- 映射大小(以字节为单位),必须是页面大小; - perms
- 保护位图的乘积,定义此内存范围是否可读、可写和/或可执行(可选); - info
- 将字符串标签设置为映射范围以方便识别(可选)
主要也是关注前两个参数,这里显然是执行 ql.mem.map(0x1337// 4096 * 4096 , 0x1000);
参数: - addr
- 要取消映射的区域基地址 - size
- 区域大小(以字节为单位)
如果请求的内存范围未完全映射,则引发: QlMemoryMappedError
从部分内存范围中搜索字符串,begin和end参数均是可选的,去除则是整个内存范围
从内存中读取
写入内存
要求在uname返回系统信息时的sysname == "QilingOS" and version == "ChallengeStart"
。
需要通过劫持结构体
POSIX 系统调用可以被挂钩以允许用户修改其参数 、改变返回值 或完全替换其功能 。系统调用可以通过其名称或编号进行挂钩,并在一个或多个阶段进行拦截: - QL_INTERCEPT.CALL - :当指定的系统调用即将被调用时,可用于完全替换系统调用功能;- QL_INTERCEPT.ENTER - :在进入系统调用之前;可用于篡改系统调用参数值 - QL_INTERCEPT.EXIT - :退出系统调用后,可能被用来篡改返回值
因此要通过ql.os.set_syscall中的QL_INTERCEPT.EXIT对返回值进行篡改
这里有个要注意的点,my_uname_ret的参数要能接受三个参数,不然会报错
这里看到qiling的一个example
Qiling都是通过一些函数,来进行hook的
Qiling的这些hook,都是一些在程序的hook,并不像正常的程序的输入输出,例如这里在退出的地方执行这个oxexit_hook函数(个人理解)
这里的第一个想法就是hook掉getrandom,控制其函数体,然后将虚拟路径/dev/urandom
映射到文件对象下
以下示例将虚拟路径/dev/urandom
映射到托管系统上现有的/dev/urandom
文件。当模拟程序访问/dev/random
时,将访问映射文件。
以下示例将虚拟路径/dev/random
映射到用户定义的文件对象,以允许对交互进行更细粒度的控制。请注意,映射对象扩展了QlFsMappedObject
。
注意到
这里其实是一个类,然后定义了一些函数,用self来代替/dev/urandom
这个对象
这里会是一个循环,我们想要的是使参数为真,而loc_E35块可以满足我们的要求,但是jl short loc_E35
是显然无法满足的,因为-4和-8的位置都被置为0了是相等的,而jl是小于跳转,因此要想办法跳转到loc_E35块上
读寄存器
还有一些跨架构寄存器的获取方法
Hook
挂钩具体地址。执行指定地址时将调用已注册的回调。
挂钩所有说明。注册的回调将在每个汇编指令执行之前调用
还有一些hook,例如挂钩一段代码、挂钩中断号以调用自定义函数、拦截特定类型的指令等等
我的想法是在
这个地址执行前,在eax里写入1,这样就使得jl条件达成,就成功跳转到成功片段
这里就是让rand的返回值为0即可
这里不是系统调用的劫持,而是劫持libc函数,这里看个例子
与系统调用一样,POSIX libc 函数可以以类似的方式挂钩,从而允许用户控制其功能。
这里会有一个无限循环,movzx eax, [rbp+var_5]
先eax零扩展赋值为1,之后test al, al
对al进行逻辑与的运算,结果存入ZF中,而jnz是ZF不为0则跳转,显然会有跳转,之后便是无限循环,我的想法 便是hook rax为0即可
这里有两种想法,第一种便是修改rdi,在call sleep前将rdi改为0,第二种便是hook掉sleep,直接return
这一题的target是**Unpack the struct and write at the target address.**解包结构体,然后写入目标地址
那么我的想法就是在malloc之后hook将rax返回值存入,但是我突然想到,之前我们不是刚学了如何搜索内存的吗,那么我们先通过搜索内存获得地址,然后再写入a1会不会更高端一点
第一个想法就是直接hook掉strcmp函数,或者也可以hook掉tolower函数
这里尽量hook tolower函数,不然下题就做不了啦~~~
想法就是hook掉strcmp,但是上个challenge已经成功hook掉strcmp了,因此就劫持cmdline成一个结构体,直接返回qilinglab即可
就是让esi==696C6951h && ecx==614C676Eh && eax=20202062h,就可以解决了,所以直接hook掉1195,使得这些寄存器为相应的值
如此便解决了所有的挑战
Method
Description
map
Map a memory region at a certain location so it become available for access
unmap
Reclaim a mapped memory region
unmap_all
Reclaim all mapped memory regions
map_anywhere
Map a memory region in an unspecified location
protect
Modify access protection bits of a mapped region (rwx)
find_free_space
Find an available memory region
is_available
Query whether a memory region is available
is_mapped
Query whether a memory region is mapped
ql.mem.
map
(addr:
int
, size:
int
, perms:
int
=
UC_PROT_ALL, info: Optional[
str
]
=
None
)
-
>
None
ql.mem.
map
(addr:
int
, size:
int
, perms:
int
=
UC_PROT_ALL, info: Optional[
str
]
=
None
)
-
>
None
ql.mem.unmap(addr:
int
, size:
int
)
-
>
None
:
ql.mem.unmap(addr:
int
, size:
int
)
-
>
None
:
address
=
ql.mem.search(b
"\xFF\xFE\xFD\xFC\xFB\xFA"
, begin
=
0x1000
, end
=
0x2000
)
address
=
ql.mem.search(b
"\xFF\xFE\xFD\xFC\xFB\xFA"
, begin
=
0x1000
, end
=
0x2000
)
ql.mem.read(address, size)
ql.mem.read(address, size)
ql.mem.write(address, data)
ql.mem.write(address, data)
def challenge1(ql):
ql.mem.map(0x1337
ql.mem.write(0x1337, b
"\x39\x05"
)
def challenge1(ql):
ql.mem.map(0x1337
ql.mem.write(0x1337, b
"\x39\x05"
)
unsigned
__int64
__fastcall challenge2(_BYTE *a1)
{
unsigned
int
v2;
int
v3;
int
v4;
int
v5;
struct
utsname name;
char
s[10];
char
v8[24];
unsigned
__int64
v9;
v9 = __readfsqword(0x28u);
if
( uname(&name) )
{
perror
(
"uname"
);
}
else
{
strcpy
(s,
"QilingOS"
);
s[9] = 0;
strcpy
(v8,
"ChallengeStart"
);
v8[15] = 0;
v2 = 0;
v3 = 0;
while
( v4 <
strlen
(s) )
{
if
( name.sysname[v4] == s[v4] )
++v2;
++v4;
}
while
( v5 <
strlen
(v8) )
{
if
( name.version[v5] == v8[v5] )
++v3;
++v5;
}
if
( v2 ==
strlen
(s) && v3 ==
strlen
(v8) && v2 > 5 )
*a1 = 1;
}
return
__readfsqword(0x28u) ^ v9;
}
unsigned
__int64
__fastcall challenge2(_BYTE *a1)
{
unsigned
int
v2;
int
v3;
int
v4;
int
v5;
struct
utsname name;
char
s[10];
char
v8[24];
unsigned
__int64
v9;
v9 = __readfsqword(0x28u);
if
( uname(&name) )
{
perror
(
"uname"
);
}
else
{
strcpy
(s,
"QilingOS"
);
s[9] = 0;
strcpy
(v8,
"ChallengeStart"
);
v8[15] = 0;
v2 = 0;
v3 = 0;
while
( v4 <
strlen
(s) )
{
if
( name.sysname[v4] == s[v4] )
++v2;
++v4;
}
while
( v5 <
strlen
(v8) )
{
if
( name.version[v5] == v8[v5] )
++v3;
++v5;
}
if
( v2 ==
strlen
(s) && v3 ==
strlen
(v8) && v2 > 5 )
*a1 = 1;
}
return
__readfsqword(0x28u) ^ v9;
}
from
qiling
import
Qiling
from
qiling.const
import
QL_INTERCEPT
def
my_syscall_write(ql: Qiling, fd:
int
, buf:
int
, count:
int
)
-
>
int
:
try
:
data
=
ql.mem.read(buf, count)
fobj
=
ql.os.fd[fd]
if
hasattr
(fobj,
'write'
):
fobj.write(data)
except
:
ret
=
-
1
else
:
ret
=
count
ql.log.info(f
'my_syscall_write({fd}, {buf:#x}, {count}) = {ret}'
)
return
ret
if
__name__
=
=
"__main__"
:
ql
=
Qiling([r
'rootfs/arm_linux/bin/arm_hello'
], r
'rootfs/arm_linux'
)
ql.os.set_syscall(
'write'
, my_syscall_write, QL_INTERCEPT.CALL)
ql.run()
from
qiling
import
Qiling
from
qiling.const
import
QL_INTERCEPT
def
my_syscall_write(ql: Qiling, fd:
int
, buf:
int
, count:
int
)
-
>
int
:
try
:
data
=
ql.mem.read(buf, count)
fobj
=
ql.os.fd[fd]
if
hasattr
(fobj,
'write'
):
fobj.write(data)
except
:
ret
=
-
1
else
:
ret
=
count
ql.log.info(f
'my_syscall_write({fd}, {buf:#x}, {count}) = {ret}'
)
return
ret
if
__name__
=
=
"__main__"
:
ql
=
Qiling([r
'rootfs/arm_linux/bin/arm_hello'
], r
'rootfs/arm_linux'
)
ql.os.set_syscall(
'write'
, my_syscall_write, QL_INTERCEPT.CALL)
ql.run()
unsigned
__int64
__fastcall challenge3(_BYTE *a1)
{
int
v2;
int
i;
int
fd;
char
v5;
char
buf[32];
char
v7[40];
unsigned
__int64
v8;
v8 = __readfsqword(0x28u);
fd = open(
"/dev/urandom"
, 0);
read(fd, buf, 0x20uLL);
read(fd, &v5, 1uLL);
close(fd);
getrandom((
__int64
)v7, 32LL, 1LL);
v2 = 0;
for
( i = 0; i <= 31; ++i )
{
if
( buf[i] == v7[i] && buf[i] != v5 )
++v2;
}
if
( v2 ==
' '
)
*a1 = 1;
return
__readfsqword(0x28u) ^ v8;
}
unsigned
__int64
__fastcall challenge3(_BYTE *a1)
{
int
v2;
int
i;
int
fd;
char
v5;
char
buf[32];
char
v7[40];
unsigned
__int64
v8;
v8 = __readfsqword(0x28u);
fd = open(
"/dev/urandom"
, 0);
read(fd, buf, 0x20uLL);
read(fd, &v5, 1uLL);
close(fd);
getrandom((
__int64
)v7, 32LL, 1LL);
v2 = 0;
for
( i = 0; i <= 31; ++i )
{
if
( buf[i] == v7[i] && buf[i] != v5 )
++v2;
}
if
( v2 ==
' '
)
*a1 = 1;
return
__readfsqword(0x28u) ^ v8;
}
from
qiling
import
Qiling
from
qiling.os.mapper
import
QlFsMappedObject
class
FakeUrandom(QlFsMappedObject):
def
read(
self
, size:
int
)
-
> bytes:
return
b
"\x04"
def
fstat(
self
)
-
>
int
:
return
-
1
def
close(
self
)
-
>
int
:
return
0
if
__name__
=
=
"__main__"
:
ql
=
Qiling([r
'rootfs/x86_linux/bin/x86_fetch_urandom'
], r
'rootfs/x86_linux'
)
ql.add_fs_mapper(r
'/dev/urandom'
, FakeUrandom())
ql.run()
from
qiling
import
Qiling
from
qiling.os.mapper
import
QlFsMappedObject
class
FakeUrandom(QlFsMappedObject):
def
read(
self
, size:
int
)
-
> bytes:
return
b
"\x04"
def
fstat(
self
)
-
>
int
:
return
-
1
def
close(
self
)
-
>
int
:
return
0
if
__name__
=
=
"__main__"
:
ql
=
Qiling([r
'rootfs/x86_linux/bin/x86_fetch_urandom'
], r
'rootfs/x86_linux'
)
ql.add_fs_mapper(r
'/dev/urandom'
, FakeUrandom())
ql.run()
class
FakeUrandom(QlFsMappedObject):
def
read(
self
, size:
int
)
-
> bytes:
return
b
"\x04"
def
fstat(
self
)
-
>
int
:
return
-
1
def
close(
self
)
-
>
int
:
return
0
class
FakeUrandom(QlFsMappedObject):
def
read(
self
, size:
int
)
-
> bytes:
return
b
"\x04"
def
fstat(
self
)
-
>
int
:
return
-
1
def
close(
self
)
-
>
int
:
return
0
class
FakeUrandom(QlFsMappedObject):
def
read(
self
, size
=
int
)
-
> bytes:
if
size
=
=
0x20
:
return
b
"\x02"
*
32
return
b
"\x01"
def
fstat(
self
)
-
>
int
:
return
-
1
def
close(
self
)
-
>
int
:
return
0
def
my_getrandom_func(ql, buf, count:
int
, flag:
int
)
-
>
int
:
ql.mem.write(buf,b
'\x02'
*
count)
return
count
def
challenge3(ql):
ql.add_fs_mapper(r
'/dev/urandom'
, FakeUrandom())
ql.os.set_syscall(
"getrandom"
,my_getrandom_func,QL_INTERCEPT.CALL)
class
FakeUrandom(QlFsMappedObject):
def
read(
self
, size
=
int
)
-
> bytes:
if
size
=
=
0x20
:
return
b
"\x02"
*
32
return
b
"\x01"
def
fstat(
self
)
-
>
int
:
return
-
1
def
close(
self
)
-
>
int
:
return
0
def
my_getrandom_func(ql, buf, count:
int
, flag:
int
)
-
>
int
:
ql.mem.write(buf,b
'\x02'
*
count)
return
count
def
challenge3(ql):
ql.add_fs_mapper(r
'/dev/urandom'
, FakeUrandom())
ql.os.set_syscall(
"getrandom"
,my_getrandom_func,QL_INTERCEPT.CALL)
ql.arch.regs.read(
"EAX"
)
ql.arch.regs.read(UC_X86_REG_EAX)
ql.arch.regs.read(UC_X86_REG_EAX)
eax
=
ql.arch.regs.eax
ql.arch.regs.write(UC_X86_REG_EAX,
0xFF
)
ql.arch.regs.write(UC_X86_REG_EAX,
0xFF
)
ql.arch.regs.eax
=
0xFF
ql.arch.regs.arch_pc
ql.arch.regs.arch_sp
ql.arch.regs.arch_pc
=
0xFF
ql.arch.regs.arch_sp
=
0xFF
ql.arch.regs.arch_pc
ql.arch.regs.arch_sp
ql.arch.regs.arch_pc
=
0xFF
ql.arch.regs.arch_sp
=
0xFF
ql.arch.regs.register_mapping()
ql.arch.regs.register_mapping()
ql.arch.reg_bits(
"rax"
)
ql.arch.reg_bits(
"eax"
)
from
qiling
import
Qiling
def
stop(ql: Qiling)
-
>
None
:
ql.log.info(
'killer switch found, stopping'
)
ql.emu_stop()
ql
=
Qiling([r
'examples/rootfs/x86_windows/bin/wannacry.bin'
], r
'examples/rootfs/x86_windows'
)
ql.hook_address(stop,
0x40819a
)
ql.run()
from
qiling
import
Qiling
def
stop(ql: Qiling)
-
>
None
:
ql.log.info(
'killer switch found, stopping'
)
ql.emu_stop()
ql
=
Qiling([r
'examples/rootfs/x86_windows/bin/wannacry.bin'
], r
'examples/rootfs/x86_windows'
)
ql.hook_address(stop,
0x40819a
)
ql.run()
from
capstone
import
Cs
from
qiling
import
Qiling
from
qiling.const
import
QL_VERBOSE
def
simple_diassembler(ql: Qiling, address:
int
, size:
int
, md: Cs)
-
>
None
:
buf
=
ql.mem.read(address, size)
for
insn
in
md.disasm(buf, address):
ql.log.debug(f
':: {insn.address:#x} : {insn.mnemonic:24s} {insn.op_str}'
)
if
__name__
=
=
"__main__"
:
ql
=
Qiling([r
'examples/rootfs/x8664_linux/bin/x8664_hello'
], r
'examples/rootfs/x8664_linux'
, verbose
=
QL_VERBOSE.DEBUG)
ql.hook_code(simple_diassembler, user_data
=
ql.arch.disassembler)
ql.run()
from
capstone
import
Cs
from
qiling
import
Qiling
from
qiling.const
import
QL_VERBOSE
def
simple_diassembler(ql: Qiling, address:
int
, size:
int
, md: Cs)
-
>
None
:
buf
=
ql.mem.read(address, size)
for
insn
in
md.disasm(buf, address):
ql.log.debug(f
':: {insn.address:#x} : {insn.mnemonic:24s} {insn.op_str}'
)
if
__name__
=
=
"__main__"
:
ql
=
Qiling([r
'examples/rootfs/x8664_linux/bin/x8664_hello'
], r
'examples/rootfs/x8664_linux'
, verbose
=
QL_VERBOSE.DEBUG)
ql.hook_code(simple_diassembler, user_data
=
ql.arch.disassembler)
ql.run()
def
write_eax_1(ql):
ql.arch.regs.write(
"EAX"
,
1
)
def
challenge4(ql):
base_addr
=
ql.mem.get_lib_base(ql.path)
ql.hook_address(write_eax_1,base_addr
+
0x0E43
)
def
write_eax_1(ql):
ql.arch.regs.write(
"EAX"
,
1
)
def
challenge4(ql):
base_addr
=
ql.mem.get_lib_base(ql.path)
ql.hook_address(write_eax_1,base_addr
+
0x0E43
)
unsigned
__int64
__fastcall challenge5(_BYTE *a1)
{
unsigned
int
v1;
int
i;
int
j;
int
v5[14];
unsigned
__int64
v6;
v6 = __readfsqword(0x28u);
v1 =
time
(0LL);
srand
(v1);
for
( i = 0; i <= 4; ++i )
{
v5[i] = 0;
v5[i + 8] =
rand
();
}
for
( j = 0; j <= 4; ++j )
{
if
( v5[j] != v5[j + 8] )
{
*a1 = 0;
return
__readfsqword(0x28u) ^ v6;
}
}
*a1 = 1;
return
__readfsqword(0x28u) ^ v6;
}
unsigned
__int64
__fastcall challenge5(_BYTE *a1)
{
unsigned
int
v1;
int
i;
int
j;
int
v5[14];
unsigned
__int64
v6;
v6 = __readfsqword(0x28u);
v1 =
time
(0LL);
srand
(v1);
for
( i = 0; i <= 4; ++i )
{
v5[i] = 0;
v5[i + 8] =
rand
();
}
for
( j = 0; j <= 4; ++j )
{
if
( v5[j] != v5[j + 8] )
{
*a1 = 0;
return
__readfsqword(0x28u) ^ v6;
}
}
*a1 = 1;
return
__readfsqword(0x28u) ^ v6;
}
from
qiling
import
Qiling
from
qiling.const
import
QL_INTERCEPT
from
qiling.os.const
import
STRING
def
my_puts(ql: Qiling):
params
=
ql.os.resolve_fcall_params({
's'
: STRING})
s
=
params[
's'
]
ql.log.info(f
'my_puts: got "{s}" as an argument'
)
print
(s)
return
len
(s)
if
__name__
=
=
"__main__"
:
ql
=
Qiling([r
'rootfs/x8664_linux/bin/x8664_hello'
], r
'rootfs/x8664_linux'
)
ql.os.set_api(
'puts'
, my_puts, QL_INTERCEPT.CALL)
ql.run()
from
qiling
import
Qiling
from
qiling.const
import
QL_INTERCEPT
from
qiling.os.const
import
STRING
def
my_puts(ql: Qiling):
params
=
ql.os.resolve_fcall_params({
's'
: STRING})
s
=
params[
's'
]
ql.log.info(f
'my_puts: got "{s}" as an argument'
)
print
(s)
return
len
(s)
if
__name__
=
=
"__main__"
:
ql
=
Qiling([r
'rootfs/x8664_linux/bin/x8664_hello'
], r
'rootfs/x8664_linux'
)
ql.os.set_api(
'puts'
, my_puts, QL_INTERCEPT.CALL)
ql.run()
def
rand_rets(ql ,
*
args):
ql.arch.regs.write(
"rax"
,
0
)
def
challenge5(ql):
ql.os.set_api(
'rand'
, rand_rets)
return
def
rand_rets(ql ,
*
args):
ql.arch.regs.write(
"rax"
,
0
)
def
challenge5(ql):
ql.os.set_api(
'rand'
, rand_rets)
return
def
write_rax_0(ql):
ql.arch.regs.write(
"rax"
,
0
)
def
challenge6(ql):
base_addr
=
ql.mem.get_lib_base(ql.path)
if
base_addr
is
None
:
raise
ValueError(
"base_addr is not set correctly"
)
ql.hook_address(write_rax_0,base_addr
+
0xF16
)
def
write_rax_0(ql):
ql.arch.regs.write(
"rax"
,
0
)
def
challenge6(ql):
base_addr
=
ql.mem.get_lib_base(ql.path)
if
base_addr
is
None
:
raise
ValueError(
"base_addr is not set correctly"
)
ql.hook_address(write_rax_0,base_addr
+
0xF16
)
def
write_rax_0(ql):
ql.arch.regs.write(
"rax"
,
0
)
def
challenge6(ql):
base_addr
=
ql.mem.get_lib_base(ql.path)
if
base_addr
is
None
:
raise
ValueError(
"base_addr is not set correctly"
)
ql.hook_address(write_rax_0,base_addr
+
0xF16
)
def
write_rax_0(ql):
ql.arch.regs.write(
"rax"
,
0
)
def
challenge6(ql):
base_addr
=
ql.mem.get_lib_base(ql.path)
if
base_addr
is
None
:
raise
ValueError(
"base_addr is not set correctly"
)
ql.hook_address(write_rax_0,base_addr
+
0xF16
)
def
write_rdi_0(ql):
ql.arch.regs.write(
"rdi"
,
0
)
def
my_sleep(ql,
*
args):
return
def
challenge7(ql):
base_addr
=
ql.mem.get_lib_base(ql.path)
if
base_addr
is
None
:
raise
ValueError(
"base_addr is not set correctly"
)
ql.hook_address(write_rdi_0,base_addr
+
0x00F3C
)
def
write_rdi_0(ql):
ql.arch.regs.write(
"rdi"
,
0
)
def
my_sleep(ql,
*
args):
return
def
challenge7(ql):
[注意]APP应用上架合规检测服务,协助应用顺利上架!