Flare-On4 解题复现

01

是一个 html 页面, 用开发者工具看看,发现是简单的 js 加密。

猜测加密算法可逆,试着用 PyvragFvqrYbtvafNerRnfl@syner-ba.pbz 作为输入,然后调试 ,得到 flagClientSideLoginsAreEasy@flare-on.com

02

程序逻辑如下

首先 获取输入, 然后 调用 check 进行判断, 下面分析 check 函数

通过异或操作加密我们的输入, 首先获取一个固定的初始 key , 后面每一步 key 从输入中取,获取到密文后就和 程序中已有的密文做对比。

那么 flag 应该就是程序里面那段密文解密后的字符串, 对加密算法求反,写出解密的 idapython 脚本

import idc

encoded_data = get_bytes(0x403000, 0x27)

key = 0x4
flag = ""

i = 0x26

while i >= 0:
    key = ord(encoded_data[i])^key
    flag = chr(key) + flag
    i = i - 1

print flag

由于 key 是输入中来的,所以这里的 key 应该是解密后的数据。得出 flag

R_y0u_H0t_3n0ugH_t0_1gn1t3@flare-on.com

03

分析

程序首先监听 2222 端口,然后接收 4 个字节

然后用刚接收的 4 个字节的其中一个字节作为 key , 对 0x40107C 开始的 0x79 字节的代码进行解密,然后校验解密后的数据,校验成功继续执行,如果不成功则退出。

所以想要继续分析,首先得解出解密代码的 key , key 的大小为 1 个字节,255 中可能。爆破之即可。

解密

借助 unicorn

把解密逻辑用 python 实现, 然后把 校验解密结果的代码用 unicorn 模拟运行,然后整合一下爆破出正确的解密 key

import binascii
import struct
from unicorn import *
from unicorn.x86_const import *


def list_to_str(arr):
    res = ""
    for i in arr:
        res += chr(i)
    return res


verify_code = list_to_str([
    0x55, 0x8B, 0xEC, 0x51, 0x8B, 0x55, 0x0C, 0xB9, 0xFF, 0x00, 0x00, 0x00, 0x89, 0x4D, 0xFC, 0x85,
    0xD2, 0x74, 0x51, 0x53, 0x8B, 0x5D, 0x08, 0x56, 0x57, 0x6A, 0x14, 0x58, 0x66, 0x8B, 0x7D, 0xFC,
    0x3B, 0xD0, 0x8B, 0xF2, 0x0F, 0x47, 0xF0, 0x2B, 0xD6, 0x0F, 0xB6, 0x03, 0x66, 0x03, 0xF8, 0x66,
    0x89, 0x7D, 0xFC, 0x03, 0x4D, 0xFC, 0x43, 0x83, 0xEE, 0x01, 0x75, 0xED, 0x0F, 0xB6, 0x45, 0xFC,
    0x66, 0xC1, 0xEF, 0x08, 0x66, 0x03, 0xC7, 0x0F, 0xB7, 0xC0, 0x89, 0x45, 0xFC, 0x0F, 0xB6, 0xC1,
    0x66, 0xC1, 0xE9, 0x08, 0x66, 0x03, 0xC1, 0x0F, 0xB7, 0xC8, 0x6A, 0x14, 0x58, 0x85, 0xD2, 0x75,
    0xBB, 0x5F, 0x5E, 0x5B, 0x0F, 0xB6, 0x55, 0xFC, 0x8B, 0xC1, 0xC1, 0xE1, 0x08, 0x25, 0x00, 0xFF,
    0x00, 0x00, 0x03, 0xC1, 0x66, 0x8B, 0x4D, 0xFC, 0x66, 0xC1, 0xE9, 0x08, 0x66, 0x03, 0xD1, 0x66,
    0x0B, 0xC2, 0x8B, 0xE5, 0x5D
])

