首页
社区
课程
招聘
[原创]看雪CTF2019晋级赛Q1 第7题
2019-3-22 16:36 5333

[原创]看雪CTF2019晋级赛Q1 第7题

2019-3-22 16:36
5333
(写的比较多。。。。)
一、去掉混淆和程序修改检测                                           
1、程序加入了混淆代码,分析起来比较费劲,先把混淆bypass掉。代码如下:

void ByPassOb( void )
{
	char		box[1024];
	int		size;
	int		start		= 0x1000;
	int		end		= 0xBC000;
	unsigned char	jmp_code[16][4] =
	{
		0x7A, 0x03, 0x7B, 0x01,
		0x77, 0x03, 0x76, 0x01,
		0x71, 0x03, 0x70, 0x01,
		0x7C, 0x03, 0x7D, 0x01,
		0x70, 0x03, 0x71, 0x01,
		0x7F, 0x03, 0x7E, 0x01,
		0x78, 0x03, 0x79, 0x01,
		0x75, 0x03, 0x74, 0x01,
		0x7B, 0x03, 0x7A, 0x01,
		0x72, 0x03, 0x73, 0x01,
		0x7D, 0x03, 0x7C, 0x01,
		0x74, 0x03, 0x75, 0x01,
		0x76, 0x03, 0x77, 0x01,
		0x79, 0x03, 0x78, 0x01,
		0x7e, 0x03, 0x7f, 0x01,
		0x73, 0x03, 0x72, 0x01,
	};
	unsigned char	call_code1[5]	= { 0xE8, 0x02, 0x00, 0x00, 0x00 };
	unsigned char	call_code11[5]	= { 0xE8, 0x03, 0x00, 0x00, 0x00 };
	unsigned char	call_code12[5]	= { 0xE8, 0x01, 0x00, 0x00, 0x00 };
	unsigned char	call_code13[6]	= { 0x50, 0xE8, 0x01, 0x00, 0x00, 0x00 };
	unsigned char	call_code15[5]	= { 0xE8, 0x04, 0x00, 0x00, 0x00 };
	unsigned char	call_code2[3]	= { 0x83, 0xC4, 0x04 };
	unsigned char	call_code21[5]	= { 0x83, 0x04, 0x24, 0x06, 0xC3 };
	unsigned char	call_code23[2]	= { 0x58, 0x58 };
	unsigned char	call_code24[1]	= { 0x58 };
	unsigned char	call_code25[2]	= { 0xeb, 0x0c };
	unsigned char	jmp_code1[4]	= { 0x7c, 0x03, 0xeb, 0x03 };
	unsigned char	jmp_code2[2]	= { 0x74, 0xFB };
	unsigned char	stcjb_code[3]	= { 0xF9, 0x72, 0x02 };
	unsigned char	clcjnb_code[3]	= { 0xF8, 0x73, 0x02 };
	unsigned char	clcjnb_code1[3] = { 0xF8, 0x73, 0x01 };

	unsigned char	main_code[15]		= { 0xC6, 0x55, 0x5F, 0xF3, 0x00, 0xE8, 0x02, 0x00, 0x00, 0x00, 0xA2, 0x0C, 0x83, 0xC4, 0x04 };
	unsigned char	change_code[0x14]	= { 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90 };
	unsigned char	main_stack_code1[]	= { 0x83, 0xec, 0x0c };
	unsigned char	jmpEax_code[3]		= { 0x40, 0xEB, 0x01 };
	unsigned char	jmpEax_code2[2]		= { 0xFF, 0xE0 };

	unsigned char* buf = GetFileNameBuffer( "DancingCircle.exe", &size );

	/* bypass 随机数 */
	buf[0xF77]	= 0xEB;
	buf[0xF78]	= 0x47;

	/* by pass anti debug */
	buf[0xB8798]			= 0x33;
	buf[0xB8799]			= 0xc0;
	*(int *) (buf + 0xBB31D)	= 0x24230201;

	/* bp pass main开始4B8DFA地址的混淆代码 */
	memcpy( buf + 0x99f, change_code, 5 );

	/* 使部分函数F5 OK */
	memcpy( buf + 0xb81e8, main_stack_code1, 3 );
	memcpy( buf + 0xb81fa, change_code, 5 );

	int	findJmpCnt	= 0;
	int	findCallCnt	= 0;
	int	findMainCnt	= 0;
	int	findStcjbCnt	= 0;
	int	findClcjnbCnt	= 0;

	/* 分析代码段,bypass混淆代码 */
	for ( int i = start; i < end; i++ )
	{
		if ( 0 == memcmp( buf + i, main_code, 15 ) )
		{
			memcpy( buf + i, change_code, 15 );
			i = i + 14;
			findMainCnt++;
		}
		if ( 0 == memcmp( buf + i, stcjb_code, 3 ) )
		{
			memcpy( buf + i, change_code, 5 );
			i = i + 4;
			findStcjbCnt++;
		}
		if ( 0 == memcmp( buf + i, clcjnb_code, 3 ) )
		{
			memcpy( buf + i, change_code, 5 );
			i = i + 4;
			findClcjnbCnt++;
		}

		if ( 0 == memcmp( buf + i, clcjnb_code1, 3 ) )
		{
			memcpy( buf + i, change_code, 4 );
			i = i + 3;
			findClcjnbCnt++;
		}
		if ( 0 == memcmp( buf + i, call_code1, 5 ) )
		{
			if ( 0 == memcmp( buf + i + 7, call_code2, 3 ) )
			{
				memcpy( buf + i, change_code, 10 );
				i = i + 9;
				findCallCnt++;
				continue;
			}
		}

		if ( 0 == memcmp( buf + i, call_code11, 5 ) )
		{
			if ( 0 == memcmp( buf + i + 8, call_code2, 3 ) )
			{
				memcpy( buf + i, change_code, 11 );
				i = i + 10;
				findCallCnt++;
				continue;
			}
			if ( 0 == memcmp( buf + i + 8, call_code24, 1 ) )
			{
				memcpy( buf + i, change_code, 9 );
				i = i + 8;
				findCallCnt++;
				continue;
			}
		}
		if ( 0 == memcmp( buf + i, call_code12, 5 ) )
		{
			if ( 0 == memcmp( buf + i + 6, call_code2, 3 ) )
			{
				memcpy( buf + i, change_code, 9 );
				i = i + 8;
				findCallCnt++;
				continue;
			}

			if ( 0 == memcmp( buf + i + 6, call_code21, 5 ) )
			{
				memcpy( buf + i, change_code, 11 );
				i = i + 10;
				findCallCnt++;
				continue;
			}
		}

		if ( 0 == memcmp( buf + i, call_code13, 6 ) )
		{
			if ( 0 == memcmp( buf + i + 7, call_code23, 2 ) )
			{
				memcpy( buf + i, change_code, 9 );
				i = i + 7;
				findCallCnt++;
				continue;
			}
		}
		if ( 0 == memcmp( buf + i, call_code15, 5 ) )
		{
			if ( 0 == memcmp( buf + i + 6, call_code25, 2 ) )
			{
				memcpy( buf + i, change_code, 0x14 );
				i = i + 0x13;
				findCallCnt++;
			}
		}
		if ( 0 == memcmp( buf + i, jmp_code1, 4 ) )
		{
			if ( 0 == memcmp( buf + i + 5, jmp_code2, 2 ) )
			{
				memcpy( buf + i, change_code, 7 );
				i = i + 6;
				findJmpCnt++;
				continue;
			}
		}
		if ( 0 == memcmp( buf + i, jmpEax_code, 3 ) )
		{
			if ( 0 == memcmp( buf + i + 4, jmpEax_code2, 2 ) )
			{
				memcpy( buf + i, change_code, 6 );
				i = i + 6;
				findJmpCnt++;
				continue;
			}
		}
		for ( int j = 0; j < 16; j++ )
		{
			if ( 0 == memcmp( buf + i, jmp_code[j], 4 ) )
			{
				memcpy( buf + i, change_code, 5 );
				i = i + 4;
				findJmpCnt++;
				break;
			}
		}
	}
	unlink( "DancingCircle-bypassob.exe" );
	WriteFileNameBuffer( "DancingCircle-bypassob.exe", buf, size );
}
去掉混淆后代码如下:
.text:00401F58                                     ChangeKeyByAddr proc near               ; CODE XREF: sub_4025D3+1A↓p
.text:00401F58                                                                             ; ChangeKeyAndAntiDebug+25↓p
.text:00401F58                                                                             ; _main+71↓p
.text:00401F58                                                                             ; DATA XREF: sub_4025D3+13↓o
.text:00401F58
.text:00401F58                                     arg_0           = dword ptr  8
.text:00401F58                                     arg_4           = dword ptr  0Ch
.text:00401F58
.text:00401F58 55                                                  push    ebp
.text:00401F59 89 E5                                               mov     ebp, esp
.text:00401F5B 60                                                  pusha
.text:00401F5C 9C                                                  pushf
.text:00401F5D BA 80 C0 4B 00                                      mov     edx, offset source
.text:00401F62 90                                                  nop
.text:00401F63 90                                                  nop
.text:00401F64 90                                                  nop
.text:00401F65 90                                                  nop
.text:00401F66 90                                                  nop
.text:00401F67 90                                                  nop
.text:00401F68 90                                                  nop

int __usercall ChangeKeyByAddr@<eax>(int a1@<edx>, int a2@<ecx>, int a3@<esi>, _DWORD *a4, int a5)
{
  unsigned int v5; // et0
  _DWORD *v6; // esi
  int v7; // ecx
  _DWORD *v8; // esi
  int v9; // eax
  int v10; // eax
  unsigned int v12; // [esp-24h] [ebp-24h]
  int v13; // [esp-18h] [ebp-18h]
  int v14; // [esp-4h] [ebp-4h]
  int savedregs; // [esp+0h] [ebp+0h]
  _DWORD *retaddr; // [esp+4h] [ebp+4h]

  v14 = a3;
  v13 = a2;
  v5 = __readeflags();
  v12 = v5;
  v6 = a4;
  v7 = a5;
  if ( !a4 )
    v6 = retaddr;
  v8 = (_DWORD *)((char *)v6 + 5);
  v9 = 0;
  do
  {
    v10 = *v8 ^ v9;
    ++v8;
    v9 = __ROR4__(v10 ^ 0x78563412, 9);
    --v7;
  }
  while ( v7 );
  *(_DWORD *)(&source + *((unsigned __int8 *)retaddr + 4)) = *retaddr ^ v9;
  __writeeflags(v12);
  savedregs = v14;
  retaddr = (_DWORD *)((char *)retaddr + 5);
  return nullsub_2(v13, a1);
}
3、关键key生成函数 401F58(如上)
程序去掉混淆后,使得401F58计算的checksum有误,因此在后面需要修正。
程序共三处调用401F58
1)计算main函数的checksum,放入到全局变量中4BC080 该值为: 0x1093ACBD
2)计算4025D3反调试函数的checksum,放入到全局变量中4BC084 该值为0x2cF346
3)计算401F58本身的checksum,并放入到 0x4BC080+0x9D位置, 该值为 0x24230201
下面是调试时,修正check的相关脚本
#coding=utf-8

import struct
from idaapi import *
from idc import *
from idautils import *

