首页
社区
课程
招聘
[已解决] [求助]求解2个CTF的初赛的逆向题 50.00雪花
2023-11-4 11:57 2557

[已解决] [求助]求解2个CTF的初赛的逆向题 50.00雪花

2023-11-4 11:57
2557

求解2个CTF的初赛的逆向题,请告知解题的过程,谢谢


[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课

最后于 2023-11-4 12:10 被慕容溯雪编辑 ,原因:
上传的附件:
收藏
点赞0
打赏
分享
最新回复 (3)
雪    币: 267
活跃值: (620)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
hypersine 2023-11-5 14:21
2
0

第一个JLRE0203.exe是一个典型的SMC(Self Modified Code),关键部分在于它VirtualAlloc的0x74字节的机器码(位于ida中地址0x140005060处)。


0x74字节的机器码分为两个部分,一部分是开头和结尾的解密代码,另一部分是中间(地址范围0x14000506B ~  0x1400050A5)被加密的代码,长度是0x3a字节。


从VirtualAlloc前面输入flag的代码逻辑来看,flag[8:10]处的两个字节为加密的key,加密算法为word by word的xor加密。

所以这里比较麻烦的是必须爆破出对应的key。key的取值空间大约是0x10000量级。所以我们用unicode模拟执行跑一下就行:

#!/usr/bin/env python3
import struct
import unicorn

# idaapi.get_bytes(0x140005060, 0x74).hex()
data = bytes.fromhex('415741564155e83c000000293e293f21e0a728e8ae7d20e98e9769686921ea8f6621e0a620e98f9769686921a8966d21ea8e6621a88f6d25609625e1996c7f5c7f29372936eb26415e4d31ff4983ff3a731866438b043e66448b2a664431e8664389043e4983c702ebe241ffe6415d415e415fc3')

for i in range(0, 0x10000):
    k = struct.pack('<H', i)

    uc = unicorn.Uc(unicorn.UC_ARCH_X86, unicorn.UC_MODE_64)
    uc.mem_map(0x3000, 0x1000)      # code区域
    uc.mem_map(0x4000, 0x1000, unicorn.UC_PROT_READ | unicorn.UC_PROT_WRITE)    # data区域
    uc.mem_map(0x5000, 0x1000, unicorn.UC_PROT_READ | unicorn.UC_PROT_WRITE)    # stack区域

    uc.mem_write(0x3000, data)
    uc.mem_write(0x4000, b'flag{aaa' + k + b'a' * 28 + b'\x00') # 用flag填充

    uc.reg_write(unicorn.x86_const.UC_X86_REG_RCX, ord('f'))    # flag的第一个字符,也就是f
    uc.reg_write(unicorn.x86_const.UC_X86_REG_RDX, 0x4000 + 8)  # 0x4000 + 8,即key所在的地址

    uc.mem_write(0x5200, struct.pack('<Q', 0xdead0000))    # 设置ret返回地址
    uc.reg_write(unicorn.x86_const.UC_X86_REG_RSP, 0x5200)

    try:
        uc.emu_start(0x3000, 0xdead0000, timeout = 1000)  # 防止尝试解密后的代码死循环
        al = uc.reg_read(unicorn.x86_const.UC_X86_REG_AL) # 读取代码执行后的结果
        if al == 0xda:  # 从题目代码逻辑来看,flag的第一个字符f经过代码运算之后应当与0x140005050处的第一个字节相等
            print(k)
    except:
        pass  # 模拟过程如果出现任何异常,即解密失败

    del uc


运行之后结果应该会输出b'hi',即hi为解密的key。


之后直接在ida控制台里解密对应代码:

import itertools
bytes(a ^ b for a, b in zip(idaapi.get_bytes(0x14000506B, 0x3a), itertools.cycle(b'hi'))).hex()

得到

415741564989cf4180c7154981e7ff0000004983e70f4989ce4981e6ff00000049c1fe044983e60f49c1e7044d09fe4c89f004163416415e415f

丢到cstool反汇编

$ cstool x64 415741564989cf4180c7154981e7ff0000004983e70f4989ce4981e6ff00000049c1fe044983e60f49c1e7044d09fe4c89f004163416415e415f
 0  41 57                                            push       r15
 2  41 56                                            push       r14
 4  49 89 cf                                         mov        r15, rcx
 7  41 80 c7 15                                      add        r15b, 0x15
 b  49 81 e7 ff 00 00 00                             and        r15, 0xff
12  49 83 e7 0f                                      and        r15, 0xf
16  49 89 ce                                         mov        r14, rcx
19  49 81 e6 ff 00 00 00                             and        r14, 0xff
20  49 c1 fe 04                                      sar        r14, 4
24  49 83 e6 0f                                      and        r14, 0xf
28  49 c1 e7 04                                      shl        r15, 4
2c  4d 09 fe                                         or r14, r15
2f  4c 89 f0                                         mov        rax, r14
32  04 16                                            add        al, 0x16
34  34 16                                            xor        al, 0x16
36  41 5e                                            pop        r14
38  41 5f                                            pop        r15

rcx就是call时传入的第一个参数。代码逻辑还是很简单的,无非就是xor、add、交换高低4位blablabla...

照这个逻辑写个解密脚本

# idaapi.get_bytes(0x140005050, 8).hex()
# idaapi.get_bytes(0x1400050D8, 0x1c).hex()
data = bytes.fromhex('da3a6aca0baaba8a' + '9aafdf6a8aefafda9acf9f7fdfdfdfda8a6fdf6f8f6f6ada7faf6f2b')

buf = []
for b in data:
    b ^= 0x16
    b = (b - 0x16) & 0xff

    lo = b & 0xf
    hi = (b >> 4) & 0xf
    b = (lo << 4) | ((hi - 0x15) & 0xf)

    buf.append(b)

flag = bytes(buf)
print(flag[:8] + b'hi' + flag[8:])

即得到flag{edchib56ac95fb720666fc16131af051}

雪    币: 267
活跃值: (620)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
hypersine 2023-11-5 14:49
3
0

第二个题目re1203.exe,关键部分在sub_14001105A函数。严格意义上讲,这个函数只是个stub,真正的本体在sub_140011FE5。

这个函数很明显是加花了,将跳转改为动态计算,以此干扰ida。好在这个函数不长,人工看可以看懂:

.text:140011FE5 sub_140011FE5   proc near               ; CODE XREF: sub_14001105A↑j
.text:140011FE5                 push    rbp
.text:140011FE6                 mov     rbp, rsp
.text:140011FE9                 mov     rax, gs:60h   # 取得PEB指针
.text:140011FF2                 sub     rsp, 20h
.text:140011FF6                 push    rbx
.text:140011FF7                 push    r9
.text:140011FF9                 push    r10
.text:140011FFB                 push    r8
.text:140011FFD                 call    $+5        # 这里一处加花,
                                                   # 理解代码可知下面的retn会跳到0x140012002+0xA=0x14001200C处
.text:140012002
.text:140012002 loc_140012002:                          ; DATA XREF: sub_140011FE5+1F↓o
.text:140012002                 pop     r8
.text:140012004                 add     r8, 0Ah
.text:140012008                 push    r8
.text:14001200A                 retn
.text:14001200A sub_140011FE5   endp ; sp-analysis failed
.text:14001200A
.text:14001200A ; ---------------------------------------------------------------------------
.text:14001200B                 db 0EBh
.text:14001200C ; ---------------------------------------------------------------------------
.text:14001200C                 movzx   rax, byte ptr [rax+2]  # rax是peb指针,rax+2是peb.BeingDebugged
                                                                      # 所以这里检测了是否有调试器
.text:140012011                 pop     r8
.text:140012013                 mov     rbx, rcx        # 函数第一个参数放在rbx
.text:140012016                 push    r8
.text:140012018                 call    loc_140012020    # 又一处加花,跳到下面的loc_140012020处
.text:14001201D                 retn
.text:14001201D ; ---------------------------------------------------------------------------
.text:14001201E                 dw 0C3E9h
.text:140012020 ; ---------------------------------------------------------------------------
.text:140012020
.text:140012020 loc_140012020:                          ; CODE XREF: .text:140012018↑p
.text:140012020                 pop     r8         # 通过计算,在下面的0x140012028处跳到0x14001201D+0xD=0x14001202A
.text:140012022                 add     r8, 0Dh
.text:140012026                 push    r8
.text:140012028                 retn
.text:140012028 ; ---------------------------------------------------------------------------
.text:140012029                 db 0EBh
.text:14001202A ; ---------------------------------------------------------------------------
.text:14001202A                 pop     r8
.text:14001202C                 mov     r9, rdx        # 函数第二个参数放在r9
.text:14001202F                 jmp     short loc_140012035
.text:14001202F ; ---------------------------------------------------------------------------
.text:140012031                 db  48h ; H
.text:140012032                 db  83h
.text:140012033                 db 0C0h
.text:140012034                 db    2
.text:140012035 ; ---------------------------------------------------------------------------
.text:140012035
.text:140012035 loc_140012035:                          ; CODE XREF: .text:14001202F↑j
.text:140012035                 cmp     rax, 1              # rax是前面读取的peb.BeingDebugged
.text:140012039                 jz      short loc_140012059 # 有调试的情况下跳到loc_140012059

# ===========================以下才是真正的代码逻辑=======================

.text:14001203B                 xor     r10, r10
.text:14001203E
.text:14001203E loc_14001203E:                          ; CODE XREF: .text:140012057↓j
.text:14001203E                 cmp     r10, r9
.text:140012041                 jnb     short loc_140012077
.text:140012043                 movzx   rax, byte ptr [rbx+r10] # rbx在前面说了,存放了函数的第一个参数
                                                                # r10是下标,从0一直到rbx指向字符串的长度
.text:140012048                 add     rax, 1          # 取1个字符,加1
.text:14001204C                 xor     rax, 10h        # 异或0x10
.text:140012050                 mov     [rbx+r10], al   # 放回原地
.text:140012054                 inc     r10
.text:140012057                 jmp     short loc_14001203E
.text:140012059 ; ---------------------------------------------------------------------------
.text:140012059

# ===========================以上才是真正的代码逻辑===========================
# ===========================     下面不用看了    ===========================

.text:140012059 loc_140012059:                          ; CODE XREF: .text:140012039↑j
.text:140012059                 xor     r10, r10
.text:14001205C
.text:14001205C loc_14001205C:                          ; CODE XREF: .text:140012075↓j
.text:14001205C                 cmp     r10, r9
.text:14001205F                 jnb     short loc_140012077
.text:140012061                 movzx   rax, byte ptr [rbx+r10]
.text:140012066                 add     rax, 2
.text:14001206A                 xor     rax, 10h
.text:14001206E                 mov     [rbx+r10], al
.text:140012072                 inc     r10
.text:140012075                 jmp     short loc_14001205C
.text:140012077 ; ---------------------------------------------------------------------------
.text:140012077
.text:140012077 loc_140012077:                          ; CODE XREF: .text:140012041↑j
.text:140012077                                         ; .text:14001205F↑j
.text:140012077                 pop     r10
.text:140012079                 pop     r9
.text:14001207B                 pop     rbx
.text:14001207C                 leave
.text:14001207D                 retn

从中可以看到对函数的逻辑就是对字符串原地加1再xor。

那么再结合外部函数的f5伪代码,就可以写出解密脚本:

import itertools

key = b'High_Ground'
key = bytes((b + 1) ^ 0x10 for b in key)

# idaapi.get_bytes(0x14001E000, 38).hex()
data = bytes.fromhex('3f16191e0b6b01515e464261191e41146d5554514c136d4d4140436d0156574d476c4f1a490d')

print(bytes(a ^ b for a, b in zip(data, itertools.cycle(key))))

得到flag{3b18978cf8d56473f479935b612255b0}

最后于 2023-11-5 14:51 被hypersine编辑 ,原因:
雪    币: 2655
活跃值: (158)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
慕容溯雪 2023-11-6 11:34
4
0
非常感谢@hypersine 解答
游客
登录 | 注册 方可回帖
返回