幼儿园小班 发表于 2023-8-28 03:27

qiling framework + qilingLab x86_64 题解

本帖最后由 幼儿园小班 于 2023-9-8 11:33 编辑

# qiling framework + qilingLab x86_64 题解

## 环境

- mac m1
- Qiling framework 1.47dev0
- git地址: https://github.com/qilingframework/qiling
- 文档地址: https://docs.qiling.io
- ida 7.5
- qilingLab
- x86_64版本: https://www.shielder.com/attachments/qilinglab-x86_64
- aarch64版本: https://www.shielder.com/attachments/qilinglab-aarch64

### 先模拟运行

```python
ql = Qiling(["qilinglab-x86_64"], "rootfs/x8664_linux",
            verbose=QL_VERBOSE.OFF, multithread=True)
ql.run()
```

- 运行后发现报错.
- unicorn.unicorn.UcError: Invalid memory read (UC_ERR_READ_UNMAPPED)
- 说明内存读取不到.不用慌,这是因为第一题我们还没解出来.解出来就解决了.原因下面会讲

### 第1题

- 先看下ida反编译的伪c代码.

!(https://cdn.db-kj.com/markdown/image-20230828014725142.png)

- 比较简单,就是要地址0x1337的值等于1337即可.但是我们之前一运行就报错.是因为这里的0x1337不存在,所以报错.

- 接下来我们直接写代码

-

- ```python
def challenge1():
      ql.mem.map(0x1000, 0x1000)
      ql.mem.write(0x1337, ql.pack16(1337))
```



### 第2题

!(https://cdn.db-kj.com/markdown/image-20230828020206027.png)

- 逻辑解释

- 调用uname函数赋值给name变量

- name.sysname 要等于 "QilingOS"

- name.version 要等于 "ChallengeStart"

- 所以我们只需要直接修改 uname函数即可

- ```python
def challenge2():
      def my_syscall_uname(q: Qiling, *args):
          rdi = ql.arch.regs.rdi
          ql.mem.write(rdi, b'QilingOS\x00')
          ql.mem.write(rdi + 65 * 3, b'ChallengeStart\x00')

      ql.os.set_api("uname", my_syscall_uname, QL_INTERCEPT.EXIT)
```

- 可能各位会疑问,为什么写rdi和rdi+65*3,大家看张图即可明白了
- !(https://cdn.db-kj.com/markdown/image-20230828021355516.png)


### 第3题

!(https://cdn.db-kj.com/markdown/image-20230828021512165.png)

- 逻辑解释

- 打开/dev/urandom

- 取0x20个字符放到buf

- 取1个字符放到v5

- 生成一个32位的字符放到v7

- 对比buf的每个字符等于v7的每个字符,且buf的字符不等于v5

- 所以解题方式就是做一个假的 /dev/urandom 和 一个我们自己的getrandom

-

- ```python
def challenge3():
      class FKUrandom(QlFsMappedObject):
          def read(self, expected_len):
            if expected_len > 1:
                  return b"\x65" * expected_len
            else:
                  return b"\x00"

          def fstat(self):
            return -1

          def close(self):
            return 0

      def my_get_random(q: Qiling, *args):
          p = q.os.resolve_fcall_params({"buf": POINTER, "count": SIZE_T, "flags": INT})
          if p["count"] == 32:
            q.mem.write(p["buf"], b"\x65" * 32)
          return 0

      ql.add_fs_mapper("/dev/urandom", FKUrandom())

      ql.os.set_api("getrandom", my_get_random, QL_INTERCEPT.CALL)
```



### 第4题

- 该题反编译看不到伪c代码,所以我们直接看汇编码
- !(https://cdn.db-kj.com/markdown/image-20230828022258784.png)
- !(https://cdn.db-kj.com/markdown/image-20230828022514993.png)

- 逻辑解释

- 初始化 = 0 和 = 0

- 0xe43处进行cmp判断,如果相等则死循环

- 所以我们只需要让他们两的值不同即可

- 两种解法:

- 第一种:

    - 直接在0xe43处修改eax的值

    - ```python
      def challenge4():
          base_ptr = ql.mem.get_lib_base("qilinglab-x86_64")
          hook_addr = base_ptr + 0xe43
      
          def update_eax(q: Qiling, *args):
            q.arch.regs.eax = 1
      
          ql.hook_address(update_eax, hook_addr)
      ```

- 第二种:

    - 直接在赋值后进行修改,让其不同

    -

    - ```python
      def challenge4_1():
          base_ptr = ql.mem.get_lib_base("qilinglab-x86_64")
          hook_addr = base_ptr + 0xe33
      
          def update_var8(q: Qiling, *args):
            rbp = q.arch.regs.rbp
            q.mem.write(rbp - 0x8, b"\x01")
      
          ql.hook_address(update_var8, hook_addr)
      ```



### 第5题

- !(https://cdn.db-kj.com/markdown/image-20230828023259185.png)

- 逻辑解释

- v5 = 0

- V5=rand() 随机数

- 所以解题方式就是直接改rand 让其返回0即可

-

- ```python
    def challenge5():
      def my_rand(q: Qiling, *args):
            q.arch.regs.eax = 0
   
      ql.os.set_api("rand", my_rand, QL_INTERCEPT.EXIT)
    ```

   

### 第6题

- !(https://cdn.db-kj.com/markdown/image-20230828024829697.png)

- 逻辑解释,直接一个死循环....参考第4题解题思路

-

- ```python
def challenge6():
      base_ptr = ql.mem.get_lib_base("qilinglab-x86_64")
      hook_addr = base_ptr + 0xf16

      def update_rax(q: Qiling, *args):
          q.arch.regs.rax = 0

      ql.hook_address(update_rax, hook_addr)
```



### 第7题

!(https://cdn.db-kj.com/markdown/image-20230828025012040.png)

- 逻辑解释,直接睡死.....

- 解题思路,修改sleep即可

- ```python
def challenge7():
      def my_sleep(q: Qiling, *args):
          q.arch.regs.edi = 0
          return 0

      ql.os.set_api("sleep", my_sleep, QL_INTERCEPT.ENTER)
```



### 第8题

!(https://cdn.db-kj.com/markdown/image-20230828025151930.png)

- 逻辑解释

- v2 申请0x18大小的字节, 就是24个字节

- 而dword=4字节,所以v2是6个dowrd

- qword = 8个字节.

- 为v2起始位置指向的地址 申请0x1e的空间,这里相当于v2 + v2 = malloc(0x1euLL)

- v2 和 v2 分别等于 1337 和 1039980266

- 所以v2的第二个指向地址的值是 1039980266 << 32 + 1337,为什么1039980266在前,因为要注意大小端问题,这里为什么要这么算,因为我们要取一个内存魔数,然后通过内存搜索来定位该结构.

- 为v2的起始位置指向的地址赋值成 "Random data"

- v2+2相当于v2的第三个指向的地址的值=a1,这个就是check值.

- 所以解题方式就是找到这个结构,然后修改第三个值为1即可.

- ```python
def challenge8():
      def hook(q: Qiling, *args):
          magic = (1039980266 << 32) + 1337
          addr_list = q.mem.search(q.pack64(magic))
          for addr in addr_list:
            st_addr = addr - 8
            tmp_struct = q.mem.read(st_addr, 24)
            str_addr, _, check_addr = struct.unpack("QQQ", tmp_struct)
            # print(q.mem.string(str_addr))
            if q.mem.string(str_addr) == "Random data":
                  # print("Check", q.mem.read(q.arch.regs.rdx, 8))
                  q.mem.write(check_addr, q.pack8(1))

      base_ptr = ql.mem.get_lib_base("qilinglab-x86_64")
      ql.hook_address(hook, base_ptr + 0xfb5)
```



### 第9题

!(https://cdn.db-kj.com/markdown/image-20230828031401868.png)

- 逻辑解释

- 对src赋值"aBcdeFghiJKlMnopqRstuVWxYz"

- dest = src 即 dest = "aBcdeFghiJKlMnopqRstuVWxYz"

- 循环对dest做小写处理

- 判断src要等于dest

- 解题思路

- 所以我们只需要处理strcmp或者tolower函数即可.

- 建议不要处理strcmp 因为第10题也用到了这个不利于我们学习.会直接把第10题也过了.

- 所以我们处理tolower

- ```python
    def challenge9():
      def hook(q: Qiling, *args):
            p = q.os.resolve_fcall_params({"s": BYTE})
            # print(p["s"])
            q.arch.regs.eax = p["s"]
   
      ql.os.set_api("tolower", hook, QL_INTERCEPT.EXIT)
    ```



### 第10题

!(https://cdn.db-kj.com/markdown/image-20230828031752756.png)

- 逻辑解释

- 打开文件 /proc/self/cmdline

- 读取 0x3f 字节,赋值给buf

- 对buf进行处理,0值赋值成32

- 判断buf是否等于 "qilinglab"

- 所以解题思路就是,直接模拟该文件,让read函数直接读到"qilinglab" 即可

-

- ```python
def challenge10():
      class FSHook(QlFsMappedObject):
          def read(self, expected_len):
            return "qilinglab".encode()

          def close(self):
            return 0

      ql.add_fs_mapper("/proc/self/cmdline", FSHook())
```







### 第11题

!(https://cdn.db-kj.com/markdown/image-20230828032049842.png)

- 逻辑解释

- 执行汇编指令 cpuid

- 然后判断 rbx+rcx组合后的值要等于0x696C6951614C676ELL

- 同时 rdx的值要等于538976354

- 这题比较关键的是 cpuid,不清楚的小伙伴可以自行查询资料.

- 所以我们的解题思路就是在执行cpuid的时候直接不执行,同时对rbc rcx rdx进行赋值即可

-

- ```python
def challenge11():
      def hook1(q: Qiling, address, size):
          if ql.mem.read(address, size) == b'\x0f\xa2':
            regs = ql.arch.regs
            regs.ebx = 0x696C6951
            regs.ecx = 0x614C676E
            regs.edx = 0x20202062
            regs.rip += 2

      ql.hook_code(hook1)
```







## 到此为止,11题全部拿下.受益匪浅,今后准备在有空的情况下,尝试用qiling框架刷一下ctf的逆向题,然后分享给大家一起学习qiling这个框架.该框架真的非常好.

## 其中第8题和第11题确实学到了很多的东西......

youxiaxy 发表于 2023-8-28 08:10

解析的不错。感谢分享

yb66vs 发表于 2023-8-28 08:50

对于,小白,,,长见识了{:1_921:}

AA082 发表于 2023-8-28 09:27

分析详细,非常感谢

weiyanli 发表于 2023-8-28 10:50

学习中,感谢大神分享

appointment 发表于 2023-8-28 11:03

Hmily 发表于 2023-9-6 17:09

@幼儿园小班 有个图好像地址好像不对:image-20230828021355516.png

幼儿园小班 发表于 2023-9-8 11:33

Hmily 发表于 2023-9-6 17:09
@幼儿园小班 有个图好像地址好像不对:image-20230828021355516.png

感谢,之前都没发现,已经修复了

abc14258 发表于 2023-9-8 16:17

牛掰啊大佬学到了
页: [1]
查看完整版本: qiling framework + qilingLab x86_64 题解