'''====================================================
函数名:GetLibAddr
用  途:根据模块名获得模块起始地址
备  注:此函数在SHT被破坏时将不起作用
====================================================='''
def GetLibAddr(targetName):
    targetBase = 0
    for i in Modules():
        #print i.name
        if targetName in i.name:
            targetBase = int("%x"%(i.base), 16)
            #print 'targetName:=' + targetName + 'targetBase:=' + str(targetBase)
            break
    if targetBase == 0:
        #print 'targetBase None!!!'
        return False

    return targetBase

'''====================================================
函数名:GetSegAddrByName
用  途:根据段名获得段的起始地址
备  注:
====================================================='''
def GetSegAddrByName(targetName):
    tempAddr = FirstSeg()
    while tempAddr!=0:
        if tempAddr == 0:
            break
        name = SegName(tempAddr)
        if targetName in name:
            return tempAddr
        tempAddr = NextSeg(tempAddr)
    return 0

'''====================================================
函数名:writeMemoryForAddr
用  途:向模块地址写入指定shellcode
备  注:
====================================================='''
def writeMemoryForAddr(targetName, offset, buf):
    targetBase = 0
    targetBase = GetSegAddrByName(targetName)
    if targetBase == 0:
        #print 'targetBase None!!!'
        return False

    addr = targetBase + offset
    if not dbg_write_memory(addr, buf):
        return False

    refresh_debugger_memory()
    return True

'''====================================================
函数名:addBptForAddr
用  途:给模块指定偏移下断点
备  注:
====================================================='''
def addBptForAddr(targetName, offset, comm):
    soAddr = GetSegAddrByName(targetName)
    if soAddr > 0 :
        AddBpt(soAddr + offset)
        SetBptAttr(soAddr + offset, BPTATTR_FLAGS, BPT_ENABLED | BPT_BRK)
        MakeComm(soAddr + offset, comm)
    else :
        print 'create point fail'
    return True

'''====================================================
函数名:DelBptForAddr
用  途:删除模块指定偏移的断点
备  注:
====================================================='''
def DelBptForAddr(targetName, offset):
    soAddr = GetSegAddrByName(targetName)
    if soAddr > 0 :
        AddBpt(soAddr + offset)
        DelBpt(soAddr + offset)
    return True

'''====================================================
函数名:makenameForAddr
用  途:给指定模块函数重命名
备  注:
====================================================='''
def makenameForAddr(targetName, offset, comm):
    soAddr = GetSegAddrByName(targetName)
    if soAddr > 0 :
        MakeName(soAddr + offset, comm)
    else :
        print 'makenameForAddr fail'
    return True

'''====================================================
函数名:makeCommForAddr
用  途:给指定模块地址下注释
备  注:
====================================================='''
def makeCommForAddr(targetName, offset, comm):
    soAddr = GetSegAddrByName(targetName)
    if soAddr > 0 :
        MakeComm(soAddr + offset, comm)
    else :
        print 'makeCommForAddr fail'
    return True


'''====================================================
函数名:dumpMem
用  途:dump 指定大小的内存数据
备  注:
====================================================='''
def dumpMem(start, l, target):
    block = 1024
    c = l / block
    left = l % block
    fd = open(target, 'wb')
    if c > 0:
        for i in range(c):
            rawdex = idaapi.dbg_read_memory(start, block)
            fd.write(rawdex)
            start += block

    if left > 0:
        rawdex = idaapi.dbg_read_memory(start, left)
        print rawdex == True
        fd.write(rawdex)
    fd.close()

'''====================================================
函数名:doDump
用  途:通过窗口dump内存数据
备  注:
====================================================='''
def doDump():
    start = AskAddr(0, 'Input Addr start in hex: ')
    print('start is ' + str(hex(start)))

    end = AskAddr(0, 'Input Addr end in hex: ')
    print('end is ' + str(hex(end)))

    l = end - start
    target = AskStr('./dump.mem', 'Input the dump file path')

    if l > 0 and start > 0x0 and target and AskYN(1, 'start is 0x%0x, len is %d, dump to %s' % (start, l, target)) == 1:
        dumpMem(start, l, target)
        print('Dump Finish')

'''====================================================
函数名:readIntFromMemory
用  途:从内存中读取一个int
备  注:
====================================================='''
def readIntFromMemory(addr):
    flag=0
    temp = addr
    temp1= temp
    if  temp%2:
        temp1= temp-1
        flag=1
    else:
        temp1=temp
    m4=dbg_read_memory(temp1, 4+flag)
    return  struct.unpack('i', m4)[0+flag]

'''====================================================
函数名:dbg_read_every_memory
用  途:从内存中读取一个int
备  注:优化过的
====================================================='''
def dbg_read_every_memory(start, len):

    tempStart=start - (start%0x1000)
    maxLen = (start%0x1000) + len
    #print maxLen
    #print tempStart
    rawdex = idaapi.dbg_read_memory(tempStart, maxLen)
    #print rawdex
    left = start%0x1000
    #print left
    #print rawdex[left:maxLen]
    return bytearray(rawdex[left:maxLen])

'''====================================================
函数名:readShortFromMemory
用  途:从内存中读取一个short
备  注:
====================================================='''
def readShortFromMemory(addr):
    m2=dbg_read_memory(addr, 2)
    return  struct.unpack('h', m2)[0]

'''====================================================
函数名:readCharFromMemory
用  途:从内存中读取一个char
备  注:
====================================================='''
def readCharFromMemory(addr):
    m1=dbg_read_memory(addr, 1)
    return  struct.unpack('c', m2)[0]

'''====================================================
函数名:readUIntFromMemory
用  途:从内存中读取一个uint
备  注:
====================================================='''
def readUIntFromMemory(addr):
    m4=dbg_read_memory(addr, 4)
    return  struct.unpack('I', m4)[0]

'''====================================================
函数名:readUShortFromMemory
用  途:从内存中读取一个ushort
备  注:
====================================================='''
def readUShortFromMemory(addr):
    m2=dbg_read_memory(addr, 2)
    return  struct.unpack('H', m2)[0]

'''====================================================
函数名:readUCharFromMemory
用  途:从内存中读取一个uchar
备  注:
====================================================='''
def readUCharFromMemory(addr):
    m1=dbg_read_memory(addr, 1)
    return  struct.unpack('C', m1)[0]

'''====================================================
函数名:copyMem
用  途:拷贝内存到另外一个区域r
备  注:
====================================================='''
def copyMem(start, l, target):
    addrTemp = target
    print 'copy start'
    block = 1024
    c = l / block
    left = l % block
    if c > 0:
        for i in range(c):
            print str(hex(start))
            print str(hex(addrTemp))
            rawdex = idaapi.dbg_read_memory(start, block)
            dbg_write_memory(addrTemp, rawdex)
            refresh_debugger_memory()
            start += block
            addrTemp += block

    if left > 0:
        rawdex = idaapi.dbg_read_memory(start, left)
        dbg_write_memory(addrTemp, rawdex)
        refresh_debugger_memory()
    print 'copy end'


'''====================================================
函数名:FindData
用  途:从指定位置查找指定字节的数据
备  注:返回第一个找到的位置
====================================================='''
def FindData(addr, target):
    a = -1
    segStart = SegStart(addr)
    segEnd = SegEnd(addr)
    len=segEnd-segStart
    for i in range(len):
        rawdex1 = idaapi.dbg_read_memory(segStart, 1024)
        a = rawdex1.find(target)
        if a>= 0:
            a = a + segStart
            break;
        segStart = segStart+512
    return a

'''====================================================
函数名:FindDataEx
用  途:从指定位置查找指定字节的数据
备  注:返回第一个找到的位置
====================================================='''
def FindDataEx(addr, target,offset, target2):
    a = -1
    segStart = SegStart(addr)
    segEnd = SegEnd(addr)
    len=segEnd-segStart
    for i in range(len):
        rawdex1 = idaapi.dbg_read_memory(segStart, 1024)
        a = rawdex1.find(target)
        if a>= 0:
            rawdex2 = dbg_read_every_memory(segStart+a+offset, 512)
            b = rawdex2.find(target2)
            if b == 0:
                a = a + segStart
                break;
        segStart = segStart+512
    return a

#set debug breakpoint


'''-------------------------------------------------------------
#################start#######################
-------------------------------------------------------------'''
print 'by pass anti debug'
libBase = GetLibAddr('kernel32.dll')
print 'KernelBase addr = ' + str(hex(libBase))
addr = libBase + 0x163E0
print 'kernel32_SetThreadContext addr = ' + str(hex(addr))
write_dbg_memory(addr, "\xc2\x08\x00")
addr = libBase + 0x14030
print 'kernel32_CheckRemoteDebuggerPresent addr = ' + str(hex(addr))
write_dbg_memory(addr, "\x33\xc0\xc2\x08\x00")
print 'kernel32_CheckRemoteDebuggerPresent addr = ' + str(hex(addr))

AddBpt(0x4B8EBF)
SetBptAttr(0x4B8EBF, BPTATTR_FLAGS, BPT_ENABLED | BPT_BRK)

write_dbg_memory(0x004B9788, changecode)
'''-------------------------------------------------------------
执行so 初始化函数开始时的处理
-------------------------------------------------------------'''
pcValue=GetRegValue('eip')
libBase=GetLibAddr('linker')
if pcValue == 0x4B8EBF:
    print 'bypass memory check'
    write_dbg_memory(0x004BC080, "\xBD\xA3\x9C\x10\x46\xF3\x2C\x00")

三、去掉反调试
1、40299C函数
    将设置DRX硬件断点为401000、401001、401002、401003,感觉可以不用管,但是先用脚本让SetThreadContext函数直接返回。
signed int __usercall ChangeKeyAndAntiDebug@<eax>(int a1@<edx>, int a2@<ecx>, char a3@<bl>, int a4@<esi>, int a5)
{
  int v5; // eax
  int v6; // ebx
  int v7; // esi
  int v9; // [esp+14h] [ebp-2E4h]
  int v10; // [esp+18h] [ebp-2E0h]
  int v11; // [esp+1Ch] [ebp-2DCh]
  int v12; // [esp+20h] [ebp-2D8h]
  int v13; // [esp+24h] [ebp-2D4h]
  int v14; // [esp+2Ch] [ebp-2CCh]

  v5 = ChangeKeyByAddr(a1, a2, a4, sub_4025D3, 240);
  *(_BYTE *)(v5 + 82) &= a3;
  v6 = a5 + GetModuleHandleA(0);
  v9 = 65552;
  v7 = GetCurrentThread();
  GetThreadContext(v7, &v9);
  v10 = v6;
  v11 = v6 + 1;
  v13 = v6 + 3;
  v12 = v6 + 2;
  v14 |= 0x11110055u;
  SetThreadContext(v7, &v9);
  return 1;
}
2、反调试函数执行talbe.
main函数计算key的过程中,在某个时间点会调用如下9个反函数数组:
.data:004BC020 D3 25 40 00                         off_4BC020      dd offset sub_4025D3    ; DATA XREF: _main+5A4↑r
.data:004BC024 19 26 40 00                                         dd offset sub_402619
.data:004BC028 75 26 40 00                                         dd offset sub_402675
.data:004BC02C 22 27 40 00                                         dd offset sub_402722
.data:004BC030 6B 27 40 00                                         dd offset sub_40276B
.data:004BC034 66 25 40 00                                         dd offset sub_402566
.data:004BC038 D1 26 40 00                                         dd offset sub_4026D1
.data:004BC03C BC 27 40 00                                         dd offset sub_4027BC
main的如下位置调用上述函数
.text:004B9382 8B 85 44 D4 FF FF                                               mov     eax, [ebp-2BBCh]
.text:004B9388 8B 80 20 C0 4B 00                                               mov     eax, dword_4BC020[eax]
.text:004B938E C7 85 58 D4 FF FF 04 00 00 00                                   mov     dword ptr [ebp-2BA8h], 4
.text:004B9398 FF D0                                                           call    eax
.text:004B939A 83 F8 01                                                        cmp     eax, 1
.text:004B939D 83 95 48 D4 FF FF 00                                            adc     dword ptr [ebp-2BB8h], 0
.text:004B93A4 83 85 44 D4 FF FF 04                                            add     dword ptr [ebp-2BBCh], 4
如果返回结果为0,则累加计数器,只有非在调试器状态下,累加的结果为9,才可以使key计算正确。同时:
 1)在函数sub_4025D3中对401F58进行计算checksum;
 2)  sub_4028B3设置了异常处理程序;
