某道RE题分析

作者:太空独角兽    发布于:

找学姐要的的一道RE题,折磨了笔者两天(笔者菜鸡一枚)。最后写程序debug快吐了。可见编程能力能走多远RE就能走多远

#分析文件

查看文件格式

$ file barely_reversible
barely_reversible: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, stripped

是一个64位的ELF文件,拖入64位IDA进行分析

根据文字交叉索引找到关键函数位置,一键生成代码。根据分析可知道关键的函数为sub_1046sub_D16

 sub_11A1("Input flag:", a2, &v7);             // 输入
 sub_1046(&v7, 64, (__int64)&unk_203020);
 if ( sub_D16((__int64)&v7, 64LL) )            // 验证
  {
    v3 = "Correct";
    sub_11A1("Correct", 64LL, v2);
  }

本以为sub_1046会对输入的数据进行处理,然而通过动态跟进发现里面一大堆东西都没有用对校验没有影响,校验函数sub_D16依旧取得原始输入。
当然动态跟进需要花费耐心和时间。(觉得出题人就是想给人心里压力:P

那么分析得重点就是校验函数了

#深入校验函数

bool __fastcall sub_D16(__int64 flag, __int64 len)
{
  signed int i; // [rsp+1Ch] [rbp-4h]

  for ( i = 0; i <= 63; ++i )                   // 输入非负
  {
    if ( *(_BYTE *)(i + flag) < 0 )
      return 0;                                 // unk_1DA0是最终比对数据
                                                // 
  }
  sub_A83(flag, len);
  return (unsigned int)check(flag, (__int64)&unk_1DA0, 64LL) == 0;

显而易见根据原始输入计算校验数的函数是sub_A83,一进来看到大量的循环。

将大循环for ( i = 0; i <= 7; ++i )中的内容其拆分5为部分,其中mark的初始值为0x4E65534532303138f_add指向`flag

1.

for ( j = 0; j <= 7; ++j )                  // 8
  v16[j] = sub_859(8 * j + f_add);

sub_859对每8个数进行移位拼接成64位有符号数

2.

for ( k = 0; k <= 7; ++k )                  // 8
      v16[k] = sub_A01(v16[k], mark++);

sub_A01对64位有符号数进行处理

3.

for ( l = 0; l <= 9; ++l )
    {
      for ( m = 0; m <= 3; ++m )
      {
        v2 = v16[2 * m + 1];
        v3 = sub_A01(v16[2 * m], mark);
        v16[2 * m] = v2;
        v16[2 * m + 1] = sub_A01(v3, v2);       // f(f(v[2*M],mark),v[2*m+1])
        ++mark;
      }
    }

v16中前后两个64位有符号数进行二次处理后交换,如此10次

4.

for ( n = 0; n <= 7; ++n )
    {
      for ( ii = 0; ii <= 7; ++ii )
        *(_BYTE *)(8 * (ii ^ n) + ii + f_add) = ((unsigned __int64)v16[n] >> 7 * (unsigned __int8)ii) & 0x7F;// f_add
    }

单个bit处理

5.

mark += sub_7D1(mark, v15, 0x5F5E100);

最后对mark进行处理

sub_A01->sub_7D1->sub_72D的分析:

sub_A01:

中有一些很坑的点,值得好好分析

__int64 __fastcall sub_A01(__int64 temp, __int64 mark)
{
  __int64 rdtsc_v2; // ST18_8
  __int64 v3; // ST20_8
  __int64 v4; // ST28_8
  __int64 v5; // rbx
  __int64 v6; // rax

  rdtsc_v2 = _rdtsc();
  v3 = sub_99C(temp, mark);
  v4 = sub_9EB(v3, mark);
  v5 = sub_7D1(mark, rdtsc_v2, 0x5F5E100);
  v6 = sub_944(v4);
  return sub_99C(v6, v5);
}

可以看到这个东西跟俄罗斯套娃似的,里三层外三层。可仔细看整个函数简化为:

v3=(0x59D39E717F7 * m2 + 0x35E55F10E61 * temp_v - 0xC40BF11DDFCD22E) & 0xFFFFFFFFFFFFFF
v4=v3^m2
v5=sub_7D1(mark, rdtsc_v2, 0x5F5E100);
v6=(v4 >> 48) ^ (v4 >> 40) ^ (v4 >> 32) ^ (v4 >> 24) ^ v4 ^ (v4 >> 8) ^ (v4 >> 16) ^ (v4 >> 56)
#        1-2        1 -3         1 -4         1-5     1-8     1-7         1 -6            1
return (0x59D39E717F7 * v5 + 0x35E55F10E61 * v6 - 0xC40BF11DDFCD22E) & 0xFFFFFFFFFFFFFF

值得注意这里也用了sub_7D1,观察它

sub_7D1

__int64 __fastcall sub_7D1(int mark, __int64 v2, int a3)
{
  int v3; // ST08_4
  unsigned __int64 v4; // rax

  v3 = a3;                                      // 0x5F5E100
  v4 = _rdtsc();
  return sub_72D((unsigned int)((v4 - v2) / v3) + mark);// (v4-v2)/0x5f5e100 < 0 故为0
}

原来它也是一个俄罗斯套娃,可以看到从sub_A01传进来的参数rdtsc_v2 = _rdtsc();终于通过层层包裹到达了使用它的地方。((v4 - v2) / v3)

_rdtsc() 指令返回的是自开机始CPU的周期数

这里笔者在动态单步调试时发现这个即使mark相同的sub_72D传入的参数依旧时不同,纠结了很久。这个东西明显是跟最终的flag是无关的才对。后来经过学姐点拨才恍然大悟:真正程序执行的时候这一部分为0,而动态调试的时候其中的时间会增大相减大于被除数0x5F5E100,使之无法归为0.

这个地方也让我思考了以下反调试软件的编写,可以向程序中加入类似得到期间运行时间的代码来干扰调试

sub_72D

sub_72D中最终的函数是读取文件的一个函数sub_650

__int64 __fastcall sub_650(__int64 retaddr, __int16 mark)
{
  return *(unsigned int *)(4LL * ((mark + (_WORD)retaddr) & 0x3FF) + (retaddr & 0xFFFFFFFFFFFFF000LL));// 读取内存某一位置
                                                // retaddr&0x3ff=8
                                            // (8+mark&3ff)<<2+基址
}

retaddr & 0xFFFFFFFFFFFFF000获得的是整个程序在运行时的基址

#逆向

整个程序的流程已分析完毕,接下来的逆向程序编写才是考验的人的时候。

笔者认为其中最难的是对有符号数无符号数的的处理,以及对不同大小的整型的处理。分析只是对它有个大概的了解,从这里开始要求真正理解完全这个程序。

import ctypes
import struct
import gmpy2


def tosigned_long(var): #有符号长整型
    return var & ((1 << 64) - 1)
    # return ctypes.c_long(var).value

#sub_7d1
def get_mark(var):
    offset=(8+var & 0x3ff)*4
    return struct.unpack('<i',content[offset:offset+4])[0] #读取指定位置

#SUB_72D
def so_3( var ):

    t1 = get_mark(var)
    t2 = get_mark(2 + var)
    t3 = get_mark(2 * var)
    t4 = get_mark(3 * var)
    t5 = get_mark((var + 13))
    return tosigned_long((t3 * t4 ^ ((t2 + t1) << 32)) ^ t5)

#sub_a01的逆
def so_2(retval , markval):
    # 正向
    # 注意:数据类型皆为__int64
    # v3=(0x59D39E717F7 * m2 + 0x35E55F10E61 * temp_v - 0xC40BF11DDFCD22E) & 0xFFFFFFFFFFFFFF
    # v4=v3^m2
    # v5=get_mark(m2)
    # v6=(v4 >> 48) ^ (v4 >> 40) ^ (v4 >> 32) ^ (v4 >> 24) ^ v4 ^ (v4 >> 8) ^ (v4 >> 16) ^ (v4 >> 56)
    # #        1-2        1 -3         1 -4         1-5     1-8     1-7         1 -6            1
    # return (0x59D39E717F7 * v5 + 0x35E55F10E61 * v6 - 0xC40BF11DDFCD22E) & 0xFFFFFFFFFFFFFF

    #逆
    temp1=tosigned_long(retval+0xC40BF11DDFCD22E)

    v5=so_3(markval)
    temp2=tosigned_long(v5*0x59D39E717F7)& 0xFFFFFFFFFFFFFF
    t1 = tosigned_long(tosigned_long(temp1-temp2)%(1<<56))& 0xFFFFFFFFFFFFFF #保留56位(数字)
    t2 = gmpy2.invert(0x35E55F10E61,(1<<56))   #求逆元
    v6 = (tosigned_long(t1*t2) & 0xFFFFFFFFFFFFFF)
    mark_high=markval& 0xff00000000000000
    v6=v6| mark_high #v6=数字加上了符号位

    tmp=[]
    for i in range(7,-1,-1):
        tmp.append((v6>>(8*i))&0xff)

    g=[0]*8
    g[0]=tmp[0]
    v4=g[0]<<56

    for n in range(8):
        g[n] = tmp[n]
        for j in range(n):
            g[n]^=g[j]
        v4|=g[n]<<((8-n-1)*8)


    v3=tosigned_long(tosigned_long(v4)^tosigned_long(markval))
    temp3=tosigned_long(v3+0xC40BF11DDFCD22E)
    temp4=tosigned_long(markval)*0x59D39E717F7
    t3=(temp3-temp4)%(1<<56)
    fin= tosigned_long(t3*tosigned_long(t2)) & 0xffffffffffffff

    return fin





if __name__=="__main__":
    # 读取整个文件
    binary = open("./barely_reversible", "rb")
    content = binary.read()
    binary.close()

    # 读取最终校验值
    fp = open("./barely_reversible", "rb")
    fp.seek(0x1da0)
    content2 = fp.read(64)
    start_input = list(content2)  # 读入最终结果
    print(start_input)

    fin_input = [0] * 64  # 正确的序列
    # 标记
    mark = 0x4E65534532303138
    mark_array = []

    # 标记还原
    for i in range(8):
        for i in range(8):
            mark += 1

        for i in range(10):
            for i in range(4):
                mark += 1

        mark_array.append(tosigned_long(mark))


        if i == 7: break  # 此时无后续,计算mark无意义

        mark +=so_3(mark)

    print(mark_array)
    #主循环
    # temp_list=[0]*8
    for i in range(8):

        temp_list = [0]*8
        #第一步
        for n in range(8):

            for ii in range(8):
                shift=7*ii
                temp_list[n]= temp_list[n] | (start_input[8*(ii^n)+ii]<<shift)
                # print(temp_list[n])
        #第二步
        marker = mark_array[-1-i]-4
        for l in range(10):

            for m in range(4):

                temp = so_2(temp_list[2*m+1],temp_list[2*m])
                former = so_2(temp,marker)
                temp_list[2 * m + 1] = temp_list[2*m]
                temp_list[2*m] = former
                # print(temp_list[2 * m + 1])
                # print(temp_list[2 * m])
                marker += 1
            marker -= 8   #-4-4,回到上上的mark
        #     for i in temp_list:
        #         print(hex(i),end=" ")
        #     print("\n")
        #
        for i in temp_list:
            print(hex(i))
        #第三步
        marker-=4
        for k in range(8):
            temp_list[k]=so_2(temp_list[k],marker)
            marker+=1

        #第三步

        for j in range(8):
            start_input[j * 8]=(temp_list[j] & (0x7f << 28) ) >> 28     #取7位
            start_input[j * 8+1] = (temp_list[j] & (0x7f << 35)) >> 35
            start_input[j * 8+2] = (temp_list[j] & (0x7f << 42)) >> 42
            start_input[j * 8+3] = (temp_list[j] & (0x7f << 49)) >> 49
            start_input[j * 8+4] = (temp_list[j] & 0x7f)
            start_input[j * 8+5] = (temp_list[j] & (0x7f << 7)) >> 7
            start_input[j * 8+6] = (temp_list[j] & (0x7f << 14)) >> 14
            start_input[j * 8+7] = (temp_list[j] & (0x7f << 21)) >> 21

    #输出结果
    for i in start_input:
        print(chr(i),end='')

还没写完,先过个周末再说:)

format_list_numbered

(无)

  1. 1. #分析文件
  2. 2. #深入校验函数
    1. 2.1. sub_A01:
    2. 2.2. sub_7D1
    3. 2.3. sub_72D
  3. 3. #逆向
vertical_align_top

Copyright © 2018 太空独角兽