找学姐要的的一道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_1046
和sub_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的初始值为0x4E65534532303138
,·f_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='')
还没写完,先过个周末再说:)