nt __usercall sub_4025D3@<eax>(int a1@<edx>, int a2@<ecx>, int a3@<esi>)
{
  int v3; // edx
  unsigned __int8 v4; // cf
  unsigned int v5; // ST00_4
  int v6; // eax
  int v8; // [esp+1Ch] [ebp-Ch]

  ChangeKeyByAddr(a1, a2, a3, ChangeKeyByAddr, 104);
  JUMPOUT(!(v4 | (v3 == -1)), (char *)&loc_402624 + 6);
  __writeeflags(v5);
  v6 = GetCurrentProcess();
  CheckRemoteDebuggerPresent(v6, (char *)&v8 + 1);
  return *(int *)((char *)&v8 + 1);
}

signed int sub_4028B3()
{
  signed int v1; // [esp+18h] [ebp-10h]
  int v2; // [esp+1Ch] [ebp-Ch]

  v2 = 0;
  v1 = 0;
  if ( !setjmp3(Buf, 0) )
  {
    __debugbreak();
    v2 = SetUnhandledExceptionFilter(sub_40254C);
    v1 = 1;
  }
  SetUnhandledExceptionFilter(v2);
  return v1;
}
 在前面去混淆的时候直接让call eax,变为xor eax,eax,不去调用9个反调试函数,并能够使返回结果累加为9。(可能会使后面的计算key有误,先暂时这样处理)
至此准备工作做完,开始进行分析。
三、main 函数
去掉混淆后main函数如下:
int __cdecl main(int argc, const char **argv, const char **envp)
{
  int v3; // ebx
  int v4; // ebp
  int cur1; // edi
  int v6; // esi
  void *v7; // esp
  int v8; // edx
  int v9; // ecx
  int v10; // edx
  int v11; // ecx
  int v12; // ecx
  int v13; // eax
  int i; // edx
  int v15; // eax
  char *v16; // ebx
  unsigned __int8 *dataSN; // esi
  unsigned __int8 *dataSnEnd; // edx
  char v19; // t2
  char v20; // ah
  int v21; // eax
  int num; // eax
  int *tempBuf; // eax
  int *malocBufEnd; // edx
  int *keyVectorBuf; // ebx
  int k; // eax
  int curIndex1; // ebx
  int muti9_Value; // ebx
  int *tempBuf1; // eax
  int i1; // edx
  int v31; // ebx
  int *pmalloc2Buf; // edx
  int *v33; // eax
  int v34; // ecx
  signed int nextKeyValue; // eax
  int v36; // edx
  int v37; // et1
  int *v38; // ebx
  int cnt_9; // edi
  __int64 m3; // rax
  signed __int64 v41; // rt2
  __int64 m1; // rax
  signed __int64 leftValue; // rt2
  int keyValue1; // edi
  char v45; // al
  int v46; // ebx
  int v47; // eax
  signed int v48; // eax
  int v49; // edx
  char *v50; // ecx
  int v51; // eax
  int v52; // ebx
  bool v53; // zf
  _DWORD *v54; // eax
  int curIndex2; // [esp+0h] [ebp-2C0Ch]
  int sn_value; // [esp+4h] [ebp-2C08h]
  int *dataSn; // [esp+18h] [ebp-2BF4h]
  int max; // [esp+1Ch] [ebp-2BF0h]
  int v60; // [esp+20h] [ebp-2BECh]
  int m_Index; // [esp+24h] [ebp-2BE8h]
  int v62; // [esp+28h] [ebp-2BE4h]
  int index; // [esp+2Ch] [ebp-2BE0h]
  int stepIndex; // [esp+30h] [ebp-2BDCh]
  int v65; // [esp+34h] [ebp-2BD8h]
  int curIndex; // [esp+38h] [ebp-2BD4h]
  int keyValue; // [esp+3Ch] [ebp-2BD0h]
  int m; // [esp+40h] [ebp-2BCCh]
  int m2; // [esp+44h] [ebp-2BC8h]
  int snLen; // [esp+48h] [ebp-2BC4h]
  int v71; // [esp+4Ch] [ebp-2BC0h]
  int step; // [esp+50h] [ebp-2BBCh]
  char *(__cdecl *v73)(int, int, __int64, _BYTE *, int); // [esp+64h] [ebp-2BA8h]
  int *v74; // [esp+68h] [ebp-2BA4h]
  int *v75; // [esp+6Ch] [ebp-2BA0h]
  void *v76; // [esp+70h] [ebp-2B9Ch]
  int *v77; // [esp+74h] [ebp-2B98h]
  struct vector lastKeyTable; // [esp+80h] [ebp-2B8Ch]
  struct vector v79; // [esp+8Ch] [ebp-2B80h]
  struct vector keyVector; // [esp+98h] [ebp-2B74h]
  struct vector temKeyValueVetor; // [esp+A4h] [ebp-2B68h]
  int inputSn; // [esp+C0h] [ebp-2B4Ch]
  unsigned __int8 v83; // [esp+1C0h] [ebp-2A4Ch]
  int v84; // [esp+2C0h] [ebp-294Ch]
  struct DataTable newKeyTable; // [esp+3C0h] [ebp-284Ch]
  struct DataTable key2Table; // [esp+17D0h] [ebp-143Ch]
  int v87; // [esp+2BE0h] [ebp-2Ch]
  int *v88; // [esp+2BE8h] [ebp-24h]
  int v89; // [esp+2BECh] [ebp-20h]
  int v90; // [esp+2BF0h] [ebp-1Ch]
  int v91; // [esp+2BF4h] [ebp-18h]
  int v92; // [esp+2BF8h] [ebp-14h]
  int v93; // [esp+2BFCh] [ebp-10h]
  int retaddr; // [esp+2C0Ch] [ebp+0h]

  v93 = retaddr;
  v92 = v4;
  v91 = cur1;
  v90 = v6;
  v89 = v3;
  v88 = &argc;
  v7 = alloca(11240);
  v73 = sub_4B85B0;
  v74 = dword_4B9D1C;
  v76 = &loc_4B9827;
  v75 = &v87;
  v77 = &curIndex2;
  sub_40D9D0(&v71);
  sub_40C510();
  ChangeKeyByAddr(v8, v9, v6, 0, 0x25A);
  step = -1;
  ChangeKeyAndAntiDebug(v10, v11, v3, v6, 0x1000);
  create0x400Random(&newKeyTable);
  step = 1;
  create0x400Random(&key2Table);
  step = 2;
  sub_4B5240((int)&stdio, &inputSn);
  for ( snLen = 0; ; ++snLen )
  {
    v13 = snLen;
    if ( !*((_BYTE *)&v92 + snLen - 0x2B38) )
      break;
  }
  i = 0;
  LOBYTE(v3) = 0x37;
  while ( i != snLen )
  {
    LOBYTE(v13) = *((_BYTE *)&v92 + i - 0x2B38);// sn
    LOBYTE(v12) = '0';
    if ( (char)v13 > '9' )
    {
      LOBYTE(v12) = '=';
      if ( (char)v13 < 'a' )
        v12 = v3;
    }
    v15 = v13 - v12;
    *((_BYTE *)&v92 + i - 0x2A38) = v15;        // data_sn
    v13 = v15 - 1;
    if ( (unsigned __int8)v13 > 0xEu )
      goto LABEL_61;
    ++i;
  }
  v16 = (char *)&v84;
  dataSN = &v83;
  *((_BYTE *)&v92 + snLen - 0x2A38) = 0;
  while ( snLen )
  {
    dataSnEnd = &v83 + snLen;
    do
    {
      v19 = *dataSnEnd % 0xAu;
      *dataSnEnd /= 0xAu;
      *(dataSnEnd-- - 1) += 16 * v19;
    }
    while ( &v83 != dataSnEnd );
    v20 = v83 % 0xAu;
    v83 /= 0xAu;
    *v16 = v20;                                 // 余数
    do
    {
      cur1 = snLen;
      v21 = snLen - 1;
      if ( *(&v83 + snLen - 1) )
        break;
      --snLen;
    }                                           // ((c0*0X0a+c1)*0x0a + c2) ....
    while ( v21 );
    ++v16;
  }
  step = 2;
  SetTableAndDiv0x0A(&newKeyTable, &source, 0x10);
  sn_value = SetTableAndDiv0x0A(&key2Table, &byte_4BC088, 16);
  table_Mul_Tablue((struct DataTable *)v16, &newKeyTable, &key2Table);
  curIndex2 = cur1;
  CallTableDiv(&newKeyTable);                   // 转换成new constKeyTable 
  lastKeyTable.start = 0;
  lastKeyTable.last = 0;
  lastKeyTable.end = 0;
  v62 = 0;
  v65 = 0;
  index = 0;
  m = 0;
  while ( 1 )
  {
    step = 3;
    num = GetTableNum(&newKeyTable);
    if ( index >= num )
      break;
    keyValue = GetTableValueByIndex(&newKeyTable, index);
    keyVector.start = 0;
    keyVector.last = 0;
    keyVector.end = 0;
    tempBuf = (int *)malloc_0(0x510);
    malocBufEnd = tempBuf + 0x144;
    keyVectorBuf = tempBuf;
    keyVector.start = tempBuf;
    k = 0;
    keyVector.end = malocBufEnd;
    do
    {
      keyVectorBuf[k] = 0;                      // clear buf
      ++k;
    }
    while ( k != 0x144 );
    keyVector.last = malocBufEnd;
    if ( keyValue )
    {
      if ( keyValue <= 9 )
      {
        m1 = m;
        keyVectorBuf[(_DWORD)m1] = 1;
        leftValue = m1 % 9;
        keyValue1 = keyValue;
        keyVectorBuf[keyValue + 9 * (unsigned int)(m1 / 9) + 0x50] = 1;
        LODWORD(m1) = keyValue1 + 9 * (unsigned __int64)(m1 % 9) + 0xA1;
        cur1 = 3;
        keyVectorBuf[(_DWORD)m1] = 1;
        dataSN = (unsigned __int8 *)keyValue;
        keyVectorBuf[keyValue + 9 * (3 * (m / 27) + (signed int)leftValue / 3) + 0xF2] = 1;
        step = 4;
        Vector_push(&lastKeyTable, &keyVector);
        stepIndex = v65 + 1;
        sn_value = SetTableValueByIndex(&g_snTable, v65, keyValue);// 对于constKey 小于等于9 插入到g_snTable中,大于不放
        curIndex2 = sn_value;
        ++m;
      }
      else
      {
        curIndex = 0;
        dataSn = (int *)((char *)&v92 + v62 - 0x2938);// 获取SN指针
        while ( 1 )
        {
          m_Index = curIndex + v62;
          stepIndex = curIndex + v65;
          m2 = curIndex + m;
          if ( curIndex >= keyValue - 9 )
            break;
          curIndex1 = curIndex;
          sn_value = *((unsigned __int8 *)dataSn + curIndex);// 取出SN
          step = 4;
          SetTableValueByIndex(&g_snTable, stepIndex, sn_value);
          sn_value = curIndex1;
          curIndex2 = curIndex1;
          m_Index = 4 * m2;
          muti9_Value = 9 * (m2 / 9);
          stepIndex = 36 * (m2 / 9) + 0x144;
          max = 36 * (m2 / 9) + 0x168;
          v60 = 4 * (9 * (m2 % 9) - muti9_Value);
          m2 = 4 * (9 * (m2 % 9 / 3 + 3 * (m2 / 27)) - muti9_Value);
          do
          {
            temKeyValueVetor.start = 0;
            temKeyValueVetor.last = 0;
            temKeyValueVetor.end = 0;
            step = 4;
            tempBuf1 = (int *)malloc_0(0x510);
            temKeyValueVetor.start = tempBuf1;
            i1 = 0;
            temKeyValueVetor.end = tempBuf1 + 0x144;
            do
            {
              tempBuf1[i1] = 0;
              ++i1;
            }
            while ( i1 != 0x144 );
            cur1 = stepIndex;
            v31 = v60;
            temKeyValueVetor.last = tempBuf1 + 0x144;
            *(int *)((char *)tempBuf1 + m_Index) = 1;// 0
            *(int *)((char *)tempBuf1 + cur1) = 1;// 51
            pmalloc2Buf = (int *)((char *)tempBuf1 + v31);
            v33 = (int *)((char *)tempBuf1 + m2);
            *(int *)((char *)pmalloc2Buf + cur1 + 0x144) = 1;// A1
            *(int *)((char *)v33 + cur1 + 0x288) = 1;// F2
            step = 5;
            Vector_push(&lastKeyTable, &temKeyValueVetor);
            curIndex2 = v34;
            free_0((int *)&temKeyValueVetor);
            stepIndex += 4;
            dataSN = (unsigned __int8 *)stepIndex;
          }
          while ( max != stepIndex );
          ++curIndex;
        }
        step = 4;
        nextKeyValue = GetTableValueByIndex(&newKeyTable, index + 1);
        curIndex2 = v36;
        if ( nextKeyValue <= 9 )
        {
          v62 = m_Index;
          m = m2;
        }
        else
        {
          m = 0;
          keyValue = 0;
          do
          {
            v37 = *(int *)((char *)&off_4BC020 + keyValue);
            step = 4;
            ++m;                                // 应该等于9
            keyValue += 4;
          }
          while ( keyValue != 0x24 );
          v38 = keyVector.start;
          cnt_9 = m;
          m3 = m2;
          keyVector.start[m3] = 1;
          v41 = m3 % 9;
          v38[cnt_9 + 9 * (unsigned int)(m3 / 9) + 0x50] = 1;
          LODWORD(m3) = cnt_9 + 9 * (unsigned __int64)(m3 % 9) + 0xA1;
          cur1 = 3;
          v38[(_DWORD)m3] = 1;
          dataSN = (unsigned __int8 *)m;
          v38[m + 9 * (3 * (m2 / 27) + (signed int)v41 / 3) + 0xF2] = 1;
          step = 4;
          curIndex2 = (int)Vector_push(&lastKeyTable, &keyVector);
          m = m2 + 1;
          v62 = m_Index;
        }
      }
    }
    else
    {
      stepIndex = v65;
    }
    free_0((int *)&keyVector);
    ++index;
    v65 = stepIndex;
  }
  step = 3;
  sub_402BEE(
    &temKeyValueVetor,
    (int)&lastKeyTable,
    0xAAAAAAAB * ((_DWORD)((char *)lastKeyTable.last - (char *)lastKeyTable.start) >> 2),
    0x144);
  step = 6;
  v45 = sub_402DB6(&temKeyValueVetor, 0);
  curIndex2 = cur1;
  if ( v45 )
  {
    step = 6;
    vectorCpy(&v79, (struct vector *)((char *)&temKeyValueVetor + 4));
    curIndex2 = (int)dataSN;
    m = 0;
    keyValue = 0;
    do
    {
      v46 = keyValue;
      curIndex2 = (int)&lastKeyTable.start[3 * (*(int *)((char *)v79.start + keyValue) - 1)];
      step = 8;
      vectorCpy(&keyVector, (struct vector *)curIndex2);
      curIndex2 = v46;
      m2 = 0;
      do
      {
        if ( keyVector.start[m2] == 1 )
          break;
        ++m2;
      }
      while ( m2 != 81 );
      if ( m2 == 0x28 )
      {
        --m;
      }
      else
      {
        m2 -= m2 >= 41;
        v47 = 0;
        do
        {
          if ( keyVector.start[v47 + 81] == 1 )
            break;
          ++v47;
        }
        while ( v47 != 81 );
        curIndex = v47 % 9 + 1;
        step = 7;
        GetTableValueByIndex(&g_snTable, m2);
        v65 = (int)off_4BC060;
        v48 = GetTableValueByIndex(&g_snTable, m2);
        curIndex2 = v49;
        m += curIndex == *(unsigned __int8 *)(v65 + v48);
      }
      free_0((int *)&keyVector);
      keyValue += 4;
    }
    while ( keyValue != 0x144 );
    v50 = off_4BC05C;
    v51 = 0;
    do
    {
      v52 = (unsigned __int8)v50[v51];
      snLen += 9 * (v52 ^ m) ^ 0x37;
      v53 = m == v52;
      v50[v51] = v52 ^ m;
      if ( v53 )
        break;
      ++v51;
    }
    while ( v51 != 0x201 );
    if ( snLen == 0x1F1A )                      // 成功
    {
      step = 8;
      v54 = (_DWORD *)sub_4B3F00((int)&dword_4BD9A0, off_4BC05C);// 输出
      sub_4B0DB0(v54);
    }
    free_0((int *)&v79);
  }
  j_free(temKeyValueVetor.start);
  free_0((int *)&temKeyValueVetor.last);
  sub_402966((int *)&lastKeyTable);
LABEL_61:
  nop();
  nop();
  sub_40DA40(&v71);
  return 0;
}
流程如下:
1、调用 40D9D0  40C510 进行初始化变量同时初始化g_snTable(4C8020)用于最后校验的一个表,里面包含输入sn信息。
2、进行如下调用,
ChangeKeyByAddr  
ChangeKeyAndAntiDebug(v10, v11, v3, v6, 0x1000);

 计算出2个常数 ,我们称之为 constKey1 ,constKey2,如下:

4BC080 该值为: 0x1093ACBD

         4BC084 该值为:  0x2cF346

3、调用  create0x400Random 函数 将变量 key1Table和key2table 初始化,后面会将 0x1093ACBD与 0x2cF346放入其中
create0x400Random(&key1Table );
create0x400Random(&key2Table); 
1) create0x400Random函数,如下:
这个表的结构如下
00000000 vtable          dd ?
00000004 num             dd ?                    ; XREF: table_mul_oneValue+A7/r
00000008 value           db 1024 dup(?)
00000408 index           dd 1024 dup(?)
00001408 clock1          dd ?
0000140C clock2          dd ?
00001410 DataTable       ends

利用clock生成随机数,放在index数组里面,然后根据index的索引定位value数组的位置。
为了分析方便,直接将生成随机数的代码bypass掉,使其顺序为0-0x3FF,这样观察表中的数据比较清晰直观。相应的函数如下:

signed int __thiscall create0x400Random(struct DataTable *this)
{
  struct DataTable *v1; // ST0C_4
  int v2; // eax

  this->vtable = (int)off_4C5F30;
  v1 = this;
  v2 = clock();
  v1->clock2 = v2;
  v1->clock1 = v2;
  return create0x400RandomByClock(v1);
}

signed int __thiscall create0x400RandomByClock(struct DataTable *this)
{
  signed int result; // eax

  result = 0;
  do
  {
    this->index[result] = result;
    ++result;
  }
  while ( result != 0x400 );
//去掉后面的随机代码,让函数直接返回
  return result;
}
4、调用sub_4B5240 获取输入的sn
     将sn转换为数字,并且要求输入的字符必须是  123456789ABCDEF,其他的都是非法字符,尤其是 0字符。
     将转换为数字的sn再次进行16to10进制转换,此时生成的为一个10进制大数。比如输入是‘ABC‘’,则对应的数据为 ‘8 5 2 3‘ 小端存储。
5、调用SetTable_16To10函数
       1、将上面的两个常量放入到前面初始化的2个结构体中:key1Table和key2table

       2、同时将 将2个表中的数据由16进制转换为10进制。  0x1093ACBD  -->278113469    0x2cF346 ->2945862

       

int __thiscall SetTable_16To10(struct DataTable *this, _BYTE *source, int len)
{
  _BYTE *p; // eax
  int curNum; // edx
  struct DataTable *v5; // esi
  int curNum_1; // eax

  this->num = 0;
  for ( p = source; *p; this->value[v5->index[1]] = *(p - 1) & 0xF )
  {
    curNum = this->num;
    ++p;
    v5 = (struct DataTable *)((char *)this + 4 * curNum);
    this->num = curNum + 1;
    this->value[v5->index[0]] = (signed int)(unsigned __int8)*(p - 1) >> 4;
    this->num = curNum + 2;
  }
  while ( 1 )
  {                                             // 去掉0
    curNum_1 = this->num;
    if ( this->value[*(_DWORD *)&this->value[4 * curNum_1 + 0x3FC]] )
      break;
    this->num = curNum_1 - 1;
  }
  return TableDiv(this, len, 0xA);
}
6、调用table_Mul_Tablue将上面的2个10进制数据进行相乘。使其放入到 newKeyTable中。

table_Mul_Tablue((struct DataTable *)v16, &newKeyTable, &key2Table);

signed int __userpurge table_Mul_Tablue@<eax>(struct DataTable *key1Table@<ebx>, struct DataTable *a2@<ecx>, struct DataTable *key2Table)
{
  int i; // esi
  void *v4; // esp
  struct DataTable *v5; // ecx
  struct DataTable *key1Table_1; // ebx
  int value; // ST00_4
  signed int v9; // edx
  int v10; // eax
  bool v11; // zf
  bool v12; // sf
  unsigned __int8 v13; // of
  struct DataTable tempVtable1; // [esp+Ch] [ebp-2838h]
  struct DataTable tempVtable2; // [esp+141Ch] [ebp-1428h]
  int div_result; // [esp+2838h] [ebp-Ch]

  sub_40D480(0x284Cu, (int)a2, div_result);
  i = 0;
  v4 = alloca(10316);
  key1Table_1 = v5;
  create0x400Random(&tempVtable1);
  div_result = CreatetTableAndSetValue(&tempVtable2, 0);
  if ( key1Table_1->num + key2Table->num > 0x400 )
    return 1;
  while ( i < key2Table->num )
  {
    value = (unsigned __int8)key2Table->value[key2Table->index[i]];
    tabeCopy(&tempVtable1, key1Table_1);
    tableRoR(&tempVtable1, i);
    table_mul_oneValue(&tempVtable1, value);
    v10 = table_add(&tempVtable2, v9, &tempVtable1);
    v13 = __OFSUB__(key1Table_1->num, 0x3FF);
    v11 = key1Table_1->num == 0x3FF;
    v12 = key1Table_1->num - 0x3FF < 0;
    div_result = v10;
    if ( !((unsigned __int8)(v12 ^ v13) | v11) )
      return 1;
    ++i;
  }
  create0x400RandomByClock(key1Table_1);
  tabeCopy(key1Table_1, &tempVtable2);
  return 0;
}
7、调用table_10to16 将  newKeyTable再次转换成16进制。
       table_10to16(&newKeyTable);