encoded_code = list_to_str(
    [0x33, 0xE1, 0xC4, 0x99, 0x11, 0x06, 0x81, 0x16, 0xF0, 0x32, 0x9F, 0xC4, 0x91, 0x17, 0x06, 0x81,
     0x14, 0xF0, 0x06, 0x81, 0x15, 0xF1, 0xC4, 0x91, 0x1A, 0x06, 0x81, 0x1B, 0xE2, 0x06, 0x81, 0x18,
     0xF2, 0x06, 0x81, 0x19, 0xF1, 0x06, 0x81, 0x1E, 0xF0, 0xC4, 0x99, 0x1F, 0xC4, 0x91, 0x1C, 0x06,
     0x81, 0x1D, 0xE6, 0x06, 0x81, 0x62, 0xEF, 0x06, 0x81, 0x63, 0xF2, 0x06, 0x81, 0x60, 0xE3, 0xC4,
     0x99, 0x61, 0x06, 0x81, 0x66, 0xBC, 0x06, 0x81, 0x67, 0xE6, 0x06, 0x81, 0x64, 0xE8, 0x06, 0x81,
     0x65, 0x9D, 0x06, 0x81, 0x6A, 0xF2, 0xC4, 0x99, 0x6B, 0x06, 0x81, 0x68, 0xA9, 0x06, 0x81, 0x69,
     0xEF, 0x06, 0x81, 0x6E, 0xEE, 0x06, 0x81, 0x6F, 0xAE, 0x06, 0x81, 0x6C, 0xE3, 0x06, 0x81, 0x6D,
     0xEF, 0x06, 0x81, 0x72, 0xE9, 0x06, 0x81, 0x73, 0x7C])


def decode_bytes(i):
    decoded_bytes = ""
    for byte in encoded_code:
        decoded_bytes += chr(((ord(byte) ^ i) + 0x22) & 0xFF)
    return decoded_bytes


def emulate_checksum(decoded_bytes):
    # establish memory addresses for checksum code, stack, and decoded bytes
    address = 0
    stack_addr = 0x10000
    dec_bytes_addr = 0x20000

    # write checksum code and decoded bytes into memory
    mu = Uc(UC_ARCH_X86, UC_MODE_32)
    mu.mem_map(address, 2 * 1024 * 1024)
    mu.mem_write(address, verify_code)
    mu.mem_write(dec_bytes_addr, decoded_bytes)
    # place the address of decoded bytes and size on the stack
    mu.reg_write(UC_X86_REG_ESP, stack_addr)
    mu.mem_write(stack_addr + 4, struct.pack('<I', dec_bytes_addr))   # arg1 , address
    mu.mem_write(stack_addr + 8, struct.pack('<I', 0x79))  # arg2 , len

    # emulate and read result in AX
    mu.emu_start(address, address + len(verify_code))
    checksum = mu.reg_read(UC_X86_REG_AX)
    return checksum


for i in range(256):
    checksum = emulate_checksum(decode_bytes(i))
    if checksum & 0xffff == 0xFB5E:
        print(hex(i))
        break`

其中 verify_code 不需要 ret 指令,因为我们只需要函数的返回值。

最后得到的 key0xa2, 然后在调试的时候,设置正常的 key,解密代码后发现是一段复制语句,调试 得到 flag`

flag

et_tu_brute_force@flare-on.com

借助 frida

# -*- coding:utf-8 -*-
from __future__ import print_function
import frida
from time import sleep
retval = 0
is_ret = 0


def list_to_str(arr):
    res = ""
    for i in arr:
        res += chr(i)
    return res

def str_to_list(string):
    res = []
    for i in string:
        res.append(ord(i))
    return res


