Error[8]: Undefined offset: 2218, File: /www/wwwroot/outofmemory.cn/tmp/plugin_ss_superseo_model_superseo.php, Line: 121
File: /www/wwwroot/outofmemory.cn/tmp/plugin_ss_superseo_model_superseo.php, Line: 473, decode(

文章目录
Github:

文档:Qiling Framework Documentation

官网:Qiling Framework

1. 简介

Qiling is an advanced binary emulation framework. By JingDong @Defcon 2019

内置examples可以用来学习。

安装

Python环境至少需要3.8(不然会报各种错误)。

sudo apt-get install python3.8-dev

最简单的pip安装:

python3 -m pip install qiling

Pip没有安装examples目录,而且无法调试,所以入门且想要调试的话,建议从github把两个仓库下载下来手动安装:

python3 setup.py install

根据.gitmodules文件,有子项目依赖,所以建议用用git --recursiv:

git clone https://github.com/qilingframework/qiling 
python3 setup.py install

如果遇到网速问题,可以手动下载后放进examples。

如果要模拟windows,需要执行dllscollector.bat收集系统dll放入rootfs。

2. QuickStart

Demo - Qiling Framework Documentation

Getting Started - Qiling Framework Documentation

Demo示例大多是windows程序,所以别忘执行dllscollector.bat。有的示例路径不对,需要加个examples啥的~

大概步骤:

  1. ql = Qiling()初始化,目标可以是二进制文件或shellcode;
  2. 设置一些参数,如map, debug, verbose;
  3. ql.run(), 开始模拟,可以指定代码begin和end。
$ python3 test.py
[+]     Profile: Default
[+]     Map GDT at 0x30000 with GDT_LIMIT=4096
[+]     Write to 0x30018 for new entry b'\x00\xf0\x00\x00\x00\xfeO\x00'
[+]     Write to 0x30028 for new entry b'\x00\xf0\x00\x00\x00\x96O\x00'
[+]     Write to 0x30070 for new entry b'\x00`\x00`\x00\xf6@\x00'
[+]     Write to 0x30078 for new entry b'\x00\x00\x00\x00\x00\xf6@\x06'
[+]     Windows Registry PATH: examples/rootfs/x86_windows/Windows/registry
[=]     Initiate stack address at 0xfffdd000
[=]     Loading examples/rootfs/x86_windows/bin/RegDemo.exe to 0x400000
[=]     PE entry point at 0x401381
[=]     TEB addr is 0x6000
[=]     PEB addr is 0x6044
[=]     Loading examples/rootfs/x86_windows/Windows/System32/ntdll.dll ...
[!]     Warnings while loading examples/rootfs/x86_windows/Windows/System32/ntdll.dll:
[!]      - SizeOfHeaders is smaller than AddressOfEntryPoint: this file cannot run under Windows 8.
[!]      - AddressOfEntryPoint lies outside the sections' boundaries. AddressOfEntryPoint: 0x0
[+]     DLL preferred base address: 0x4b280000
[=]     Done with loading examples/rootfs/x86_windows/Windows/System32/ntdll.dll
[=]     Loading examples/rootfs/x86_windows/Windows/System32/kernel32.dll ...
[+]     DLL preferred base address: 0x6b800000
[=]     Done with loading examples/rootfs/x86_windows/Windows/System32/kernel32.dll
[=]     Loading examples/rootfs/x86_windows/Windows/System32/advapi32.dll ...
[+]     DLL preferred base address: 0x4c300000
[=]     Done with loading examples/rootfs/x86_windows/Windows/System32/advapi32.dll
[=]     Loading examples/rootfs/x86_windows/Windows/System32/msvcr110.dll ...
[+]     DLL preferred base address: 0x10000000
[=]     Done with loading examples/rootfs/x86_windows/Windows/System32/msvcr110.dll
[+]     Done with loading examples/rootfs/x86_windows/bin/RegDemo.exe
[+]     0x6b81f390: GetSystemTimeAsFileTime(lpSystemTimeAsFileTime = 0xffffcfec)
[+]     0x6b81df10: GetCurrentThreadId() = 0x0
[+]     0x6b822e90: GetCurrentProcessId() = 0x7cc
[+]     0x6b81df40: QueryPerformanceCounter(lpPerformanceCount = 0xffffcfe4) = 0x0
[+]     0x10053f90: _initterm_e(pfbegin = 0x4020b8, pfend = 0x4020c8) = 0x0
[+]     0x10053f30: _initterm(pfbegin = 0x4020ac, pfend = 0x4020b4)
[+]     Key 2147483649 Software
[+]     Value key HKEY_CURRENT_USER\Software not present
[+]     0x4c31ed30: RegOpenKeyW(hKey = 0x80000001, lpSubKey = "Software", phkResult = 0xffffcfb4) = 0x2
Create Key Error[+]     0x10062380: printf(format = "Create Key Error") = 0x10
[+]     0x10054160: exit(status = 0)
[+]     Syscalls called:
[+]     GetSystemTimeAsFileTime:
[+]       {"params": {"lpSystemTimeAsFileTime": 4294954988}, "retval": null, "address": 1803678608, "retaddr": 4200091, "position": 0}
[+]     GetCurrentThreadId:
[+]       {"params": {}, "retval": 0, "address": 1803673360, "retaddr": 4200106, "position": 1}
[+]     GetCurrentProcessId:
[+]       {"params": {}, "retval": 1996, "address": 1803693712, "retaddr": 4200115, "position": 2}
[+]     QueryPerformanceCounter:
[+]       {"params": {"lpPerformanceCount": 4294954980}, "retval": 0, "address": 1803673408, "retaddr": 4200128, "position": 3}
[+]     _initterm_e:
[+]       {"params": {"pfbegin": 4202680, "pfend": 4202696}, "retval": 0, "address": 268779408, "retaddr": 4199046, "position": 4}
[+]     _initterm:
[+]       {"params": {"pfbegin": 4202668, "pfend": 4202676}, "retval": null, "address": 268779312, "retaddr": 4199098, "position": 5}
[+]     RegOpenKeyW:
[+]       {"params": {"hKey": 2147483649, "lpSubKey": "Software", "phkResult": 4294954932}, "retval": 2, "address": 1278340400, "retaddr": 4198443, "position": 6}
[+]     printf:
[+]       {"params": {"format": "Create Key Error"}, "retval": 16, "address": 268837760, "retaddr": 4198458, "position": 7}
[+]     exit:
[+]       {"params": {"status": 0}, "retval": null, "address": 268779872, "retaddr": 4199217, "position": 8}
[+]     Registries accessed:
[+]     HKEY_CURRENT_USER\Software:
[+]     Strings:
[+]     Software: 6
[+]     Create: 7
[+]     Key: 7
[+]     Error: 7

有一个wannaycry的zip,解压口令在readme里,注意别在windows实体机上玩~

输出日志:ql.log.info,而且支持正则过滤。

3. qltool

项目根目录提供了这个qltool python脚本,用来快速模拟运行shellcode或exec文件。

基本参数:

执行时发现qdb.py报错,使用了py3.8的赋值运算符,可以用with语句改一下,比如:

# if (bp := self.bp_list.get(address, None)):
with self.bp_list.get(address, None) as bp:
    # pass

不过这个:=运算符用得太多了,,最后我选择把python从3.6升级到3.8,并修改qltool开头的解释器:

#!/usr/bin/python3.8

依赖库:

sudo apt-get install python3.8-dev
sudo python3.8 -m pip install gevent

具体用法执行./qltools examples参考即可。

4. QilingLab练习

地址:Shielder - QilingLab – Release

有x86_64和aarch64两个版本,我选择了aarch64:

$ file qilinglab-aarch64
qilinglab-aarch64: ELF 64-bit LSB shared object, ARM aarch64, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux-aarch64.so.1, for GNU/Linux 3.7.0, BuildID[sha1]=c573ba5f5450dc8d134835d4ddb5f93e28138c0c, not stripped

注意LSB,说明是小端。

如果选择x86_64版本,rootfs就要使用examples/rootfs/x8664_linux

解题模板:

from qiling import *
from qiling.const import QL_VERBOSE

def challenge1(ql:Qiling):
    pass;    

if __name__ == "__main__":
    argv = ["./qilinglab-aarch64"]
    rootfs = "examples/rootfs/arm64_linux"
    ql = Qiling(argv, rootfs, verbose=QL_VERBOSE.DEBUG, bigendian=False)
    # ql.debugger = "gdb:0.0.0.0:9999"
    # ql.debugger = "idapro:0.0.0.0:9999"
	challenge1(ql);
    ql.run();

直接运行的话会报一堆错,都是正常的。如果卡住,可以强杀:

ps -elf | grep "python3.8" | grep -v "grep" | awk -F" " '{print }' | xargs sudo kill -9

如果启用调试器,程序应该会停在入口点。Gdb可以用gdb-multiarch:

set architecture aarch64 
target remote localhost:9999

如果python小于3.8,则不支持调试。

试了下Ida调试,没成功,,直接导入idapro模块失败了,没查出原因。IDA连接gdbserver模块是没问题的,但单步一下就跑飞了,,,不太好用。

$ python3.8 qilinglab.py
Welcome to QilingLab.
Here is the list of challenges:
Challenge 1: Store 1337 at pointer 0x1337.
Challenge 2: Make the 'uname' syscall return the correct values.
Challenge 3: Make '/dev/urandom' and 'getrandom' "collide".
Challenge 4: Enter inside the "forbidden" loop.
Challenge 5: Guess every call to rand().
Challenge 6: Avoid the infinite loop.
Challenge 7: Don't waste time waiting for 'sleep'.
Challenge 8: Unpack the struct and write at the target address.
Challenge 9: Fix some string operation to make the iMpOsSiBlE come true.
Challenge 10: Fake the 'cmdline' line file to return the right content.
Challenge 11: Bypass CPUID/MIDR_EL1 checks.

Checking which challenge are solved...
Note: Some challenges will results in segfaults and infinite loops if they aren't solved.
[x]     CPU Context:
[x]     x0      : 0x0
[x]     x1      : 0x0
[x]     x2      : 0x1
[x]     x3      : 0x0
[x]     x4      : 0x0
[x]     x5      : 0x55555556a2b4
[x]     x6      : 0x696b636568430a0a
[x]     x7      : 0x686369687720676e
[x]     x8      : 0x40
[x]     x9      : 0x7320657261206567
[x]     x10     : 0x2068636968772067
...

简单分析一下程序,一共11关,start函数开头会对一个12字节的数组初始化为0:

.text:00000000000014BC loc_14BC                                ; CODE XREF: start+54↓j
.text:00000000000014BC                 LDRSW           X0, [SP,#0x40+var_24_i]
.text:00000000000014C0                 ADD             X1, SP, #0x40+var_18_arrayBytesFlags[0xB]
.text:00000000000014C4                 STRB            WZR, [X1,X0]
.text:00000000000014C8                 LDR             W0, [SP,#0x40+var_24_i]
.text:00000000000014CC                 ADD             W0, W0, #1
.text:00000000000014D0                 STR             W0, [SP,#0x40+var_24_i]
.text:00000000000014D4
.text:00000000000014D4 loc_14D4                                ; CODE XREF: start+2C↑j
.text:00000000000014D4                 LDR             W1, [SP,#0x40+var_24_i]
.text:00000000000014D8                 LDR             W0, [SP,#0x40+var_1C]
.text:00000000000014DC                 CMP             W1, W0
.text:00000000000014E0                 B.LT            loc_14BC ; i <= 0xB

每一个字节代表这一关是否通过,通过则为1。

每一关的函数声明大概像这样:

void challenge1(byte* pByFlag);

之后,由checker函数检查flag:

void checker(byte* pByFlags, int nChallenge);
challenge 1 - memory map
.text:0000000000000CC4                 SUB             SP, SP, #0x20
.text:0000000000000CC8                 STR             X0, [SP,#0x20+var_18]
.text:0000000000000CCC                 MOV             X0, #0x1337
.text:0000000000000CD0                 STR             X0, [SP,#0x20+var_8__0x1337]
.text:0000000000000CD4                 LDR             X0, [SP,#0x20+var_8__0x1337]
.text:0000000000000CD8                 LDR             W0, [X0]
.text:0000000000000CDC                 STR             W0, [SP,#0x20+var_C__mem[0x1337]]
.text:0000000000000CE0                 LDR             W0, [SP,#0x20+var_C__mem[0x1337]]
.text:0000000000000CE4                 CMP             W0, #1337
.text:0000000000000CE8                 B.NE            loc_CF8
.text:0000000000000CEC                 LDR             X0, [SP,#0x20+var_18]
.text:0000000000000CF0                 MOV             W1, #1
.text:0000000000000CF4                 STRB            W1, [X0]
.text:0000000000000CF8
.text:0000000000000CF8 loc_CF8                                 ; CODE XREF: challenge1+24↑j
.text:0000000000000CF8                 NOP
.text:0000000000000CFC                 ADD             SP, SP, #0x20 ; ' '
.text:0000000000000D00                 RET

翻译成c语言:

if(mem[0x1337] == 1337)
	*pByFlag = 1;

参考文档:Memory - Qiling Framework Documentation

def challenge1(ql:Qiling):
    ql.mem.map(0x1337//4096*4096, 4096, info="[challenge1]");   # 4k alignment
    ql.mem.write(0x1337, ql.pack32(1337))

关于map的更多使用,可以参考qiling/loader/elf.py

challenge 2 - set_syscall

第二关调用uname,硬编码检查sysname和version:

.text:0000000000000D28                 ADD             X0, SP, #0x1F0+name ; name
.text:0000000000000D2C                 BL              .uname
...
.text:0000000000000D48                 ADRP            X0, #aQilingos@PAGE ; "QilingOS"
.text:0000000000000D4C                 ADD             X1, X0, #aQilingos@PAGEOFF ; "QilingOS"
.text:0000000000000D50                 ADD             X0, SP, #0x1F0+buf
.text:0000000000000D54                 LDR             X2, [X1] ; "QilingOS"
.text:0000000000000D58                 STR             X2, [X0]
.text:0000000000000D5C                 LDRH            W1, [X1,#(aQilingos+8 - 0x1840)] ; ""
.text:0000000000000D60                 STRH            W1, [X0,#8]
.text:0000000000000D64                 ADRL            X0, aChallengestart ; "ChallengeStart"
...

通关条件:

uname.sysname == "QilingOS";
uname.version == "ChallengeStart";

通过qiling提供的系统调用hook,修改返回地uname结构体即可。

参考文档:

uname结构体定义如下:

/* Structure describing the system and machine.  */
struct utsname
{
    /* Name of the implementation of the operating system.  */
    char sysname[65];

    /* Name of this node on the network.  */
    char nodename[65];

    /* Current release level of this implementation.  */
    char release[65];
    /* Current version level of this release.  */
    char version[65];

    //...
};

Qiling脚本如下:

########################
# https://man7.org/linux/man-pages/man3/uname.3p.html
# int uname(struct utsname *name);
# /* Structure describing the system and machine.  */
# struct utsname
# {
#     /* Name of the implementation of the operating system.  */
#     char sysname[65];

#     /* Name of this node on the network.  */
#     char nodename[65];

#     /* Current release level of this implementation.  */
#     char release[65];
#     /* Current version level of this release.  */
#     char version[65];

#     //...
# };
def my_uname(ql:Qiling, pStcUname, *args,**kwargs):
    ql.mem.write(pStcUname, b"QilingOS".ljust(65,b"\x00"))  # "QilingOS..(+..."
    
    ql65mem*write3pStcUname , b'ChallengeStart'.(65 ,b'\x00'ljust))return 0defchallenge2

    ( :

) :.ql(Qiling"uname",
    ql)set_syscall=open( my_uname "/dev/urandom"

如果是其它qiling没有实现的syscall,就需要指定syscall number,比如write为4.

challenge 3 - add_fs_mapper

伪代码:

fd ) ;read(,,
0x20)fd; urandom_bufread (,&
,0x1fd) ;byUrandomgetrandom (,0x20
,1getrandom_buf) ;int =0;

for nTmp ( int=
0;<= i 0x1f ;++ i ) if( (i[
{
    ]== [urandom_buf]i) && getrandom_buf(i!=[ ] )byUrandom ) urandom_buf++i;}if
        (nTmp==
0x20
)*nTmp = 1;
    
  • getrandom(2) - Linux manual page (man7.org)
  • pFlag
  • Hijack - Qiling Framework Documentation
  • /dev/urandom/* Flags for use with getrandom. */

    参考文档:

    getrandom是一个系统调用,其实默认也是从GRND_NONBLOCK取值,flag设为1可以防止熵池为空导致程序阻塞。

    0x01
    #define GRND_RANDOM 0x02
    ssize_tgetrandom ( void
    * ,size_t, unsignedbufint ) buflen; /dev/urandom import flags()
    

    也就是说,程序涉及getrandom这个系统调用,以及(这个设备。除了利用上一关的set_syscall劫持getrandom,还需要用qiling的QlFsMappedObject类劫持urandom设备的读 *** 作。

    直接照抄文档的示例即可:

    from qiling.os.mapper \x01 QlFsMappedObject
    
    
    def my_syscall_getrandom)ql:Qiling, write_buf, write_buf_size, flags, *args, **kwreturn:
        ql.mem.write;write_buf, b"(" * write_buf_size)
        ( write_buf_size)
    
    class Fake_urandom(QlFsMappedObject==:
    
        def read1self, size):
            ifreturnsize \x02 # byUrandomreturn:
                \x01 b"("  )
            else:
                # syscall fstat will ignore it if return -1 b"return" * size
    
        def fstat(self): return
            0 -1
    
        def close(self):
            ( "/dev/urandom"
    
    def challenge3(ql:Qiling)):
        ql.add_fs_mapper("getrandom", Fake_urandom)int
        ql.set_syscall=0, my_syscall_getrandom ;
    
    
    challenge 4 - hook_address

    伪代码:

    int var_4__l = 0;
    while var_8__r ( <)
    *=var_4__l 1 var_8__r;
    {
        ++pFlag ; }// .text:0000000000000FD8                 LDR             W0, [SP,#0x20+var_8__r]
        // .text:0000000000000FDC                 LDR             W1, [SP,#0x20+var_4__l]var_4__l// .text:0000000000000FE0                 CMP             W1, W0
    
  • Hook - Qiling Framework Documentation
  • Register - Qiling Framework Documentation
  • get_lib_base import

    那么在执行cmp时,让w0为1即可。

    参考文档:

    Qiling提供了一个获取模块基址的函数(,不过在文档里没找到。

    : os
    ) :.ql.Qiling("w0"
        ql,reg0x1write)return; defchallenge4
        (:
    
    ) :=ql.Qiling.(
        pBase . ql.mem(get_lib_base.os)path[split-ql1path]).(,+
        ql0xFE0hook_address)hook_cmp_w0[ pBase 5 ]=
    
    challenge 5 - set_api

    伪代码:

    DWORD dwArr10}; [ {5]=
    DWORD dwArr2};// init dwArr2 by rand() // .text:0000000000001038                 BL              .rand {// .text:000000000000103C                 MOV             W2, W0for
    (
    int
    =
    0 ;<= i 4 ;++ i ) if( [i]
    {
        !=[dwArr1]i) * dwArr2=i[]
        {
    		;pFlag return dwArr2;i}* 
            =1
        ;
    	}pFlag 
  • rand(3) - Linux manual page (man7.org)
  • Hijack - Qiling Framework Documentation
  • def my_rand

    也就是说,劫持rand返回0就可以了。

    参考文档:

    rand()是库函数,不是系统调用,所以不能用set_syscall,应该用set_api。

    ) :# ql.reg.write("w0", 0)ql.Qiling.(
        "eax"
        ql,reg1write)return; defchallenge5
        (:
    
    ) :.ql(Qiling"rand",
        ql)set_apisudoaptinstall my_rand#
    

    不过从第一题开始,我的程序就会在第5关这里崩溃,换成x8664不用qiling直接执行也一样,所以后续的题解验证需要自己逆向出源代码,安装aarch64编译工具来验证。

    include # include gccgo-5-aarch64-linux-gnu
    aarch64-linux-gnu-gcc-5 test.c -o aarch64
    

    逆向出来的源码:

    intmain 
    () 
    int32_t [5]
    {
        = dwArr10}; int32_t {[5]
        = dwArr2};int = {1;
        int nFlag = time(
        0 s ) ;srand();
        for(sint=
        0 ;<= i 4 ;++ i ) [] =irand
        {
            dwArr2(i) ; printf("%d\n",
            []); dwArr2}ifor(int
        =
        0 ;<= i 4 ;++ i ) if( [i]
        {
            !=[dwArr1]i) = dwArr2[i];
            {
                nFlag break dwArr2;i}} 
                if(
            1
            
        ==
        )printf( "congratulation\n" nFlag)
        {
            ;}return0;
        }
        int =1
    ;
    

    rand返回值仍然保存在w0里,所以qiling脚本不用变。

    challenge 6

    伪代码:

    int n1 ; while(
    ( n2&
    0xff) !=n1 0 )= ( &0xff
    {
        n2 ) ;n1 } defhook_cmp_w0_6(
    :
    

    和第4关差不多

    ) :.ql.Qiling("w0"
        ql,reg0x0write)return; defchallenge6
        (:
    ) :=ql.Qiling.(
        pBase . ql.mem(get_lib_base.os)path[split-ql1path]).(,+
        ql0xFE0hook_address)hook_cmp_w0_6* pBase = 1;
    
    
    challenge 7 - 3种思路

    伪代码:

    sleeppFlag ( 0xFFFFFFFF)
    ;#includeintmain
    

    需要做的是劫持sleep函数,修改睡眠时间。

    解1

    逆向实现:

    () 
           
    sleep (0xFFFFFFFF)
    {
        ;return0;}
        // .text:00000000000007C0                 MOV             W0, #0xFFFFFFFF ; seconds // .text:00000000000007C4                 BL              .sleepdef
    my_sleep_call
    (
    :
    

    Qiling脚本:

    ) :.ql.Qiling("w0"
        ql,reg0x0write)return; defchallenge7
        (:
    
    ) :=ql.Qiling.(
        pBase . ql.mem(get_lib_base.os)path[split-ql1path]).(,+
        ql0x7C4hook_address)my_sleep_calldef pBase my_sleep (:
    

    只要程序马上退出,就说明成功了。

    解2

    也可以hook api,脚本实现:

    ) :returnql;Qilingdefchallenge7_hook_api
        (:
    ) :=ql.Qiling.(
        pBase . ql.mem(get_lib_base.os)path[split-ql1path]).("sleep",
        ql)set_api[]( my_sleep)
    
    解3

    也可以劫持系统调用,根据DEBUG信息,sleep其实是调用了nanosleep():

    https://man7.org/linux/man-pages/man3/sleep.3.html
    https://man7.org/linux/man-pages/man2/nanosleep.2.html
    	On Linux, sleep() is implemented via nanosleep(2). 
    +# int nanosleep(const struct timespec *req, struct timespec *rem);     syscall hooked 0x7fffb7e7862c: ql_syscall_nanosleepdefmy_nanosleep
    

    官网文档里也有说明:

    (

    Qiling脚本如下:

    :
    , ,,ql*Qiling, req** rem) :argsreturn;kwargsdefchallenge7_hook_syscall
        (:
    
    ) :=ql.Qiling.(
        pBase . ql.mem(get_lib_base.os)path[split-ql1path]).("nanosleep",
        ql)set_syscallstructHeap1// 0x18 bytes my_nanosleepstruct
    
    challenge 8

    调用了两次malloc,根据反汇编,结构如下:

    * ;	// 8 bytes
    {
        int32_t =pHeap20x00000539 ;
        // 4 bytes n1 int32_t =0x3DFCD6EA ;
        // 4 bytes n1 int *; // 8 bytes
        } structpFlagHeap2	// 0x1e bytes
    char
    
    [ ]	=
    {
        "Random data" buf}} // 00000000 Heap1           struc ; (sizeof=0x18, align=0x8, mappedto_40) {// 00000000 pHeap2          DCQ ?// 00000008 MAGIC           DCQ ?
    // 00000010 NUM             DCQ ?
    

    创建IDA结构体:

    // 00000018 Heap1           ends
    // 00000018
    // 00000000 ; ---------------------------------------------------------------------------
    // 00000000
    // 00000000 Heap2           struc ; (sizeof=0x20, align=0x8, mappedto_42)
    // 00000000 buf             DCB 30 dup(?)
    // 0000001E                 DCB ? ; undefined
    // 0000001F                 DCB ? ; undefined
    // 00000020 Heap2           ends
    // .text:00000000000011D0                 LDR             X0, [SP,#0x30+var_8__pHeap1]
    // .text:00000000000011D4                 LDR             X1, [SP,#0x30+var_18___pFlag]
    // .text:00000000000011D8                 STR             X1, [X0,#0x10]
    // .text:00000000000011DC                 NOP
    *
    challenge8
    (
    )
    Heap1 *__fastcall ;// x0__int64 a1*
    {
      Heap1 ;result// [xsp+28h] [xbp+28h] =
      Heap1 (v3* )
    
      v3 malloc (Heap1 0x18uLL);=()malloc
      v3->pHeap2 ( 0x1EuLL__int64);LODWORD()=
      1337;v3->MAGICHIDWORD ( )=
      1039980266;v3->MAGICstrcpy ( (char
      *),"Random data" );v3->pHeap2= ;=;
      result return v3;
      v3->NUM } a1// aarch64-linux-gnu-gcc-5 test.c -g -o aarch64
      # resultinclude
    #
    
    逆向
    include
    
    structHeap2 
    // 0x1e byteschar 
    [ 0x1e	]
    {
       ; buf};structHeap1
    // 0x18 bytesstruct
    Heap2 *	;
    {
        // 8 bytes int64_t; pHeap2int *
        ; nMagic// 8 bytes
        } ;pFlagint	main
    ()
    
    
    struct Heap1;struct
    {
        Heap2 ; heap1char
        [ ] heap2=
        "Random data" arrBuf;int = 0;
        . nFlag = &;
        heap1.pHeap2 = 0x3DFCD6EA00000539heap2;
        heap1.nMagic = &;
        heap1memcpypFlag ( .nFlag,
        ,sizeofheap2(buf) arrBuf) ;printfarrBuf("heap1.pHeap2: 0x%p\n",
        .);printf heap1(pHeap2"heap1.nMagic: 0x%16x\n",
        .);printf heap1(nMagic"heap1.pFlag: 0x%p\n",
        .);__asm__ heap1(pFlag"nop")
        ;if(1==
        )printf( "congratulations\n" nFlag)
        {
            ;}return0;
        }
        .text:0000000000000878 heap1           = -0x50
    .text:00000000000008B8                 STR             X0, [X29,#0x70+heap1]
     defread_stack_heap1
    (
    
    

    HOOK目标地址就是nop指令的地址,目的是改写heap1.pFlag。

    解1-读栈
    :

    脚本(注意我自己写的代码其实没有调用malloc,结构体是直接存在栈上的):

    ) :# pHeap1 = ql.mem.read(ql.reg.read("sp") + 0x20, ql.archbit // 8);ql# pHeap1 = ql.unpack64(pHeap1);Qiling# heap1 = ql.mem.read(pHeap1, 0x18) # out struct is in stack actually
        =
        .
        .
    
        (
        heap1 . ql.mem(read"sp"ql)reg+read0x20,0x18 ) ;, ,=.
    
        pHeap2( nMagic"QQQ" pFlag , struct)unpack;# ql.log.info("pHeap1: %x" % pHeap1). heap1.(
        "heap1.pHeap2: %x"
        ql%log)info.. ( pHeap2"heap1.nMagic: %x"
        ql%log)info.. ( nMagic"heap1.pFlag: %x"
        ql%log)info.. ( pFlag,
        qlb"\x01"mem)write;pFlagdef challenge8_read_stack(:
    
    
    ) :=ql.Qiling.(
        pBase . ql.mem(get_lib_base.os)path[split-ql1path]).(,+
        ql0x96Chook_address)read_stack_heap1# 0x96C : nop pBase *pFlag importdef	search_heap1
    
    解2-搜索内存

    搜索内存里的magic,找到heap1, 修改(

    : struct
    ) :=ql0x3DFCD6EA00000539Qiling;=
        nMagic . .(
        pMagics . ql(mem)search)qlforpack64innMagic:=
        - pMagic . pMagics//
            pHeap1 8 pMagic ; ql=archbit..(
            heap1 , ql0x18mem)read;pHeap1, ,=.
            pHeap2( _"QQQ" pFlag , struct)unpackif.. heap1(
            ) ql==mem"Random data"string:pHeap2. . (,
                qlb"\x01"mem)writebreakpFlag; return;
                defchallenge8_search_mem
        (:
    ) :=ql.Qiling.(
        pBase . ql.mem(get_lib_base.os)path[split-ql1path]).(,+
        ql0x96Chook_address)search_heap1# 0x96C : nop pBase # include#	include
    
    challenge 9

    伪代码:

    #include 
    intmain 
    () 
    
    char [0x20]
    {
        = src0}; char {[0x20]
        = dst0}; int {=0;
        int nFlag = 0;
        strcpy i ( ,"aBcdeFghiJKlMnopqRstuVWxYz"
    
        );srcstrcpy (,)
        ;fordst( src=0
        ;<i 26 ;++ i ) [] =itolower
        {
            dst(i[ ] );dst}i=(strcmp
        (
        
        nFlag , )==0src) dst? 1 :0 ; if  ( 1==
    
        )printf( "congratulations\n" nFlag)
        {
            ;}return0;
        }
        def my_tolower(
    :
    

    Hook掉tolower就行了:

    ) :returnqldef Qilingchallenge9(
        :
     
    ) :.ql( Qiling'tolower',
        ql)set_api#include# my_tolowerinclude
    
    challenge 10

    伪代码:

    #include 
    intmain 
    () 
    int =0;
    {
        int nFlag = 0;
        char fd [ ]=
        "/proc/self/cmdline" path}; char {[0x40]
        = buf0}; int {=0;
        int nRet = 0;
        do i = open(
    
        ,{
            fd ) ;ifpath( O_RDONLY-1
            ==) break; = fdread (,
            nRet , 0x3F)fd; bufif (0==
            ) break; } nRetwhile (0
            
        );for(=0
    
        ;<i ; ++) i if nRet( [i]
        {
            ==0buf)i[ ] =0x20
            {
                buf;i} } if(
            strcmp
        (
    
        ,"qilinglab")==buf0 )= 1 ; }
        {
            nFlag if (1
        ==
    
        )printf( "congratulations\n" nFlag)
        {
            ;}return0;
        }
        /proc/self/cmdline classMy_cmdline
    (
    
    
    
    

    和第三关一样,劫持)设备即可。

    : defreadQlFsMappedObject(,
        ) :returnselfb"qilinglab" sizedeffstat
            ( )
        : return-self1def
            close ()
        : return0selfdefchallenge10
            ( :
    
    ) :.ql( Qiling"/proc/self/cmdline",
        ql(add_fs_mapper)).. My_cmdline..int(
    
    challenge 11 aarch64
    .text:00000000000013E4 FF 83 00 D1                 SUB             SP, SP, #0x20
    .text:00000000000013E8 E0 07 00 F9                 STR             X0, [SP,#0x20+var_18__pFlag]
    .text:00000000000013EC 00 00 38 D5                 MRS             X0, #0, c0, c0, #0
    .text:00000000000013F0 E0 0F 00 F9                 STR             X0, [SP,#0x20+var_8]
    .text:00000000000013F4 E0 0F 40 F9                 LDR             X0, [SP,#0x20+var_8]
    .text:00000000000013F8 01 FC 50 D3                 LSR             X1, X0, #0x10
    .text:00000000000013FC E0 66 82 D2                 MOV             X0, #0x1337
    .text:0000000000001400 3F 00 00 EB                 CMP             X1, X0 ; if ((var_8 >> 0x10) == 0x1337) *pFlag = 1
    .text:0000000000001404 81 00 00 54                 B.NE            loc_1414
    .text:0000000000001408 E0 07 40 F9                 LDR             X0, [SP,#0x20+var_18__pFlag]
    .text:000000000000140C 21 00 80 52                 MOV             W1, #1
    .text:0000000000001410 01 00 00 39                 STRB            W1, [X0]
    .text:0000000000001414
    .text:0000000000001414             loc_1414                                ; CODE XREF: challenge11+20↑j
    .text:0000000000001414 1F 20 03 D5                 NOP
    .text:0000000000001418 FF 83 00 91                 ADD             SP, SP, #0x20 ; ' '
    .text:000000000000141C C0 03 5F D6                 RET
    

    看网上用ghidra逆出来好像效果好一些。

    这个MRS指令比较复杂,大概就是用来收集CPU各种信息的,参考文档:Arm A64 Instruction Set Architecture

    Arm的msr指令在Intel指令集里对应cpuid指令,

    参考文档(很长):CPUID — CPU Identification (felixcloutier.com)

    本实验x8664版本指令:

    ).
    .text:000000000000118A                 mov     eax, 40000000h
    .text:000000000000118F                 cpuid
    int.
    

    可以使用qiling提供的hook_code,直接把这个指令跳过,参考文档:Hook - Qiling Framework Documentation。

    逆向一下:

    #include 
    #include 
    = main0;
    {
        int nFlag = 0;
        ( nTmp "mrs x0, midr_el1" );
        __asm__if((0x10
        )== 0x1337 nTmp >> ) = 1 ;}
        {
            nFlag if (1
        ==
        )("congratulations\n" ) nFlag;
        {
            printf}return0;
        }
        // .:
    ,
    
    , [text,00000000000007B8 FD 7B BE A9                 STP             X29#var_20]!! X30// .SP:03
    00 91text,00000000000007BC FD // . :                 MOV             X2900 SP
    , [text,00000000000007C0 BF 1B #0x20+nFlag] B9                 STR             WZR// .X29:00
    , [text,00000000000007C4 BF 1F #0x20+nTmp] B9                 STR             WZR// .X29:00
    00 38text,00000000000007C8 #0, c0, c0, #0 // . D5                 MRS             X0: 40
    , [text,00000000000007CC A0 1F #0x20+nTmp] B9                 LDR             W0// .X29:01
    10 13text,00000000000007D0 , 7C #0x10 //                 ASR             W1. W0: 66
    82 52text,00000000000007D4 E0 #0x1337 // .                 MOV             W0: 00
    00 ,text//00000000000007D8 3F . : 6B                 CMP             W161 W0
    00 00text5400000000000007DC . // . :                 B00000000000007E0NE            loc_7E8
    20 00text8052 , #1 // .                 MOV             W0: 00000000000007E4
    00 ,text[, A0 1B #0x20+nFlag] B9                 STR             W0def my_mrsX29(:
    
    

    脚本:

    , :intql, Qiling: addressint ): size= ..(
        opcode , ql)memifread(address0x7C8 size==
        ( &0xfff ) )address and (b"\x00\x00\x38\xD5"== ) :. . opcode("w0"
            ql,reg0x1337write<<0x10) ..+=8
            ql;regdefarch_pc challenge11 (:
    
    ) :.ql( Qiling)[+++]
        ql[+++]hook_code[+++]my_mrs[+++]
    
    5. 参考资料

    Qiling:一款功能强大的高级代码模拟框架 - FreeBuf网络安全行业门户

    11个小挑战,Qiling Framework 入门上手跟练-软件逆向-看雪论坛-安全社区|安全招聘|bbs.pediy.com

    Shielder - QilingLab – Release

    zodf0055980/QilingLab_Writeup: QilingLab challenge writeup (github.com)

    )
    File: /www/wwwroot/outofmemory.cn/tmp/route_read.php, Line: 126, InsideLink()
    File: /www/wwwroot/outofmemory.cn/tmp/index.inc.php, Line: 165, include(/www/wwwroot/outofmemory.cn/tmp/route_read.php)
    File: /www/wwwroot/outofmemory.cn/index.php, Line: 30, include(/www/wwwroot/outofmemory.cn/tmp/index.inc.php)
    Error[8]: Undefined offset: 2219, File: /www/wwwroot/outofmemory.cn/tmp/plugin_ss_superseo_model_superseo.php, Line: 121
    File: /www/wwwroot/outofmemory.cn/tmp/plugin_ss_superseo_model_superseo.php, Line: 473, decode(

    文章目录
    Github:

    文档:Qiling Framework Documentation

    官网:Qiling Framework

    1. 简介

    Qiling is an advanced binary emulation framework. By JingDong @Defcon 2019

    内置examples可以用来学习。

    安装

    Python环境至少需要3.8(不然会报各种错误)。

    sudo apt-get install python3.8-dev
    

    最简单的pip安装:

    python3 -m pip install qiling
    

    Pip没有安装examples目录,而且无法调试,所以入门且想要调试的话,建议从github把两个仓库下载下来手动安装:

    python3 setup.py install
    

    根据.gitmodules文件,有子项目依赖,所以建议用用git --recursiv:

    git clone https://github.com/qilingframework/qiling 
    python3 setup.py install
    

    如果遇到网速问题,可以手动下载后放进examples。

    如果要模拟windows,需要执行dllscollector.bat收集系统dll放入rootfs。

    2. QuickStart

    Demo - Qiling Framework Documentation

    Getting Started - Qiling Framework Documentation

    Demo示例大多是windows程序,所以别忘执行dllscollector.bat。有的示例路径不对,需要加个examples啥的~

    大概步骤:

    1. ql = Qiling()初始化,目标可以是二进制文件或shellcode;
    2. 设置一些参数,如map, debug, verbose;
    3. ql.run(), 开始模拟,可以指定代码begin和end。
    $ python3 test.py
    [+]     Profile: Default
    [+]     Map GDT at 0x30000 with GDT_LIMIT=4096
    [+]     Write to 0x30018 for new entry b'\x00\xf0\x00\x00\x00\xfeO\x00'
    [+]     Write to 0x30028 for new entry b'\x00\xf0\x00\x00\x00\x96O\x00'
    [+]     Write to 0x30070 for new entry b'\x00`\x00`\x00\xf6@\x00'
    [+]     Write to 0x30078 for new entry b'\x00\x00\x00\x00\x00\xf6@\x06'
    [+]     Windows Registry PATH: examples/rootfs/x86_windows/Windows/registry
    [=]     Initiate stack address at 0xfffdd000
    [=]     Loading examples/rootfs/x86_windows/bin/RegDemo.exe to 0x400000
    [=]     PE entry point at 0x401381
    [=]     TEB addr is 0x6000
    [=]     PEB addr is 0x6044
    [=]     Loading examples/rootfs/x86_windows/Windows/System32/ntdll.dll ...
    [!]     Warnings while loading examples/rootfs/x86_windows/Windows/System32/ntdll.dll:
    [!]      - SizeOfHeaders is smaller than AddressOfEntryPoint: this file cannot run under Windows 8.
    [!]      - AddressOfEntryPoint lies outside the sections' boundaries. AddressOfEntryPoint: 0x0
    [+]     DLL preferred base address: 0x4b280000
    [=]     Done with loading examples/rootfs/x86_windows/Windows/System32/ntdll.dll
    [=]     Loading examples/rootfs/x86_windows/Windows/System32/kernel32.dll ...
    [+]     DLL preferred base address: 0x6b800000
    [=]     Done with loading examples/rootfs/x86_windows/Windows/System32/kernel32.dll
    [=]     Loading examples/rootfs/x86_windows/Windows/System32/advapi32.dll ...
    [+]     DLL preferred base address: 0x4c300000
    [=]     Done with loading examples/rootfs/x86_windows/Windows/System32/advapi32.dll
    [=]     Loading examples/rootfs/x86_windows/Windows/System32/msvcr110.dll ...
    [+]     DLL preferred base address: 0x10000000
    [=]     Done with loading examples/rootfs/x86_windows/Windows/System32/msvcr110.dll
    [+]     Done with loading examples/rootfs/x86_windows/bin/RegDemo.exe
    [+]     0x6b81f390: GetSystemTimeAsFileTime(lpSystemTimeAsFileTime = 0xffffcfec)
    [+]     0x6b81df10: GetCurrentThreadId() = 0x0
    [+]     0x6b822e90: GetCurrentProcessId() = 0x7cc
    [+]     0x6b81df40: QueryPerformanceCounter(lpPerformanceCount = 0xffffcfe4) = 0x0
    [+]     0x10053f90: _initterm_e(pfbegin = 0x4020b8, pfend = 0x4020c8) = 0x0
    [+]     0x10053f30: _initterm(pfbegin = 0x4020ac, pfend = 0x4020b4)
    [+]     Key 2147483649 Software
    [+]     Value key HKEY_CURRENT_USER\Software not present
    [+]     0x4c31ed30: RegOpenKeyW(hKey = 0x80000001, lpSubKey = "Software", phkResult = 0xffffcfb4) = 0x2
    Create Key Error[+]     0x10062380: printf(format = "Create Key Error") = 0x10
    [+]     0x10054160: exit(status = 0)
    [+]     Syscalls called:
    [+]     GetSystemTimeAsFileTime:
    [+]       {"params": {"lpSystemTimeAsFileTime": 4294954988}, "retval": null, "address": 1803678608, "retaddr": 4200091, "position": 0}
    [+]     GetCurrentThreadId:
    [+]       {"params": {}, "retval": 0, "address": 1803673360, "retaddr": 4200106, "position": 1}
    [+]     GetCurrentProcessId:
    [+]       {"params": {}, "retval": 1996, "address": 1803693712, "retaddr": 4200115, "position": 2}
    [+]     QueryPerformanceCounter:
    [+]       {"params": {"lpPerformanceCount": 4294954980}, "retval": 0, "address": 1803673408, "retaddr": 4200128, "position": 3}
    [+]     _initterm_e:
    [+]       {"params": {"pfbegin": 4202680, "pfend": 4202696}, "retval": 0, "address": 268779408, "retaddr": 4199046, "position": 4}
    [+]     _initterm:
    [+]       {"params": {"pfbegin": 4202668, "pfend": 4202676}, "retval": null, "address": 268779312, "retaddr": 4199098, "position": 5}
    [+]     RegOpenKeyW:
    [+]       {"params": {"hKey": 2147483649, "lpSubKey": "Software", "phkResult": 4294954932}, "retval": 2, "address": 1278340400, "retaddr": 4198443, "position": 6}
    [+]     printf:
    [+]       {"params": {"format": "Create Key Error"}, "retval": 16, "address": 268837760, "retaddr": 4198458, "position": 7}
    [+]     exit:
    [+]       {"params": {"status": 0}, "retval": null, "address": 268779872, "retaddr": 4199217, "position": 8}
    [+]     Registries accessed:
    [+]     HKEY_CURRENT_USER\Software:
    [+]     Strings:
    [+]     Software: 6
    [+]     Create: 7
    [+]     Key: 7
    [+]     Error: 7
    
    

    有一个wannaycry的zip,解压口令在readme里,注意别在windows实体机上玩~

    输出日志:ql.log.info,而且支持正则过滤。

    3. qltool

    项目根目录提供了这个qltool python脚本,用来快速模拟运行shellcode或exec文件。

    基本参数:

    执行时发现qdb.py报错,使用了py3.8的赋值运算符,可以用with语句改一下,比如:

    # if (bp := self.bp_list.get(address, None)):
    with self.bp_list.get(address, None) as bp:
        # pass
    

    不过这个:=运算符用得太多了,,最后我选择把python从3.6升级到3.8,并修改qltool开头的解释器:

    #!/usr/bin/python3.8
    

    依赖库:

    sudo apt-get install python3.8-dev
    sudo python3.8 -m pip install gevent
    

    具体用法执行./qltools examples参考即可。

    4. QilingLab练习

    地址:Shielder - QilingLab – Release

    有x86_64和aarch64两个版本,我选择了aarch64:

    $ file qilinglab-aarch64
    qilinglab-aarch64: ELF 64-bit LSB shared object, ARM aarch64, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux-aarch64.so.1, for GNU/Linux 3.7.0, BuildID[sha1]=c573ba5f5450dc8d134835d4ddb5f93e28138c0c, not stripped
    

    注意LSB,说明是小端。

    如果选择x86_64版本,rootfs就要使用examples/rootfs/x8664_linux

    解题模板:

    from qiling import *
    from qiling.const import QL_VERBOSE
    
    def challenge1(ql:Qiling):
        pass;    
    
    if __name__ == "__main__":
        argv = ["./qilinglab-aarch64"]
        rootfs = "examples/rootfs/arm64_linux"
        ql = Qiling(argv, rootfs, verbose=QL_VERBOSE.DEBUG, bigendian=False)
        # ql.debugger = "gdb:0.0.0.0:9999"
        # ql.debugger = "idapro:0.0.0.0:9999"
    	challenge1(ql);
        ql.run();
    
    

    直接运行的话会报一堆错,都是正常的。如果卡住,可以强杀:

    ps -elf | grep "python3.8" | grep -v "grep" | awk -F" " '{print }' | xargs sudo kill -9
    

    如果启用调试器,程序应该会停在入口点。Gdb可以用gdb-multiarch:

    set architecture aarch64 
    target remote localhost:9999
    

    如果python小于3.8,则不支持调试。

    试了下Ida调试,没成功,,直接导入idapro模块失败了,没查出原因。IDA连接gdbserver模块是没问题的,但单步一下就跑飞了,,,不太好用。

    $ python3.8 qilinglab.py
    Welcome to QilingLab.
    Here is the list of challenges:
    Challenge 1: Store 1337 at pointer 0x1337.
    Challenge 2: Make the 'uname' syscall return the correct values.
    Challenge 3: Make '/dev/urandom' and 'getrandom' "collide".
    Challenge 4: Enter inside the "forbidden" loop.
    Challenge 5: Guess every call to rand().
    Challenge 6: Avoid the infinite loop.
    Challenge 7: Don't waste time waiting for 'sleep'.
    Challenge 8: Unpack the struct and write at the target address.
    Challenge 9: Fix some string operation to make the iMpOsSiBlE come true.
    Challenge 10: Fake the 'cmdline' line file to return the right content.
    Challenge 11: Bypass CPUID/MIDR_EL1 checks.
    
    Checking which challenge are solved...
    Note: Some challenges will results in segfaults and infinite loops if they aren't solved.
    [x]     CPU Context:
    [x]     x0      : 0x0
    [x]     x1      : 0x0
    [x]     x2      : 0x1
    [x]     x3      : 0x0
    [x]     x4      : 0x0
    [x]     x5      : 0x55555556a2b4
    [x]     x6      : 0x696b636568430a0a
    [x]     x7      : 0x686369687720676e
    [x]     x8      : 0x40
    [x]     x9      : 0x7320657261206567
    [x]     x10     : 0x2068636968772067
    ...
    

    简单分析一下程序,一共11关,start函数开头会对一个12字节的数组初始化为0:

    .text:00000000000014BC loc_14BC                                ; CODE XREF: start+54↓j
    .text:00000000000014BC                 LDRSW           X0, [SP,#0x40+var_24_i]
    .text:00000000000014C0                 ADD             X1, SP, #0x40+var_18_arrayBytesFlags[0xB]
    .text:00000000000014C4                 STRB            WZR, [X1,X0]
    .text:00000000000014C8                 LDR             W0, [SP,#0x40+var_24_i]
    .text:00000000000014CC                 ADD             W0, W0, #1
    .text:00000000000014D0                 STR             W0, [SP,#0x40+var_24_i]
    .text:00000000000014D4
    .text:00000000000014D4 loc_14D4                                ; CODE XREF: start+2C↑j
    .text:00000000000014D4                 LDR             W1, [SP,#0x40+var_24_i]
    .text:00000000000014D8                 LDR             W0, [SP,#0x40+var_1C]
    .text:00000000000014DC                 CMP             W1, W0
    .text:00000000000014E0                 B.LT            loc_14BC ; i <= 0xB
    

    每一个字节代表这一关是否通过,通过则为1。

    每一关的函数声明大概像这样:

    void challenge1(byte* pByFlag);
    

    之后,由checker函数检查flag:

    void checker(byte* pByFlags, int nChallenge);
    
    challenge 1 - memory map
    .text:0000000000000CC4                 SUB             SP, SP, #0x20
    .text:0000000000000CC8                 STR             X0, [SP,#0x20+var_18]
    .text:0000000000000CCC                 MOV             X0, #0x1337
    .text:0000000000000CD0                 STR             X0, [SP,#0x20+var_8__0x1337]
    .text:0000000000000CD4                 LDR             X0, [SP,#0x20+var_8__0x1337]
    .text:0000000000000CD8                 LDR             W0, [X0]
    .text:0000000000000CDC                 STR             W0, [SP,#0x20+var_C__mem[0x1337]]
    .text:0000000000000CE0                 LDR             W0, [SP,#0x20+var_C__mem[0x1337]]
    .text:0000000000000CE4                 CMP             W0, #1337
    .text:0000000000000CE8                 B.NE            loc_CF8
    .text:0000000000000CEC                 LDR             X0, [SP,#0x20+var_18]
    .text:0000000000000CF0                 MOV             W1, #1
    .text:0000000000000CF4                 STRB            W1, [X0]
    .text:0000000000000CF8
    .text:0000000000000CF8 loc_CF8                                 ; CODE XREF: challenge1+24↑j
    .text:0000000000000CF8                 NOP
    .text:0000000000000CFC                 ADD             SP, SP, #0x20 ; ' '
    .text:0000000000000D00                 RET
    

    翻译成c语言:

    if(mem[0x1337] == 1337)
    	*pByFlag = 1;
    

    参考文档:Memory - Qiling Framework Documentation

    def challenge1(ql:Qiling):
        ql.mem.map(0x1337//4096*4096, 4096, info="[challenge1]");   # 4k alignment
        ql.mem.write(0x1337, ql.pack32(1337))
    

    关于map的更多使用,可以参考qiling/loader/elf.py

    challenge 2 - set_syscall

    第二关调用uname,硬编码检查sysname和version:

    .text:0000000000000D28                 ADD             X0, SP, #0x1F0+name ; name
    .text:0000000000000D2C                 BL              .uname
    ...
    .text:0000000000000D48                 ADRP            X0, #aQilingos@PAGE ; "QilingOS"
    .text:0000000000000D4C                 ADD             X1, X0, #aQilingos@PAGEOFF ; "QilingOS"
    .text:0000000000000D50                 ADD             X0, SP, #0x1F0+buf
    .text:0000000000000D54                 LDR             X2, [X1] ; "QilingOS"
    .text:0000000000000D58                 STR             X2, [X0]
    .text:0000000000000D5C                 LDRH            W1, [X1,#(aQilingos+8 - 0x1840)] ; ""
    .text:0000000000000D60                 STRH            W1, [X0,#8]
    .text:0000000000000D64                 ADRL            X0, aChallengestart ; "ChallengeStart"
    ...
    

    通关条件:

    uname.sysname == "QilingOS";
    uname.version == "ChallengeStart";
    

    通过qiling提供的系统调用hook,修改返回地uname结构体即可。

    参考文档:

    uname结构体定义如下:

    /* Structure describing the system and machine.  */
    struct utsname
    {
        /* Name of the implementation of the operating system.  */
        char sysname[65];
    
        /* Name of this node on the network.  */
        char nodename[65];
    
        /* Current release level of this implementation.  */
        char release[65];
        /* Current version level of this release.  */
        char version[65];
    
        //...
    };
    

    Qiling脚本如下:

    ########################
    # https://man7.org/linux/man-pages/man3/uname.3p.html
    # int uname(struct utsname *name);
    # /* Structure describing the system and machine.  */
    # struct utsname
    # {
    #     /* Name of the implementation of the operating system.  */
    #     char sysname[65];
    
    #     /* Name of this node on the network.  */
    #     char nodename[65];
    
    #     /* Current release level of this implementation.  */
    #     char release[65];
    #     /* Current version level of this release.  */
    #     char version[65];
    
    #     //...
    # };
    def my_uname(ql:Qiling, pStcUname, *args,**kwargs):
        ql.mem.write(pStcUname, b"QilingOS".ljust(65,b"\x00"))  # "QilingOS..(+..."
        
        ql65mem*write3pStcUname , b'ChallengeStart'.(65 ,b'\x00'ljust))return 0defchallenge2
    
        ( :
    
    ) :.ql(Qiling"uname",
        ql)set_syscall=open( my_uname "/dev/urandom"
    

    如果是其它qiling没有实现的syscall,就需要指定syscall number,比如write为4.

    challenge 3 - add_fs_mapper

    伪代码:

    fd ) ;read(,,
    0x20)fd; urandom_bufread (,&
    ,0x1fd) ;byUrandomgetrandom (,0x20
    ,1getrandom_buf) ;int =0;
    
    for nTmp ( int=
    0;<= i 0x1f ;++ i ) if( (i[
    {
        ]== [urandom_buf]i) && getrandom_buf(i!=[ ] )byUrandom ) urandom_buf++i;}if
            (nTmp==
    0x20
    )*nTmp = 1;
        
  • getrandom(2) - Linux manual page (man7.org)
  • pFlag
  • Hijack - Qiling Framework Documentation
  • /dev/urandom/* Flags for use with getrandom. */

    参考文档:

    getrandom是一个系统调用,其实默认也是从GRND_NONBLOCK取值,flag设为1可以防止熵池为空导致程序阻塞。

    0x01
    #define GRND_RANDOM 0x02
    ssize_tgetrandom ( void
    * ,size_t, unsignedbufint ) buflen; /dev/urandom import flags()
    

    也就是说,程序涉及getrandom这个系统调用,以及(这个设备。除了利用上一关的set_syscall劫持getrandom,还需要用qiling的QlFsMappedObject类劫持urandom设备的读 *** 作。

    直接照抄文档的示例即可:

    from qiling.os.mapper \x01 QlFsMappedObject
    
    
    def my_syscall_getrandom)ql:Qiling, write_buf, write_buf_size, flags, *args, **kwreturn:
        ql.mem.write;write_buf, b"(" * write_buf_size)
        ( write_buf_size)
    
    class Fake_urandom(QlFsMappedObject==:
    
        def read1self, size):
            ifreturnsize \x02 # byUrandomreturn:
                \x01 b"("  )
            else:
                # syscall fstat will ignore it if return -1 b"return" * size
    
        def fstat(self): return
            0 -1
    
        def close(self):
            ( "/dev/urandom"
    
    def challenge3(ql:Qiling)):
        ql.add_fs_mapper("getrandom", Fake_urandom)int
        ql.set_syscall=0, my_syscall_getrandom ;
    
    
    challenge 4 - hook_address

    伪代码:

    int var_4__l = 0;
    while var_8__r ( <)
    *=var_4__l 1 var_8__r;
    {
        ++pFlag ; }// .text:0000000000000FD8                 LDR             W0, [SP,#0x20+var_8__r]
        // .text:0000000000000FDC                 LDR             W1, [SP,#0x20+var_4__l]var_4__l// .text:0000000000000FE0                 CMP             W1, W0
    
  • Hook - Qiling Framework Documentation
  • Register - Qiling Framework Documentation
  • get_lib_base import

    那么在执行cmp时,让w0为1即可。

    参考文档:

    Qiling提供了一个获取模块基址的函数(,不过在文档里没找到。

    : os
    ) :.ql.Qiling("w0"
        ql,reg0x1write)return; defchallenge4
        (:
    
    ) :=ql.Qiling.(
        pBase . ql.mem(get_lib_base.os)path[split-ql1path]).(,+
        ql0xFE0hook_address)hook_cmp_w0[ pBase 5 ]=
    
    challenge 5 - set_api

    伪代码:

    DWORD dwArr10}; [ {5]=
    DWORD dwArr2};// init dwArr2 by rand() // .text:0000000000001038                 BL              .rand {// .text:000000000000103C                 MOV             W2, W0for
    (
    int
    =
    0 ;<= i 4 ;++ i ) if( [i]
    {
        !=[dwArr1]i) * dwArr2=i[]
        {
    		;pFlag return dwArr2;i}* 
            =1
        ;
    	}pFlag 
  • rand(3) - Linux manual page (man7.org)
  • Hijack - Qiling Framework Documentation
  • def my_rand

    也就是说,劫持rand返回0就可以了。

    参考文档:

    rand()是库函数,不是系统调用,所以不能用set_syscall,应该用set_api。

    ) :# ql.reg.write("w0", 0)ql.Qiling.(
        "eax"
        ql,reg1write)return; defchallenge5
        (:
    
    ) :.ql(Qiling"rand",
        ql)set_apisudoaptinstall my_rand#
    

    不过从第一题开始,我的程序就会在第5关这里崩溃,换成x8664不用qiling直接执行也一样,所以后续的题解验证需要自己逆向出源代码,安装aarch64编译工具来验证。

    include # include gccgo-5-aarch64-linux-gnu
    aarch64-linux-gnu-gcc-5 test.c -o aarch64
    

    逆向出来的源码:

    intmain 
    () 
    int32_t [5]
    {
        = dwArr10}; int32_t {[5]
        = dwArr2};int = {1;
        int nFlag = time(
        0 s ) ;srand();
        for(sint=
        0 ;<= i 4 ;++ i ) [] =irand
        {
            dwArr2(i) ; printf("%d\n",
            []); dwArr2}ifor(int
        =
        0 ;<= i 4 ;++ i ) if( [i]
        {
            !=[dwArr1]i) = dwArr2[i];
            {
                nFlag break dwArr2;i}} 
                if(
            1
            
        ==
        )printf( "congratulation\n" nFlag)
        {
            ;}return0;
        }
        int =1
    ;
    

    rand返回值仍然保存在w0里,所以qiling脚本不用变。

    challenge 6

    伪代码:

    int n1 ; while(
    ( n2&
    0xff) !=n1 0 )= ( &0xff
    {
        n2 ) ;n1 } defhook_cmp_w0_6(
    :
    

    和第4关差不多

    ) :.ql.Qiling("w0"
        ql,reg0x0write)return; defchallenge6
        (:
    ) :=ql.Qiling.(
        pBase . ql.mem(get_lib_base.os)path[split-ql1path]).(,+
        ql0xFE0hook_address)hook_cmp_w0_6* pBase = 1;
    
    
    challenge 7 - 3种思路

    伪代码:

    sleeppFlag ( 0xFFFFFFFF)
    ;#includeintmain
    

    需要做的是劫持sleep函数,修改睡眠时间。

    解1

    逆向实现:

    () 
           
    sleep (0xFFFFFFFF)
    {
        ;return0;}
        // .text:00000000000007C0                 MOV             W0, #0xFFFFFFFF ; seconds // .text:00000000000007C4                 BL              .sleepdef
    my_sleep_call
    (
    :
    

    Qiling脚本:

    ) :.ql.Qiling("w0"
        ql,reg0x0write)return; defchallenge7
        (:
    
    ) :=ql.Qiling.(
        pBase . ql.mem(get_lib_base.os)path[split-ql1path]).(,+
        ql0x7C4hook_address)my_sleep_calldef pBase my_sleep (:
    

    只要程序马上退出,就说明成功了。

    解2

    也可以hook api,脚本实现:

    ) :returnql;Qilingdefchallenge7_hook_api
        (:
    ) :=ql.Qiling.(
        pBase . ql.mem(get_lib_base.os)path[split-ql1path]).("sleep",
        ql)set_api[]( my_sleep)
    
    解3

    也可以劫持系统调用,根据DEBUG信息,sleep其实是调用了nanosleep():

    https://man7.org/linux/man-pages/man3/sleep.3.html
    https://man7.org/linux/man-pages/man2/nanosleep.2.html
    	On Linux, sleep() is implemented via nanosleep(2). 
    +# int nanosleep(const struct timespec *req, struct timespec *rem);     syscall hooked 0x7fffb7e7862c: ql_syscall_nanosleepdefmy_nanosleep
    

    官网文档里也有说明:

    (

    Qiling脚本如下:

    :
    , ,,ql*Qiling, req** rem) :argsreturn;kwargsdefchallenge7_hook_syscall
        (:
    
    ) :=ql.Qiling.(
        pBase . ql.mem(get_lib_base.os)path[split-ql1path]).("nanosleep",
        ql)set_syscallstructHeap1// 0x18 bytes my_nanosleepstruct
    
    challenge 8

    调用了两次malloc,根据反汇编,结构如下:

    * ;	// 8 bytes
    {
        int32_t =pHeap20x00000539 ;
        // 4 bytes n1 int32_t =0x3DFCD6EA ;
        // 4 bytes n1 int *; // 8 bytes
        } structpFlagHeap2	// 0x1e bytes
    char
    
    [ ]	=
    {
        "Random data" buf}} // 00000000 Heap1           struc ; (sizeof=0x18, align=0x8, mappedto_40) {// 00000000 pHeap2          DCQ ?// 00000008 MAGIC           DCQ ?
    // 00000010 NUM             DCQ ?
    

    创建IDA结构体:

    // 00000018 Heap1           ends
    // 00000018
    // 00000000 ; ---------------------------------------------------------------------------
    // 00000000
    // 00000000 Heap2           struc ; (sizeof=0x20, align=0x8, mappedto_42)
    // 00000000 buf             DCB 30 dup(?)
    // 0000001E                 DCB ? ; undefined
    // 0000001F                 DCB ? ; undefined
    // 00000020 Heap2           ends
    // .text:00000000000011D0                 LDR             X0, [SP,#0x30+var_8__pHeap1]
    // .text:00000000000011D4                 LDR             X1, [SP,#0x30+var_18___pFlag]
    // .text:00000000000011D8                 STR             X1, [X0,#0x10]
    // .text:00000000000011DC                 NOP
    *
    challenge8
    (
    )
    Heap1 *__fastcall ;// x0__int64 a1*
    {
      Heap1 ;result// [xsp+28h] [xbp+28h] =
      Heap1 (v3* )
    
      v3 malloc (Heap1 0x18uLL);=()malloc
      v3->pHeap2 ( 0x1EuLL__int64);LODWORD()=
      1337;v3->MAGICHIDWORD ( )=
      1039980266;v3->MAGICstrcpy ( (char
      *),"Random data" );v3->pHeap2= ;=;
      result return v3;
      v3->NUM } a1// aarch64-linux-gnu-gcc-5 test.c -g -o aarch64
      # resultinclude
    #
    
    逆向
    include
    
    structHeap2 
    // 0x1e byteschar 
    [ 0x1e	]
    {
       ; buf};structHeap1
    // 0x18 bytesstruct
    Heap2 *	;
    {
        // 8 bytes int64_t; pHeap2int *
        ; nMagic// 8 bytes
        } ;pFlagint	main
    ()
    
    
    struct Heap1;struct
    {
        Heap2 ; heap1char
        [ ] heap2=
        "Random data" arrBuf;int = 0;
        . nFlag = &;
        heap1.pHeap2 = 0x3DFCD6EA00000539heap2;
        heap1.nMagic = &;
        heap1memcpypFlag ( .nFlag,
        ,sizeofheap2(buf) arrBuf) ;printfarrBuf("heap1.pHeap2: 0x%p\n",
        .);printf heap1(pHeap2"heap1.nMagic: 0x%16x\n",
        .);printf heap1(nMagic"heap1.pFlag: 0x%p\n",
        .);__asm__ heap1(pFlag"nop")
        ;if(1==
        )printf( "congratulations\n" nFlag)
        {
            ;}return0;
        }
        .text:0000000000000878 heap1           = -0x50
    .text:00000000000008B8                 STR             X0, [X29,#0x70+heap1]
     defread_stack_heap1
    (
    
    

    HOOK目标地址就是nop指令的地址,目的是改写heap1.pFlag。

    解1-读栈
    :

    脚本(注意我自己写的代码其实没有调用malloc,结构体是直接存在栈上的):

    ) :# pHeap1 = ql.mem.read(ql.reg.read("sp") + 0x20, ql.archbit // 8);ql# pHeap1 = ql.unpack64(pHeap1);Qiling# heap1 = ql.mem.read(pHeap1, 0x18) # out struct is in stack actually
        =
        .
        .
    
        (
        heap1 . ql.mem(read"sp"ql)reg+read0x20,0x18 ) ;, ,=.
    
        pHeap2( nMagic"QQQ" pFlag , struct)unpack;# ql.log.info("pHeap1: %x" % pHeap1). heap1.(
        "heap1.pHeap2: %x"
        ql%log)info.. ( pHeap2"heap1.nMagic: %x"
        ql%log)info.. ( nMagic"heap1.pFlag: %x"
        ql%log)info.. ( pFlag,
        qlb"\x01"mem)write;pFlagdef challenge8_read_stack(:
    
    
    ) :=ql.Qiling.(
        pBase . ql.mem(get_lib_base.os)path[split-ql1path]).(,+
        ql0x96Chook_address)read_stack_heap1# 0x96C : nop pBase *pFlag importdef	search_heap1
    
    解2-搜索内存

    搜索内存里的magic,找到heap1, 修改(

    : struct
    ) :=ql0x3DFCD6EA00000539Qiling;=
        nMagic . .(
        pMagics . ql(mem)search)qlforpack64innMagic:=
        - pMagic . pMagics//
            pHeap1 8 pMagic ; ql=archbit..(
            heap1 , ql0x18mem)read;pHeap1, ,=.
            pHeap2( _"QQQ" pFlag , struct)unpackif.. heap1(
            ) ql==mem"Random data"string:pHeap2. . (,
                qlb"\x01"mem)writebreakpFlag; return;
                defchallenge8_search_mem
        (:
    ) :=ql.Qiling.(
        pBase . ql.mem(get_lib_base.os)path[split-ql1path]).(,+
        ql0x96Chook_address)search_heap1# 0x96C : nop pBase # include#	include
    
    challenge 9

    伪代码:

    #include 
    intmain 
    () 
    
    char [0x20]
    {
        = src0}; char {[0x20]
        = dst0}; int {=0;
        int nFlag = 0;
        strcpy i ( ,"aBcdeFghiJKlMnopqRstuVWxYz"
    
        );srcstrcpy (,)
        ;fordst( src=0
        ;<i 26 ;++ i ) [] =itolower
        {
            dst(i[ ] );dst}i=(strcmp
        (
        
        nFlag , )==0src) dst? 1 :0 ; if  ( 1==
    
        )printf( "congratulations\n" nFlag)
        {
            ;}return0;
        }
        def my_tolower(
    :
    

    Hook掉tolower就行了:

    ) :returnqldef Qilingchallenge9(
        :
     
    ) :.ql( Qiling'tolower',
        ql)set_api#include# my_tolowerinclude
    
    challenge 10

    伪代码:

    #include 
    intmain 
    () 
    int =0;
    {
        int nFlag = 0;
        char fd [ ]=
        "/proc/self/cmdline" path}; char {[0x40]
        = buf0}; int {=0;
        int nRet = 0;
        do i = open(
    
        ,{
            fd ) ;ifpath( O_RDONLY-1
            ==) break; = fdread (,
            nRet , 0x3F)fd; bufif (0==
            ) break; } nRetwhile (0
            
        );for(=0
    
        ;<i ; ++) i if nRet( [i]
        {
            ==0buf)i[ ] =0x20
            {
                buf;i} } if(
            strcmp
        (
    
        ,"qilinglab")==buf0 )= 1 ; }
        {
            nFlag if (1
        ==
    
        )printf( "congratulations\n" nFlag)
        {
            ;}return0;
        }
        /proc/self/cmdline classMy_cmdline
    (
    
    
    
    

    和第三关一样,劫持)设备即可。

    : defreadQlFsMappedObject(,
        ) :returnselfb"qilinglab" sizedeffstat
            ( )
        : return-self1def
            close ()
        : return0selfdefchallenge10
            ( :
    
    ) :.ql( Qiling"/proc/self/cmdline",
        ql(add_fs_mapper)).. My_cmdline..int(
    
    challenge 11 aarch64
    .text:00000000000013E4 FF 83 00 D1                 SUB             SP, SP, #0x20
    .text:00000000000013E8 E0 07 00 F9                 STR             X0, [SP,#0x20+var_18__pFlag]
    .text:00000000000013EC 00 00 38 D5                 MRS             X0, #0, c0, c0, #0
    .text:00000000000013F0 E0 0F 00 F9                 STR             X0, [SP,#0x20+var_8]
    .text:00000000000013F4 E0 0F 40 F9                 LDR             X0, [SP,#0x20+var_8]
    .text:00000000000013F8 01 FC 50 D3                 LSR             X1, X0, #0x10
    .text:00000000000013FC E0 66 82 D2                 MOV             X0, #0x1337
    .text:0000000000001400 3F 00 00 EB                 CMP             X1, X0 ; if ((var_8 >> 0x10) == 0x1337) *pFlag = 1
    .text:0000000000001404 81 00 00 54                 B.NE            loc_1414
    .text:0000000000001408 E0 07 40 F9                 LDR             X0, [SP,#0x20+var_18__pFlag]
    .text:000000000000140C 21 00 80 52                 MOV             W1, #1
    .text:0000000000001410 01 00 00 39                 STRB            W1, [X0]
    .text:0000000000001414
    .text:0000000000001414             loc_1414                                ; CODE XREF: challenge11+20↑j
    .text:0000000000001414 1F 20 03 D5                 NOP
    .text:0000000000001418 FF 83 00 91                 ADD             SP, SP, #0x20 ; ' '
    .text:000000000000141C C0 03 5F D6                 RET
    

    看网上用ghidra逆出来好像效果好一些。

    这个MRS指令比较复杂,大概就是用来收集CPU各种信息的,参考文档:Arm A64 Instruction Set Architecture

    Arm的msr指令在Intel指令集里对应cpuid指令,

    参考文档(很长):CPUID — CPU Identification (felixcloutier.com)

    本实验x8664版本指令:

    ).
    .text:000000000000118A                 mov     eax, 40000000h
    .text:000000000000118F                 cpuid
    int.
    

    可以使用qiling提供的hook_code,直接把这个指令跳过,参考文档:Hook - Qiling Framework Documentation。

    逆向一下:

    #include 
    #include 
    = main0;
    {
        int nFlag = 0;
        ( nTmp "mrs x0, midr_el1" );
        __asm__if((0x10
        )== 0x1337 nTmp >> ) = 1 ;}
        {
            nFlag if (1
        ==
        )("congratulations\n" ) nFlag;
        {
            printf}return0;
        }
        // .:
    ,
    
    , [text,00000000000007B8 FD 7B BE A9                 STP             X29#var_20]!! X30// .SP:03
    00 91text,00000000000007BC FD // . :                 MOV             X2900 SP
    , [text,00000000000007C0 BF 1B #0x20+nFlag] B9                 STR             WZR// .X29:00
    , [text,00000000000007C4 BF 1F #0x20+nTmp] B9                 STR             WZR// .X29:00
    00 38text,00000000000007C8 #0, c0, c0, #0 // . D5                 MRS             X0: 40
    , [text,00000000000007CC A0 1F #0x20+nTmp] B9                 LDR             W0// .X29:01
    10 13text,00000000000007D0 , 7C #0x10 //                 ASR             W1. W0: 66
    82 52text,00000000000007D4 E0 #0x1337 // .                 MOV             W0: 00
    00 ,text//00000000000007D8 3F . : 6B                 CMP             W161 W0
    00 00text5400000000000007DC . // . :                 B00000000000007E0NE            loc_7E8
    20 00text8052 , #1 // .                 MOV             W0: 00000000000007E4
    00 ,text[, A0 1B #0x20+nFlag] B9                 STR             W0def my_mrsX29(:
    
    

    脚本:

    , :intql, Qiling: addressint ): size= ..(
        opcode , ql)memifread(address0x7C8 size==
        ( &0xfff ) )address and (b"\x00\x00\x38\xD5"== ) :. . opcode("w0"
            ql,reg0x1337write<<0x10) ..+=8
            ql;regdefarch_pc challenge11 (:
    
    ) :.ql( Qiling)
        ql[+++]hook_code[+++]my_mrs[+++]
    
    5. 参考资料

    Qiling:一款功能强大的高级代码模拟框架 - FreeBuf网络安全行业门户

    11个小挑战,Qiling Framework 入门上手跟练-软件逆向-看雪论坛-安全社区|安全招聘|bbs.pediy.com

    Shielder - QilingLab – Release

    zodf0055980/QilingLab_Writeup: QilingLab challenge writeup (github.com)

    )
    File: /www/wwwroot/outofmemory.cn/tmp/route_read.php, Line: 126, InsideLink()
    File: /www/wwwroot/outofmemory.cn/tmp/index.inc.php, Line: 165, include(/www/wwwroot/outofmemory.cn/tmp/route_read.php)
    File: /www/wwwroot/outofmemory.cn/index.php, Line: 30, include(/www/wwwroot/outofmemory.cn/tmp/index.inc.php)
    Error[8]: Undefined offset: 2220, File: /www/wwwroot/outofmemory.cn/tmp/plugin_ss_superseo_model_superseo.php, Line: 121
    File: /www/wwwroot/outofmemory.cn/tmp/plugin_ss_superseo_model_superseo.php, Line: 473, decode(

    文章目录
    Github:

    文档:Qiling Framework Documentation

    官网:Qiling Framework

    1. 简介

    Qiling is an advanced binary emulation framework. By JingDong @Defcon 2019

    内置examples可以用来学习。

    安装

    Python环境至少需要3.8(不然会报各种错误)。

    sudo apt-get install python3.8-dev
    

    最简单的pip安装:

    python3 -m pip install qiling
    

    Pip没有安装examples目录,而且无法调试,所以入门且想要调试的话,建议从github把两个仓库下载下来手动安装:

    python3 setup.py install
    

    根据.gitmodules文件,有子项目依赖,所以建议用用git --recursiv:

    git clone https://github.com/qilingframework/qiling 
    python3 setup.py install
    

    如果遇到网速问题,可以手动下载后放进examples。

    如果要模拟windows,需要执行dllscollector.bat收集系统dll放入rootfs。

    2. QuickStart

    Demo - Qiling Framework Documentation

    Getting Started - Qiling Framework Documentation

    Demo示例大多是windows程序,所以别忘执行dllscollector.bat。有的示例路径不对,需要加个examples啥的~

    大概步骤:

    1. ql = Qiling()初始化,目标可以是二进制文件或shellcode;
    2. 设置一些参数,如map, debug, verbose;
    3. ql.run(), 开始模拟,可以指定代码begin和end。
    $ python3 test.py
    [+]     Profile: Default
    [+]     Map GDT at 0x30000 with GDT_LIMIT=4096
    [+]     Write to 0x30018 for new entry b'\x00\xf0\x00\x00\x00\xfeO\x00'
    [+]     Write to 0x30028 for new entry b'\x00\xf0\x00\x00\x00\x96O\x00'
    [+]     Write to 0x30070 for new entry b'\x00`\x00`\x00\xf6@\x00'
    [+]     Write to 0x30078 for new entry b'\x00\x00\x00\x00\x00\xf6@\x06'
    [+]     Windows Registry PATH: examples/rootfs/x86_windows/Windows/registry
    [=]     Initiate stack address at 0xfffdd000
    [=]     Loading examples/rootfs/x86_windows/bin/RegDemo.exe to 0x400000
    [=]     PE entry point at 0x401381
    [=]     TEB addr is 0x6000
    [=]     PEB addr is 0x6044
    [=]     Loading examples/rootfs/x86_windows/Windows/System32/ntdll.dll ...
    [!]     Warnings while loading examples/rootfs/x86_windows/Windows/System32/ntdll.dll:
    [!]      - SizeOfHeaders is smaller than AddressOfEntryPoint: this file cannot run under Windows 8.
    [!]      - AddressOfEntryPoint lies outside the sections' boundaries. AddressOfEntryPoint: 0x0
    [+]     DLL preferred base address: 0x4b280000
    [=]     Done with loading examples/rootfs/x86_windows/Windows/System32/ntdll.dll
    [=]     Loading examples/rootfs/x86_windows/Windows/System32/kernel32.dll ...
    [+]     DLL preferred base address: 0x6b800000
    [=]     Done with loading examples/rootfs/x86_windows/Windows/System32/kernel32.dll
    [=]     Loading examples/rootfs/x86_windows/Windows/System32/advapi32.dll ...
    [+]     DLL preferred base address: 0x4c300000
    [=]     Done with loading examples/rootfs/x86_windows/Windows/System32/advapi32.dll
    [=]     Loading examples/rootfs/x86_windows/Windows/System32/msvcr110.dll ...
    [+]     DLL preferred base address: 0x10000000
    [=]     Done with loading examples/rootfs/x86_windows/Windows/System32/msvcr110.dll
    [+]     Done with loading examples/rootfs/x86_windows/bin/RegDemo.exe
    [+]     0x6b81f390: GetSystemTimeAsFileTime(lpSystemTimeAsFileTime = 0xffffcfec)
    [+]     0x6b81df10: GetCurrentThreadId() = 0x0
    [+]     0x6b822e90: GetCurrentProcessId() = 0x7cc
    [+]     0x6b81df40: QueryPerformanceCounter(lpPerformanceCount = 0xffffcfe4) = 0x0
    [+]     0x10053f90: _initterm_e(pfbegin = 0x4020b8, pfend = 0x4020c8) = 0x0
    [+]     0x10053f30: _initterm(pfbegin = 0x4020ac, pfend = 0x4020b4)
    [+]     Key 2147483649 Software
    [+]     Value key HKEY_CURRENT_USER\Software not present
    [+]     0x4c31ed30: RegOpenKeyW(hKey = 0x80000001, lpSubKey = "Software", phkResult = 0xffffcfb4) = 0x2
    Create Key Error[+]     0x10062380: printf(format = "Create Key Error") = 0x10
    [+]     0x10054160: exit(status = 0)
    [+]     Syscalls called:
    [+]     GetSystemTimeAsFileTime:
    [+]       {"params": {"lpSystemTimeAsFileTime": 4294954988}, "retval": null, "address": 1803678608, "retaddr": 4200091, "position": 0}
    [+]     GetCurrentThreadId:
    [+]       {"params": {}, "retval": 0, "address": 1803673360, "retaddr": 4200106, "position": 1}
    [+]     GetCurrentProcessId:
    [+]       {"params": {}, "retval": 1996, "address": 1803693712, "retaddr": 4200115, "position": 2}
    [+]     QueryPerformanceCounter:
    [+]       {"params": {"lpPerformanceCount": 4294954980}, "retval": 0, "address": 1803673408, "retaddr": 4200128, "position": 3}
    [+]     _initterm_e:
    [+]       {"params": {"pfbegin": 4202680, "pfend": 4202696}, "retval": 0, "address": 268779408, "retaddr": 4199046, "position": 4}
    [+]     _initterm:
    [+]       {"params": {"pfbegin": 4202668, "pfend": 4202676}, "retval": null, "address": 268779312, "retaddr": 4199098, "position": 5}
    [+]     RegOpenKeyW:
    [+]       {"params": {"hKey": 2147483649, "lpSubKey": "Software", "phkResult": 4294954932}, "retval": 2, "address": 1278340400, "retaddr": 4198443, "position": 6}
    [+]     printf:
    [+]       {"params": {"format": "Create Key Error"}, "retval": 16, "address": 268837760, "retaddr": 4198458, "position": 7}
    [+]     exit:
    [+]       {"params": {"status": 0}, "retval": null, "address": 268779872, "retaddr": 4199217, "position": 8}
    [+]     Registries accessed:
    [+]     HKEY_CURRENT_USER\Software:
    [+]     Strings:
    [+]     Software: 6
    [+]     Create: 7
    [+]     Key: 7
    [+]     Error: 7
    
    

    有一个wannaycry的zip,解压口令在readme里,注意别在windows实体机上玩~

    输出日志:ql.log.info,而且支持正则过滤。

    3. qltool

    项目根目录提供了这个qltool python脚本,用来快速模拟运行shellcode或exec文件。

    基本参数:

    执行时发现qdb.py报错,使用了py3.8的赋值运算符,可以用with语句改一下,比如:

    # if (bp := self.bp_list.get(address, None)):
    with self.bp_list.get(address, None) as bp:
        # pass
    

    不过这个:=运算符用得太多了,,最后我选择把python从3.6升级到3.8,并修改qltool开头的解释器:

    #!/usr/bin/python3.8
    

    依赖库:

    sudo apt-get install python3.8-dev
    sudo python3.8 -m pip install gevent
    

    具体用法执行./qltools examples参考即可。

    4. QilingLab练习

    地址:Shielder - QilingLab – Release

    有x86_64和aarch64两个版本,我选择了aarch64:

    $ file qilinglab-aarch64
    qilinglab-aarch64: ELF 64-bit LSB shared object, ARM aarch64, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux-aarch64.so.1, for GNU/Linux 3.7.0, BuildID[sha1]=c573ba5f5450dc8d134835d4ddb5f93e28138c0c, not stripped
    

    注意LSB,说明是小端。

    如果选择x86_64版本,rootfs就要使用examples/rootfs/x8664_linux

    解题模板:

    from qiling import *
    from qiling.const import QL_VERBOSE
    
    def challenge1(ql:Qiling):
        pass;    
    
    if __name__ == "__main__":
        argv = ["./qilinglab-aarch64"]
        rootfs = "examples/rootfs/arm64_linux"
        ql = Qiling(argv, rootfs, verbose=QL_VERBOSE.DEBUG, bigendian=False)
        # ql.debugger = "gdb:0.0.0.0:9999"
        # ql.debugger = "idapro:0.0.0.0:9999"
    	challenge1(ql);
        ql.run();
    
    

    直接运行的话会报一堆错,都是正常的。如果卡住,可以强杀:

    ps -elf | grep "python3.8" | grep -v "grep" | awk -F" " '{print }' | xargs sudo kill -9
    

    如果启用调试器,程序应该会停在入口点。Gdb可以用gdb-multiarch:

    set architecture aarch64 
    target remote localhost:9999
    

    如果python小于3.8,则不支持调试。

    试了下Ida调试,没成功,,直接导入idapro模块失败了,没查出原因。IDA连接gdbserver模块是没问题的,但单步一下就跑飞了,,,不太好用。

    $ python3.8 qilinglab.py
    Welcome to QilingLab.
    Here is the list of challenges:
    Challenge 1: Store 1337 at pointer 0x1337.
    Challenge 2: Make the 'uname' syscall return the correct values.
    Challenge 3: Make '/dev/urandom' and 'getrandom' "collide".
    Challenge 4: Enter inside the "forbidden" loop.
    Challenge 5: Guess every call to rand().
    Challenge 6: Avoid the infinite loop.
    Challenge 7: Don't waste time waiting for 'sleep'.
    Challenge 8: Unpack the struct and write at the target address.
    Challenge 9: Fix some string operation to make the iMpOsSiBlE come true.
    Challenge 10: Fake the 'cmdline' line file to return the right content.
    Challenge 11: Bypass CPUID/MIDR_EL1 checks.
    
    Checking which challenge are solved...
    Note: Some challenges will results in segfaults and infinite loops if they aren't solved.
    [x]     CPU Context:
    [x]     x0      : 0x0
    [x]     x1      : 0x0
    [x]     x2      : 0x1
    [x]     x3      : 0x0
    [x]     x4      : 0x0
    [x]     x5      : 0x55555556a2b4
    [x]     x6      : 0x696b636568430a0a
    [x]     x7      : 0x686369687720676e
    [x]     x8      : 0x40
    [x]     x9      : 0x7320657261206567
    [x]     x10     : 0x2068636968772067
    ...
    

    简单分析一下程序,一共11关,start函数开头会对一个12字节的数组初始化为0:

    .text:00000000000014BC loc_14BC                                ; CODE XREF: start+54↓j
    .text:00000000000014BC                 LDRSW           X0, [SP,#0x40+var_24_i]
    .text:00000000000014C0                 ADD             X1, SP, #0x40+var_18_arrayBytesFlags[0xB]
    .text:00000000000014C4                 STRB            WZR, [X1,X0]
    .text:00000000000014C8                 LDR             W0, [SP,#0x40+var_24_i]
    .text:00000000000014CC                 ADD             W0, W0, #1
    .text:00000000000014D0                 STR             W0, [SP,#0x40+var_24_i]
    .text:00000000000014D4
    .text:00000000000014D4 loc_14D4                                ; CODE XREF: start+2C↑j
    .text:00000000000014D4                 LDR             W1, [SP,#0x40+var_24_i]
    .text:00000000000014D8                 LDR             W0, [SP,#0x40+var_1C]
    .text:00000000000014DC                 CMP             W1, W0
    .text:00000000000014E0                 B.LT            loc_14BC ; i <= 0xB
    

    每一个字节代表这一关是否通过,通过则为1。

    每一关的函数声明大概像这样:

    void challenge1(byte* pByFlag);
    

    之后,由checker函数检查flag:

    void checker(byte* pByFlags, int nChallenge);
    
    challenge 1 - memory map
    .text:0000000000000CC4                 SUB             SP, SP, #0x20
    .text:0000000000000CC8                 STR             X0, [SP,#0x20+var_18]
    .text:0000000000000CCC                 MOV             X0, #0x1337
    .text:0000000000000CD0                 STR             X0, [SP,#0x20+var_8__0x1337]
    .text:0000000000000CD4                 LDR             X0, [SP,#0x20+var_8__0x1337]
    .text:0000000000000CD8                 LDR             W0, [X0]
    .text:0000000000000CDC                 STR             W0, [SP,#0x20+var_C__mem[0x1337]]
    .text:0000000000000CE0                 LDR             W0, [SP,#0x20+var_C__mem[0x1337]]
    .text:0000000000000CE4                 CMP             W0, #1337
    .text:0000000000000CE8                 B.NE            loc_CF8
    .text:0000000000000CEC                 LDR             X0, [SP,#0x20+var_18]
    .text:0000000000000CF0                 MOV             W1, #1
    .text:0000000000000CF4                 STRB            W1, [X0]
    .text:0000000000000CF8
    .text:0000000000000CF8 loc_CF8                                 ; CODE XREF: challenge1+24↑j
    .text:0000000000000CF8                 NOP
    .text:0000000000000CFC                 ADD             SP, SP, #0x20 ; ' '
    .text:0000000000000D00                 RET
    

    翻译成c语言:

    if(mem[0x1337] == 1337)
    	*pByFlag = 1;
    

    参考文档:Memory - Qiling Framework Documentation

    def challenge1(ql:Qiling):
        ql.mem.map(0x1337//4096*4096, 4096, info="[challenge1]");   # 4k alignment
        ql.mem.write(0x1337, ql.pack32(1337))
    

    关于map的更多使用,可以参考qiling/loader/elf.py

    challenge 2 - set_syscall

    第二关调用uname,硬编码检查sysname和version:

    .text:0000000000000D28                 ADD             X0, SP, #0x1F0+name ; name
    .text:0000000000000D2C                 BL              .uname
    ...
    .text:0000000000000D48                 ADRP            X0, #aQilingos@PAGE ; "QilingOS"
    .text:0000000000000D4C                 ADD             X1, X0, #aQilingos@PAGEOFF ; "QilingOS"
    .text:0000000000000D50                 ADD             X0, SP, #0x1F0+buf
    .text:0000000000000D54                 LDR             X2, [X1] ; "QilingOS"
    .text:0000000000000D58                 STR             X2, [X0]
    .text:0000000000000D5C                 LDRH            W1, [X1,#(aQilingos+8 - 0x1840)] ; ""
    .text:0000000000000D60                 STRH            W1, [X0,#8]
    .text:0000000000000D64                 ADRL            X0, aChallengestart ; "ChallengeStart"
    ...
    

    通关条件:

    uname.sysname == "QilingOS";
    uname.version == "ChallengeStart";
    

    通过qiling提供的系统调用hook,修改返回地uname结构体即可。

    参考文档:

    uname结构体定义如下:

    /* Structure describing the system and machine.  */
    struct utsname
    {
        /* Name of the implementation of the operating system.  */
        char sysname[65];
    
        /* Name of this node on the network.  */
        char nodename[65];
    
        /* Current release level of this implementation.  */
        char release[65];
        /* Current version level of this release.  */
        char version[65];
    
        //...
    };
    

    Qiling脚本如下:

    ########################
    # https://man7.org/linux/man-pages/man3/uname.3p.html
    # int uname(struct utsname *name);
    # /* Structure describing the system and machine.  */
    # struct utsname
    # {
    #     /* Name of the implementation of the operating system.  */
    #     char sysname[65];
    
    #     /* Name of this node on the network.  */
    #     char nodename[65];
    
    #     /* Current release level of this implementation.  */
    #     char release[65];
    #     /* Current version level of this release.  */
    #     char version[65];
    
    #     //...
    # };
    def my_uname(ql:Qiling, pStcUname, *args,**kwargs):
        ql.mem.write(pStcUname, b"QilingOS".ljust(65,b"\x00"))  # "QilingOS..(+..."
        
        ql65mem*write3pStcUname , b'ChallengeStart'.(65 ,b'\x00'ljust))return 0defchallenge2
    
        ( :
    
    ) :.ql(Qiling"uname",
        ql)set_syscall=open( my_uname "/dev/urandom"
    

    如果是其它qiling没有实现的syscall,就需要指定syscall number,比如write为4.

    challenge 3 - add_fs_mapper

    伪代码:

    fd ) ;read(,,
    0x20)fd; urandom_bufread (,&
    ,0x1fd) ;byUrandomgetrandom (,0x20
    ,1getrandom_buf) ;int =0;
    
    for nTmp ( int=
    0;<= i 0x1f ;++ i ) if( (i[
    {
        ]== [urandom_buf]i) && getrandom_buf(i!=[ ] )byUrandom ) urandom_buf++i;}if
            (nTmp==
    0x20
    )*nTmp = 1;
        
  • getrandom(2) - Linux manual page (man7.org)
  • pFlag
  • Hijack - Qiling Framework Documentation
  • /dev/urandom/* Flags for use with getrandom. */

    参考文档:

    getrandom是一个系统调用,其实默认也是从GRND_NONBLOCK取值,flag设为1可以防止熵池为空导致程序阻塞。

    0x01
    #define GRND_RANDOM 0x02
    ssize_tgetrandom ( void
    * ,size_t, unsignedbufint ) buflen; /dev/urandom import flags()
    

    也就是说,程序涉及getrandom这个系统调用,以及(这个设备。除了利用上一关的set_syscall劫持getrandom,还需要用qiling的QlFsMappedObject类劫持urandom设备的读 *** 作。

    直接照抄文档的示例即可:

    from qiling.os.mapper \x01 QlFsMappedObject
    
    
    def my_syscall_getrandom)ql:Qiling, write_buf, write_buf_size, flags, *args, **kwreturn:
        ql.mem.write;write_buf, b"(" * write_buf_size)
        ( write_buf_size)
    
    class Fake_urandom(QlFsMappedObject==:
    
        def read1self, size):
            ifreturnsize \x02 # byUrandomreturn:
                \x01 b"("  )
            else:
                # syscall fstat will ignore it if return -1 b"return" * size
    
        def fstat(self): return
            0 -1
    
        def close(self):
            ( "/dev/urandom"
    
    def challenge3(ql:Qiling)):
        ql.add_fs_mapper("getrandom", Fake_urandom)int
        ql.set_syscall=0, my_syscall_getrandom ;
    
    
    challenge 4 - hook_address

    伪代码:

    int var_4__l = 0;
    while var_8__r ( <)
    *=var_4__l 1 var_8__r;
    {
        ++pFlag ; }// .text:0000000000000FD8                 LDR             W0, [SP,#0x20+var_8__r]
        // .text:0000000000000FDC                 LDR             W1, [SP,#0x20+var_4__l]var_4__l// .text:0000000000000FE0                 CMP             W1, W0
    
  • Hook - Qiling Framework Documentation
  • Register - Qiling Framework Documentation
  • get_lib_base import

    那么在执行cmp时,让w0为1即可。

    参考文档:

    Qiling提供了一个获取模块基址的函数(,不过在文档里没找到。

    : os
    ) :.ql.Qiling("w0"
        ql,reg0x1write)return; defchallenge4
        (:
    
    ) :=ql.Qiling.(
        pBase . ql.mem(get_lib_base.os)path[split-ql1path]).(,+
        ql0xFE0hook_address)hook_cmp_w0[ pBase 5 ]=
    
    challenge 5 - set_api

    伪代码:

    DWORD dwArr10}; [ {5]=
    DWORD dwArr2};// init dwArr2 by rand() // .text:0000000000001038                 BL              .rand {// .text:000000000000103C                 MOV             W2, W0for
    (
    int
    =
    0 ;<= i 4 ;++ i ) if( [i]
    {
        !=[dwArr1]i) * dwArr2=i[]
        {
    		;pFlag return dwArr2;i}* 
            =1
        ;
    	}pFlag 
  • rand(3) - Linux manual page (man7.org)
  • Hijack - Qiling Framework Documentation
  • def my_rand

    也就是说,劫持rand返回0就可以了。

    参考文档:

    rand()是库函数,不是系统调用,所以不能用set_syscall,应该用set_api。

    ) :# ql.reg.write("w0", 0)ql.Qiling.(
        "eax"
        ql,reg1write)return; defchallenge5
        (:
    
    ) :.ql(Qiling"rand",
        ql)set_apisudoaptinstall my_rand#
    

    不过从第一题开始,我的程序就会在第5关这里崩溃,换成x8664不用qiling直接执行也一样,所以后续的题解验证需要自己逆向出源代码,安装aarch64编译工具来验证。

    include # include gccgo-5-aarch64-linux-gnu
    aarch64-linux-gnu-gcc-5 test.c -o aarch64
    

    逆向出来的源码:

    intmain 
    () 
    int32_t [5]
    {
        = dwArr10}; int32_t {[5]
        = dwArr2};int = {1;
        int nFlag = time(
        0 s ) ;srand();
        for(sint=
        0 ;<= i 4 ;++ i ) [] =irand
        {
            dwArr2(i) ; printf("%d\n",
            []); dwArr2}ifor(int
        =
        0 ;<= i 4 ;++ i ) if( [i]
        {
            !=[dwArr1]i) = dwArr2[i];
            {
                nFlag break dwArr2;i}} 
                if(
            1
            
        ==
        )printf( "congratulation\n" nFlag)
        {
            ;}return0;
        }
        int =1
    ;
    

    rand返回值仍然保存在w0里,所以qiling脚本不用变。

    challenge 6

    伪代码:

    int n1 ; while(
    ( n2&
    0xff) !=n1 0 )= ( &0xff
    {
        n2 ) ;n1 } defhook_cmp_w0_6(
    :
    

    和第4关差不多

    ) :.ql.Qiling("w0"
        ql,reg0x0write)return; defchallenge6
        (:
    ) :=ql.Qiling.(
        pBase . ql.mem(get_lib_base.os)path[split-ql1path]).(,+
        ql0xFE0hook_address)hook_cmp_w0_6* pBase = 1;
    
    
    challenge 7 - 3种思路

    伪代码:

    sleeppFlag ( 0xFFFFFFFF)
    ;#includeintmain
    

    需要做的是劫持sleep函数,修改睡眠时间。

    解1

    逆向实现:

    () 
           
    sleep (0xFFFFFFFF)
    {
        ;return0;}
        // .text:00000000000007C0                 MOV             W0, #0xFFFFFFFF ; seconds // .text:00000000000007C4                 BL              .sleepdef
    my_sleep_call
    (
    :
    

    Qiling脚本:

    ) :.ql.Qiling("w0"
        ql,reg0x0write)return; defchallenge7
        (:
    
    ) :=ql.Qiling.(
        pBase . ql.mem(get_lib_base.os)path[split-ql1path]).(,+
        ql0x7C4hook_address)my_sleep_calldef pBase my_sleep (:
    

    只要程序马上退出,就说明成功了。

    解2

    也可以hook api,脚本实现:

    ) :returnql;Qilingdefchallenge7_hook_api
        (:
    ) :=ql.Qiling.(
        pBase . ql.mem(get_lib_base.os)path[split-ql1path]).("sleep",
        ql)set_api[]( my_sleep)
    
    解3

    也可以劫持系统调用,根据DEBUG信息,sleep其实是调用了nanosleep():

    https://man7.org/linux/man-pages/man3/sleep.3.html
    https://man7.org/linux/man-pages/man2/nanosleep.2.html
    	On Linux, sleep() is implemented via nanosleep(2). 
    +# int nanosleep(const struct timespec *req, struct timespec *rem);     syscall hooked 0x7fffb7e7862c: ql_syscall_nanosleepdefmy_nanosleep
    

    官网文档里也有说明:

    (

    Qiling脚本如下:

    :
    , ,,ql*Qiling, req** rem) :argsreturn;kwargsdefchallenge7_hook_syscall
        (:
    
    ) :=ql.Qiling.(
        pBase . ql.mem(get_lib_base.os)path[split-ql1path]).("nanosleep",
        ql)set_syscallstructHeap1// 0x18 bytes my_nanosleepstruct
    
    challenge 8

    调用了两次malloc,根据反汇编,结构如下:

    * ;	// 8 bytes
    {
        int32_t =pHeap20x00000539 ;
        // 4 bytes n1 int32_t =0x3DFCD6EA ;
        // 4 bytes n1 int *; // 8 bytes
        } structpFlagHeap2	// 0x1e bytes
    char
    
    [ ]	=
    {
        "Random data" buf}} // 00000000 Heap1           struc ; (sizeof=0x18, align=0x8, mappedto_40) {// 00000000 pHeap2          DCQ ?// 00000008 MAGIC           DCQ ?
    // 00000010 NUM             DCQ ?
    

    创建IDA结构体:

    // 00000018 Heap1           ends
    // 00000018
    // 00000000 ; ---------------------------------------------------------------------------
    // 00000000
    // 00000000 Heap2           struc ; (sizeof=0x20, align=0x8, mappedto_42)
    // 00000000 buf             DCB 30 dup(?)
    // 0000001E                 DCB ? ; undefined
    // 0000001F                 DCB ? ; undefined
    // 00000020 Heap2           ends
    // .text:00000000000011D0                 LDR             X0, [SP,#0x30+var_8__pHeap1]
    // .text:00000000000011D4                 LDR             X1, [SP,#0x30+var_18___pFlag]
    // .text:00000000000011D8                 STR             X1, [X0,#0x10]
    // .text:00000000000011DC                 NOP
    *
    challenge8
    (
    )
    Heap1 *__fastcall ;// x0__int64 a1*
    {
      Heap1 ;result// [xsp+28h] [xbp+28h] =
      Heap1 (v3* )
    
      v3 malloc (Heap1 0x18uLL);=()malloc
      v3->pHeap2 ( 0x1EuLL__int64);LODWORD()=
      1337;v3->MAGICHIDWORD ( )=
      1039980266;v3->MAGICstrcpy ( (char
      *),"Random data" );v3->pHeap2= ;=;
      result return v3;
      v3->NUM } a1// aarch64-linux-gnu-gcc-5 test.c -g -o aarch64
      # resultinclude
    #
    
    逆向
    include
    
    structHeap2 
    // 0x1e byteschar 
    [ 0x1e	]
    {
       ; buf};structHeap1
    // 0x18 bytesstruct
    Heap2 *	;
    {
        // 8 bytes int64_t; pHeap2int *
        ; nMagic// 8 bytes
        } ;pFlagint	main
    ()
    
    
    struct Heap1;struct
    {
        Heap2 ; heap1char
        [ ] heap2=
        "Random data" arrBuf;int = 0;
        . nFlag = &;
        heap1.pHeap2 = 0x3DFCD6EA00000539heap2;
        heap1.nMagic = &;
        heap1memcpypFlag ( .nFlag,
        ,sizeofheap2(buf) arrBuf) ;printfarrBuf("heap1.pHeap2: 0x%p\n",
        .);printf heap1(pHeap2"heap1.nMagic: 0x%16x\n",
        .);printf heap1(nMagic"heap1.pFlag: 0x%p\n",
        .);__asm__ heap1(pFlag"nop")
        ;if(1==
        )printf( "congratulations\n" nFlag)
        {
            ;}return0;
        }
        .text:0000000000000878 heap1           = -0x50
    .text:00000000000008B8                 STR             X0, [X29,#0x70+heap1]
     defread_stack_heap1
    (
    
    

    HOOK目标地址就是nop指令的地址,目的是改写heap1.pFlag。

    解1-读栈
    :

    脚本(注意我自己写的代码其实没有调用malloc,结构体是直接存在栈上的):

    ) :# pHeap1 = ql.mem.read(ql.reg.read("sp") + 0x20, ql.archbit // 8);ql# pHeap1 = ql.unpack64(pHeap1);Qiling# heap1 = ql.mem.read(pHeap1, 0x18) # out struct is in stack actually
        =
        .
        .
    
        (
        heap1 . ql.mem(read"sp"ql)reg+read0x20,0x18 ) ;, ,=.
    
        pHeap2( nMagic"QQQ" pFlag , struct)unpack;# ql.log.info("pHeap1: %x" % pHeap1). heap1.(
        "heap1.pHeap2: %x"
        ql%log)info.. ( pHeap2"heap1.nMagic: %x"
        ql%log)info.. ( nMagic"heap1.pFlag: %x"
        ql%log)info.. ( pFlag,
        qlb"\x01"mem)write;pFlagdef challenge8_read_stack(:
    
    
    ) :=ql.Qiling.(
        pBase . ql.mem(get_lib_base.os)path[split-ql1path]).(,+
        ql0x96Chook_address)read_stack_heap1# 0x96C : nop pBase *pFlag importdef	search_heap1
    
    解2-搜索内存

    搜索内存里的magic,找到heap1, 修改(

    : struct
    ) :=ql0x3DFCD6EA00000539Qiling;=
        nMagic . .(
        pMagics . ql(mem)search)qlforpack64innMagic:=
        - pMagic . pMagics//
            pHeap1 8 pMagic ; ql=archbit..(
            heap1 , ql0x18mem)read;pHeap1, ,=.
            pHeap2( _"QQQ" pFlag , struct)unpackif.. heap1(
            ) ql==mem"Random data"string:pHeap2. . (,
                qlb"\x01"mem)writebreakpFlag; return;
                defchallenge8_search_mem
        (:
    ) :=ql.Qiling.(
        pBase . ql.mem(get_lib_base.os)path[split-ql1path]).(,+
        ql0x96Chook_address)search_heap1# 0x96C : nop pBase # include#	include
    
    challenge 9

    伪代码:

    #include 
    intmain 
    () 
    
    char [0x20]
    {
        = src0}; char {[0x20]
        = dst0}; int {=0;
        int nFlag = 0;
        strcpy i ( ,"aBcdeFghiJKlMnopqRstuVWxYz"
    
        );srcstrcpy (,)
        ;fordst( src=0
        ;<i 26 ;++ i ) [] =itolower
        {
            dst(i[ ] );dst}i=(strcmp
        (
        
        nFlag , )==0src) dst? 1 :0 ; if  ( 1==
    
        )printf( "congratulations\n" nFlag)
        {
            ;}return0;
        }
        def my_tolower(
    :
    

    Hook掉tolower就行了:

    ) :returnqldef Qilingchallenge9(
        :
     
    ) :.ql( Qiling'tolower',
        ql)set_api#include# my_tolowerinclude
    
    challenge 10

    伪代码:

    #include 
    intmain 
    () 
    int =0;
    {
        int nFlag = 0;
        char fd [ ]=
        "/proc/self/cmdline" path}; char {[0x40]
        = buf0}; int {=0;
        int nRet = 0;
        do i = open(
    
        ,{
            fd ) ;ifpath( O_RDONLY-1
            ==) break; = fdread (,
            nRet , 0x3F)fd; bufif (0==
            ) break; } nRetwhile (0
            
        );for(=0
    
        ;<i ; ++) i if nRet( [i]
        {
            ==0buf)i[ ] =0x20
            {
                buf;i} } if(
            strcmp
        (
    
        ,"qilinglab")==buf0 )= 1 ; }
        {
            nFlag if (1
        ==
    
        )printf( "congratulations\n" nFlag)
        {
            ;}return0;
        }
        /proc/self/cmdline classMy_cmdline
    (
    
    
    
    

    和第三关一样,劫持)设备即可。

    : defreadQlFsMappedObject(,
        ) :returnselfb"qilinglab" sizedeffstat
            ( )
        : return-self1def
            close ()
        : return0selfdefchallenge10
            ( :
    
    ) :.ql( Qiling"/proc/self/cmdline",
        ql(add_fs_mapper)).. My_cmdline..int(
    
    challenge 11 aarch64
    .text:00000000000013E4 FF 83 00 D1                 SUB             SP, SP, #0x20
    .text:00000000000013E8 E0 07 00 F9                 STR             X0, [SP,#0x20+var_18__pFlag]
    .text:00000000000013EC 00 00 38 D5                 MRS             X0, #0, c0, c0, #0
    .text:00000000000013F0 E0 0F 00 F9                 STR             X0, [SP,#0x20+var_8]
    .text:00000000000013F4 E0 0F 40 F9                 LDR             X0, [SP,#0x20+var_8]
    .text:00000000000013F8 01 FC 50 D3                 LSR             X1, X0, #0x10
    .text:00000000000013FC E0 66 82 D2                 MOV             X0, #0x1337
    .text:0000000000001400 3F 00 00 EB                 CMP             X1, X0 ; if ((var_8 >> 0x10) == 0x1337) *pFlag = 1
    .text:0000000000001404 81 00 00 54                 B.NE            loc_1414
    .text:0000000000001408 E0 07 40 F9                 LDR             X0, [SP,#0x20+var_18__pFlag]
    .text:000000000000140C 21 00 80 52                 MOV             W1, #1
    .text:0000000000001410 01 00 00 39                 STRB            W1, [X0]
    .text:0000000000001414
    .text:0000000000001414             loc_1414                                ; CODE XREF: challenge11+20↑j
    .text:0000000000001414 1F 20 03 D5                 NOP
    .text:0000000000001418 FF 83 00 91                 ADD             SP, SP, #0x20 ; ' '
    .text:000000000000141C C0 03 5F D6                 RET
    

    看网上用ghidra逆出来好像效果好一些。

    这个MRS指令比较复杂,大概就是用来收集CPU各种信息的,参考文档:Arm A64 Instruction Set Architecture

    Arm的msr指令在Intel指令集里对应cpuid指令,

    参考文档(很长):CPUID — CPU Identification (felixcloutier.com)

    本实验x8664版本指令:

    ).
    .text:000000000000118A                 mov     eax, 40000000h
    .text:000000000000118F                 cpuid
    int.
    

    可以使用qiling提供的hook_code,直接把这个指令跳过,参考文档:Hook - Qiling Framework Documentation。

    逆向一下:

    #include 
    #include 
    = main0;
    {
        int nFlag = 0;
        ( nTmp "mrs x0, midr_el1" );
        __asm__if((0x10
        )== 0x1337 nTmp >> ) = 1 ;}
        {
            nFlag if (1
        ==
        )("congratulations\n" ) nFlag;
        {
            printf}return0;
        }
        // .:
    ,
    
    , [text,00000000000007B8 FD 7B BE A9                 STP             X29#var_20]!! X30// .SP:03
    00 91text,00000000000007BC FD // . :                 MOV             X2900 SP
    , [text,00000000000007C0 BF 1B #0x20+nFlag] B9                 STR             WZR// .X29:00
    , [text,00000000000007C4 BF 1F #0x20+nTmp] B9                 STR             WZR// .X29:00
    00 38text,00000000000007C8 #0, c0, c0, #0 // . D5                 MRS             X0: 40
    , [text,00000000000007CC A0 1F #0x20+nTmp] B9                 LDR             W0// .X29:01
    10 13text,00000000000007D0 , 7C #0x10 //                 ASR             W1. W0: 66
    82 52text,00000000000007D4 E0 #0x1337 // .                 MOV             W0: 00
    00 ,text//00000000000007D8 3F . : 6B                 CMP             W161 W0
    00 00text5400000000000007DC . // . :                 B00000000000007E0NE            loc_7E8
    20 00text8052 , #1 // .                 MOV             W0: 00000000000007E4
    00 ,text[, A0 1B #0x20+nFlag] B9                 STR             W0def my_mrsX29(:
    
    

    脚本:

    , :intql, Qiling: addressint ): size= ..(
        opcode , ql)memifread(address0x7C8 size==
        ( &0xfff ) )address and (b"\x00\x00\x38\xD5"== ) :. . opcode("w0"
            ql,reg0x1337write<<0x10) ..+=8
            ql;regdefarch_pc challenge11 (:
    
    ) :.ql( Qiling)
        qlhook_code[+++]my_mrs[+++]
    
    5. 参考资料

    Qiling:一款功能强大的高级代码模拟框架 - FreeBuf网络安全行业门户

    11个小挑战,Qiling Framework 入门上手跟练-软件逆向-看雪论坛-安全社区|安全招聘|bbs.pediy.com

    Shielder - QilingLab – Release

    zodf0055980/QilingLab_Writeup: QilingLab challenge writeup (github.com)

    )
    File: /www/wwwroot/outofmemory.cn/tmp/route_read.php, Line: 126, InsideLink()
    File: /www/wwwroot/outofmemory.cn/tmp/index.inc.php, Line: 165, include(/www/wwwroot/outofmemory.cn/tmp/route_read.php)
    File: /www/wwwroot/outofmemory.cn/index.php, Line: 30, include(/www/wwwroot/outofmemory.cn/tmp/index.inc.php)
    Error[8]: Undefined offset: 2221, File: /www/wwwroot/outofmemory.cn/tmp/plugin_ss_superseo_model_superseo.php, Line: 121
    File: /www/wwwroot/outofmemory.cn/tmp/plugin_ss_superseo_model_superseo.php, Line: 473, decode(

    文章目录
    Github:

    文档:Qiling Framework Documentation

    官网:Qiling Framework

    1. 简介

    Qiling is an advanced binary emulation framework. By JingDong @Defcon 2019

    内置examples可以用来学习。

    安装

    Python环境至少需要3.8(不然会报各种错误)。

    sudo apt-get install python3.8-dev
    

    最简单的pip安装:

    python3 -m pip install qiling
    

    Pip没有安装examples目录,而且无法调试,所以入门且想要调试的话,建议从github把两个仓库下载下来手动安装:

    python3 setup.py install
    

    根据.gitmodules文件,有子项目依赖,所以建议用用git --recursiv:

    git clone https://github.com/qilingframework/qiling 
    python3 setup.py install
    

    如果遇到网速问题,可以手动下载后放进examples。

    如果要模拟windows,需要执行dllscollector.bat收集系统dll放入rootfs。

    2. QuickStart

    Demo - Qiling Framework Documentation

    Getting Started - Qiling Framework Documentation

    Demo示例大多是windows程序,所以别忘执行dllscollector.bat。有的示例路径不对,需要加个examples啥的~

    大概步骤:

    1. ql = Qiling()初始化,目标可以是二进制文件或shellcode;
    2. 设置一些参数,如map, debug, verbose;
    3. ql.run(), 开始模拟,可以指定代码begin和end。
    $ python3 test.py
    [+]     Profile: Default
    [+]     Map GDT at 0x30000 with GDT_LIMIT=4096
    [+]     Write to 0x30018 for new entry b'\x00\xf0\x00\x00\x00\xfeO\x00'
    [+]     Write to 0x30028 for new entry b'\x00\xf0\x00\x00\x00\x96O\x00'
    [+]     Write to 0x30070 for new entry b'\x00`\x00`\x00\xf6@\x00'
    [+]     Write to 0x30078 for new entry b'\x00\x00\x00\x00\x00\xf6@\x06'
    [+]     Windows Registry PATH: examples/rootfs/x86_windows/Windows/registry
    [=]     Initiate stack address at 0xfffdd000
    [=]     Loading examples/rootfs/x86_windows/bin/RegDemo.exe to 0x400000
    [=]     PE entry point at 0x401381
    [=]     TEB addr is 0x6000
    [=]     PEB addr is 0x6044
    [=]     Loading examples/rootfs/x86_windows/Windows/System32/ntdll.dll ...
    [!]     Warnings while loading examples/rootfs/x86_windows/Windows/System32/ntdll.dll:
    [!]      - SizeOfHeaders is smaller than AddressOfEntryPoint: this file cannot run under Windows 8.
    [!]      - AddressOfEntryPoint lies outside the sections' boundaries. AddressOfEntryPoint: 0x0
    [+]     DLL preferred base address: 0x4b280000
    [=]     Done with loading examples/rootfs/x86_windows/Windows/System32/ntdll.dll
    [=]     Loading examples/rootfs/x86_windows/Windows/System32/kernel32.dll ...
    [+]     DLL preferred base address: 0x6b800000
    [=]     Done with loading examples/rootfs/x86_windows/Windows/System32/kernel32.dll
    [=]     Loading examples/rootfs/x86_windows/Windows/System32/advapi32.dll ...
    [+]     DLL preferred base address: 0x4c300000
    [=]     Done with loading examples/rootfs/x86_windows/Windows/System32/advapi32.dll
    [=]     Loading examples/rootfs/x86_windows/Windows/System32/msvcr110.dll ...
    [+]     DLL preferred base address: 0x10000000
    [=]     Done with loading examples/rootfs/x86_windows/Windows/System32/msvcr110.dll
    [+]     Done with loading examples/rootfs/x86_windows/bin/RegDemo.exe
    [+]     0x6b81f390: GetSystemTimeAsFileTime(lpSystemTimeAsFileTime = 0xffffcfec)
    [+]     0x6b81df10: GetCurrentThreadId() = 0x0
    [+]     0x6b822e90: GetCurrentProcessId() = 0x7cc
    [+]     0x6b81df40: QueryPerformanceCounter(lpPerformanceCount = 0xffffcfe4) = 0x0
    [+]     0x10053f90: _initterm_e(pfbegin = 0x4020b8, pfend = 0x4020c8) = 0x0
    [+]     0x10053f30: _initterm(pfbegin = 0x4020ac, pfend = 0x4020b4)
    [+]     Key 2147483649 Software
    [+]     Value key HKEY_CURRENT_USER\Software not present
    [+]     0x4c31ed30: RegOpenKeyW(hKey = 0x80000001, lpSubKey = "Software", phkResult = 0xffffcfb4) = 0x2
    Create Key Error[+]     0x10062380: printf(format = "Create Key Error") = 0x10
    [+]     0x10054160: exit(status = 0)
    [+]     Syscalls called:
    [+]     GetSystemTimeAsFileTime:
    [+]       {"params": {"lpSystemTimeAsFileTime": 4294954988}, "retval": null, "address": 1803678608, "retaddr": 4200091, "position": 0}
    [+]     GetCurrentThreadId:
    [+]       {"params": {}, "retval": 0, "address": 1803673360, "retaddr": 4200106, "position": 1}
    [+]     GetCurrentProcessId:
    [+]       {"params": {}, "retval": 1996, "address": 1803693712, "retaddr": 4200115, "position": 2}
    [+]     QueryPerformanceCounter:
    [+]       {"params": {"lpPerformanceCount": 4294954980}, "retval": 0, "address": 1803673408, "retaddr": 4200128, "position": 3}
    [+]     _initterm_e:
    [+]       {"params": {"pfbegin": 4202680, "pfend": 4202696}, "retval": 0, "address": 268779408, "retaddr": 4199046, "position": 4}
    [+]     _initterm:
    [+]       {"params": {"pfbegin": 4202668, "pfend": 4202676}, "retval": null, "address": 268779312, "retaddr": 4199098, "position": 5}
    [+]     RegOpenKeyW:
    [+]       {"params": {"hKey": 2147483649, "lpSubKey": "Software", "phkResult": 4294954932}, "retval": 2, "address": 1278340400, "retaddr": 4198443, "position": 6}
    [+]     printf:
    [+]       {"params": {"format": "Create Key Error"}, "retval": 16, "address": 268837760, "retaddr": 4198458, "position": 7}
    [+]     exit:
    [+]       {"params": {"status": 0}, "retval": null, "address": 268779872, "retaddr": 4199217, "position": 8}
    [+]     Registries accessed:
    [+]     HKEY_CURRENT_USER\Software:
    [+]     Strings:
    [+]     Software: 6
    [+]     Create: 7
    [+]     Key: 7
    [+]     Error: 7
    
    

    有一个wannaycry的zip,解压口令在readme里,注意别在windows实体机上玩~

    输出日志:ql.log.info,而且支持正则过滤。

    3. qltool

    项目根目录提供了这个qltool python脚本,用来快速模拟运行shellcode或exec文件。

    基本参数:

    执行时发现qdb.py报错,使用了py3.8的赋值运算符,可以用with语句改一下,比如:

    # if (bp := self.bp_list.get(address, None)):
    with self.bp_list.get(address, None) as bp:
        # pass
    

    不过这个:=运算符用得太多了,,最后我选择把python从3.6升级到3.8,并修改qltool开头的解释器:

    #!/usr/bin/python3.8
    

    依赖库:

    sudo apt-get install python3.8-dev
    sudo python3.8 -m pip install gevent
    

    具体用法执行./qltools examples参考即可。

    4. QilingLab练习

    地址:Shielder - QilingLab – Release

    有x86_64和aarch64两个版本,我选择了aarch64:

    $ file qilinglab-aarch64
    qilinglab-aarch64: ELF 64-bit LSB shared object, ARM aarch64, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux-aarch64.so.1, for GNU/Linux 3.7.0, BuildID[sha1]=c573ba5f5450dc8d134835d4ddb5f93e28138c0c, not stripped
    

    注意LSB,说明是小端。

    如果选择x86_64版本,rootfs就要使用examples/rootfs/x8664_linux

    解题模板:

    from qiling import *
    from qiling.const import QL_VERBOSE
    
    def challenge1(ql:Qiling):
        pass;    
    
    if __name__ == "__main__":
        argv = ["./qilinglab-aarch64"]
        rootfs = "examples/rootfs/arm64_linux"
        ql = Qiling(argv, rootfs, verbose=QL_VERBOSE.DEBUG, bigendian=False)
        # ql.debugger = "gdb:0.0.0.0:9999"
        # ql.debugger = "idapro:0.0.0.0:9999"
    	challenge1(ql);
        ql.run();
    
    

    直接运行的话会报一堆错,都是正常的。如果卡住,可以强杀:

    ps -elf | grep "python3.8" | grep -v "grep" | awk -F" " '{print }' | xargs sudo kill -9
    

    如果启用调试器,程序应该会停在入口点。Gdb可以用gdb-multiarch:

    set architecture aarch64 
    target remote localhost:9999
    

    如果python小于3.8,则不支持调试。

    试了下Ida调试,没成功,,直接导入idapro模块失败了,没查出原因。IDA连接gdbserver模块是没问题的,但单步一下就跑飞了,,,不太好用。

    $ python3.8 qilinglab.py
    Welcome to QilingLab.
    Here is the list of challenges:
    Challenge 1: Store 1337 at pointer 0x1337.
    Challenge 2: Make the 'uname' syscall return the correct values.
    Challenge 3: Make '/dev/urandom' and 'getrandom' "collide".
    Challenge 4: Enter inside the "forbidden" loop.
    Challenge 5: Guess every call to rand().
    Challenge 6: Avoid the infinite loop.
    Challenge 7: Don't waste time waiting for 'sleep'.
    Challenge 8: Unpack the struct and write at the target address.
    Challenge 9: Fix some string operation to make the iMpOsSiBlE come true.
    Challenge 10: Fake the 'cmdline' line file to return the right content.
    Challenge 11: Bypass CPUID/MIDR_EL1 checks.
    
    Checking which challenge are solved...
    Note: Some challenges will results in segfaults and infinite loops if they aren't solved.
    [x]     CPU Context:
    [x]     x0      : 0x0
    [x]     x1      : 0x0
    [x]     x2      : 0x1
    [x]     x3      : 0x0
    [x]     x4      : 0x0
    [x]     x5      : 0x55555556a2b4
    [x]     x6      : 0x696b636568430a0a
    [x]     x7      : 0x686369687720676e
    [x]     x8      : 0x40
    [x]     x9      : 0x7320657261206567
    [x]     x10     : 0x2068636968772067
    ...
    

    简单分析一下程序,一共11关,start函数开头会对一个12字节的数组初始化为0:

    .text:00000000000014BC loc_14BC                                ; CODE XREF: start+54↓j
    .text:00000000000014BC                 LDRSW           X0, [SP,#0x40+var_24_i]
    .text:00000000000014C0                 ADD             X1, SP, #0x40+var_18_arrayBytesFlags[0xB]
    .text:00000000000014C4                 STRB            WZR, [X1,X0]
    .text:00000000000014C8                 LDR             W0, [SP,#0x40+var_24_i]
    .text:00000000000014CC                 ADD             W0, W0, #1
    .text:00000000000014D0                 STR             W0, [SP,#0x40+var_24_i]
    .text:00000000000014D4
    .text:00000000000014D4 loc_14D4                                ; CODE XREF: start+2C↑j
    .text:00000000000014D4                 LDR             W1, [SP,#0x40+var_24_i]
    .text:00000000000014D8                 LDR             W0, [SP,#0x40+var_1C]
    .text:00000000000014DC                 CMP             W1, W0
    .text:00000000000014E0                 B.LT            loc_14BC ; i <= 0xB
    

    每一个字节代表这一关是否通过,通过则为1。

    每一关的函数声明大概像这样:

    void challenge1(byte* pByFlag);
    

    之后,由checker函数检查flag:

    void checker(byte* pByFlags, int nChallenge);
    
    challenge 1 - memory map
    .text:0000000000000CC4                 SUB             SP, SP, #0x20
    .text:0000000000000CC8                 STR             X0, [SP,#0x20+var_18]
    .text:0000000000000CCC                 MOV             X0, #0x1337
    .text:0000000000000CD0                 STR             X0, [SP,#0x20+var_8__0x1337]
    .text:0000000000000CD4                 LDR             X0, [SP,#0x20+var_8__0x1337]
    .text:0000000000000CD8                 LDR             W0, [X0]
    .text:0000000000000CDC                 STR             W0, [SP,#0x20+var_C__mem[0x1337]]
    .text:0000000000000CE0                 LDR             W0, [SP,#0x20+var_C__mem[0x1337]]
    .text:0000000000000CE4                 CMP             W0, #1337
    .text:0000000000000CE8                 B.NE            loc_CF8
    .text:0000000000000CEC                 LDR             X0, [SP,#0x20+var_18]
    .text:0000000000000CF0                 MOV             W1, #1
    .text:0000000000000CF4                 STRB            W1, [X0]
    .text:0000000000000CF8
    .text:0000000000000CF8 loc_CF8                                 ; CODE XREF: challenge1+24↑j
    .text:0000000000000CF8                 NOP
    .text:0000000000000CFC                 ADD             SP, SP, #0x20 ; ' '
    .text:0000000000000D00                 RET
    

    翻译成c语言:

    if(mem[0x1337] == 1337)
    	*pByFlag = 1;
    

    参考文档:Memory - Qiling Framework Documentation

    def challenge1(ql:Qiling):
        ql.mem.map(0x1337//4096*4096, 4096, info="[challenge1]");   # 4k alignment
        ql.mem.write(0x1337, ql.pack32(1337))
    

    关于map的更多使用,可以参考qiling/loader/elf.py

    challenge 2 - set_syscall

    第二关调用uname,硬编码检查sysname和version:

    .text:0000000000000D28                 ADD             X0, SP, #0x1F0+name ; name
    .text:0000000000000D2C                 BL              .uname
    ...
    .text:0000000000000D48                 ADRP            X0, #aQilingos@PAGE ; "QilingOS"
    .text:0000000000000D4C                 ADD             X1, X0, #aQilingos@PAGEOFF ; "QilingOS"
    .text:0000000000000D50                 ADD             X0, SP, #0x1F0+buf
    .text:0000000000000D54                 LDR             X2, [X1] ; "QilingOS"
    .text:0000000000000D58                 STR             X2, [X0]
    .text:0000000000000D5C                 LDRH            W1, [X1,#(aQilingos+8 - 0x1840)] ; ""
    .text:0000000000000D60                 STRH            W1, [X0,#8]
    .text:0000000000000D64                 ADRL            X0, aChallengestart ; "ChallengeStart"
    ...
    

    通关条件:

    uname.sysname == "QilingOS";
    uname.version == "ChallengeStart";
    

    通过qiling提供的系统调用hook,修改返回地uname结构体即可。

    参考文档:

    uname结构体定义如下:

    /* Structure describing the system and machine.  */
    struct utsname
    {
        /* Name of the implementation of the operating system.  */
        char sysname[65];
    
        /* Name of this node on the network.  */
        char nodename[65];
    
        /* Current release level of this implementation.  */
        char release[65];
        /* Current version level of this release.  */
        char version[65];
    
        //...
    };
    

    Qiling脚本如下:

    ########################
    # https://man7.org/linux/man-pages/man3/uname.3p.html
    # int uname(struct utsname *name);
    # /* Structure describing the system and machine.  */
    # struct utsname
    # {
    #     /* Name of the implementation of the operating system.  */
    #     char sysname[65];
    
    #     /* Name of this node on the network.  */
    #     char nodename[65];
    
    #     /* Current release level of this implementation.  */
    #     char release[65];
    #     /* Current version level of this release.  */
    #     char version[65];
    
    #     //...
    # };
    def my_uname(ql:Qiling, pStcUname, *args,**kwargs):
        ql.mem.write(pStcUname, b"QilingOS".ljust(65,b"\x00"))  # "QilingOS..(+..."
        
        ql65mem*write3pStcUname , b'ChallengeStart'.(65 ,b'\x00'ljust))return 0defchallenge2
    
        ( :
    
    ) :.ql(Qiling"uname",
        ql)set_syscall=open( my_uname "/dev/urandom"
    

    如果是其它qiling没有实现的syscall,就需要指定syscall number,比如write为4.

    challenge 3 - add_fs_mapper

    伪代码:

    fd ) ;read(,,
    0x20)fd; urandom_bufread (,&
    ,0x1fd) ;byUrandomgetrandom (,0x20
    ,1getrandom_buf) ;int =0;
    
    for nTmp ( int=
    0;<= i 0x1f ;++ i ) if( (i[
    {
        ]== [urandom_buf]i) && getrandom_buf(i!=[ ] )byUrandom ) urandom_buf++i;}if
            (nTmp==
    0x20
    )*nTmp = 1;
        
  • getrandom(2) - Linux manual page (man7.org)
  • pFlag
  • Hijack - Qiling Framework Documentation
  • /dev/urandom/* Flags for use with getrandom. */

    参考文档:

    getrandom是一个系统调用,其实默认也是从GRND_NONBLOCK取值,flag设为1可以防止熵池为空导致程序阻塞。

    0x01
    #define GRND_RANDOM 0x02
    ssize_tgetrandom ( void
    * ,size_t, unsignedbufint ) buflen; /dev/urandom import flags()
    

    也就是说,程序涉及getrandom这个系统调用,以及(这个设备。除了利用上一关的set_syscall劫持getrandom,还需要用qiling的QlFsMappedObject类劫持urandom设备的读 *** 作。

    直接照抄文档的示例即可:

    from qiling.os.mapper \x01 QlFsMappedObject
    
    
    def my_syscall_getrandom)ql:Qiling, write_buf, write_buf_size, flags, *args, **kwreturn:
        ql.mem.write;write_buf, b"(" * write_buf_size)
        ( write_buf_size)
    
    class Fake_urandom(QlFsMappedObject==:
    
        def read1self, size):
            ifreturnsize \x02 # byUrandomreturn:
                \x01 b"("  )
            else:
                # syscall fstat will ignore it if return -1 b"return" * size
    
        def fstat(self): return
            0 -1
    
        def close(self):
            ( "/dev/urandom"
    
    def challenge3(ql:Qiling)):
        ql.add_fs_mapper("getrandom", Fake_urandom)int
        ql.set_syscall=0, my_syscall_getrandom ;
    
    
    challenge 4 - hook_address

    伪代码:

    int var_4__l = 0;
    while var_8__r ( <)
    *=var_4__l 1 var_8__r;
    {
        ++pFlag ; }// .text:0000000000000FD8                 LDR             W0, [SP,#0x20+var_8__r]
        // .text:0000000000000FDC                 LDR             W1, [SP,#0x20+var_4__l]var_4__l// .text:0000000000000FE0                 CMP             W1, W0
    
  • Hook - Qiling Framework Documentation
  • Register - Qiling Framework Documentation
  • get_lib_base import

    那么在执行cmp时,让w0为1即可。

    参考文档:

    Qiling提供了一个获取模块基址的函数(,不过在文档里没找到。

    : os
    ) :.ql.Qiling("w0"
        ql,reg0x1write)return; defchallenge4
        (:
    
    ) :=ql.Qiling.(
        pBase . ql.mem(get_lib_base.os)path[split-ql1path]).(,+
        ql0xFE0hook_address)hook_cmp_w0[ pBase 5 ]=
    
    challenge 5 - set_api

    伪代码:

    DWORD dwArr10}; [ {5]=
    DWORD dwArr2};// init dwArr2 by rand() // .text:0000000000001038                 BL              .rand {// .text:000000000000103C                 MOV             W2, W0for
    (
    int
    =
    0 ;<= i 4 ;++ i ) if( [i]
    {
        !=[dwArr1]i) * dwArr2=i[]
        {
    		;pFlag return dwArr2;i}* 
            =1
        ;
    	}pFlag 
  • rand(3) - Linux manual page (man7.org)
  • Hijack - Qiling Framework Documentation
  • def my_rand

    也就是说,劫持rand返回0就可以了。

    参考文档:

    rand()是库函数,不是系统调用,所以不能用set_syscall,应该用set_api。

    ) :# ql.reg.write("w0", 0)ql.Qiling.(
        "eax"
        ql,reg1write)return; defchallenge5
        (:
    
    ) :.ql(Qiling"rand",
        ql)set_apisudoaptinstall my_rand#
    

    不过从第一题开始,我的程序就会在第5关这里崩溃,换成x8664不用qiling直接执行也一样,所以后续的题解验证需要自己逆向出源代码,安装aarch64编译工具来验证。

    include # include gccgo-5-aarch64-linux-gnu
    aarch64-linux-gnu-gcc-5 test.c -o aarch64
    

    逆向出来的源码:

    intmain 
    () 
    int32_t [5]
    {
        = dwArr10}; int32_t {[5]
        = dwArr2};int = {1;
        int nFlag = time(
        0 s ) ;srand();
        for(sint=
        0 ;<= i 4 ;++ i ) [] =irand
        {
            dwArr2(i) ; printf("%d\n",
            []); dwArr2}ifor(int
        =
        0 ;<= i 4 ;++ i ) if( [i]
        {
            !=[dwArr1]i) = dwArr2[i];
            {
                nFlag break dwArr2;i}} 
                if(
            1
            
        ==
        )printf( "congratulation\n" nFlag)
        {
            ;}return0;
        }
        int =1
    ;
    

    rand返回值仍然保存在w0里,所以qiling脚本不用变。

    challenge 6

    伪代码:

    int n1 ; while(
    ( n2&
    0xff) !=n1 0 )= ( &0xff
    {
        n2 ) ;n1 } defhook_cmp_w0_6(
    :
    

    和第4关差不多

    ) :.ql.Qiling("w0"
        ql,reg0x0write)return; defchallenge6
        (:
    ) :=ql.Qiling.(
        pBase . ql.mem(get_lib_base.os)path[split-ql1path]).(,+
        ql0xFE0hook_address)hook_cmp_w0_6* pBase = 1;
    
    
    challenge 7 - 3种思路

    伪代码:

    sleeppFlag ( 0xFFFFFFFF)
    ;#includeintmain
    

    需要做的是劫持sleep函数,修改睡眠时间。

    解1

    逆向实现:

    () 
           
    sleep (0xFFFFFFFF)
    {
        ;return0;}
        // .text:00000000000007C0                 MOV             W0, #0xFFFFFFFF ; seconds // .text:00000000000007C4                 BL              .sleepdef
    my_sleep_call
    (
    :
    

    Qiling脚本:

    ) :.ql.Qiling("w0"
        ql,reg0x0write)return; defchallenge7
        (:
    
    ) :=ql.Qiling.(
        pBase . ql.mem(get_lib_base.os)path[split-ql1path]).(,+
        ql0x7C4hook_address)my_sleep_calldef pBase my_sleep (:
    

    只要程序马上退出,就说明成功了。

    解2

    也可以hook api,脚本实现:

    ) :returnql;Qilingdefchallenge7_hook_api
        (:
    ) :=ql.Qiling.(
        pBase . ql.mem(get_lib_base.os)path[split-ql1path]).("sleep",
        ql)set_api[]( my_sleep)
    
    解3

    也可以劫持系统调用,根据DEBUG信息,sleep其实是调用了nanosleep():

    https://man7.org/linux/man-pages/man3/sleep.3.html
    https://man7.org/linux/man-pages/man2/nanosleep.2.html
    	On Linux, sleep() is implemented via nanosleep(2). 
    +# int nanosleep(const struct timespec *req, struct timespec *rem);     syscall hooked 0x7fffb7e7862c: ql_syscall_nanosleepdefmy_nanosleep
    

    官网文档里也有说明:

    (

    Qiling脚本如下:

    :
    , ,,ql*Qiling, req** rem) :argsreturn;kwargsdefchallenge7_hook_syscall
        (:
    
    ) :=ql.Qiling.(
        pBase . ql.mem(get_lib_base.os)path[split-ql1path]).("nanosleep",
        ql)set_syscallstructHeap1// 0x18 bytes my_nanosleepstruct
    
    challenge 8

    调用了两次malloc,根据反汇编,结构如下:

    * ;	// 8 bytes
    {
        int32_t =pHeap20x00000539 ;
        // 4 bytes n1 int32_t =0x3DFCD6EA ;
        // 4 bytes n1 int *; // 8 bytes
        } structpFlagHeap2	// 0x1e bytes
    char
    
    [ ]	=
    {
        "Random data" buf}} // 00000000 Heap1           struc ; (sizeof=0x18, align=0x8, mappedto_40) {// 00000000 pHeap2          DCQ ?// 00000008 MAGIC           DCQ ?
    // 00000010 NUM             DCQ ?
    

    创建IDA结构体:

    // 00000018 Heap1           ends
    // 00000018
    // 00000000 ; ---------------------------------------------------------------------------
    // 00000000
    // 00000000 Heap2           struc ; (sizeof=0x20, align=0x8, mappedto_42)
    // 00000000 buf             DCB 30 dup(?)
    // 0000001E                 DCB ? ; undefined
    // 0000001F                 DCB ? ; undefined
    // 00000020 Heap2           ends
    // .text:00000000000011D0                 LDR             X0, [SP,#0x30+var_8__pHeap1]
    // .text:00000000000011D4                 LDR             X1, [SP,#0x30+var_18___pFlag]
    // .text:00000000000011D8                 STR             X1, [X0,#0x10]
    // .text:00000000000011DC                 NOP
    *
    challenge8
    (
    )
    Heap1 *__fastcall ;// x0__int64 a1*
    {
      Heap1 ;result// [xsp+28h] [xbp+28h] =
      Heap1 (v3* )
    
      v3 malloc (Heap1 0x18uLL);=()malloc
      v3->pHeap2 ( 0x1EuLL__int64);LODWORD()=
      1337;v3->MAGICHIDWORD ( )=
      1039980266;v3->MAGICstrcpy ( (char
      *),"Random data" );v3->pHeap2= ;=;
      result return v3;
      v3->NUM } a1// aarch64-linux-gnu-gcc-5 test.c -g -o aarch64
      # resultinclude
    #
    
    逆向
    include
    
    structHeap2 
    // 0x1e byteschar 
    [ 0x1e	]
    {
       ; buf};structHeap1
    // 0x18 bytesstruct
    Heap2 *	;
    {
        // 8 bytes int64_t; pHeap2int *
        ; nMagic// 8 bytes
        } ;pFlagint	main
    ()
    
    
    struct Heap1;struct
    {
        Heap2 ; heap1char
        [ ] heap2=
        "Random data" arrBuf;int = 0;
        . nFlag = &;
        heap1.pHeap2 = 0x3DFCD6EA00000539heap2;
        heap1.nMagic = &;
        heap1memcpypFlag ( .nFlag,
        ,sizeofheap2(buf) arrBuf) ;printfarrBuf("heap1.pHeap2: 0x%p\n",
        .);printf heap1(pHeap2"heap1.nMagic: 0x%16x\n",
        .);printf heap1(nMagic"heap1.pFlag: 0x%p\n",
        .);__asm__ heap1(pFlag"nop")
        ;if(1==
        )printf( "congratulations\n" nFlag)
        {
            ;}return0;
        }
        .text:0000000000000878 heap1           = -0x50
    .text:00000000000008B8                 STR             X0, [X29,#0x70+heap1]
     defread_stack_heap1
    (
    
    

    HOOK目标地址就是nop指令的地址,目的是改写heap1.pFlag。

    解1-读栈
    :

    脚本(注意我自己写的代码其实没有调用malloc,结构体是直接存在栈上的):

    ) :# pHeap1 = ql.mem.read(ql.reg.read("sp") + 0x20, ql.archbit // 8);ql# pHeap1 = ql.unpack64(pHeap1);Qiling# heap1 = ql.mem.read(pHeap1, 0x18) # out struct is in stack actually
        =
        .
        .
    
        (
        heap1 . ql.mem(read"sp"ql)reg+read0x20,0x18 ) ;, ,=.
    
        pHeap2( nMagic"QQQ" pFlag , struct)unpack;# ql.log.info("pHeap1: %x" % pHeap1). heap1.(
        "heap1.pHeap2: %x"
        ql%log)info.. ( pHeap2"heap1.nMagic: %x"
        ql%log)info.. ( nMagic"heap1.pFlag: %x"
        ql%log)info.. ( pFlag,
        qlb"\x01"mem)write;pFlagdef challenge8_read_stack(:
    
    
    ) :=ql.Qiling.(
        pBase . ql.mem(get_lib_base.os)path[split-ql1path]).(,+
        ql0x96Chook_address)read_stack_heap1# 0x96C : nop pBase *pFlag importdef	search_heap1
    
    解2-搜索内存

    搜索内存里的magic,找到heap1, 修改(

    : struct
    ) :=ql0x3DFCD6EA00000539Qiling;=
        nMagic . .(
        pMagics . ql(mem)search)qlforpack64innMagic:=
        - pMagic . pMagics//
            pHeap1 8 pMagic ; ql=archbit..(
            heap1 , ql0x18mem)read;pHeap1, ,=.
            pHeap2( _"QQQ" pFlag , struct)unpackif.. heap1(
            ) ql==mem"Random data"string:pHeap2. . (,
                qlb"\x01"mem)writebreakpFlag; return;
                defchallenge8_search_mem
        (:
    ) :=ql.Qiling.(
        pBase . ql.mem(get_lib_base.os)path[split-ql1path]).(,+
        ql0x96Chook_address)search_heap1# 0x96C : nop pBase # include#	include
    
    challenge 9

    伪代码:

    #include 
    intmain 
    () 
    
    char [0x20]
    {
        = src0}; char {[0x20]
        = dst0}; int {=0;
        int nFlag = 0;
        strcpy i ( ,"aBcdeFghiJKlMnopqRstuVWxYz"
    
        );srcstrcpy (,)
        ;fordst( src=0
        ;<i 26 ;++ i ) [] =itolower
        {
            dst(i[ ] );dst}i=(strcmp
        (
        
        nFlag , )==0src) dst? 1 :0 ; if  ( 1==
    
        )printf( "congratulations\n" nFlag)
        {
            ;}return0;
        }
        def my_tolower(
    :
    

    Hook掉tolower就行了:

    ) :returnqldef Qilingchallenge9(
        :
     
    ) :.ql( Qiling'tolower',
        ql)set_api#include# my_tolowerinclude
    
    challenge 10

    伪代码:

    #include 
    intmain 
    () 
    int =0;
    {
        int nFlag = 0;
        char fd [ ]=
        "/proc/self/cmdline" path}; char {[0x40]
        = buf0}; int {=0;
        int nRet = 0;
        do i = open(
    
        ,{
            fd ) ;ifpath( O_RDONLY-1
            ==) break; = fdread (,
            nRet , 0x3F)fd; bufif (0==
            ) break; } nRetwhile (0
            
        );for(=0
    
        ;<i ; ++) i if nRet( [i]
        {
            ==0buf)i[ ] =0x20
            {
                buf;i} } if(
            strcmp
        (
    
        ,"qilinglab")==buf0 )= 1 ; }
        {
            nFlag if (1
        ==
    
        )printf( "congratulations\n" nFlag)
        {
            ;}return0;
        }
        /proc/self/cmdline classMy_cmdline
    (
    
    
    
    

    和第三关一样,劫持)设备即可。

    : defreadQlFsMappedObject(,
        ) :returnselfb"qilinglab" sizedeffstat
            ( )
        : return-self1def
            close ()
        : return0selfdefchallenge10
            ( :
    
    ) :.ql( Qiling"/proc/self/cmdline",
        ql(add_fs_mapper)).. My_cmdline..int(
    
    challenge 11 aarch64
    .text:00000000000013E4 FF 83 00 D1                 SUB             SP, SP, #0x20
    .text:00000000000013E8 E0 07 00 F9                 STR             X0, [SP,#0x20+var_18__pFlag]
    .text:00000000000013EC 00 00 38 D5                 MRS             X0, #0, c0, c0, #0
    .text:00000000000013F0 E0 0F 00 F9                 STR             X0, [SP,#0x20+var_8]
    .text:00000000000013F4 E0 0F 40 F9                 LDR             X0, [SP,#0x20+var_8]
    .text:00000000000013F8 01 FC 50 D3                 LSR             X1, X0, #0x10
    .text:00000000000013FC E0 66 82 D2                 MOV             X0, #0x1337
    .text:0000000000001400 3F 00 00 EB                 CMP             X1, X0 ; if ((var_8 >> 0x10) == 0x1337) *pFlag = 1
    .text:0000000000001404 81 00 00 54                 B.NE            loc_1414
    .text:0000000000001408 E0 07 40 F9                 LDR             X0, [SP,#0x20+var_18__pFlag]
    .text:000000000000140C 21 00 80 52                 MOV             W1, #1
    .text:0000000000001410 01 00 00 39                 STRB            W1, [X0]
    .text:0000000000001414
    .text:0000000000001414             loc_1414                                ; CODE XREF: challenge11+20↑j
    .text:0000000000001414 1F 20 03 D5                 NOP
    .text:0000000000001418 FF 83 00 91                 ADD             SP, SP, #0x20 ; ' '
    .text:000000000000141C C0 03 5F D6                 RET
    

    看网上用ghidra逆出来好像效果好一些。

    这个MRS指令比较复杂,大概就是用来收集CPU各种信息的,参考文档:Arm A64 Instruction Set Architecture

    Arm的msr指令在Intel指令集里对应cpuid指令,

    参考文档(很长):CPUID — CPU Identification (felixcloutier.com)

    本实验x8664版本指令:

    ).
    .text:000000000000118A                 mov     eax, 40000000h
    .text:000000000000118F                 cpuid
    int.
    

    可以使用qiling提供的hook_code,直接把这个指令跳过,参考文档:Hook - Qiling Framework Documentation。

    逆向一下:

    #include 
    #include 
    = main0;
    {
        int nFlag = 0;
        ( nTmp "mrs x0, midr_el1" );
        __asm__if((0x10
        )== 0x1337 nTmp >> ) = 1 ;}
        {
            nFlag if (1
        ==
        )("congratulations\n" ) nFlag;
        {
            printf}return0;
        }
        // .:
    ,
    
    , [text,00000000000007B8 FD 7B BE A9                 STP             X29#var_20]!! X30// .SP:03
    00 91text,00000000000007BC FD // . :                 MOV             X2900 SP
    , [text,00000000000007C0 BF 1B #0x20+nFlag] B9                 STR             WZR// .X29:00
    , [text,00000000000007C4 BF 1F #0x20+nTmp] B9                 STR             WZR// .X29:00
    00 38text,00000000000007C8 #0, c0, c0, #0 // . D5                 MRS             X0: 40
    , [text,00000000000007CC A0 1F #0x20+nTmp] B9                 LDR             W0// .X29:01
    10 13text,00000000000007D0 , 7C #0x10 //                 ASR             W1. W0: 66
    82 52text,00000000000007D4 E0 #0x1337 // .                 MOV             W0: 00
    00 ,text//00000000000007D8 3F . : 6B                 CMP             W161 W0
    00 00text5400000000000007DC . // . :                 B00000000000007E0NE            loc_7E8
    20 00text8052 , #1 // .                 MOV             W0: 00000000000007E4
    00 ,text[, A0 1B #0x20+nFlag] B9                 STR             W0def my_mrsX29(:
    
    

    脚本:

    , :intql, Qiling: addressint ): size= ..(
        opcode , ql)memifread(address0x7C8 size==
        ( &0xfff ) )address and (b"\x00\x00\x38\xD5"== ) :. . opcode("w0"
            ql,reg0x1337write<<0x10) ..+=8
            ql;regdefarch_pc challenge11 (:
    
    ) :.ql( Qiling)
        qlhook_codemy_mrs[+++]
    
    5. 参考资料

    Qiling:一款功能强大的高级代码模拟框架 - FreeBuf网络安全行业门户

    11个小挑战,Qiling Framework 入门上手跟练-软件逆向-看雪论坛-安全社区|安全招聘|bbs.pediy.com

    Shielder - QilingLab – Release

    zodf0055980/QilingLab_Writeup: QilingLab challenge writeup (github.com)

    )
    File: /www/wwwroot/outofmemory.cn/tmp/route_read.php, Line: 126, InsideLink()
    File: /www/wwwroot/outofmemory.cn/tmp/index.inc.php, Line: 165, include(/www/wwwroot/outofmemory.cn/tmp/route_read.php)
    File: /www/wwwroot/outofmemory.cn/index.php, Line: 30, include(/www/wwwroot/outofmemory.cn/tmp/index.inc.php)
    QilingLab练习_java_内存溢出

    QilingLab练习

    QilingLab练习,第1张

    文章目录
    • 1. 简介
      • 安装
    • 2. QuickStart
    • 3. qltool
    • 4. QilingLab练习
      • challenge 1 - memory map
      • challenge 2 - set_syscall
      • challenge 3 - add_fs_mapper
      • challenge 4 - hook_address
      • challenge 5 - set_api
      • challenge 6
      • challenge 7 - 3种思路
        • 解1
        • 解2
        • 解3
      • challenge 8
        • 逆向
        • 解1-读栈
        • 解2-搜索内存
      • challenge 9
      • challenge 10
      • challenge 11
      • aarch64
    • 5. 参考资料

    Github:

    • qilingframework/qiling: Qiling Advanced Binary Emulation Framework (github.com)
    • 子项目:qilingframework/rootfs (github.com)

    文档:Qiling Framework Documentation

    官网:Qiling Framework

    1. 简介

    Qiling is an advanced binary emulation framework. By JingDong @Defcon 2019

    内置examples可以用来学习。

    安装

    Python环境至少需要3.8(不然会报各种错误)。

    sudo apt-get install python3.8-dev
    

    最简单的pip安装:

    python3 -m pip install qiling
    

    Pip没有安装examples目录,而且无法调试,所以入门且想要调试的话,建议从github把两个仓库下载下来手动安装:

    python3 setup.py install
    

    根据.gitmodules文件,有子项目依赖,所以建议用用git --recursiv:

    git clone https://github.com/qilingframework/qiling 
    python3 setup.py install
    

    如果遇到网速问题,可以手动下载后放进examples。

    如果要模拟windows,需要执行dllscollector.bat收集系统dll放入rootfs。

    2. QuickStart

    Demo - Qiling Framework Documentation

    Getting Started - Qiling Framework Documentation

    Demo示例大多是windows程序,所以别忘执行dllscollector.bat。有的示例路径不对,需要加个examples啥的~

    大概步骤:

    1. ql = Qiling()初始化,目标可以是二进制文件或shellcode;
    2. 设置一些参数,如map, debug, verbose;
    3. ql.run(), 开始模拟,可以指定代码begin和end。
    $ python3 test.py
    [+]     Profile: Default
    [+]     Map GDT at 0x30000 with GDT_LIMIT=4096
    [+]     Write to 0x30018 for new entry b'\x00\xf0\x00\x00\x00\xfeO\x00'
    [+]     Write to 0x30028 for new entry b'\x00\xf0\x00\x00\x00\x96O\x00'
    [+]     Write to 0x30070 for new entry b'\x00`\x00`\x00\xf6@\x00'
    [+]     Write to 0x30078 for new entry b'\x00\x00\x00\x00\x00\xf6@\x06'
    [+]     Windows Registry PATH: examples/rootfs/x86_windows/Windows/registry
    [=]     Initiate stack address at 0xfffdd000
    [=]     Loading examples/rootfs/x86_windows/bin/RegDemo.exe to 0x400000
    [=]     PE entry point at 0x401381
    [=]     TEB addr is 0x6000
    [=]     PEB addr is 0x6044
    [=]     Loading examples/rootfs/x86_windows/Windows/System32/ntdll.dll ...
    [!]     Warnings while loading examples/rootfs/x86_windows/Windows/System32/ntdll.dll:
    [!]      - SizeOfHeaders is smaller than AddressOfEntryPoint: this file cannot run under Windows 8.
    [!]      - AddressOfEntryPoint lies outside the sections' boundaries. AddressOfEntryPoint: 0x0
    [+]     DLL preferred base address: 0x4b280000
    [=]     Done with loading examples/rootfs/x86_windows/Windows/System32/ntdll.dll
    [=]     Loading examples/rootfs/x86_windows/Windows/System32/kernel32.dll ...
    [+]     DLL preferred base address: 0x6b800000
    [=]     Done with loading examples/rootfs/x86_windows/Windows/System32/kernel32.dll
    [=]     Loading examples/rootfs/x86_windows/Windows/System32/advapi32.dll ...
    [+]     DLL preferred base address: 0x4c300000
    [=]     Done with loading examples/rootfs/x86_windows/Windows/System32/advapi32.dll
    [=]     Loading examples/rootfs/x86_windows/Windows/System32/msvcr110.dll ...
    [+]     DLL preferred base address: 0x10000000
    [=]     Done with loading examples/rootfs/x86_windows/Windows/System32/msvcr110.dll
    [+]     Done with loading examples/rootfs/x86_windows/bin/RegDemo.exe
    [+]     0x6b81f390: GetSystemTimeAsFileTime(lpSystemTimeAsFileTime = 0xffffcfec)
    [+]     0x6b81df10: GetCurrentThreadId() = 0x0
    [+]     0x6b822e90: GetCurrentProcessId() = 0x7cc
    [+]     0x6b81df40: QueryPerformanceCounter(lpPerformanceCount = 0xffffcfe4) = 0x0
    [+]     0x10053f90: _initterm_e(pfbegin = 0x4020b8, pfend = 0x4020c8) = 0x0
    [+]     0x10053f30: _initterm(pfbegin = 0x4020ac, pfend = 0x4020b4)
    [+]     Key 2147483649 Software
    [+]     Value key HKEY_CURRENT_USER\Software not present
    [+]     0x4c31ed30: RegOpenKeyW(hKey = 0x80000001, lpSubKey = "Software", phkResult = 0xffffcfb4) = 0x2
    Create Key Error[+]     0x10062380: printf(format = "Create Key Error") = 0x10
    [+]     0x10054160: exit(status = 0)
    [+]     Syscalls called:
    [+]     GetSystemTimeAsFileTime:
    [+]       {"params": {"lpSystemTimeAsFileTime": 4294954988}, "retval": null, "address": 1803678608, "retaddr": 4200091, "position": 0}
    [+]     GetCurrentThreadId:
    [+]       {"params": {}, "retval": 0, "address": 1803673360, "retaddr": 4200106, "position": 1}
    [+]     GetCurrentProcessId:
    [+]       {"params": {}, "retval": 1996, "address": 1803693712, "retaddr": 4200115, "position": 2}
    [+]     QueryPerformanceCounter:
    [+]       {"params": {"lpPerformanceCount": 4294954980}, "retval": 0, "address": 1803673408, "retaddr": 4200128, "position": 3}
    [+]     _initterm_e:
    [+]       {"params": {"pfbegin": 4202680, "pfend": 4202696}, "retval": 0, "address": 268779408, "retaddr": 4199046, "position": 4}
    [+]     _initterm:
    [+]       {"params": {"pfbegin": 4202668, "pfend": 4202676}, "retval": null, "address": 268779312, "retaddr": 4199098, "position": 5}
    [+]     RegOpenKeyW:
    [+]       {"params": {"hKey": 2147483649, "lpSubKey": "Software", "phkResult": 4294954932}, "retval": 2, "address": 1278340400, "retaddr": 4198443, "position": 6}
    [+]     printf:
    [+]       {"params": {"format": "Create Key Error"}, "retval": 16, "address": 268837760, "retaddr": 4198458, "position": 7}
    [+]     exit:
    [+]       {"params": {"status": 0}, "retval": null, "address": 268779872, "retaddr": 4199217, "position": 8}
    [+]     Registries accessed:
    [+]     HKEY_CURRENT_USER\Software:
    [+]     Strings:
    [+]     Software: 6
    [+]     Create: 7
    [+]     Key: 7
    [+]     Error: 7
    
    

    有一个wannaycry的zip,解压口令在readme里,注意别在windows实体机上玩~

    输出日志:ql.log.info,而且支持正则过滤。

    3. qltool

    项目根目录提供了这个qltool python脚本,用来快速模拟运行shellcode或exec文件。

    基本参数:

    • run, 执行exec文件;
    • code,执行shellcode;
    • examples,显示demo。

    执行时发现qdb.py报错,使用了py3.8的赋值运算符,可以用with语句改一下,比如:

    # if (bp := self.bp_list.get(address, None)):
    with self.bp_list.get(address, None) as bp:
        # pass
    

    不过这个:=运算符用得太多了,,最后我选择把python从3.6升级到3.8,并修改qltool开头的解释器:

    #!/usr/bin/python3.8
    

    依赖库:

    sudo apt-get install python3.8-dev
    sudo python3.8 -m pip install gevent
    

    具体用法执行./qltools examples参考即可。

    4. QilingLab练习

    地址:Shielder - QilingLab – Release

    有x86_64和aarch64两个版本,我选择了aarch64:

    $ file qilinglab-aarch64
    qilinglab-aarch64: ELF 64-bit LSB shared object, ARM aarch64, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux-aarch64.so.1, for GNU/Linux 3.7.0, BuildID[sha1]=c573ba5f5450dc8d134835d4ddb5f93e28138c0c, not stripped
    

    注意LSB,说明是小端。

    如果选择x86_64版本,rootfs就要使用examples/rootfs/x8664_linux

    解题模板:

    from qiling import *
    from qiling.const import QL_VERBOSE
    
    def challenge1(ql:Qiling):
        pass;    
    
    if __name__ == "__main__":
        argv = ["./qilinglab-aarch64"]
        rootfs = "examples/rootfs/arm64_linux"
        ql = Qiling(argv, rootfs, verbose=QL_VERBOSE.DEBUG, bigendian=False)
        # ql.debugger = "gdb:0.0.0.0:9999"
        # ql.debugger = "idapro:0.0.0.0:9999"
    	challenge1(ql);
        ql.run();
    
    

    直接运行的话会报一堆错,都是正常的。如果卡住,可以强杀:

    ps -elf | grep "python3.8" | grep -v "grep" | awk -F" " '{print }' | xargs sudo kill -9
    

    如果启用调试器,程序应该会停在入口点。Gdb可以用gdb-multiarch:

    set architecture aarch64 
    target remote localhost:9999
    

    如果python小于3.8,则不支持调试。

    试了下Ida调试,没成功,,直接导入idapro模块失败了,没查出原因。IDA连接gdbserver模块是没问题的,但单步一下就跑飞了,,,不太好用。

    $ python3.8 qilinglab.py
    Welcome to QilingLab.
    Here is the list of challenges:
    Challenge 1: Store 1337 at pointer 0x1337.
    Challenge 2: Make the 'uname' syscall return the correct values.
    Challenge 3: Make '/dev/urandom' and 'getrandom' "collide".
    Challenge 4: Enter inside the "forbidden" loop.
    Challenge 5: Guess every call to rand().
    Challenge 6: Avoid the infinite loop.
    Challenge 7: Don't waste time waiting for 'sleep'.
    Challenge 8: Unpack the struct and write at the target address.
    Challenge 9: Fix some string operation to make the iMpOsSiBlE come true.
    Challenge 10: Fake the 'cmdline' line file to return the right content.
    Challenge 11: Bypass CPUID/MIDR_EL1 checks.
    
    Checking which challenge are solved...
    Note: Some challenges will results in segfaults and infinite loops if they aren't solved.
    [x]     CPU Context:
    [x]     x0      : 0x0
    [x]     x1      : 0x0
    [x]     x2      : 0x1
    [x]     x3      : 0x0
    [x]     x4      : 0x0
    [x]     x5      : 0x55555556a2b4
    [x]     x6      : 0x696b636568430a0a
    [x]     x7      : 0x686369687720676e
    [x]     x8      : 0x40
    [x]     x9      : 0x7320657261206567
    [x]     x10     : 0x2068636968772067
    ...
    

    简单分析一下程序,一共11关,start函数开头会对一个12字节的数组初始化为0:

    .text:00000000000014BC loc_14BC                                ; CODE XREF: start+54↓j
    .text:00000000000014BC                 LDRSW           X0, [SP,#0x40+var_24_i]
    .text:00000000000014C0                 ADD             X1, SP, #0x40+var_18_arrayBytesFlags[0xB]
    .text:00000000000014C4                 STRB            WZR, [X1,X0]
    .text:00000000000014C8                 LDR             W0, [SP,#0x40+var_24_i]
    .text:00000000000014CC                 ADD             W0, W0, #1
    .text:00000000000014D0                 STR             W0, [SP,#0x40+var_24_i]
    .text:00000000000014D4
    .text:00000000000014D4 loc_14D4                                ; CODE XREF: start+2C↑j
    .text:00000000000014D4                 LDR             W1, [SP,#0x40+var_24_i]
    .text:00000000000014D8                 LDR             W0, [SP,#0x40+var_1C]
    .text:00000000000014DC                 CMP             W1, W0
    .text:00000000000014E0                 B.LT            loc_14BC ; i <= 0xB
    

    每一个字节代表这一关是否通过,通过则为1。

    每一关的函数声明大概像这样:

    void challenge1(byte* pByFlag);
    

    之后,由checker函数检查flag:

    void checker(byte* pByFlags, int nChallenge);
    
    challenge 1 - memory map
    .text:0000000000000CC4                 SUB             SP, SP, #0x20
    .text:0000000000000CC8                 STR             X0, [SP,#0x20+var_18]
    .text:0000000000000CCC                 MOV             X0, #0x1337
    .text:0000000000000CD0                 STR             X0, [SP,#0x20+var_8__0x1337]
    .text:0000000000000CD4                 LDR             X0, [SP,#0x20+var_8__0x1337]
    .text:0000000000000CD8                 LDR             W0, [X0]
    .text:0000000000000CDC                 STR             W0, [SP,#0x20+var_C__mem[0x1337]]
    .text:0000000000000CE0                 LDR             W0, [SP,#0x20+var_C__mem[0x1337]]
    .text:0000000000000CE4                 CMP             W0, #1337
    .text:0000000000000CE8                 B.NE            loc_CF8
    .text:0000000000000CEC                 LDR             X0, [SP,#0x20+var_18]
    .text:0000000000000CF0                 MOV             W1, #1
    .text:0000000000000CF4                 STRB            W1, [X0]
    .text:0000000000000CF8
    .text:0000000000000CF8 loc_CF8                                 ; CODE XREF: challenge1+24↑j
    .text:0000000000000CF8                 NOP
    .text:0000000000000CFC                 ADD             SP, SP, #0x20 ; ' '
    .text:0000000000000D00                 RET
    

    翻译成c语言:

    if(mem[0x1337] == 1337)
    	*pByFlag = 1;
    

    参考文档:Memory - Qiling Framework Documentation

    def challenge1(ql:Qiling):
        ql.mem.map(0x1337//4096*4096, 4096, info="[challenge1]");   # 4k alignment
        ql.mem.write(0x1337, ql.pack32(1337))
    

    关于map的更多使用,可以参考qiling/loader/elf.py

    challenge 2 - set_syscall

    第二关调用uname,硬编码检查sysname和version:

    .text:0000000000000D28                 ADD             X0, SP, #0x1F0+name ; name
    .text:0000000000000D2C                 BL              .uname
    ...
    .text:0000000000000D48                 ADRP            X0, #aQilingos@PAGE ; "QilingOS"
    .text:0000000000000D4C                 ADD             X1, X0, #aQilingos@PAGEOFF ; "QilingOS"
    .text:0000000000000D50                 ADD             X0, SP, #0x1F0+buf
    .text:0000000000000D54                 LDR             X2, [X1] ; "QilingOS"
    .text:0000000000000D58                 STR             X2, [X0]
    .text:0000000000000D5C                 LDRH            W1, [X1,#(aQilingos+8 - 0x1840)] ; ""
    .text:0000000000000D60                 STRH            W1, [X0,#8]
    .text:0000000000000D64                 ADRL            X0, aChallengestart ; "ChallengeStart"
    ...
    

    通关条件:

    uname.sysname == "QilingOS";
    uname.version == "ChallengeStart";
    

    通过qiling提供的系统调用hook,修改返回地uname结构体即可。

    参考文档:

    • uname(3p) - Linux manual page (man7.org)

    • Hijack - Qiling Framework Documentation

    uname结构体定义如下:

    /* Structure describing the system and machine.  */
    struct utsname
    {
        /* Name of the implementation of the operating system.  */
        char sysname[65];
    
        /* Name of this node on the network.  */
        char nodename[65];
    
        /* Current release level of this implementation.  */
        char release[65];
        /* Current version level of this release.  */
        char version[65];
    
        //...
    };
    

    Qiling脚本如下:

    ########################
    # https://man7.org/linux/man-pages/man3/uname.3p.html
    # int uname(struct utsname *name);
    # /* Structure describing the system and machine.  */
    # struct utsname
    # {
    #     /* Name of the implementation of the operating system.  */
    #     char sysname[65];
    
    #     /* Name of this node on the network.  */
    #     char nodename[65];
    
    #     /* Current release level of this implementation.  */
    #     char release[65];
    #     /* Current version level of this release.  */
    #     char version[65];
    
    #     //...
    # };
    def my_uname(ql:Qiling, pStcUname, *args,**kwargs):
        ql.mem.write(pStcUname, b"QilingOS".ljust(65,b"\x00"))  # "QilingOS..(+..."
        
        ql65mem*write3pStcUname , b'ChallengeStart'.(65 ,b'\x00'ljust))return 0defchallenge2
    
        ( :
    
    ) :.ql(Qiling"uname",
        ql)set_syscall=open( my_uname "/dev/urandom"
    

    如果是其它qiling没有实现的syscall,就需要指定syscall number,比如write为4.

    challenge 3 - add_fs_mapper

    伪代码:

    fd ) ;read(,,
    0x20)fd; urandom_bufread (,&
    ,0x1fd) ;byUrandomgetrandom (,0x20
    ,1getrandom_buf) ;int =0;
    
    for nTmp ( int=
    0;<= i 0x1f ;++ i ) if( (i[
    {
        ]== [urandom_buf]i) && getrandom_buf(i!=[ ] )byUrandom ) urandom_buf++i;}if
            (nTmp==
    0x20
    )*nTmp = 1;
        
  • getrandom(2) - Linux manual page (man7.org)
  • pFlag
  • Hijack - Qiling Framework Documentation
  • /dev/urandom/* Flags for use with getrandom. */

    参考文档:

      #define

    getrandom是一个系统调用,其实默认也是从GRND_NONBLOCK取值,flag设为1可以防止熵池为空导致程序阻塞。

    0x01
    #define GRND_RANDOM 0x02
    ssize_tgetrandom ( void
    * ,size_t, unsignedbufint ) buflen; /dev/urandom import flags()
    

    也就是说,程序涉及getrandom这个系统调用,以及(这个设备。除了利用上一关的set_syscall劫持getrandom,还需要用qiling的QlFsMappedObject类劫持urandom设备的读 *** 作。

    直接照抄文档的示例即可:

    from qiling.os.mapper \x01 QlFsMappedObject
    
    
    def my_syscall_getrandom)ql:Qiling, write_buf, write_buf_size, flags, *args, **kwreturn:
        ql.mem.write;write_buf, b"(" * write_buf_size)
        ( write_buf_size)
    
    class Fake_urandom(QlFsMappedObject==:
    
        def read1self, size):
            ifreturnsize \x02 # byUrandomreturn:
                \x01 b"("  )
            else:
                # syscall fstat will ignore it if return -1 b"return" * size
    
        def fstat(self): return
            0 -1
    
        def close(self):
            ( "/dev/urandom"
    
    def challenge3(ql:Qiling)):
        ql.add_fs_mapper("getrandom", Fake_urandom)int
        ql.set_syscall=0, my_syscall_getrandom ;
    
    
    challenge 4 - hook_address

    伪代码:

    int var_4__l = 0;
    while var_8__r ( <)
    *=var_4__l 1 var_8__r;
    {
        ++pFlag ; }// .text:0000000000000FD8                 LDR             W0, [SP,#0x20+var_8__r]
        // .text:0000000000000FDC                 LDR             W1, [SP,#0x20+var_4__l]var_4__l// .text:0000000000000FE0                 CMP             W1, W0
    
  • Hook - Qiling Framework Documentation
  • Register - Qiling Framework Documentation
  • get_lib_base import

    那么在执行cmp时,让w0为1即可。

    参考文档:

      defhook_cmp_w0

    Qiling提供了一个获取模块基址的函数(,不过在文档里没找到。

    : os
    ) :.ql.Qiling("w0"
        ql,reg0x1write)return; defchallenge4
        (:
    
    ) :=ql.Qiling.(
        pBase . ql.mem(get_lib_base.os)path[split-ql1path]).(,+
        ql0xFE0hook_address)hook_cmp_w0[ pBase 5 ]=
    
    challenge 5 - set_api

    伪代码:

    DWORD dwArr10}; [ {5]=
    DWORD dwArr2};// init dwArr2 by rand() // .text:0000000000001038                 BL              .rand {// .text:000000000000103C                 MOV             W2, W0for
    (
    int
    =
    0 ;<= i 4 ;++ i ) if( [i]
    {
        !=[dwArr1]i) * dwArr2=i[]
        {
    		;pFlag return dwArr2;i}* 
            =1
        ;
    	}pFlag 
  • rand(3) - Linux manual page (man7.org)
  • Hijack - Qiling Framework Documentation
  • def my_rand

    也就是说,劫持rand返回0就可以了。

    参考文档:

      (:

    rand()是库函数,不是系统调用,所以不能用set_syscall,应该用set_api。

    ) :# ql.reg.write("w0", 0)ql.Qiling.(
        "eax"
        ql,reg1write)return; defchallenge5
        (:
    
    ) :.ql(Qiling"rand",
        ql)set_apisudoaptinstall my_rand#
    

    不过从第一题开始,我的程序就会在第5关这里崩溃,换成x8664不用qiling直接执行也一样,所以后续的题解验证需要自己逆向出源代码,安装aarch64编译工具来验证。

    include # include gccgo-5-aarch64-linux-gnu
    aarch64-linux-gnu-gcc-5 test.c -o aarch64
    

    逆向出来的源码:

    intmain 
    () 
    int32_t [5]
    {
        = dwArr10}; int32_t {[5]
        = dwArr2};int = {1;
        int nFlag = time(
        0 s ) ;srand();
        for(sint=
        0 ;<= i 4 ;++ i ) [] =irand
        {
            dwArr2(i) ; printf("%d\n",
            []); dwArr2}ifor(int
        =
        0 ;<= i 4 ;++ i ) if( [i]
        {
            !=[dwArr1]i) = dwArr2[i];
            {
                nFlag break dwArr2;i}} 
                if(
            1
            
        ==
        )printf( "congratulation\n" nFlag)
        {
            ;}return0;
        }
        int =1
    ;
    

    rand返回值仍然保存在w0里,所以qiling脚本不用变。

    challenge 6

    伪代码:

    int n1 ; while(
    ( n2&
    0xff) !=n1 0 )= ( &0xff
    {
        n2 ) ;n1 } defhook_cmp_w0_6(
    :
    

    和第4关差不多

    ) :.ql.Qiling("w0"
        ql,reg0x0write)return; defchallenge6
        (:
    ) :=ql.Qiling.(
        pBase . ql.mem(get_lib_base.os)path[split-ql1path]).(,+
        ql0xFE0hook_address)hook_cmp_w0_6* pBase = 1;
    
    
    challenge 7 - 3种思路

    伪代码:

    sleeppFlag ( 0xFFFFFFFF)
    ;#includeintmain
    

    需要做的是劫持sleep函数,修改睡眠时间。

    解1

    逆向实现:

    () 
           
    sleep (0xFFFFFFFF)
    {
        ;return0;}
        // .text:00000000000007C0                 MOV             W0, #0xFFFFFFFF ; seconds // .text:00000000000007C4                 BL              .sleepdef
    my_sleep_call
    (
    :
    

    Qiling脚本:

    ) :.ql.Qiling("w0"
        ql,reg0x0write)return; defchallenge7
        (:
    
    ) :=ql.Qiling.(
        pBase . ql.mem(get_lib_base.os)path[split-ql1path]).(,+
        ql0x7C4hook_address)my_sleep_calldef pBase my_sleep (:
    

    只要程序马上退出,就说明成功了。

    解2

    也可以hook api,脚本实现:

    ) :returnql;Qilingdefchallenge7_hook_api
        (:
    ) :=ql.Qiling.(
        pBase . ql.mem(get_lib_base.os)path[split-ql1path]).("sleep",
        ql)set_api[]( my_sleep)
    
    解3

    也可以劫持系统调用,根据DEBUG信息,sleep其实是调用了nanosleep():

    https://man7.org/linux/man-pages/man3/sleep.3.html
    https://man7.org/linux/man-pages/man2/nanosleep.2.html
    	On Linux, sleep() is implemented via nanosleep(2). 
    +# int nanosleep(const struct timespec *req, struct timespec *rem);     syscall hooked 0x7fffb7e7862c: ql_syscall_nanosleepdefmy_nanosleep
    

    官网文档里也有说明:

    (

    Qiling脚本如下:

    :
    , ,,ql*Qiling, req** rem) :argsreturn;kwargsdefchallenge7_hook_syscall
        (:
    
    ) :=ql.Qiling.(
        pBase . ql.mem(get_lib_base.os)path[split-ql1path]).("nanosleep",
        ql)set_syscallstructHeap1// 0x18 bytes my_nanosleepstruct
    
    challenge 8

    调用了两次malloc,根据反汇编,结构如下:

    * ;	// 8 bytes
    {
        int32_t =pHeap20x00000539 ;
        // 4 bytes n1 int32_t =0x3DFCD6EA ;
        // 4 bytes n1 int *; // 8 bytes
        } structpFlagHeap2	// 0x1e bytes
    char
    
    [ ]	=
    {
        "Random data" buf}} // 00000000 Heap1           struc ; (sizeof=0x18, align=0x8, mappedto_40) {// 00000000 pHeap2          DCQ ?// 00000008 MAGIC           DCQ ?
    // 00000010 NUM             DCQ ?
    

    创建IDA结构体:

    // 00000018 Heap1           ends
    // 00000018
    // 00000000 ; ---------------------------------------------------------------------------
    // 00000000
    // 00000000 Heap2           struc ; (sizeof=0x20, align=0x8, mappedto_42)
    // 00000000 buf             DCB 30 dup(?)
    // 0000001E                 DCB ? ; undefined
    // 0000001F                 DCB ? ; undefined
    // 00000020 Heap2           ends
    // .text:00000000000011D0                 LDR             X0, [SP,#0x30+var_8__pHeap1]
    // .text:00000000000011D4                 LDR             X1, [SP,#0x30+var_18___pFlag]
    // .text:00000000000011D8                 STR             X1, [X0,#0x10]
    // .text:00000000000011DC                 NOP
    *
    challenge8
    (
    )
    Heap1 *__fastcall ;// x0__int64 a1*
    {
      Heap1 ;result// [xsp+28h] [xbp+28h] =
      Heap1 (v3* )
    
      v3 malloc (Heap1 0x18uLL);=()malloc
      v3->pHeap2 ( 0x1EuLL__int64);LODWORD()=
      1337;v3->MAGICHIDWORD ( )=
      1039980266;v3->MAGICstrcpy ( (char
      *),"Random data" );v3->pHeap2= ;=;
      result return v3;
      v3->NUM } a1// aarch64-linux-gnu-gcc-5 test.c -g -o aarch64
      # resultinclude
    #
    
    逆向
    include
    
    structHeap2 
    // 0x1e byteschar 
    [ 0x1e	]
    {
       ; buf};structHeap1
    // 0x18 bytesstruct
    Heap2 *	;
    {
        // 8 bytes int64_t; pHeap2int *
        ; nMagic// 8 bytes
        } ;pFlagint	main
    ()
    
    
    struct Heap1;struct
    {
        Heap2 ; heap1char
        [ ] heap2=
        "Random data" arrBuf;int = 0;
        . nFlag = &;
        heap1.pHeap2 = 0x3DFCD6EA00000539heap2;
        heap1.nMagic = &;
        heap1memcpypFlag ( .nFlag,
        ,sizeofheap2(buf) arrBuf) ;printfarrBuf("heap1.pHeap2: 0x%p\n",
        .);printf heap1(pHeap2"heap1.nMagic: 0x%16x\n",
        .);printf heap1(nMagic"heap1.pFlag: 0x%p\n",
        .);__asm__ heap1(pFlag"nop")
        ;if(1==
        )printf( "congratulations\n" nFlag)
        {
            ;}return0;
        }
        .text:0000000000000878 heap1           = -0x50
    .text:00000000000008B8                 STR             X0, [X29,#0x70+heap1]
     defread_stack_heap1
    (
    
    

    HOOK目标地址就是nop指令的地址,目的是改写heap1.pFlag。

    解1-读栈
    :

    脚本(注意我自己写的代码其实没有调用malloc,结构体是直接存在栈上的):

    ) :# pHeap1 = ql.mem.read(ql.reg.read("sp") + 0x20, ql.archbit // 8);ql# pHeap1 = ql.unpack64(pHeap1);Qiling# heap1 = ql.mem.read(pHeap1, 0x18) # out struct is in stack actually
        =
        .
        .
    
        (
        heap1 . ql.mem(read"sp"ql)reg+read0x20,0x18 ) ;, ,=.
    
        pHeap2( nMagic"QQQ" pFlag , struct)unpack;# ql.log.info("pHeap1: %x" % pHeap1). heap1.(
        "heap1.pHeap2: %x"
        ql%log)info.. ( pHeap2"heap1.nMagic: %x"
        ql%log)info.. ( nMagic"heap1.pFlag: %x"
        ql%log)info.. ( pFlag,
        qlb"\x01"mem)write;pFlagdef challenge8_read_stack(:
    
    
    ) :=ql.Qiling.(
        pBase . ql.mem(get_lib_base.os)path[split-ql1path]).(,+
        ql0x96Chook_address)read_stack_heap1# 0x96C : nop pBase *pFlag importdef	search_heap1
    
    解2-搜索内存

    搜索内存里的magic,找到heap1, 修改(

    : struct
    ) :=ql0x3DFCD6EA00000539Qiling;=
        nMagic . .(
        pMagics . ql(mem)search)qlforpack64innMagic:=
        - pMagic . pMagics//
            pHeap1 8 pMagic ; ql=archbit..(
            heap1 , ql0x18mem)read;pHeap1, ,=.
            pHeap2( _"QQQ" pFlag , struct)unpackif.. heap1(
            ) ql==mem"Random data"string:pHeap2. . (,
                qlb"\x01"mem)writebreakpFlag; return;
                defchallenge8_search_mem
        (:
    ) :=ql.Qiling.(
        pBase . ql.mem(get_lib_base.os)path[split-ql1path]).(,+
        ql0x96Chook_address)search_heap1# 0x96C : nop pBase # include#	include
    
    challenge 9

    伪代码:

    #include 
    intmain 
    () 
    
    char [0x20]
    {
        = src0}; char {[0x20]
        = dst0}; int {=0;
        int nFlag = 0;
        strcpy i ( ,"aBcdeFghiJKlMnopqRstuVWxYz"
    
        );srcstrcpy (,)
        ;fordst( src=0
        ;<i 26 ;++ i ) [] =itolower
        {
            dst(i[ ] );dst}i=(strcmp
        (
        
        nFlag , )==0src) dst? 1 :0 ; if  ( 1==
    
        )printf( "congratulations\n" nFlag)
        {
            ;}return0;
        }
        def my_tolower(
    :
    

    Hook掉tolower就行了:

    ) :returnqldef Qilingchallenge9(
        :
     
    ) :.ql( Qiling'tolower',
        ql)set_api#include# my_tolowerinclude
    
    challenge 10

    伪代码:

    #include 
    intmain 
    () 
    int =0;
    {
        int nFlag = 0;
        char fd [ ]=
        "/proc/self/cmdline" path}; char {[0x40]
        = buf0}; int {=0;
        int nRet = 0;
        do i = open(
    
        ,{
            fd ) ;ifpath( O_RDONLY-1
            ==) break; = fdread (,
            nRet , 0x3F)fd; bufif (0==
            ) break; } nRetwhile (0
            
        );for(=0
    
        ;<i ; ++) i if nRet( [i]
        {
            ==0buf)i[ ] =0x20
            {
                buf;i} } if(
            strcmp
        (
    
        ,"qilinglab")==buf0 )= 1 ; }
        {
            nFlag if (1
        ==
    
        )printf( "congratulations\n" nFlag)
        {
            ;}return0;
        }
        /proc/self/cmdline classMy_cmdline
    (
    
    
    
    

    和第三关一样,劫持)设备即可。

    : defreadQlFsMappedObject(,
        ) :returnselfb"qilinglab" sizedeffstat
            ( )
        : return-self1def
            close ()
        : return0selfdefchallenge10
            ( :
    
    ) :.ql( Qiling"/proc/self/cmdline",
        ql(add_fs_mapper)).. My_cmdline..int(
    
    challenge 11 aarch64
    .text:00000000000013E4 FF 83 00 D1                 SUB             SP, SP, #0x20
    .text:00000000000013E8 E0 07 00 F9                 STR             X0, [SP,#0x20+var_18__pFlag]
    .text:00000000000013EC 00 00 38 D5                 MRS             X0, #0, c0, c0, #0
    .text:00000000000013F0 E0 0F 00 F9                 STR             X0, [SP,#0x20+var_8]
    .text:00000000000013F4 E0 0F 40 F9                 LDR             X0, [SP,#0x20+var_8]
    .text:00000000000013F8 01 FC 50 D3                 LSR             X1, X0, #0x10
    .text:00000000000013FC E0 66 82 D2                 MOV             X0, #0x1337
    .text:0000000000001400 3F 00 00 EB                 CMP             X1, X0 ; if ((var_8 >> 0x10) == 0x1337) *pFlag = 1
    .text:0000000000001404 81 00 00 54                 B.NE            loc_1414
    .text:0000000000001408 E0 07 40 F9                 LDR             X0, [SP,#0x20+var_18__pFlag]
    .text:000000000000140C 21 00 80 52                 MOV             W1, #1
    .text:0000000000001410 01 00 00 39                 STRB            W1, [X0]
    .text:0000000000001414
    .text:0000000000001414             loc_1414                                ; CODE XREF: challenge11+20↑j
    .text:0000000000001414 1F 20 03 D5                 NOP
    .text:0000000000001418 FF 83 00 91                 ADD             SP, SP, #0x20 ; ' '
    .text:000000000000141C C0 03 5F D6                 RET
    

    看网上用ghidra逆出来好像效果好一些。

    这个MRS指令比较复杂,大概就是用来收集CPU各种信息的,参考文档:Arm A64 Instruction Set Architecture

    Arm的msr指令在Intel指令集里对应cpuid指令,

    参考文档(很长):CPUID — CPU Identification (felixcloutier.com)

    本实验x8664版本指令:

    ).
    .text:000000000000118A                 mov     eax, 40000000h
    .text:000000000000118F                 cpuid
    int.
    

    可以使用qiling提供的hook_code,直接把这个指令跳过,参考文档:Hook - Qiling Framework Documentation。

    逆向一下:

    #include 
    #include 
    = main0;
    {
        int nFlag = 0;
        ( nTmp "mrs x0, midr_el1" );
        __asm__if((0x10
        )== 0x1337 nTmp >> ) = 1 ;}
        {
            nFlag if (1
        ==
        )("congratulations\n" ) nFlag;
        {
            printf}return0;
        }
        // .:
    ,
    
    , [text,00000000000007B8 FD 7B BE A9                 STP             X29#var_20]!! X30// .SP:03
    00 91text,00000000000007BC FD // . :                 MOV             X2900 SP
    , [text,00000000000007C0 BF 1B #0x20+nFlag] B9                 STR             WZR// .X29:00
    , [text,00000000000007C4 BF 1F #0x20+nTmp] B9                 STR             WZR// .X29:00
    00 38text,00000000000007C8 #0, c0, c0, #0 // . D5                 MRS             X0: 40
    , [text,00000000000007CC A0 1F #0x20+nTmp] B9                 LDR             W0// .X29:01
    10 13text,00000000000007D0 , 7C #0x10 //                 ASR             W1. W0: 66
    82 52text,00000000000007D4 E0 #0x1337 // .                 MOV             W0: 00
    00 ,text//00000000000007D8 3F . : 6B                 CMP             W161 W0
    00 00text5400000000000007DC . // . :                 B00000000000007E0NE            loc_7E8
    20 00text8052 , #1 // .                 MOV             W0: 00000000000007E4
    00 ,text[, A0 1B #0x20+nFlag] B9                 STR             W0def my_mrsX29(:
    
    

    脚本:

    , :intql, Qiling: addressint ): size= ..(
        opcode , ql)memifread(address0x7C8 size==
        ( &0xfff ) )address and (b"\x00\x00\x38\xD5"== ) :. . opcode("w0"
            ql,reg0x1337write<<0x10) ..+=8
            ql;regdefarch_pc challenge11 (:
    
    ) :.ql( Qiling)
        qlhook_codemy_mrs
    
    5. 参考资料

    Qiling:一款功能强大的高级代码模拟框架 - FreeBuf网络安全行业门户

    11个小挑战,Qiling Framework 入门上手跟练-软件逆向-看雪论坛-安全社区|安全招聘|bbs.pediy.com

    Shielder - QilingLab – Release

    zodf0055980/QilingLab_Writeup: QilingLab challenge writeup (github.com)

    欢迎分享,转载请注明来源:内存溢出

    原文地址: http://outofmemory.cn/langs/740621.html

    (0)
    打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
    上一篇 2022-04-28
    下一篇 2022-04-28

    发表评论

    登录后才能评论

    评论列表(0条)

    保存