int __thiscall table_10to16(struct DataTable *this)
{
  return TableDiv(this, 10, 16);
}
8、里程碑,此时会生成一个长度为2A的16进制数据作为一个固定的key将参与到后面的计算中。这个key的数值入下:

char constKey2A[0x2A] =
	{ 
		0x0B, 0x07, 0x09, 0x0F, 0x03, 0x0D, 0x04, 0x0B, 0x09, 0x0D, 0x05, 0x0A, 0x06, 0x0B, 0x08, 0x0C,
		0x02, 0x03, 0x0B, 0x06, 0x0C, 0x0B, 0x01, 0x0C, 0x03, 0x05, 0x0D, 0x04, 0x0B, 0x05, 0x0A, 0x01,
		0x0B, 0x07, 0x0B, 0x02, 0x0F, 0x08, 0x0E, 0x03, 0x05, 0x0B 
	};
9、生成2个大的数据结构:
a、多个510buf 组成的vector;
b、sn与constKey2A混合在一起放入到g_sn_table中
这段代码一大坨,比较难理解。大概的流程是:
a、对 constKey2A进行索引,当索引到0x2A个数据时就结束循环。
b、如果 constKey2A的值小于9生成一个510BUF,同时将对应小于A的值放入到 g_sn_table中。继续下一个循环
c、 如果constKey2A的值大于9, 则索引输入的10进制sn数据,放入 (x-9)个字符到 g_sn_table中,同时生成  (x-9)*9个510buf放入到vector中。
d、如果本次 constKey2A的值大于9,同时下一个也大于9,则进入到反调试流程,调用9次反调试函数,返回值为9,根据这个计数9来初始化一个510的buf,如果存在调试器,则计算的数据将使错误的。
e 、将510的buf分成4个部分每个部分长度为可以放入0x51个整数。其中0-50 放入的是索引,51-F1放入的是key值,F2开始的与余数有关。里面的数据时用索引来表示值的,如果该索引位置的数据为1,则表示该值对应的索引为相依的数据信息。
最后生成2个数据其中一个是 510buf 组成的vector 其并不与输入的sn相关,而是只与 constKey2A相关,可以认为是不变的数据。 g_sn_table放入的是 constKey2A与输入的sn的混合。放入的规则为:
1、如果 constKey2A[i] 大于10, 则放入  constKey2A[i]-10个sn数据到 g_sn_table 中,
2、如果小于10 ,则直接 constKey2A[i]放入 g_sn_table 中。

10、调用  sub_402BEE
         根据前面的510 buf跟上以及一个常量 0x144生成了一个vector 链表结构,并对其进行初始化,调用如下:
       sub_402BEE(&temKeyValueVetor,(int)&lastKeyTable, ((_DWORD)((char *)lastKeyTable.last - (char *)lastKeyTable.start) >> 2), 0x144);
该函数比较大,大概分析了下数据结构。里面分配了多个0x20大小的链表结构。
11、调用 sub_402DB6
       v45 = sub_402DB6(&temKeyValueVetor, 0);
        这个函数是个递归调用的函数,仔细分析发现其中存在如下调用:
.data:004BC044 11 24 40 00                         off_4BC044      dd offset sub_402411    ; DATA XREF: sub_402CD0+32↑r
.data:004BC048 4C 24 40 00                                         dd offset sub_40244C
.data:004BC04C 38 1F 40 00                                         dd offset sub_401F38
.data:004BC050 9C 24 40 00                                         dd offset sub_40249C
.data:004BC054 10 25 40 00                                         dd offset sub_402510

_DWORD *__thiscall sub_402CD0(_DWORD *this, _DWORD *a2)
{
  _DWORD *v2; // esi
  signed int v3; // eax
  int v4; // edx
  _DWORD *result; // eax
  _DWORD *i; // edx
  _DWORD *v7; // ecx
  int v8; // ecx

  v2 = this;
  v3 = this[6];
  if ( v3 <= 1199 )
    ((void (__usercall *)(int@<eax>))off_4BC044[v3 % 5])(v3 / 5 / 0x3C);
  v4 = a2[3];
  result = (_DWORD *)a2[2];
  ++v2[6];
  result[3] = v4;
  *(_DWORD *)(a2[3] + 8) = result;
  for ( i = (_DWORD *)a2[1]; i != a2; i = (_DWORD *)i[1] )
  {
    result = (_DWORD *)i[3];
    while ( i != result )
    {
      v7 = (_DWORD *)result[1];
      *v7 = *result;
      *(_DWORD *)(*result + 4) = v7;
      v8 = result[4];
      result = (_DWORD *)result[3];
      --*(_DWORD *)(v8 + 28);
    }
  }
  return result;
}
通过分析off_4BC044数组对应的5个函数,整个流程如下:
for (int j = 0; j < 0x04; j++)
{
		for (int i = 0; i < 0x3C; i++)
		{
			int m = g_xorTable1[i] & 0x7f;
			int n = g_xorTable2[i] & 0x7f;
			OutputDebugPrintf("0x%02x,0x%02x\n", m, n);
			int a = sntable[m];
			int b = sntable[n];
			a = (a * 9) ^ 0x37;
			b = (b * 9) ^ 0x37;
			sntable[m] = b;
			sntable[n] = a;
		}
}	
根据 4Bc098与4BC1D42个table表的值亦或0x7F后得到2个索引,将g_sntable的数据 乘以9并亦或0x37后进行交换。一共循环了 4*60次。
这部分明显对g_snTable进行了置换操作,需要判断最后的置换结构,因此写个程序进行了测试,让g_sntable[0x50]的值为0-4F,只进行置换不进行 乘以9并亦或0x37的操作,发现如下规律:

a、经过4次循环后,数值与循环前是相同的
b、实际进行了6次交换。

因此上面的算法可以精简如下:
for (int i = 0; i < 0x50; i++)
{
	int a = sntable[i];
	for (int j = 0; j < 6; j++)
	{
		a = (a * 9) ^ 0x37;
		a = a &0xff;
	}
	sntable[i] = a;
}
对应的解密算法如下:
for (i = 0; i < 0x50; i++)
{
	int a = sntable[i];
	for (int m = 0; m < 6; m++)
	{
		a = (a ^ 0x37) & 0xff;
		if (a % 9)
		{
			int k = 0;
			for (int k = 0; k < 9; k++)
			{
				int temp = k * 0x100 + a;
				if (0 == (temp % 9))
				{
					a = temp;
					break;
				}
			}
			if (k >= 9)
			{
				int err = 0;
			}
		}
		int a1 = (a / 9)&0xff;
		sntable[i] = a1;
	}
}

         1、貌似 sub_402DB6函数对g_sntable的操作如上的加密和解密函数,因此我们在这里可以对g_sntable进行加密和解密回推。
2、同时其根据之前生成的510 buf的vector返回了一个整数类型的vector, 经过几次分析后发现,这个整形的vector返回的数值是固定的,并没有发生变化。其数值入下(此时我得到的是错误的,造成后面逆推时得到输入sn是存在字符0):
13 
14 
4B 
70 
83 
A8 
B2 
C5 
E1 
E2 
F5 
111
124
140
141
166

12、 下面的一大堆代码为最后SN校验的

if ( v45 )
  {
    step = 6;
    vectorCpy(&v79, (struct vector *)((char *)&temKeyValueVetor + 4));
    curIndex2 = (int)dataSN;
    m = 0;
    keyValue = 0;
    do
    {
      v46 = keyValue;
      curIndex2 = (int)&lastKeyTable.start[3 * (*(int *)((char *)v79.start + keyValue) - 1)];
      step = 8;
      vectorCpy(&keyVector, (struct vector *)curIndex2);
      curIndex2 = v46;
      m2 = 0;
      do
      {
        if ( keyVector.start[m2] == 1 )
          break;
        ++m2;
      }
      while ( m2 != 81 );
      if ( m2 == 0x28 )
      {
        --m;
      }
      else
      {
        m2 -= m2 >= 41;
        v47 = 0;
        do
        {
          if ( keyVector.start[v47 + 81] == 1 )
            break;
          ++v47;
        }
        while ( v47 != 81 );
        curIndex = v47 % 9 + 1;
        step = 7;
        GetTableValueByIndex(&g_snTable, m2);
        v65 = (int)off_4BC060;
        v48 = GetTableValueByIndex(&g_snTable, m2);
        curIndex2 = v49;
        m += curIndex == *(unsigned __int8 *)(v65 + v48);
      }
      free_0((int *)&keyVector);
      keyValue += 4;
    }
    while ( keyValue != 0x144 );
    v50 = off_4BC05C;
    v51 = 0;
    do
    {
      v52 = (unsigned __int8)v50[v51];
      snLen += 9 * (v52 ^ m) ^ 0x37;
      v53 = m == v52;
      v50[v51] = v52 ^ m;
      if ( v53 )
        break;
      ++v51;
    }
    while ( v51 != 0x201 );
    if ( snLen == 0x1F1A )                      // 成功
    {
      step = 8;
      v54 = (_DWORD *)sub_4B3F00((int)&dword_4BD9A0, off_4BC05C);// 输出
      sub_4B0DB0(v54);
    }
    free_0((int *)&v79);
  }
  j_free(temKeyValueVetor.start);
  free_0((int *)&temKeyValueVetor.last);
  sub_402966((int *)&lastKeyTable);
LABEL_61:
  nop();
  nop();
  sub_40DA40(&v71);
  return 0;

1)初始化一个计数器equCnt = 0

1)根据上面得到的索引值(0x14 0x13 ...),从之前生成的510个buf中,获取2个数值,一个为index,一个为keyvalue.

2)根据index,索引g_sntable,得到另外一个索引index2

3) 根据index2 对全局变量off_4BC060指针进行索引,得到一个数值为 constKey

4) 判断constKey 与前面的keyvalue是否相等,如果相等,则equCnt++

5) 如果 index的值等于0x28则 将equCnt--,进行下一次循环

6) 如果  index  > 0x28 则 index --

7)整个循环0x51次数。

然后利用上面的 equCnt 对 off_4BC05C进行索引并计算checksun数值,  判断checksum是否等于 0x1F1A, 如果不等于则 输入sn错误,直接退出。如果相等,则利用equCnt的数值对off_4BC05C进行解密,然后将解密后的数据打印输出。

四、分析
1、获取上述的 equCnt 数值。
通过上面分析可知 equCnt数值为某一个数值时,才会打印出正确的数值。因此需要先知道如下代码中, equCnt应该为多少,写个小程序,当checksum为 0x1F1A  是就为正确的 equCnt数值。代码如下

 v50 = off_4BC05C;
    v51 = 0;
    do
    {
      v52 = (unsigned __int8)v50[v51];
      snLen += 9 * (v52 ^ m) ^ 0x37;
      v53 = m == v52;
      v50[v51] = v52 ^ m;
      if ( v53 )
        break;
      ++v51;
    }
    while ( v51 != 0x201 );
    if ( snLen == 0x1F1A )                      // 成功

