首页
社区
课程
招聘
[原创]CTF2017 第八题 loudy-CrackMe 解题报告
2017-6-17 13:40 5883

[原创]CTF2017 第八题 loudy-CrackMe 解题报告

2017-6-17 13:40
5883

CTF2017 第八题 loudy-CrackMe

初步分析

  1. 拖入 IDA,发现有3个 TLS 回调,其中有1个回调启动了6个线程,但只有3个线程是解密函数,都类似如下代码
void thread1(LPVOID lpThreadParameter)
{
  while ( 1 )
  {
    tmp[] = {0x8Bu, 0xC0u, 0x8Bu, 0xFFu, 0x8Bu, 0xDBu};
    WaitForSingleObject(hMutex, 0xFFFFFFFF);
    if ( func1_flag == 1 )
    {
      flOldProtect = 0;
      VirtualProtect(&loc_401EC0, 0x2Eu, 0x40u, &flOldProtect);
      v1 = 1;
      do
      {
        *((_BYTE *)&loc_401EC0 + v1 - 1) ^= byte_4064F0[v1 - 1];
        *((_BYTE *)&loc_401EC0 + v1) ^= byte_4064F0[v1];
        v1 += 2;
      }
      while ( v1 - 1 < 46 );
	  // 将 401EC0 开始的 0x2E 个字节逐字节与 4064F0 开始的数组异或
      VirtualProtect(&loc_401EC0, 0x2Eu, flOldProtect, &flOldProtect);
      VirtualProtect(dword_4068E4, 6u, 0x40u, &flOldProtect);
      v2 = 0;
      v3 = (_BYTE *)dword_4068E4 - tmp;
      do
      {
        v4 = tmp[v2];
        v5 = &tmp[v2];
        v6 = *(&tmp[v2++] + v3);
        v5[v3] = v4;
        *v5 = v6;
      }
      while ( v2 < 6 );
	  // 直接将 tmp 数组写入 4068E4 指针所指向的地址
      VirtualProtect(dword_4068E4, 6u, flOldProtect, &flOldProtect);
      func1_flag = 0;
    }
    ReleaseMutex(hMutex);
    Sleep(50u);
  }
}
  1. 由于 4068E4 存的是指针,查找引用找到一个函数(现在还不知道是啥),但是能看到这个指针指向了这个函数段里的内
    int sub_402450(const char *a1)
    {
      dword_4068E4 = &loc_4025DC;
      dword_4068E0 = &loc_40263C;
      dword_4068EC = &loc_40269C;
      if ( (unsigned __int8)((int (*)(void))sub_4027F0)() )
      {
        v8 = strlen(a1);
        if ( v8 <= 256 )
        {
          v2 = 0;
          memset(&Dst, 0, 0xFFu);
          v9 = sub_401D50(&v2);
          if ( strlen(&v2) == 90 && v4 == 45 && v5 == 45 && v6 == 45 && v7 == 45 )
          {
            if ( !(unsigned __int8)sub_4027F0(90) )
              ExitProcess(1u);
            dword_99B6EC = 1;
            while ( dword_99B6EC )
              ;
          }
        }
      }
      return 0;
    }
  1. 为了让大家看到代码地址,特意将命名去除了,其他两个线程和这个差不多,只是解密的函数地址不一样,写个脚本还原代码
    import idaapi
    tmp_point = {0x4068E4:0x4025DC, 0x4068E0:0x40263C, 0x4068EC:0x40269C}
    def dec_func(func_addr, key_addr, tmp_addr, tmp):
        func = bytearray(idaapi.get_many_bytes(func_addr, 0x2E))
        key = bytearray(idaapi.get_many_bytes(key_addr, 0x2E))
        for i in xrange(len(func)):
            func[i] ^= key[i]
        idaapi.patch_many_bytes(func_addr, str(func))
        idaapi.patch_many_bytes(tmp_point[tmp_addr], str(bytearray(tmp)))
    dec_func(0x401EC0, 0x4064F0, 0x4068E4, [0x8B, 0xC0, 0x8B, 0xFF, 0x8B, 0xDB])
    dec_func(0x402090, 0x406520, 0x4068E0, [0x8B, 0xC0, 0x8B, 0xDB, 0x8B, 0xFF])
    dec_func(0x4021D0, 0x406550, 0x4068EC, [0x8B, 0xFF, 0x8B, 0xC0, 0x8B, 0xDB])

