首页
社区
课程
招聘
[原创]街机少年
2019-12-7 00:04 3631

[原创]街机少年

2019-12-7 00:04
3631

XYLearn tqllllll ! 7feilee tqllllll !
应该是非预期了
IDA载入程序,看到主程序:

  if ( get_input() )
  {
    SetUnhandledExceptionFilter(TopLevelExceptionFilter);
    if ( GAME((unsigned __int8 *)name, name_len) )
      output("You Win! You are very clever!");
    else
      output("Game Over");
    system("pause");
    result = 0;
  }
  else
  {
    output("Game Over");
    system("pause");
    result = 0;
  }

程序读入name和serial,而后走过一个判断,通过GAME函数返回值来判断是否正确
首先读入name和serial:

  for ( i = 0; i < 32; ++i )
  {
    v4 = read_input();
    if ( (v4 < 97 || v4 > 122) && (v4 < 65 || v4 > 90) && (v4 < 48 || v4 > 57) )
    {
      if ( v4 != 13 && v4 != 10 )
        return 0;
      name_len = i;
      break;
    }
    name[i] = v4;
  }
  if ( name_len < 4 || name_len > 16 )
    return 0;
  output("Please input your KEY: ( : to z )");
  for ( j = 0; j < 128; ++j )
  {
    v3 = read_input();
    if ( v3 < ':' || v3 > 'z' )
    {
      if ( v3 != 13 && v3 != 10 )
        return 0;
      serial_len = j;
      break;
    }
    serial[j] = v3;
  }
  if ( !serial_len || serial_len > 128 )
    return 0;
  dec_len = dec1((int)decode_serial, (int)serial, serial_len);

简单判断name和serial合法后decode并存入全局变量数组
decode类似一个base64,只是对应处理位不同,直接对照求逆即可:

def dec1(str1):
    ans=[]
    for i in range(len(str1)/3):
        ans.append(ord(str1[i*3])>>2)
        ans.append(((ord(str1[i*3])&0b11)<<4)+((ord(str1[i*3+1])&0b11110000)>>4))
        ans.append((ord(str1[i*3+1])&0b1111)+((ord(str1[i*3+2])&0b11000000)>>2))
        ans.append(ord(str1[i*3+2])&0b00111111)
    s=""
    for i in ans:
        s+=chr(i+ord(":"))
    return s
#后面不足对应相同位置并补'z'即可

在GAME中,首先使用大量数组查表隐藏了算法和计算过程
首先将第一轮table扣下来:

def func1(x, m):
    s = x
    none_primitive_root = 0
    for i in range(1, m):
        y = min(x, s)
        z = max(x, s)
        s = y * y % 17 ^ triangle_at(xor_table, y, z)
......

根据返回值发现是一个乘方运算(取模意义下),计算pow(x,m)
故而在第一层:

   for ( i = 1; i < 16; ++i )
    {
      v35 = v38;
      v27 = v51;
      if ( v38 > (signed int)v51 )
      {
        v35 = v51;                              // 乘方
        v27 = v38;
      }
      v38 = v35 * v35 % 17 ^ table[(unsigned __int8)(v27 + (v35 - 1) * (34 - v35) / 2 - v35)];
      if ( v38 == 1 && i != 15 )  // (p-1)/2不满足 
      {
        v51 = (v51 + 1) % 16 + 1;  
        v38 = 0;
        break;
      }
    }

其实际计算的是相对v51步长2的模17下最近的非二次剩余
而后其根据输入name初始化了一个256长度数组(前16参与运算)
而后大致流程可以取下:

from base64 import b64decode,b64encode
import os
from functools import reduce

xor_table = [0,3,2,5,4,7,6,9,8,11,10,13,12,15,14,17,0,2,12,14,8,10,20,5,7,1,3,13,15,9,11,0,5,6,8,13,14,3,4,25,11,12,1,2,7,0,19,23,27,31,18,22,26,30,17,21,25,29,0,5,9,14,3,24,12,1,6,10,15,4,0,10,12,1,11,13,6,8,18,7,9,0,10,3,13,6,31,9,2,12,5,0,9,1,14,6,15,7,12,4,0,8,3,11,2,10,29,5,0,7,14,4,11,1,8,0,15,5,3,14,4,0,11,7,2,13,0,28,24,20,0,15,10,0,6,0]