int GetCheckValue()
{
	unsigned int i;
	bool flag;
	unsigned char value;
	unsigned int index = 0;
	unsigned int checkSum = 0;
	unsigned char checkTable[0x201];
	
	for (index = 0; index < 0xff; index++)
	{
		i = 0;
		checkSum = 0;
		memcpy(checkTable, g_checkTable, 0x201);
		do
		{
			value = checkTable[i];
			checkSum += 9 * (value ^ index) ^ 0x37;
			flag = index == value;
			checkTable[i] = value ^ index;
			if (flag)
				break;
			++i;
		} while (i != 0x201);

		if (checkSum == 0x1F1A)
			return index;	
	}
	return -1;
}
算出 equCnt等于0x4F。
 2、 equCnt 数值是由以下4组数得到的
a) vector 整形常量(0x14, 0x14) 标记为 vector index
b) 512buf 的 vector数组
c)g_sntable 包含我们的输入sn
d)  off_4BC060 对应的常量表
整个校验流程为
1)根据 a的索引数据 从b 中获取对应的 512buf
2) 从得到的 512 buf 拿到2个数据 一个为index,一个为keyvalue
3) 根据index 索引  g_sntable,得到一个index2
4)根据index2 索引d 全局表 off_4BC060 得到一个数值 constKey
5) 如果 keyvalue ==  constKey则,  equCnt++
6)同时如果index为0x28自动将 equCnt减一。

因此这里的关键之一就是 从得到的 512 buf 拿到2个数据 一个为index,一个为keyvalue,这2个数据,经过分析发现,这2个数据与输入的sn无关,是固定的组合,同时 vector 整形常量(0x13, 0x14)也是固定的,与输入的sn无关,下面是相应的数据表(将顺序调整了)
索引     sntable索引   keyValue
0x13             0x02  					        ,0x07,
0x14    ,        0x03  					        ,0x09,
0x4B		,0x0A						,0x03,
0x70  	        ,0x0F  					        ,0x04,
0x83		,0x12  					        ,0x09,
0xA8		,0x17						,0x05,
0xB2		,0x19						,0x06,
0xC5		,0x1C						,0x08,
0xE1		,0x20						,0x02,
0xE2		,0x21						,0x03,
0xF5		,0x24						,0x06,
0x111		,0x28												//  让equCnt-1
0x124   ,        0x2A						,0x01,
0x140		,0x2E						,0x03,
0x141		,0x2F						,0x05,
0x166		,0x34						,0x04,
0x179		,0x37						,0x05,
0x183		,0x39						,0x01,
0x196		,0x3C						,0x07,
0x1A9		,0x3F						,0x02,
0x1E0		,0x46						,0x08,
0x20E		,0x4C						,0x03,
0x20F		,0x4D						,0x05,
0x10A		,0x27						,0x03,
0x1C0		,0x42						,0x05,
0x84		,0x13						,0x01,
0xE7		,0x22						,0x05,
0xFA		,0x25						,0x05,
0x0F		,0x01						,0x06,
0x4D		,0x0B						,0x02,
0x102		,0x26						,0x04,
0x94		,0x14						,0x08,
0x46		,0x09						,0x05,
0x04		,0x00						,0x04,
0x3D		,0x08						,0x05,
0x12F		,0x2C						,0x02,
0x148		,0x31						,0x01,
0x25		,0x5						        ,0x08,
0x118		,0x28						,0x07,
0x6C		,0x0E						,0x06,
0x64		,0x0D						,0x07,
0x55		,0x0C						,0x01,
0x82		,0x11						,0x09,
0x78		,0x10						,0x08,
0x126		,0x2B						,0x02,
0x122		,0x29						,0x08,
0x165		,0x33						,0x09,
0x13D		,0x2D						,0x07,
0xBC		,0x1B						,0x01,
0xCE		,0x1D						,0x09,
0x159		,0x32						,0x06,
0xF2		,0x23						,0x07,
0xB5		,0x1A						,0x03,
0x31		,0x07						,0x02,
0x17		,0x04						,0x03,
0x27		,0x06						,0x01,
0xAF		,0x18						,0x07,
0x149		,0x30						,0x08,
0x19C		,0x3D						,0x06,
0x1D6		,0x44						,0x09,
0x18E		,0x3B						,0x02,
0x1C8		,0x43						,0x04,
0x181		,0x38						,0x08,
0x169		,0x35						,0x03,
0x18C		,0x3A						,0x09,
0x173		,0x36						,0x04,
0x1A6		,0x3E						,0x07,
0x1B8		,0x41						,0x06,
0xD2		,0x1E						,0x04,
0x97		,0x15						,0x02,
0xA2		,0x16						,0x04,
0xDD		,0x1F						,0x06,
0x1AA		,0x40						,0x01,
0x1D9		,0x45						,0x03,
0x1E8		,0x47						,0x08,
0x1F2		,0x48						,0x09,
0x1F8		,0x49						,0x06,
0x202		,0x4A						,0x07,
0x206		,0x4B						,0x02,
0x213		,0x4E						,0x04,
0x219		,0x4F						,0x01,

由于上述的表示固定的,我们就可以根据这个表,索引 off_4BC060 来逆推出g_sn_table的可能的数据。下面是相应的代码,再继续分析,其发现如果index为0x28则将 equCnt减1 ,我们知道上述表一个是0x51个,然后对于等于index等于0x28是无效的,剩余0x50个有效的,同时由于 0x28是又减去一个,因此变成了0x4F,所以要求上述比较的数值都相等才表示正确。

开始根据上面的表逆推g_sntable

unsigned table[0x100] =
{
		0x38, 0x39, 0x07, 0xE0, 0x5A, 0x18, 0x1E, 0xE7, 0x5E, 0x3B, 0xEB, 0xA5, 0xAE, 0x07, 0x06, 0x27,
		0x0D, 0x89, 0xF1, 0x6E, 0xD8, 0x0D, 0x3A, 0xE0, 0x1E, 0x96, 0xA7, 0xB2, 0x7D, 0x15, 0x16, 0x37,
		0x01, 0x41, 0x4B, 0x08, 0x1C, 0x1D, 0x33, 0x2B, 0x40, 0x49, 0x79, 0xE3, 0x04, 0xFA, 0x05, 0x47,
		0x66, 0x1B, 0xDB, 0x9E, 0x2C, 0x2D, 0x4E, 0x20, 0x87, 0x9E, 0xBB, 0xE9, 0x04, 0x3A, 0xC6, 0xD0,
		0xD5, 0x63, 0x51, 0x71, 0x3C, 0x41, 0x37, 0xA8, 0x2E, 0x01, 0x02, 0x23, 0x24, 0x63, 0x46, 0xE9,
		0x08, 0x09, 0x19, 0x2B, 0x48, 0x4D, 0xBD, 0xBC, 0x22, 0x11, 0x6C, 0x33, 0x34, 0x77, 0xE0, 0x27,
		0x18, 0x6C, 0x35, 0x3B, 0xB6, 0x8F, 0x46, 0x10, 0xC3, 0x43, 0x22, 0x5E, 0x44, 0xB3, 0x8C, 0x53,
		0x28, 0xA4, 0xCB, 0xF0, 0x81, 0xD6, 0x96, 0x0F, 0x5C, 0x31, 0x32, 0x19, 0xF0, 0xDA, 0x50, 0x4E,
		0x94, 0xF7, 0x27, 0x4E, 0x40, 0x7A, 0x33, 0x1F, 0x20, 0x41, 0x42, 0x6C, 0x66, 0x05, 0x49, 0xA4,
		0x48, 0x49, 0x30, 0xAC, 0x0C, 0xBE, 0x2E, 0x2F, 0x30, 0x9D, 0x57, 0x65, 0x6C, 0x6C, 0x20, 0x44,
		0x6F, 0x6E, 0x65, 0x21, 0x00, 0x83, 0x3E, 0x3F, 0x51, 0x63, 0x9A, 0x03, 0x53, 0x25, 0x26, 0x48,
		0xCF, 0x4C, 0x0A, 0x0B, 0x98, 0xB7, 0x88, 0x4F, 0xE9, 0xD3, 0x1E, 0x13, 0x57, 0x35, 0xD2, 0xF2,
		0x08, 0x9A, 0x1A, 0x1B, 0x84, 0x3D, 0xC9, 0x66, 0x0E, 0x8B, 0xB4, 0x58, 0xD0, 0x45, 0xDA, 0x19,
		0xA5, 0xB9, 0x2A, 0x1C, 0x4C, 0x2B, 0xDB, 0x07, 0xFF, 0x19, 0x12, 0x31, 0x1E, 0x96, 0xE2, 0x88,
		0x4B, 0x19, 0x3A, 0x19, 0x1A, 0x19, 0xE6, 0xDE, 0x00, 0x21, 0xF7, 0x43, 0x4B, 0x32, 0x32, 0x07,
		0x46, 0x29, 0x4A, 0x4B, 0x29, 0x97, 0x0E, 0x19, 0x10, 0xE0, 0x90, 0x73, 0x65, 0xE4, 0x4C, 0x17
};

	unsigned char key[0x50][3] = 
	{
		0x04, 0x00, 0x04,
		0x0F, 0x01, 0x06,
		0x13, 0x02, 0x07,
		0x14, 0x03, 0x09,
		0x17, 0x04, 0x03,
		0x25, 0x05, 0x08,
		0x27, 0x06, 0x01,
		0x31, 0x07, 0x02,
		0x3D, 0x08, 0x05,
		0x46, 0x09, 0x05,
		0x4B, 0x0A, 0x03,
		0x4D, 0x0B, 0x02,
		0x55, 0x0C, 0x01,
		0x64, 0x0D, 0x07,
		0x6C, 0x0E, 0x06,
		0x70, 0x0F, 0x04,
		0x78, 0x10, 0x08,
		0x82, 0x11, 0x09,
		0x83, 0x12, 0x09,
		0x84, 0x13, 0x01,
		0x94, 0x14, 0x08,
		0x97, 0x15, 0x02,
		0xA2, 0x16, 0x04,
		0xA8, 0x17, 0x05,
		0xAF, 0x18, 0x07,
		0xB2, 0x19, 0x06,
		0xB5, 0x1A, 0x03,
		0xBC, 0x1B, 0x01,
		0xC5, 0x1C, 0x08,
		0xCE, 0x1D, 0x09,
		0xD2, 0x1E, 0x04,
		0xDD, 0x1F, 0x06,
		0xE1, 0x20, 0x02,
		0xE2, 0x21, 0x03,
		0xE7, 0x22, 0x05,
		0xF2, 0x23, 0x07,
		0xF5, 0x24, 0x06,
		0xFA, 0x25, 0x05,
		0x102, 0x26, 0x04,
		0x10A, 0x27, 0x03,
		//0x111, 0x28, 0x06,   //????
		0x118, 0x28, 0x07,  //29 7??
		0x122, 0x29, 0x08,
		0x124, 0x2A, 0x01,
		0x126, 0x2B, 0x02,
		0x12F, 0x2C, 0x02,
		0x13D, 0x2D, 0x07,
		0x140, 0x2E, 0x03,
		0x141, 0x2F, 0x05,
		0x149, 0x30, 0x08,
		0x148, 0x31, 0x01,
		0x159, 0x32, 0x06,
		0x165, 0x33, 0x09,
		0x166, 0x34, 0x04,
		0x169, 0x35, 0x03,
		0x173, 0x36, 0x04,
		0x179, 0x37, 0x05,
		0x181, 0x38, 0x08,
		0x183, 0x39, 0x01,
		0x18C, 0x3A, 0x09,
		0x18E, 0x3B, 0x02,
		0x196, 0x3C, 0x07,
		0x19C, 0x3D, 0x06,
		0x1A6, 0x3E, 0x07,
		0x1A9, 0x3F, 0x02,	
		0x1AA, 0x40, 0x01,
		0x1B8, 0x41, 0x06,
		0x1C0, 0x42, 0x05,
		0x1C8, 0x43, 0x04,
		0x1D6, 0x44, 0x09,
		0x1D9, 0x45, 0x03,
		0x1E0, 0x46, 0x08,
		0x1E8, 0x47, 0x08,
		0x1F2, 0x48, 0x09,
		0x1F8, 0x49, 0x06,
		0x202, 0x4A, 0x07,
		0x206, 0x4B, 0x02,
		0x20E, 0x4C, 0x03,
		0x20F, 0x4D, 0x05,
		0x213, 0x4E, 0x04,
		0x219, 0x4F, 0x01,	
		
	};

	char sn[0x100][0x10];
	memset(sn, 0, 0x100);
	int i = 0;
	int j = 0;
	snInfo strSnInfo[0x51];
	memset((char*)&strSnInfo, 0, sizeof(snInfo)*0x51);
	for (i = 0; i < 0x51; i++)
	{
		unsigned value = key[i][2];;
		for (j = 0; j < 0x100; j++)
		{
			if (table[j] == value)
			{
				strSnInfo[i].key = value;
				strSnInfo[i].value[strSnInfo[i].cnt] = j;
				strSnInfo[i].cnt++;
			}
		}
	}
	for (i = 0; i < 0x50; i++)
	{
		OutputDebugPrintf(" i = 0x%02x, key = 0x%02x, cnt = 0x%02x, value = ", i, strSnInfo[i].key, strSnInfo[i].cnt);
		for (j = 0; j < strSnInfo[i].cnt; j++)
		{
			OutputDebugPrintf("0x%02x ", strSnInfo[i].value[j]);
		}
		OutputDebugPrintf("\n");
	}