VM 部分

  1. 查看 main 函数,流程比较简单

    • 准备 虚拟机代码段(code)
    • 准备 虚拟机数据段(data)
    • 准备 虚拟机上下文(ctx)
    • 运行虚拟机(0x403540,eax=code, esi=ctx, arg0=data)
  2. OllyDBG 断 0x403540,查看 esi 所指内存,拿到 指令->执行函数 对应表


图1

  1. 在抓下 code 和 data 内容
  2. 指令比较少,查看各个执行函数,都比较简单,索性写个指令解析器(其实更简单的办法是在 OllyDBG 里为每个执行函数打 log),顺便在代码中输出了关键变量,本来分两步的
    # 代码段
    text = [0xAA, 0x15, 0x20, 0x01, 0x00, 0x00, 0xAA, 0x15, 0x40, 0x01, 0x00, 0x00, 0xA0, 0x10, 0x00, 0x00,
            0x00, 0x00, 0xA8, 0xA0, 0x10, 0xF0, 0x00, 0x00, 0x00, 0xA8, 0xA0, 0x10, 0x60, 0x01, 0x00, 0x00,
            0xA7, 0xAA, 0x11, 0x80, 0x00, 0x00, 0x00, 0xAA, 0x10, 0x60, 0x00, 0x00, 0x00, 0xAA, 0x12, 0xB0,
            0x00, 0x00, 0x00, 0xA9, 0xA2, 0xEA, 0xA6, 0x0E, 0xA0, 0x10, 0x20, 0x01, 0x00, 0x00, 0xA0, 0x11,
            0x10, 0x01, 0x00, 0x00, 0xA4, 0xA5, 0xA0, 0x10, 0x40, 0x01, 0x00, 0x00, 0xA0, 0x11, 0x10, 0x01,
            0x00, 0x00, 0xA4, 0xA5]
    # 数据段
    data = [0x2D, 0x2D, 0x2D, 0x2D, 0x2D, 0x2D, 0x2D, 0x2D, 0xBF, 0xB4, 0xD1, 0xA9, 0x43, 0x54, 0x46, 0x32,
            0x30, 0x31, 0x37, 0x2D, 0x2D, 0x2D, 0x2D, 0x2D, 0x2D, 0x2D, 0x2D, 0x0A, 0x0A, 0x0A, 0x00, 0x00,
            0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E, 0x6F, 0x70,
            0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7A, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66,
            0x67, 0x68, 0x69, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E, 0x6F, 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76,
            0x77, 0x78, 0x79, 0x7A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
            0x58, 0x5A, 0x54, 0x52, 0x50, 0x52, 0x54, 0x5A, 0x58, 0x5A, 0x52, 0x54, 0x5A, 0x58, 0x5A, 0x44,
            0x42, 0x40, 0x42, 0x44, 0x44, 0x44, 0x44, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
            0x50, 0x57, 0x57, 0x5D, 0x52, 0x5E, 0x57, 0x5E, 0x5C, 0x58, 0x5B, 0x5F, 0x5B, 0x5C, 0x5A, 0x48,
            0x45, 0x4A, 0x47, 0x40, 0x47, 0x42, 0x40, 0x4D, 0x48, 0x4D, 0x51, 0x57, 0x52, 0x54, 0x57, 0x51,
            0x5F, 0x59, 0x51, 0x52, 0x5F, 0x5F, 0x55, 0x58, 0x5E, 0x41, 0x42, 0x00, 0x00, 0x00, 0x00, 0x00,
            0x58, 0x55, 0x56, 0x57, 0x54, 0x54, 0x53, 0x5E, 0x51, 0x5A, 0x52, 0x5B, 0x58, 0x5D, 0x5E, 0x42,
            0x45, 0x44, 0x4B, 0x44, 0x4C, 0x41, 0x42, 0x4B, 0x48, 0x48, 0x55, 0x54, 0x5B, 0x54, 0x5C, 0x51,
            0x52, 0x5B, 0x58, 0x58, 0x5F, 0x5A, 0x55, 0x5E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00,
            0x70, 0x6C, 0x65, 0x61, 0x73, 0x65, 0x20, 0x69, 0x6E, 0x70, 0x75, 0x74, 0x20, 0x74, 0x68, 0x65,
            0x20, 0x6B, 0x65, 0x79, 0x3A, 0x0A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
            0xBF, 0xB4, 0xD1, 0xA9, 0x43, 0x54, 0x46, 0x32, 0x30, 0x31, 0x37, 0x00, 0x00, 0x00, 0x00, 0x00,
            0x18, 0x0D, 0x16, 0x44, 0x02, 0x09, 0x13, 0x48, 0x1D, 0x02, 0x0E, 0x4C, 0x1F, 0x07, 0x08, 0x18,
            0x05, 0x52, 0x18, 0x11, 0x0C, 0x57, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
            0x18, 0x0D, 0x16, 0x16, 0x45, 0x0D, 0x02, 0x11, 0x49, 0x03, 0x18, 0x4C, 0x03, 0x01, 0x1B, 0x50,
            0x03, 0x1B, 0x14, 0x1C, 0x01, 0x57, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
            0x18, 0x0D, 0x16, 0x44, 0x02, 0x09, 0x13, 0x48, 0x1D, 0x02, 0x0E, 0x4C, 0x1F, 0x07, 0x08, 0x18,
            0x05, 0x52, 0x18, 0x11, 0x0C, 0x57, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
            0x18, 0x0D, 0x16, 0x16, 0x45, 0x0D, 0x02, 0x11, 0x49, 0x03, 0x18, 0x4C, 0x03, 0x01, 0x1B, 0x50,
            0x03, 0x1B, 0x14, 0x1C, 0x01, 0x57, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
            0xBF, 0xB4, 0xD1, 0xA9, 0x43, 0x54, 0x46, 0x32, 0x30, 0x31, 0x37, 0x00, 0x00, 0x00, 0x00, 0x00]
    # 填充数据段到 0x1000 长度
    for _ in xrange(0x1000 - len(data)):
        data.append(0)
    def toUint(arr):
        return arr[0] | (arr[1]<<8) | (arr[2]<<16) | (arr[3]<<24)
    class Context:
        def __init__(self):
            self.ip = 0
        def cmdA0(self):
            c = text[self.ip + 1]
            p = toUint(text[self.ip+2: self.ip+6])
            self.ip += 6
            if 0x10 <= c <= 0x13:
                # 寄存器间 mov
                print "mov r{0}, {1}".format(c - 0x10, hex(p))
            elif c == 0x14:
                # 这2个是 寄存器和内存间 mov,1字节
                print "movb r0, [{0}]".format(hex(p))
            elif c == 0x15:
                print "movb [{0}], r0".format(hex(p))
            else:
                assert False
        def cmdA1(self):
            self.ip += 1
            # 异或结果存 r0
            print "xor r0, r1"
        def cmdA2(self):
            p = text[self.ip + 1]
            self.ip += 2
            # 1字节比较 r0 和 内存,存下比较状态
            print "equb r0, [{0}]".format(hex(p))
        def cmdA3(self):
            assert False
        def cmdA4(self):
            self.ip += 1
            # 弹出对话框
            print "msg [r0], [r1]"
        def cmdA5(self):
            self.ip += 1
            # 退出
            print "exit"
        def cmdA6(self):
            p = text[self.ip + 1]
            self.ip += 2
            # 比较状态为假则跳转相对地址
            print "jne +{0}".format(hex(p))
        def cmdA7(self):
            self.ip += 1
            # 输入到内存
            print "in [r0]"
        def cmdA8(self):
            self.ip += 1
            # 输出内存到屏幕
            print "out [r0]"
        def cmdA9(self):
            # 调用内置校验函数,返回值存 r0
            print "check [r0]"
            self.ip += 1
        def cmdAA(self):
            c = text[self.ip + 1]
            p = toUint(text[self.ip+2: self.ip+6])
            self.ip += 6
            if 0x10 <= c <= 0x12:
                # 将后2个内存进行按字节异或存第一个全局变量
                print "xorstr key{0}, [{1}], [32]".format(c - 0x10, hex(p))
            else:
                # 进行按字节异或存第一个内存
                print "xorstr [{0}], [32]".format(hex(p))
        def run(self):
            ops = [self.cmdA0, self.cmdA1, self.cmdA2, self.cmdA3, self.cmdA4, self.cmdA5,
                    self.cmdA6, self.cmdA7, self.cmdA8, self.cmdA9, self.cmdAA]
            while self.ip < len(text):
                c = text[self.ip]
                ops[c - 0xA0]()
    ctx = Context()
    ctx.run()
    
    print "-----------------"
    def xorstr(name, addr, xoraddr):
        txt = ""
        i = 0
        while True:
            t = data[addr + i]
            if t == 0:
                break
            txt += chr(t ^ data[32 + i])
            i += 1
        print name, txt
    xorstr("key0:", 0x60, 32)
    xorstr("key1:", 0x80, 32)
    xorstr("key2:", 0xB0, 32)
    xorstr("ok:", 0x120, 32)
    xorstr("fail:", 0x140, 32)
    print "[0xea] = ", hex(data[0xea])
  1. 输出虚拟机指令(稍作注释)
    xorstr [0x120], [32] ; you got the right key!
    xorstr [0x140], [32] ; your key is not right!
    mov r0, 0x0
    out [r0]
    mov r0, 0xf0
    out [r0]
    mov r0, 0x160
    in [r0] ; 获取用户输入,存数据地址0x160,寄存器 r0 指向它
    xorstr key1, [0x80], [32] ; 1549780652036258484424751705102781884386113
    xorstr key0, [0x60], [32] ; 98765432109876543210123
    xorstr key2, [0xb0], [32] ; 9753124680975312468097531246809753124680
    check [r0] ; 调用检验函数
    equb r0, [0xea] ; [0xea] = 1,意思是上面的函数返回真
    jne +0xe ; 假就跳到失败框
    mov r0, 0x120
    mov r1, 0x110
    msg [r0], [r1] ; 成功信息框
    exit
    mov r0, 0x140
    mov r1, 0x110
    msg [r0], [r1] ; 失败信息框
    exit
  1. 简单明了,对吧!接下来分析 check函数就行了