encoded_code_array = [0x33, 0xE1, 0xC4, 0x99, 0x11, 0x06, 0x81, 0x16, 0xF0, 0x32, 0x9F, 0xC4, 0x91, 0x17, 0x06, 0x81,
                      0x14, 0xF0, 0x06, 0x81, 0x15, 0xF1, 0xC4, 0x91, 0x1A, 0x06, 0x81, 0x1B, 0xE2, 0x06, 0x81, 0x18,
                      0xF2, 0x06, 0x81, 0x19, 0xF1, 0x06, 0x81, 0x1E, 0xF0, 0xC4, 0x99, 0x1F, 0xC4, 0x91, 0x1C, 0x06,
                      0x81, 0x1D, 0xE6, 0x06, 0x81, 0x62, 0xEF, 0x06, 0x81, 0x63, 0xF2, 0x06, 0x81, 0x60, 0xE3, 0xC4,
                      0x99, 0x61, 0x06, 0x81, 0x66, 0xBC, 0x06, 0x81, 0x67, 0xE6, 0x06, 0x81, 0x64, 0xE8, 0x06, 0x81,
                      0x65, 0x9D, 0x06, 0x81, 0x6A, 0xF2, 0xC4, 0x99, 0x6B, 0x06, 0x81, 0x68, 0xA9, 0x06, 0x81, 0x69,
                      0xEF, 0x06, 0x81, 0x6E, 0xEE, 0x06, 0x81, 0x6F, 0xAE, 0x06, 0x81, 0x6C, 0xE3, 0x06, 0x81, 0x6D,
                      0xEF, 0x06, 0x81, 0x72, 0xE9, 0x06, 0x81, 0x73, 0x7C]

encoded_code = list_to_str(encoded_code_array)

def on_message(message, data):
    global retval,is_ret
    retval = message['payload']
    is_ret = 1


def decode_bytes(i):
    decoded_bytes = ""
    for byte in encoded_code:
        decoded_bytes += chr(((ord(byte) ^ i) + 0x22) & 0xFF)
    return decoded_bytes


def main():
    global retval, is_ret
    session = frida.attach("greek_to_me.exe")
    for i in range(256):
        script = session.create_script("""
            var verify_code = ptr('0x4011E6');
            var f = new NativeFunction(verify_code, 'int', ['pointer', 'int']);
            var save_address = ptr('0x40107C');
            Memory.writeByteArray(save_address, {})
            send(f(save_address, 121));
        """.format(str_to_list(decode_bytes(i))))
        script.on('message', on_message)
        script.load()

        while is_ret != 1:  # 等待远程函数执行完
            sleep(0.2)
        is_ret = 0

        if retval & 0xffff == 0xFB5E:
            print(hex(i))
            break

    session.detach()

if __name__ == '__main__':
    main()

每次解密code后,直接用 frida 调用进程里面的校验函数,通过这样可以爆破出 key

最后附一个导出光标所在函数的二进制代码的 idapython 脚本


import idaapi

def list_to_str(arr):
    res = ""
    for i in arr:
        res += chr(i)
    return res

def str_to_list(string):
    res = []
    for i in string:
        res.append(ord(i))
    return res

compiled_functions = {}
def ida_run_python_function(func_name):
    if func_name not in compiled_functions:
        ida_func_name = "py_%s" % func_name
        idaapi.CompileLine('static %s() { RunPythonStatement("%s()"); }' 
            % (ida_func_name, func_name))
        compiled_functions[func_name] = ida_func_name
    return ida_func_name



def GetFunctionCode():
    func_start = get_func_attr(here(), FUNCATTR_START)
    func_end = get_func_attr(here(), FUNCATTR_END)
    func_name = GetFunctionName(func_start)
    data = get_bytes(func_start, func_end - func_start)
    with open(func_name, "wb") as fp:
        fp.write(data)
    
    with open(func_name + ".list", "w") as fp:
        fp.write(str(str_to_list(data)))
    
    
    Message("Write code of %s done!!!\n" %(func_name))


AddHotkey("Ctrl+Shift+A", ida_run_python_function("GetFunctionCode"));

参考

http://blog.nsfocus.net/flare-onchallenge4th/

posted @ 2018-10-08 14:18  hac425  阅读(311)  评论(0编辑  收藏  举报