得到g_sntabe的每个数据与如下可能:
 i = 0x00, key = 0x04, cnt = 0x02, value = 0x2c 0x3c 
 i = 0x01, key = 0x06, cnt = 0x01, value = 0x0e 
 i = 0x02, key = 0x07, cnt = 0x04, value = 0x02 0x0d 0xd7 0xef 
 i = 0x03, key = 0x09, cnt = 0x01, value = 0x51 
 i = 0x04, key = 0x03, cnt = 0x01, value = 0xab 
 i = 0x05, key = 0x08, cnt = 0x03, value = 0x23 0x50 0xc0 
 i = 0x06, key = 0x01, cnt = 0x02, value = 0x20 0x49 
 i = 0x07, key = 0x02, cnt = 0x01, value = 0x4a 
 i = 0x08, key = 0x05, cnt = 0x02, value = 0x2e 0x8d 
 i = 0x09, key = 0x05, cnt = 0x02, value = 0x2e 0x8d 
 i = 0x0a, key = 0x03, cnt = 0x01, value = 0xab 
 i = 0x0b, key = 0x02, cnt = 0x01, value = 0x4a 
 i = 0x0c, key = 0x01, cnt = 0x02, value = 0x20 0x49 
 i = 0x0d, key = 0x06, cnt = 0x01, value = 0x0e 
 i = 0x0e, key = 0x07, cnt = 0x04, value = 0x02 0x0d 0xd7 0xef 
 i = 0x0f, key = 0x04, cnt = 0x02, value = 0x2c 0x3c 
 i = 0x10, key = 0x08, cnt = 0x03, value = 0x23 0x50 0xc0 
 i = 0x11, key = 0x09, cnt = 0x01, value = 0x51 
 i = 0x12, key = 0x09, cnt = 0x01, value = 0x51 
 i = 0x13, key = 0x01, cnt = 0x02, value = 0x20 0x49 
 i = 0x14, key = 0x08, cnt = 0x03, value = 0x23 0x50 0xc0 
 i = 0x15, key = 0x02, cnt = 0x01, value = 0x4a 
 i = 0x16, key = 0x04, cnt = 0x02, value = 0x2c 0x3c 
 i = 0x17, key = 0x05, cnt = 0x02, value = 0x2e 0x8d 
 i = 0x18, key = 0x07, cnt = 0x04, value = 0x02 0x0d 0xd7 0xef 
 i = 0x19, key = 0x06, cnt = 0x01, value = 0x0e 
 i = 0x1a, key = 0x03, cnt = 0x01, value = 0xab 
 i = 0x1b, key = 0x01, cnt = 0x02, value = 0x20 0x49 
 i = 0x1c, key = 0x08, cnt = 0x03, value = 0x23 0x50 0xc0 
 i = 0x1d, key = 0x04, cnt = 0x02, value = 0x2c 0x3c 
 i = 0x1e, key = 0x06, cnt = 0x01, value = 0x0e 
 i = 0x1f, key = 0x09, cnt = 0x01, value = 0x51 
 i = 0x20, key = 0x02, cnt = 0x01, value = 0x4a 
 i = 0x21, key = 0x03, cnt = 0x01, value = 0xab 
 i = 0x22, key = 0x05, cnt = 0x02, value = 0x2e 0x8d 
 i = 0x23, key = 0x07, cnt = 0x04, value = 0x02 0x0d 0xd7 0xef 
 i = 0x24, key = 0x06, cnt = 0x01, value = 0x0e 
 i = 0x25, key = 0x05, cnt = 0x02, value = 0x2e 0x8d 
 i = 0x26, key = 0x09, cnt = 0x01, value = 0x51 
 i = 0x27, key = 0x03, cnt = 0x01, value = 0xab 
 i = 0x28, key = 0x04, cnt = 0x02, value = 0x2c 0x3c 
 i = 0x29, key = 0x08, cnt = 0x03, value = 0x23 0x50 0xc0 
 i = 0x2a, key = 0x01, cnt = 0x02, value = 0x20 0x49 
 i = 0x2b, key = 0x02, cnt = 0x01, value = 0x4a 
 i = 0x2c, key = 0x02, cnt = 0x01, value = 0x4a 
 i = 0x2d, key = 0x07, cnt = 0x04, value = 0x02 0x0d 0xd7 0xef 
 i = 0x2e, key = 0x03, cnt = 0x01, value = 0xab 
 i = 0x2f, key = 0x05, cnt = 0x02, value = 0x2e 0x8d 
 i = 0x30, key = 0x08, cnt = 0x03, value = 0x23 0x50 0xc0 
 i = 0x31, key = 0x01, cnt = 0x02, value = 0x20 0x49 
 i = 0x32, key = 0x06, cnt = 0x01, value = 0x0e 
 i = 0x33, key = 0x09, cnt = 0x01, value = 0x51 
 i = 0x34, key = 0x04, cnt = 0x02, value = 0x2c 0x3c 
 i = 0x35, key = 0x03, cnt = 0x01, value = 0xab 
 i = 0x36, key = 0x04, cnt = 0x02, value = 0x2c 0x3c 
 i = 0x37, key = 0x05, cnt = 0x02, value = 0x2e 0x8d 
 i = 0x38, key = 0x08, cnt = 0x03, value = 0x23 0x50 0xc0 
 i = 0x39, key = 0x01, cnt = 0x02, value = 0x20 0x49 
 i = 0x3a, key = 0x09, cnt = 0x01, value = 0x51 
 i = 0x3b, key = 0x02, cnt = 0x01, value = 0x4a 
 i = 0x3c, key = 0x07, cnt = 0x04, value = 0x02 0x0d 0xd7 0xef 
 i = 0x3d, key = 0x06, cnt = 0x01, value = 0x0e 
 i = 0x3e, key = 0x07, cnt = 0x04, value = 0x02 0x0d 0xd7 0xef 
 i = 0x3f, key = 0x02, cnt = 0x01, value = 0x4a 
 i = 0x40, key = 0x01, cnt = 0x02, value = 0x20 0x49 
 i = 0x41, key = 0x04, cnt = 0x02, value = 0x2c 0x3c 
 i = 0x42, key = 0x05, cnt = 0x02, value = 0x2e 0x8d 
 i = 0x43, key = 0x06, cnt = 0x01, value = 0x0e 
 i = 0x44, key = 0x09, cnt = 0x01, value = 0x51 
 i = 0x45, key = 0x03, cnt = 0x01, value = 0xab 
 i = 0x46, key = 0x08, cnt = 0x03, value = 0x23 0x50 0xc0 
 i = 0x47, key = 0x08, cnt = 0x03, value = 0x23 0x50 0xc0 
 i = 0x48, key = 0x09, cnt = 0x01, value = 0x51 
 i = 0x49, key = 0x06, cnt = 0x01, value = 0x0e 
 i = 0x4a, key = 0x07, cnt = 0x04, value = 0x02 0x0d 0xd7 0xef 
 i = 0x4b, key = 0x02, cnt = 0x01, value = 0x4a 
 i = 0x4c, key = 0x03, cnt = 0x01, value = 0xab 
 i = 0x4d, key = 0x05, cnt = 0x02, value = 0x2e 0x8d 
 i = 0x4e, key = 0x04, cnt = 0x02, value = 0x2c 0x3c 
 i = 0x4f, key = 0x01, cnt = 0x02, value = 0x20 0x49 