检验函数

其实 check 函数就是之前发过的 402450 函数,因为之前脚本还原了代码,现在完全不一样了,但也挺简洁明了,注意其中的 strlen(ipt) == 90

int __cdecl check(const char *src)
{
  if ( crc_check()                              // 检测函数是否被修改,也包含是否被软断点
    && (srclen = strlen(src), srclen <= 256)
    && (ipt[0] = 0, memset(&ipt[1], 0, 255u), 
	  v4 = base64_decode(src, srclen, ipt), strlen(ipt) == 90) // 这个strlen(ipt) == 90 要注意
                                                               // 代表整个 ipt 中不能有 00
    && ipt[20] == '-'                           // 序列号分隔符
    && ipt[21] == '-'
    && ipt[61] == '-'
    && ipt[62] == '-' )
  {
    if ( !crc_check() )
      ExitProcess(1u);
    thread_flag = 1;
    while ( thread_flag )
      ;
    if ( check_part1(ipt) )                     // 检验第一部分 ipt[:20]
    {
      thread_flag = 1;
      while ( thread_flag )
        ;
      if ( !crc_check() )
        ExitProcess(1u);
      thread_flag = 2;
      while ( thread_flag )
        ;
      if ( check_part2(ipt) )                   // 检验第二部分 ipt[22:61]
      {
        thread_flag = 2;
        while ( thread_flag )
          ;
        if ( !crc_check() )
          ExitProcess(1u);
        thread_flag = 3;
        while ( thread_flag )
          ;
        if ( check_part3(ipt) )                 // 检验第三部分 ipt[63:90]
        {
          thread_flag = 3;
          while ( thread_flag )
            ;
          if ( !crc_check() )
            ExitProcess(1u);
          result = 1;
        }
        else
        {
          result = 0;
        }
      }
      else
      {
        result = 0;
      }
    }
    else
    {
      result = 0;
    }
  }
  else
  {
    result = 0;
  }
  return result;
}