with open('E:/Totoro.exe', 'rb') as f:
    f.seek(0x16088, os.SEEK_SET)
    bytes_397288 = f.read(65536)
    bytes_397288 = [i for i in bytes_397288]
    f.seek(0x26088, os.SEEK_SET)
    bytes_3A7288 = f.read(256)
    bytes_3A7288 = [i for i in bytes_3A7288]
    f.seek(0x14E88, os.SEEK_SET)
    bytes_396088 = f.read(256)
    bytes_396088 = [i for i in bytes_396088]
    f.seek(0x14F88, os.SEEK_SET)
    bytes_396188 = f.read(4096)
    bytes_396188 = [i for i in bytes_396188]
    f.seek(0x15F88, os.SEEK_SET)
    bytes_417188 = f.read(256)
    bytes_417188 = [i for i in bytes_417188]

def quadratic_residues(x, m):
    while True:
        s = x
        for i in range(1, m):
            y = s
            z = x
            if s > x:
                y = x
                z = s
            idx = (z + (y - 1) * (34 - y) // 2 - y)%256
            s = (y * y % 17) ^ xor_table[idx]
            if s == 1 and i != 15:
                x = (x + 1) % 16 + 1
                s = 0
                break
        if s == 1:
            break
    return x

def _pow(x, m):
    s = x
    global flag
    for i in range(1, m):
        y = s
        z = x
        if s > x:
            y = x
            z = s
        idx = (z + (y - 1) * (34 - y) // 2 - y)%256
        s = (y * y % 17) ^ xor_table[idx]
        if s == 1 and i != 15:
            flag = 1
    return s

def triangle_index(y, z):
    return z + (y - 1) * (34 - y) // 2 - y

def triangle_at(mat, y, z):
    return mat[triangle_index(y, z)]

def get_inv(x):
    while True:
        s = x
        for i in range(1, 16):
            y = min(x, s)
            z = max(x, s)
            s = y * y % 17 ^ triangle_at(xor_table, y, z)
            if s == 1 and i != 16 - 1:
                x = (x + 1) % 16 + 1
                s = 0
                break
        if s == 1:
            break
    return x

def pows(x, m):
    s = x
    none_primitive_root = 0
    for i in range(1, m):
        y = min(x, s)
        z = max(x, s)
        s = y * y % 17 ^ triangle_at(xor_table, y, z)
        if s == 1 and i != m-1:
            none_primitive_root = 1
    return s,none_primitive_root

def map_username(username,bytes_417188):
    for i in range(len(username) - 1):
        ch = username[i + 1]
        if i >= 7:
            if i >= 10:
                if i >= 13:
                    bytes_417188[i] = ch % 8 + 8
                else:
                    bytes_417188[i] = 7 - ch % 7
            else:
                bytes_417188[i] = ch % 8 + 8
        else:
            bytes_417188[i] = 7 - ch % 7
    return bytes_417188



def get_idx(a, b, c):
    lo = ((b >> 2) ^ ((c << 4) & 0xC0)) ^ ((a << 2) & 0x3C) ^ 0xC7
    hi = (c & 3) ^ ((b << 2) & 0xf) ^ 2
    x = lo | (hi << 8)
    return x


def unknown(a, b, c):
    '''a^b^c'''
    return bytes_397288[((a + b + c) % 16) | (a << 4) | (b << 8) | (c << 12)]


def check(username, key):
   #key=  [61, 136, 200, 67, 168, 249, 66, 246, 42, 33, 212, 253, 243, 246, 246, 244, 246, 249, 244, 255, 237]
    key_len = len(key)
    ch = username[0] % 16 + 1
    username = map_username(username,bytes_417188)
    primitive_root = quadratic_residues(ch,16)
    kidx = 0
    x = 5
    while kidx < key_len:
        key_low = key[kidx] % 16 + 1
        key_high = (key[kidx]>>4) % 16 + 1
        flag = 1
        if primitive_root and key_low <= 16 and key_high <=16:
            flag = 0
            key_low = _pow(primitive_root, key_low)
            r2 = quadratic_residues(key_low,16)
            key_high = _pow(r2, key_high) 
            if flag:
                break
        primitive_root = quadratic_residues(key_high,16)
        primitive_flag = (key_high * bytes_396088[username[key_low - 1] - key_high])&0xff
        for i in range(key_low, 128):
            v4 = int(username[i] == 0)
            v5 = (((v4 + bytes_396188[get_idx(username[key_low - 1], username[i], key_high)])%256) * primitive_flag)&0xff
            primitive_flag = (((v5 + 1) & int(v5 != 0)) + v5)&0xff
        username[key_low - 1] = (username[key_low - 1] + ((((2 * ((primitive_flag + 1) & 1) - 1)&0xff) * key_high)&0xff))&0xff
        #print("test xor is {}".format(reduce(lambda x, y: x ^ y, username)))
        if not primitive_flag:
            break
        if not any(username):
            return kidx == key_len - 1
        key_low_ = (key[kidx] >> 4) % 16 + 1
        key_high_ = key[kidx + 1] % 16 + 1
        bytes_396088[key_low_ // 2] = (bytes_396088[key_low_ // 2] + key_high_) % 127 + 1
        kidx += 1
        tmp = unknown(username[2], username[1], username[0])
        tmp = unknown(username[4], username[3], tmp)
        tmp = unknown(username[6], username[5], tmp)
        tmp = unknown(username[8], username[7], tmp)
        tmp = unknown(username[10], username[9], tmp)
        tmp = unknown(username[12], username[11], tmp)
        tmp = unknown(username[14], username[13], tmp)
        if tmp:
            for i in range(15):
                val = bytes_3A7288[tmp | (username[i] << 4)]
                if val & 1:
                    username[i] = val >> 1
                    break
        else:
            print(kidx)
            fflag = 0
            while True:
                if username[x] == 1:
                    username[x] = (username[x] - 1)%256
                    x = (x + 4) % 15
                    fflag = 1
                    break
                if username[x] > 1:
                    break
                x = (x + 4) % 15
            if fflag == 0:
                username[x] = username[x] - ((x * x + 1) % username[x] + 1)
                x = (x + 4) % 15
        if not reduce(lambda x, y: x | y, username):
            break
    return False

在循环过程中,每轮12bit key参与运算,首先根据第一步结果进行乘方,再进行一次求非平方剩余,再进行一次乘方(不出现单位元),根据结果及一个数组操作更新一位username数组,而后进行一轮变换(unknown),根据变换结果更新一位username
通过观察数组及结果发现unknown操作是进行一组异或
若异或后不为0则依赖第二个数组将其每位异或结果调整为0
通过观察正确序列号发现:

正确退出应该是循环过每一位序列号经由最后: 判断username数组变换为0时 根据"循环次数==序列号长度-1"来return
在正确序列号变换下经由unknow变换的tmp值每轮都是0(即username数组每一位异或后为0)

其实到这里也没猜到具体的数学背景是什么,最开始肖神通过unknown中变换的数组搜索到AES的add field,不过后来发现只是一组异或,遂失败
不过从上面分析可以看到,每轮参与计算的只有12bit,而且通过正确序列号的程序流可以猜测到每一轮tmp=0即为控制条件,以此进行逐位爆破即可得到可能解
最后肖神用unicorn成功解出(tqlll !!!)(python爆破我写得有点问题没搞定(飞神那边应该快改好了,已经出了几位)):

import sys
import os
import struct
from base64 import b64decode, b64encode
from capstone import *
from capstone.x86_const import *
from unicorn import *
from unicorn.x86_const import *

def p32(n):
  return struct.pack("<I", n)
to = b'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
base = b':;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxy'
def base64_enc(data):
    data = b64encode(data)
    res = []
    for i in data:
        res.append(base[to.index(i)])
    return ("".join([chr(i) for i in res])).encode()
def base64_dec(data):
    res = []
    for i in data:
        res.append(to[base.index(i)])
    return b64decode("".join([chr(i) for i in res]).encode())

cs = Cs(CS_ARCH_X86, CS_MODE_32)

last_addr = 0
def hook_code(mu, addr, size, user_data):
  global last_addr
  last_addr = addr
  code = mu.mem_read(addr, 10)
  ins = next(cs.disasm(code, addr, 1))
  disasm = "{:08x}: {} {}".format(addr, ins.mnemonic, ins.op_str)
  print(disasm)

mu = None
with open("./Totoro.exe", 'rb') as f:
    f.seek(0x400, os.SEEK_SET)
    text = f.read(0xf13b - 0x400)
    f.seek(0xf320, os.SEEK_SET)
    rdata = f.read(0x14c68 - 0xf320)
    f.seek(0x14E00, os.SEEK_SET)
    data = f.read(0x26C00-0x14E00)
def check(username, key, addr_points=None):
  global mu
  mu = Uc(UC_ARCH_X86, UC_MODE_32)
  text_start , text_end  = 0x401000, 0x410000
  idata_start, idata_end = 0x410000, 0x410120
  rdata_start, rdata_end = 0x410120, 0x416000
  data_start , data_end  = 0x416000, 0x429000
  stack_start, stack_end = 0x7ffa0000, 0x80000000
  cache_start, cache_end = 0x100000, 0x110000
  stack_curr = 0x7fff0000
  mu.mem_map(text_start, data_end - text_start)
  mu.mem_write(text_start, text)
  mu.mem_write(rdata_start, rdata)
  mu.mem_write(data_start, data)
  mu.mem_map(stack_start, stack_end - stack_start)
  mu.mem_map(cache_start, cache_end - cache_start)
  mu.reg_write(UC_X86_REG_ESP, stack_curr)
  # key
  key_addr = 0x427E38
  key_len_addr = 0x427F38
  mu.mem_write(key_addr, key)
  mu.mem_write(key_addr + len(key), b'\0' * (256 - len(key)))
  mu.mem_write(key_len_addr, p32(len(key)))
  # username
  mu.mem_write(stack_curr + 4, p32(cache_start))
  mu.mem_write(cache_start, username)
  mu.mem_write(stack_curr + 8, p32(len(username)))
  # hook
  # mu.hook_add(UC_HOOK_CODE, hook_code)
  # execute
  check_addr = 0x401480
  check_ret_addr = 0x402088
  try:
    if not addr_points:
      mu.emu_start(check_addr, check_ret_addr)
    else:
      start = check_addr
      for addr in addr_points:
        mu.emu_start(start, addr)
        mu.emu_start(addr, 0, count=1)
        start = mu.reg_read(UC_X86_REG_EIP)
  except UcError as e:
    return False
  return True

username = b'KCTF'
key = [0 for i in range(0x20)]

# key = [61, 136, 200, 67, 168, 249, 66, 246, 42, 33, 212, 253, 243, 246, 246, 244, 246, 249, 244, 255, 237, 249]
end = False
for i in range(21):
  for ch in range(256):
    key[i] = ch
    if not check(username, bytes(key), [0x401F00] * (i+1)):
      continue
    if mu.reg_read(UC_X86_REG_EAX) == 0:
      print(i, ch)
      break
    key[i] = 0x100
  if key[i] == 0x100:
    break
i += 1
for ch in range(256):
  key[i] = ch
  if not check(username, bytes(key)):
    continue
  if mu.reg_read(UC_X86_REG_EAX) == 0:
    print(i, ch)
    break
print(key)
#Output:[61, 136, 200, 67, 168, 249, 66, 246, 42, 33, 212, 253, 243, 246, 246, 244, 246, 249, 244, 255, 237, 249]
-------------------------------------------------------------------------------
def dec1(str1):
    ans=[]
    for i in range(len(str1)/3):
        ans.append(ord(str1[i*3])>>2)
        ans.append(((ord(str1[i*3])&0b11)<<4)+((ord(str1[i*3+1])&0b11110000)>>4))
        ans.append((ord(str1[i*3+1])&0b1111)+((ord(str1[i*3+2])&0b11000000)>>2))
        ans.append(ord(str1[i*3+2])&0b00111111)
    ans.append(ord(str1[21])>>2)
    ans.append((ord(str1[21])&0b11)<<4)
    s=""
    for i in ans:
        s+=chr(i+ord(":"))
    return s+"zz"
s=""
final_key=[61, 136, 200, 67, 168, 249, 66, 246, 42, 33, 212, 253, 243, 246, 246, 244, 246, 249, 244, 255, 237, 249]
for i in final_key:
    s+=chr(i)
print dec1(s)
#Output:IRrBJtrsJi@dBWnwvyppwIpswIygxJzz

[培训]《安卓高级研修班(网课)》月薪三万计划,掌握调试、分析还原ollvm、vmp的方法,定制art虚拟机自动化脱壳的方法

最后于 2019-12-7 00:07 被梅零落编辑 ,原因:
收藏
点赞3
打赏
分享
最新回复 (0)
游客
登录 | 注册 方可回帖
返回