下一步将根据之前进行的6次*9亦或0x37进行解密,得到如下数据:
 i = 0x00, key = 0x04, cnt = 0x02, value = 0x04 0x14 
 i = 0x01, key = 0x06, cnt = 0x01, value = 0x06 
 i = 0x02, key = 0x07, cnt = 0x04, value = 0x5a 0x85 0x6f 0x07 
 i = 0x03, key = 0x09, cnt = 0x01, value = 0x09 
 i = 0x04, key = 0x03, cnt = 0x01, value = 0x03 
 i = 0x05, key = 0x08, cnt = 0x03, value = 0xfb 0x08 0xf8 
 i = 0x06, key = 0x01, cnt = 0x02, value = 0xd8 0x01 
 i = 0x07, key = 0x02, cnt = 0x01, value = 0x02 
 i = 0x08, key = 0x05, cnt = 0x02, value = 0xa6 0x05 
 i = 0x09, key = 0x05, cnt = 0x02, value = 0xa6 0x05 
 i = 0x0a, key = 0x03, cnt = 0x01, value = 0x03 
 i = 0x0b, key = 0x02, cnt = 0x01, value = 0x02 
 i = 0x0c, key = 0x01, cnt = 0x02, value = 0xd8 0x01 
 i = 0x0d, key = 0x06, cnt = 0x01, value = 0x06 
 i = 0x0e, key = 0x07, cnt = 0x04, value = 0x5a 0x85 0x6f 0x07 
 i = 0x0f, key = 0x04, cnt = 0x02, value = 0x04 0x14 
 i = 0x10, key = 0x08, cnt = 0x03, value = 0xfb 0x08 0xf8 
 i = 0x11, key = 0x09, cnt = 0x01, value = 0x09 
 i = 0x12, key = 0x09, cnt = 0x01, value = 0x09 
 i = 0x13, key = 0x01, cnt = 0x02, value = 0xd8 0x01 
 i = 0x14, key = 0x08, cnt = 0x03, value = 0xfb 0x08 0xf8 
 i = 0x15, key = 0x02, cnt = 0x01, value = 0x02 
 i = 0x16, key = 0x04, cnt = 0x02, value = 0x04 0x14 
 i = 0x17, key = 0x05, cnt = 0x02, value = 0xa6 0x05 
 i = 0x18, key = 0x07, cnt = 0x04, value = 0x5a 0x85 0x6f 0x07 
 i = 0x19, key = 0x06, cnt = 0x01, value = 0x06 
 i = 0x1a, key = 0x03, cnt = 0x01, value = 0x03 
 i = 0x1b, key = 0x01, cnt = 0x02, value = 0xd8 0x01 
 i = 0x1c, key = 0x08, cnt = 0x03, value = 0xfb 0x08 0xf8 
 i = 0x1d, key = 0x04, cnt = 0x02, value = 0x04 0x14 
 i = 0x1e, key = 0x06, cnt = 0x01, value = 0x06 
 i = 0x1f, key = 0x09, cnt = 0x01, value = 0x09 
 i = 0x20, key = 0x02, cnt = 0x01, value = 0x02 
 i = 0x21, key = 0x03, cnt = 0x01, value = 0x03 
 i = 0x22, key = 0x05, cnt = 0x02, value = 0xa6 0x05 
 i = 0x23, key = 0x07, cnt = 0x04, value = 0x5a 0x85 0x6f 0x07 
 i = 0x24, key = 0x06, cnt = 0x01, value = 0x06 
 i = 0x25, key = 0x05, cnt = 0x02, value = 0xa6 0x05 
 i = 0x26, key = 0x09, cnt = 0x01, value = 0x09 
 i = 0x27, key = 0x03, cnt = 0x01, value = 0x03 
 i = 0x28, key = 0x04, cnt = 0x02, value = 0x04 0x14 
 i = 0x29, key = 0x08, cnt = 0x03, value = 0xfb 0x08 0xf8 
 i = 0x2a, key = 0x01, cnt = 0x02, value = 0xd8 0x01 
 i = 0x2b, key = 0x02, cnt = 0x01, value = 0x02 
 i = 0x2c, key = 0x02, cnt = 0x01, value = 0x02 
 i = 0x2d, key = 0x07, cnt = 0x04, value = 0x5a 0x85 0x6f 0x07 
 i = 0x2e, key = 0x03, cnt = 0x01, value = 0x03 
 i = 0x2f, key = 0x05, cnt = 0x02, value = 0xa6 0x05 
 i = 0x30, key = 0x08, cnt = 0x03, value = 0xfb 0x08 0xf8 
 i = 0x31, key = 0x01, cnt = 0x02, value = 0xd8 0x01 
 i = 0x32, key = 0x06, cnt = 0x01, value = 0x06 
 i = 0x33, key = 0x09, cnt = 0x01, value = 0x09 
 i = 0x34, key = 0x04, cnt = 0x02, value = 0x04 0x14 
 i = 0x35, key = 0x03, cnt = 0x01, value = 0x03 
 i = 0x36, key = 0x04, cnt = 0x02, value = 0x04 0x14 
 i = 0x37, key = 0x05, cnt = 0x02, value = 0xa6 0x05 
 i = 0x38, key = 0x08, cnt = 0x03, value = 0xfb 0x08 0xf8 
 i = 0x39, key = 0x01, cnt = 0x02, value = 0xd8 0x01 
 i = 0x3a, key = 0x09, cnt = 0x01, value = 0x09 
 i = 0x3b, key = 0x02, cnt = 0x01, value = 0x02 
 i = 0x3c, key = 0x07, cnt = 0x04, value = 0x5a 0x85 0x6f 0x07 
 i = 0x3d, key = 0x06, cnt = 0x01, value = 0x06 
 i = 0x3e, key = 0x07, cnt = 0x04, value = 0x5a 0x85 0x6f 0x07 
 i = 0x3f, key = 0x02, cnt = 0x01, value = 0x02 
 i = 0x40, key = 0x01, cnt = 0x02, value = 0xd8 0x01 
 i = 0x41, key = 0x04, cnt = 0x02, value = 0x04 0x14 
 i = 0x42, key = 0x05, cnt = 0x02, value = 0xa6 0x05 
 i = 0x43, key = 0x06, cnt = 0x01, value = 0x06 
 i = 0x44, key = 0x09, cnt = 0x01, value = 0x09 
 i = 0x45, key = 0x03, cnt = 0x01, value = 0x03 
 i = 0x46, key = 0x08, cnt = 0x03, value = 0xfb 0x08 0xf8 
 i = 0x47, key = 0x08, cnt = 0x03, value = 0xfb 0x08 0xf8 
 i = 0x48, key = 0x09, cnt = 0x01, value = 0x09 
 i = 0x49, key = 0x06, cnt = 0x01, value = 0x06 
 i = 0x4a, key = 0x07, cnt = 0x04, value = 0x5a 0x85 0x6f 0x07 
 i = 0x4b, key = 0x02, cnt = 0x01, value = 0x02 
 i = 0x4c, key = 0x03, cnt = 0x01, value = 0x03 
 i = 0x4d, key = 0x05, cnt = 0x02, value = 0xa6 0x05 
 i = 0x4e, key = 0x04, cnt = 0x02, value = 0x04 0x14 
 i = 0x4f, key = 0x01, cnt = 0x02, value = 0xd8 0x01 

其中后面的 value就为输入sn的10进制,由于我们知道g_sntable中的数据不会大于0x9,分析上面的value数值,就可以过滤到很多,从而发现唯一解。
当然里面包括了与constKey2A中小于 10的数据。根据这个规则,可以将 constKey2A数据从中摘除,
char realSn[0x100];
	int realCnt = 0;
	OutputDebugPrintf("====================================================\n");
	j = 0;
	for (i = 0; i < cnt; i++)
	{
		if(constKey2A[i] > 9)
		{
			int x = constKey2A[i] - 9;
			for (int k = 0; k < x; k++)
			{
				realSn[realCnt] = snByte[j + k];
				realCnt++;
				OutputDebugPrintf("%d", snByte[j + k]);
			}
			j += x;
		}
		else
		{
			j++;
		}
	}
将大小端置换后,最终得到如下数据:1427698394561762984396187228734575649137428198671255218364 ,将其转换成16进制后就应该大功告成了。
转换后结构如下:CB82A940F33C575A343AFD3F90F93EAFCA7FC49B2EDD93A3。????里面结果竟然有0,由于输入时对0进行了限制,因此明显结果不对。
开始猜测是由于对程序进行了修改,可能计算的checksum数值不对造成的,经过确认貌似没有错误,陷入了困境。程序开始有3个tls回调,同时由于代码被修改后,可能引起出错。如果引起错误,嫌疑最大的可能是就是前面的生成的整形vector数据(0x13 0x14)不对。放弃用修改后的exe文件,使用新的原始exe, 
通过之前的分析,程序对402DB6函数并没有进行校验,因此将402DB6的代码改为死循环,跳到自身。然后直接运行程序,输入序列号后,改为附加调试,然后再将 402DB6函数恢复原始代码,再次查看 整形vector数据(0x13 0x14),发现果然和之前的不一致,新的如下:
                0x04, 0x00, 0x04,
		0x0F, 0x01, 0x06,
		0x13, 0x02, 0x07,
		0x14, 0x03, 0x09,
		0x17, 0x04, 0x03,
		0x25, 0x05, 0x08,
		0x27, 0x06, 0x01,
		0x31, 0x07, 0x02,
		0x3D, 0x08, 0x05,
		0x46, 0x09, 0x05,
		0x4B, 0x0A, 0x03,
		0x4D, 0x0B, 0x02,
		0x55, 0x0C, 0x01,
		0x63, 0x0D, 0x06,
		0x6D, 0x0E, 0x07,
		0x70, 0x0F, 0x04,
		0x78, 0x10, 0x08,
		0x82, 0x11, 0x09,
		0x83, 0x12, 0x09,
		0x84, 0x13, 0x01,
		0x94, 0x14, 0x08,
		0x97, 0x15, 0x02,
		0xA2, 0x16, 0x04,
		0xA8, 0x17, 0x05,
		0xAF, 0x18, 0x07,
		0xB2, 0x19, 0x06,
		0xB5, 0x1A, 0x03,
		0xBC, 0x1B, 0x01,
		0xC5, 0x1C, 0x08,
		0xC9, 0x1D, 0x04,
		0xD4, 0x1E, 0x06,
		0xE0, 0x1F, 0x09,
		0xE1, 0x20, 0x02,
		0xE2, 0x21, 0x03,
		0xE7, 0x22, 0x05,
		0xF2, 0x23, 0x07,
		0xF5, 0x24, 0x06,
		0xFA, 0x25, 0x05,
		0x107, 0x26, 0x09,
		0x10A, 0x27, 0x03,
		0x115, 0x28, 0x04,
		0x122, 0x29, 0x08,
		0x124, 0x2A, 0x01,
		0x126, 0x2B, 0x02,
		0x12F, 0x2C, 0x02,
		0x13D, 0x2D, 0x07,
		0x140, 0x2E, 0x03,
		0x141, 0x2F, 0x05,
		0x149, 0x30, 0x08,
		0x14B, 0x31, 0x01,
		0x159, 0x32, 0x06,
		0x165, 0x33, 0x09,
		0x166, 0x34, 0x04,
		0x169, 0x35, 0x03,
		0x173, 0x36, 0x04,
		0x179, 0x37, 0x05,
		0x181, 0x38, 0x08,
		0x183, 0x39, 0x01,
		0x18C, 0x3A, 0x09,
		0x18E, 0x3B, 0x02,
		0x196, 0x3C, 0x07,
		0x19C, 0x3D, 0x06,
		0x1A6, 0x3E, 0x07,
		0x1A9, 0x3F, 0x02,
		0x1AA, 0x40, 0x01,
		0x1B6, 0x41, 0x04,
		0x1C0, 0x42, 0x05,
		0x1CA, 0x43, 0x06,
		0x1D6, 0x44, 0x09,
		0x1D9, 0x45, 0x03,
		0x1E0, 0x46, 0x08,
		0x1E8, 0x47, 0x08,
		0x1F2, 0x48, 0x09,
		0x1F8, 0x49, 0x06,
		0x202, 0x4A, 0x07,
		0x206, 0x4B, 0x02,
		0x20E, 0x4C, 0x03,
		0x20F, 0x4D, 0x05,
		0x213, 0x4E, 0x04,
		0x219, 0x4F, 0x01,

再次进行前面的逆推动作,得到sn的10进制如下:
                            1427698396541762984396187228439575964137428198761255218364
转换为16进制为:CBC25EF8D9F482BC1F3DA3CA1F154EC89FC3F1414EDD93A3
即为最后的SN

ps 通过分析上面的修正后的索引表,其后面的value就为输入的sn与const2A的原始混合数据, 只要将 const2A 数据从中摘除就可以拿到sn了。不需要这样费劲。。。


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

最后于 2019-3-22 22:41 被oooAooo编辑 ,原因:
收藏
点赞3
打赏
分享
最新回复 (1)
雪    币: 6051
活跃值: (1441)
能力值: ( LV15,RANK:1473 )
在线值:
发帖
回帖
粉丝
lelfei 23 2019-3-25 18:11
2
0
其实程序只是在校验用户输入与固定值的匹配,属于明码校验的类型,只是比较绕而已
游客
登录 | 注册 方可回帖
返回