检验第一部分

signed int  check_part1(_BYTE *a1)
{
  __asm { fcmovnbe st, st(2) }
  v1 = __rdtsc();
  v10 = (unsigned int)v1 + ((unsigned __int64)HIDWORD(v1) << 32);
  v2 = a1;
  v3 = &part1_bn[1] - a1;
  v9 = 4;
  while ( 1 )
  {
    v4 = *v2 ^ v2["abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz" - a1];
    v2 += 5;
    v2[part1_bn - a1 - 5] = v4;
    v2[v3 - 5] = *(v2 - 4) ^ v2[0x405145 - (_DWORD)a1 - 5];
    v2[&part1_bn[2] - a1 - 5] = *(v2 - 3) ^ v2[0x405146 - (_DWORD)a1 - 5];
    v2[&part1_bn[3] - a1 - 5] = *(v2 - 2) ^ v2[0x405147 - (_DWORD)a1 - 5];
    v5 = v9-- == 1;
    v2[&part1_bn[4] - a1 - 5] = *(v2 - 1) ^ v2[0x405148 - (_DWORD)a1 - 5];
    if ( v5 )
      break;
    v3 = &part1_bn[1] - a1;
  }                                             // 拿ipt[:20] 与 abcdef...z 进行按字节异或存 part1_bn
  v6 = big_mul(part1_bn, key0);                 // key0 = 98765432109876543210123
  v8 = __rdtsc();                               // key1 = 1549780652036258484424751705102781884386113
  if ( !strcmp(v6, key1) && (unsigned int)v8 + ((unsigned __int64)HIDWORD(v8) << 32) - v10 < 8888888 )
  {
    operator delete(v6);
    result = 1;
  }
  else
  {
    operator delete(v6);
    result = 0;
  }
  return result;
}
  1. 很简单的流程

    • 先将 ipt[:20] 与 a..z 异或得到 part1_bn
    • big_mul 是简易大数乘法,输入是两个大数字符串,输出大数字符串
    • 检测 part1_bn * 98765432109876543210123 == 1549780652036258484424751705102781884386113
  2. python 算一下 part1_bn = 15691529100101820131

检验第二部分

signed int check_part2(_BYTE *a1)
{
  __asm { fcmovnbe st, st(2) }
  v1 = __rdtsc();
  v2 = ((unsigned __int64)HIDWORD(v1) << 32) + (unsigned int)v1;
  v3 = 0;
  do
  {
    v4 = aAbcdefghijklmn[v3] ^ a1[v3 + 22];     // abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz
    v3 += 3;
    byte_99B705[v3] = v4;
    byte_99B706[v3] = *(_BYTE *)(v3 + 0x405142) ^ a1[v3 + 20];
    byte_99B707[v3] = *(_BYTE *)(v3 + 0x405143) ^ a1[v3 + 21];
  }
  while ( v3 < 39 );
  v5 = big_mul(part2_bn, part2_bn);
  big_mul_fft(v5, key2);                        // 这里是个误导,其实没有用他的结果
  if ( strcmp(v5, a13095069099216) )            // 13095069099216326605010245808779535277211541324456558063162414338128147458401
  {
    operator delete(v5);
    return 0;
  }
  operator delete(v5);
  v7 = __rdtsc();
  if ( (unsigned int)v7 + ((unsigned __int64)HIDWORD(v7) << 32) - v2 >= 0x14B230CE38i64 )
    return 0;
  return 1;
}
  1. 流程也比较简单,中间的 big_mul_fft 函数是个误导,没有用到计算的结果,其实就是检验 part2_bn * part2_bn == 13095069099216326605010245808779535277211541324456558063162414338128147458401
  2. python 算一下 part2_bn = 114433688655117320765854989491151409201

检验第三部分

int check_part3(_BYTE *a1)
{
  v1 = __rdtsc();
  v23 = (unsigned int)v1 + ((unsigned __int64)HIDWORD(v1) << 32);
  bi_new(bi1);
  dst[0] = 0;
  memset(&dst[1], 0, 55u);
  v2 = a1 + 63;
  v3 = &dst[1];
  v4 = 27;
  do
  {
    v5 = *v2;
    *(v3 - 1) = *v2 / 16;
    *v3 = v5 % 16;
    ++v2;
    v3 += 2;
    --v4;
  }
  while ( v4 );
  v6 = 0;
  do
  {
    v7 = dst[v6];
    if ( v7 < 0 || v7 > 9 )
      v8 = v7 + 0x37;
    else
      v8 = v7 + '0';
    dst[v6] = v8;
    v9 = dst[v6 + 1];
    if ( v9 < 0 || v9 > 9 )
      v10 = v9 + 0x37;
    else
      v10 = v9 + '0';
    dst[v6 + 1] = v10;
    v11 = dst[v6 + 2];
    if ( v11 < 0 || v11 > 9 )
      v12 = v11 + 0x37;
    else
      v12 = v11 + 48;
    dst[v6 + 2] = v12;
    v13 = dst[v6 + 3];
    if ( v13 < 0 || v13 > 9 )
      v14 = v13 + 55;
    else
      v14 = v13 + 48;
    dst[v6 + 3] = v14;
    v15 = dst[v6 + 4];
    if ( v15 < 0 || v15 > 9 )
      v16 = v15 + 0x37;
    else
      v16 = v15 + 48;
    dst[v6 + 4] = v16;
    v17 = dst[v6 + 5];
    if ( v17 < 0 || v17 > 9 )
      v18 = v17 + 55;
    else
      v18 = v17 + '0';
    dst[v6 + 5] = v18;
    v6 += 6;
  }
  while ( v6 < 54 );                            // dst = 输入的 hex 编码
  bi_set(bi1, dst, 16);
  bi2_p = bi_new(bi2);
  bi_set(bi2_p, part1_bn, 10);                  // part1_bn = 15691529100101820131
  bi3_p = bi_new(bi3);
  bi_set(bi3_p, part2_bn, 10);                  // part2_bn = 114433688655117320765854989491151409201
  powmod(bi1, bi2, (int)&a2, bi3);
  result = 0;
  if ( v25 == 4 )
  {
    while ( v26[result] == byte_4064E0[result] )// 71639176673360967005214790689576394595
    {
      ++result;
      if ( result >= 4 )
      {
        v22 = __rdtsc();
        if ( (unsigned int)v22 + ((unsigned __int64)HIDWORD(v22) << 32) - v23 < 88888888 )
          return 1;
        break;
      }
    }
    result = 0;
  }
  return result * 4;
}
  1. 这个函数在处理转换 hex 的时候,对输入是当作有符号看待的,导致如果输入的字节是0x80以上的话就会导致 hex 转换错误
  2. powmod 是怎么推测出来的呢,首先在 BigInteger 算法中,有3个参数的就只有这个密码学常用的幂模(也叫模幂)算法了,然后我在调试的时候随便伪造了 dst 字符串的数值,函数输出的结果刚好是幂模的结果
  3. 接下来就是 part3_bn ** 15691529100101820131 % 114433688655117320765854989491151409201 == 71639176673360967005214790689576394595,求底数 part3_bn

模幂求底

其实这个题目换成求幂(对数,也叫离散对数),难度会大几倍

接下来就要上数论课了(看雪不支持 mathjax,发截图)


  [2]: https://zh.wikipedia.org/wiki/%E6%AC%A7%E6%8B%89%E5%AE%9A%E7%90%86_%28%E6%95%B0%E8%AE%BA%29

  [3]: https://zh.wikipedia.org/wiki/%E6%A8%A1%E5%8F%8D%E5%85%83%E7%B4%A0

  [4]: https://zh.wikipedia.org/wiki/%E6%AC%A7%E6%8B%89%E5%87%BD%E6%95%B0

最后求解

因为 第三部分 因为程序的原因有几个限制

  1. 必须 27 字节
  2. 必须没有 0
  3. 必须没有 大于 0x80
    所以有额外的工作要做,最后求解代
    import gmpy2
    import base64
    key0 = 98765432109876543210123
    key1 = 1549780652036258484424751705102781884386113
    key2 = 13095069099216326605010245808779535277211541324456558063162414338128147458401
    key3 = long("35e5341c28494bcc881e49a704971f63", 16) #71639176673360967005214790689576394595
    xordt = "abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz"
    part1_bi = 0
    part2_bi = 0
    def decode_part1():
        global part1_bi
        part1_bi = key1 / key0
        partstr = bytearray(str(part1_bi))
        for i in xrange(len(partstr)):
            partstr[i] ^= ord(xordt[i])
        return partstr
    def decode_part2():
        global part2_bi
        part2_bi = long(gmpy2.isqrt(key2))
        partstr = bytearray(str(part2_bi))
        for i in xrange(len(partstr)):
            partstr[i] ^= ord(xordt[i])
        return partstr
    # 先求出两部分大数
    decode_part1()
    decode_part2()
    g = 55986991232018409201158808992848352475
    m = part2_bi #"2a1eb3c9579dfa307cf5b6c8730114db"
    k = long("0x"+"6"*54, 16) / m
    g = g + (k-1) * m #找个能填充满 27字节,前面都是 66的最近的g
    def checkok(s):
        ttt = hex(s)[2:].rstrip("L").decode("hex")
        assert len(ttt) == 27
        for xxx in ttt:
            # 不是0,并且小于 0x80
            if (ord(xxx) == 0) or ((ord(xxx) & 0x80) != 0):
                return False
        return True
    while True:
        if checkok(g):
            break
        # 向前寻找
        g -= m
    fake = decode_part1() + "--" + decode_part2() + "--" + hex(g)[2:].rstrip("L").decode("hex")
    assert len(fake) == 90
    print base64.b64encode(fake)



[培训]二进制漏洞攻防(第3期);满10人开班;模糊测试与工具使用二次开发;网络协议漏洞挖掘;Linux内核漏洞挖掘与利用;AOSP漏洞挖掘与利用;代码审计。

收藏
点赞2
打赏
分享
最新回复 (7)
雪    币: 2361
活跃值: (3425)
能力值: ( LV13,RANK:405 )
在线值:
发帖
回帖
粉丝
奔跑的阿狸 1 2017-6-17 18:38
2
0
被数学击败了,:-(
雪    币: 1711
活跃值: (516)
能力值: ( LV12,RANK:200 )
在线值:
发帖
回帖
粉丝
supercolin 1 2017-6-18 10:57
3
0
被数学击败了+1
雪    币: 125
活跃值: (148)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
川美 2017-6-18 11:50
4
0
很详细,期待类似的文章
雪    币: 107
活跃值: (1437)
能力值: ( LV6,RANK:80 )
在线值:
发帖
回帖
粉丝
frozenrain 2017-6-18 20:41
5
0
赞,第3部分也想到过是RSA,无奈用常量数字验证第3部分的时候,死活不满足m^e%n,遂放弃。
雪    币: 16154
活跃值: (5966)
能力值: ( LV13,RANK:861 )
在线值:
发帖
回帖
粉丝
大帅锅 4 2018-2-10 15:24
6
0
不知道大佬,最后一张图由2到3是由欧拉定理怎么变形得到的?能否说下。我没找到相关的资料,我也是用欧拉定理硬推的!不过感觉好繁琐啊!
感觉你的方法变形较为简单吧。
雪    币: 3502
活跃值: (1433)
能力值: ( LV15,RANK:1045 )
在线值:
发帖
回帖
粉丝
kkHAIKE 10 2018-9-28 16:32
7
0
大帅锅 不知道大佬,最后一张图由2到3是由欧拉定理怎么变形得到的?能否说下。我没找到相关的资料,我也是用欧拉定理硬推的!不过感觉好繁琐啊!感觉你的方法变形较为简单吧。
我没有省略步骤啊,是哪里没懂
雪    币: 204
活跃值: (906)
能力值: (RANK:1324 )
在线值:
发帖
回帖
粉丝
mratlatsn 10 2019-9-6 10:28
8
0
看错了123
最后于 2019-9-8 10:08 被mratlatsn编辑 ,原因:
游客
登录 | 注册 方可回帖
返回