L4Nce 发表于 2016-3-28 23:29

吾爱破解安全大赛cm1分析

本帖最后由 L4Nce 于 2016-3-29 10:33 编辑

按钮事件被乱序加花,使用windbg的pykd脚本进行乱序修复,



花指令去除结果并整理后如下代码:
004406c0 55            push    ebp
0044217a 8bec            mov   ebp,esp
00440b83 83e4f8          and   esp,0FFFFFFF8h
004398ff 6aff            push    0FFFFFFFFh
0043b4b7 683b0f4100      push    offset 52challenge+0x10f3b (00410f3b)
00437a45 64a100000000    mov   eax,dword ptr fs: fs:0053:00000000=0018fa94
00435ce3 50            push    eax
00439393 81ec64010000    sub   esp,164h
00439eb7 26a118904100    mov   eax,dword ptr es: es:002b:00419018=d76cde0e
0043ccc6 33c4            xor   eax,esp
00443c02 8984245c010000mov   dword ptr ,eax ss:002b:0018f9dc=00100486
00443efd f3a118904100    rep mov eax,dword ptr ds:002b:00419018=d76cde0e;无效前缀
0043a888 33c4            xor   eax,esp
0043d133 50            push    eax
00440f88 8d842470010000lea   eax,
0043eaf5 64a300000000    mov   dword ptr fs:,eax fs:0053:00000000=0018fa94
00435173 8bf9            mov   edi,ecx
0043cdc0 6a32            push    32h
00443008 8d442435      lea   eax,
00438cee c644243400      mov   byte ptr ,0       ss:002b:0018f8a4=e8
004388e5 6a00            push    0
00436409 50            push    eax
00438d69 e842fcfcff      call    52challenge+0x89b0 (004089b0)
00444e2c 8b3524214100    mov   esi,dword ptr ds:002b:00412124={USER32!GetDlgItemTextA (74ae6bfe)}
0043847f 8d44243c      lea   eax,
00439f24 83c40c          add   esp,0Ch
0043f78c 6a32            push    32h
0043b113 50            push    eax
0043f6c0 68ec030000      push    3ECh
0043a989 ffd6            call    esi {USER32!GetDlgItemTextA (74ae6bfe)}
00441e84 68ff000000      push    0FFh
0043bf6c 8d442469      lea   eax,
00436bc3 c644246800      mov   byte ptr ,0       ss:002b:0018f8d8=98
0043b35c 6a00            push    0
0043edb1 50            push    eax
00443fad e8fe49fcff      call    52challenge+0x89b0 (004089b0)
004400ad 83c40c          add   esp,0Ch
0043b98e 8d442464      lea   eax,
004402c2 68ff000000      push    0FFh
00439a8b 50            push    eax
0043fedc 68ee030000      push    3EEh
004446da 57            push    edi
00441f3c ffd6            call    esi {USER32!GetDlgItemTextA (74ae6bfe)}
00441c02 8d4c2410      lea   ecx,
00437580 e87baafcff      call    52challenge+0x2000 (00402000)
004417de 83ec08          sub   esp,8
0043c4f2 c784248001000000000000 mov dword ptr ,0 ss:002b:0018f9ec=ffffffff
00438ff7 8d4c2418      lea   ecx,
00440882 e8d909fcff      call    52challenge+0x1260 (00401260)
00438f57 8bf0            mov   esi,eax
00437b53 85f6            test    esi,esi
0043ac5d 0f8b180f0000    jnp   52challenge+0x3bb7b (0043bb7b)         
00435830 51            push    ecx
004410b3 56            push    esi
0043ed44 8d4c2418      lea   ecx,
004390bf e87c84fcff      call    52challenge+0x1540 (00401540)
0043b016 85c0            test    eax,eax
0043a27d 8d4c2464      lea   ecx,
0043f380 51            push    ecx                        ;参数
0043d375 8d4c2434      lea   ecx,
0043b61c 51            push    ecx                        ;参数
0043d613 ffd0            call    eax {10001450}      ;调用检测算法
0044058c 6a40            push    40h
004387d8 68b8744100      push    offset 52challenge+0x174b8 (004174b8)
0043f876 3c01            cmp   al,1
00443349 0F85 7C090000   jnz   CrackMe.00443CCB                     ;此处可以爆破,代码修正
00443ccb 68e8744100      push    offset 52challenge+0x174e8 (004174e8)
004395ef 57            push    edi
0043900c ff152c214100    call    dword ptr ds:002b:0041212c={USER32!MessageBoxA (74adfde6)};msg
00437e02 56            push    esi



pykd去花还原脚本

# -*- coding: utf-8 -*-
#.load pykd.pyd
#!py d:\fuckcm.py
import pykd
def getmemonic(op):
      returnop.split(" ")[-1]
      
def ispushreg(op):
      if 'push' in op:
                if 'eax' in op:
                        return True
                elif 'ebx' in op:
                        return True
                elif 'ecx' in op:
                        return True
                elif 'edx' in op:
                        return True
                elif 'esi' in op:
                        return True
                elif 'edi' in op:
                        return True
                elif 'ebp' in op:
                        return True
                elif 'esp' in op:
                        return True
                else:
                        return False
      else:
                return False
def ispopreg(op):
      if 'pop' in op:
                if 'eax' in op:
                        return True
                elif 'ebx' in op:
                        return True
                elif 'ecx' in op:
                        return True
                elif 'edx' in op:
                        return True
                elif 'esi' in op:
                        return True
                elif 'edi' in op:
                        return True
                elif 'ebp' in op:
                        return True
                elif 'esp' in op:
                        return True
                else:
                        return False
      else:
                return False
      return False      
def getppreg(op):
      returnop.split(" ")[-1]
def stepin():
      pykd.trace()      #单步
      dis = pykd.disasm(pykd.reg("eip"))
      op = dis.instruction()
      return op
      
def stepover():
      pykd.step()      #单步
      dis = pykd.disasm(pykd.reg("eip"))
      op = dis.instruction()
      return op
def ispushimm(op):
      if op.split(" ") == "6" and op.split(" ") == "8":
                return True
      else:
                return False
def isret(op):
      if op.split(" ") == "c3":
                return True
      else:
                return False
def getnextop():
      dis = pykd.disasm(pykd.reg("eip"))
      #print pykd.reg("eip")+dis.length()
      dis_1 = pykd.disasm(pykd.reg("eip")+dis.length())
      op = dis_1.instruction()
      #print op
      return op
def islongjmp(op):
      if op.split(" ") == "e" and op.split(" ") == "9":
                return True
      else:
                return False
def isjunkjcc(op):
      op_n = getnextop()
      if 'j' in op and 'j' in op_n:
                addr1 =op.split("(").split(")")
                addr2 =op.split("(").split(")")
                if addr1 == addr2:
                        return True
                else:
                        return False
      else:
                return False               
def dealjunk():
      x=0;
      #------------------------------------------------
      #花指令有三种
      #pusad 修改寄存器 popad
      #push reg 修改reg pop reload
      #修改reg 真实指令处理reg
      #乱序连接 分解为jcc 和 push imm ret 和 jmp
      #------------------------------------------------
      dis = pykd.disasm(pykd.reg("eip"))
      op = dis.instruction()
      
      
      if getmemonic(op) == "pushad":      #花指令pushad类
                while True:
                        op = stepover()
                        if getmemonic(op) == "popad":
                              op = stepover()
                              break
                return op               
      elif ispushreg(op):
                reglist = []
                while ispushreg(op):                #找到所有push并把reg加入list
                        reglist.insert(0,getppreg(op))
                        op = stepover()
                while True:
                        flag = False
                        size =len(reglist)
                        if ispopreg(op):
                              for i in reglist:
                                        if i == getppreg(op):
                                                x=x+1;
                                        else:
                                                break
                                        op = stepover()
                              if      x==size or x==size-1:
                                        break
                        op = stepover()
                        x=0
      return op
def main():
      step = 0
      pykd.setBp(0x004011c5)#按钮事件
      pykd.go()
      pykd.trace()
      pykd.trace()      #代码开始
      opcode = []
#--------------------------------------------------
      while True:                #一个指令块
                opcode.append("")
                junkflag = False
                step = step+1
                dis = pykd.disasm(pykd.reg("eip"))
                op = dis.instruction()
                #print"-------------------------"
                #print op
                if step > 500:
                        break
                while True:      #指令解析
                        #print "code"
                        dis = pykd.disasm(pykd.reg("eip"))
                        op = dis.instruction()
                        if (ispushreg(op) and getmemonic(getnextop())!="pushad") or getmemonic(op) == "pushad":
                              if junkflag == False:
                                        #print "junk"
                                        op = dealjunk()
                                        junkflag = True

                              #dis = pykd.disasm(pykd.reg("eip"))
                              #print dis.instruction()
                        if ispushimm(op)and isret(getnextop()):                              
                              stepover()
                              stepover()

                              break
                        if islongjmp(op):
                              #print "jmp"
                              stepover()
                              break
                        if isjunkjcc(op):
                              #print "jcc"
                              addr = op.split("(").split(")")
                              op = stepover()
                              #print addr
                              #print op.split(" ")
                              if op.split(" ") != addr:
                                        stepover()
                                        break
                              break
                        opcode[-1] = op
                        stepover()
                if opcode[-1]!="":
                        print opcode[-1]
if __name__ == '__main__':
      main()

首先算法分成两部分:
1.一部分为3*3矩阵运算
2.8数码问题

所以key也分成两部,并对8数码步骤做了限制
if ( v2 != name + 1 && pass )
{
pass_len = strlen(pass);
if ( pass_len - 19 <= 0x31 )
{

在矩阵部分首先根据用户名累加算出hash,并根据最后一位作为flag:
name_hash = get_hash((int)name) & 1;
第一部分密码的奇数位作为标记,用来限定偶数位的正负,限定的方案用之前的flag决定
while ( 1 )
{
   v8 = v6;
   pass_p_0 = -1;
   v10 = v6 + v7;
   v11 = pass_18;
   if ( (unsigned __int8)(pass_18 - '0') > 9u )// 是不是数字
   {
   if ( (unsigned __int8)(v11 - 'A') > 5u )
   {
       if ( (unsigned __int8)(v11 - 'a') <= 5u )
         pass_p_0 = v11 - 'W';
   }
   else
   {
       pass_p_0 = v11 - '7';
   }
   }
   else
   {
   pass_p_0 = v11 - '0';
   }
   v12 = pass_p_0 % 2;
   v13 = pass_18;
   pass_p_1 = -1;
   if ( (unsigned __int8)(pass_18 - '0') > 9u )
   {
   if ( (unsigned __int8)(v13 - 'A') > 5u )
   {
       if ( (unsigned __int8)(v13 - 'a') <= 5u )
         pass_p_1 = v13 - 'W';
   }
   else
   {
       pass_p_1 = v13 - '7';
   }
   }
   else
   {
   pass_p_1 = v13 - '0';
   }
   v15 = _mm_cvtepi32_ps(_mm_cvtsi32_si128(pass_p_1));
   if ( v12 != name_hash )
   {
   v16 = 0;
   v16.m128_f32 = 0.0 - v15.m128_f32;
   v15 = v16;
   }
   v17 = i;
   if ( i >= const_3 )
   break;
   if ( v6 >= const_3_ )
   break;
   ++v6;


之后进行矩阵运算,矩阵的相乘和相加。由于给出了key,只要讲前面的矩阵固定即可。


第二部分的逻辑,首先用用户名初始化8数码。


v34 = &pass_left;
   do
       v35 = *v34++;
   while ( v35 );
   v36 = v34 - &v70;
   egiht_mov(name_);
   LOBYTE(v25) = 0;
然后用剩余的密码去恢复到123456780的形式
密码的转换方式做了一个变形
首先有四个基址分别代表上下左右


if ( *(_BYTE *)(v4 + 0x10013F10) )
{
if ( v4 <= 2 || *(_BYTE *)(v4 + 0x10013F0D) )
{
    if ( v5 >= 2 || *(_BYTE *)(v4 + 0x10013F11) )
    {
      if ( v3 >= 2 || *(_BYTE *)(v4 + 0x10013F13) )
      {
      if ( v5 > 0 && !*(_BYTE *)(v4 + 0x10013F0F) )

然后输入偏移,若基址+偏移的位置为空闲位置,则根据基址代表的意义进行移位

最后返回校验结果


do
{
if ( byte_10013F10[(unsigned __int8)v39] < byte_10013F0F[(unsigned __int8)v39] )
    break;
++v39;
}
while ( (unsigned __int8)v39 < 8u );
return v39 == 8;




最后的keygen如下
#include<stdio.h>
#include<string.h>
#include<iostream>
#include<queue>
#include<string>
using namespace std;
const int MAXN=1000000;//最多是9!/2
int fac[]={1,1,2,6,24,120,720,5040,40320,362880};//康拖展开判重
//         0!1!2!3! 4! 5!6!7!   8!    9!
bool vis;//标记
string path;//记录路径
int cantor(int s[])//康拖展开求该序列的hash值
{
    int sum=0;
    for(int i=0;i<9;i++)
    {
      int num=0;
      for(int j=i+1;j<9;j++)
          if(s<s)num++;
      sum+=(num*fac);
    }
    return sum+1;
}
struct Node
{
    int s;
    int loc;//“0”的位置
    int status;//康拖展开的hash值
    string path;//路径
};
int move={{-1,0},{1,0},{0,-1},{0,1}};//u,d,l,r
char indexs="udlr";//和上面的要相反,因为是反向搜索
int aim=46234;//123456780对应的康拖展开的hash值
void bfs()
{
    memset(vis,false,sizeof(vis));
    Node cur,next;
    for(int i=0;i<8;i++)cur.s=i+1;
    cur.s=0;
    cur.loc=8;
    cur.status=aim;
    cur.path="";
    queue<Node>q;
    q.push(cur);
    path="";
    while(!q.empty())
    {
      cur=q.front();
      q.pop();
      int x=cur.loc/3;
      int y=cur.loc%3;
      for(int i=0;i<4;i++)
      {
            int tx=x+move;
            int ty=y+move;
            if(tx<0||tx>2||ty<0||ty>2)continue;
            next=cur;
            next.loc=tx*3+ty;
            next.s=next.s;
            next.s=0;
            next.status=cantor(next.s);
            if(!vis)
            {
                vis=true;
                next.path=indexs+next.path;
                q.push(next);
                path=next.path;
            }
      }
    }

}
string game(char* table)
{
    char ch;
    Node cur;
    bfs();
    for(int i =0;i<9;i++)
    {
      cur.s = table;
    }
      cur.status=cantor(cur.s);
      if(vis)
      {
            return path;
      }
      else cout<<"unsolvable"<<endl;

    return 0;
}
int main()
{
    char username;
    int namelen;
    int hash;
    int flag;
    int step;
    int black=3;
    char table={7,1,4,0,3,8,2,5,6};
    char temp;
    string mo;
   
   
    scanf("%s",username);
    namelen = strlen(username);
    for(int i=0;i<namelen;i++)
      hash += (int)username;
    flag = hash & 1;

    for(int i=0;i<namelen;i++)
    {
      step = (int)username;
      step = step%4;
      switch(step)
      {
            case 0:
                if(black > 2)//up
                {
                  temp = table;
                  table = table;
                  table = temp;
                  black -=3;
                }
                break;
            case 1:
                if(black < 8)//right
                {
                  temp = table;
                  table = table;
                  table = temp;
                  black +=1;
                }
                break;
            case 2:   //down
                if(black < 6)
                {
                  temp = table;
                  table = table;
                  table = temp;
                  black +=3;
                }
                break;
            case 3:   //left
                if(black>0)
                {
                  temp = table;
                  table = table;
                  table = temp;
                  black -=1;
                }
                break;
      }
    }
   mo = game(table);
    //mo ="ulldrrdluurddllurdrulul";
    //cout<<endl;
    if(flag)
      printf("323031303330213032");
    else
      printf("222021202320312022");
   
    for(int i=0;i<mo.length();i++)
      switch(mo)
      {
            case 'l':
                printf("%d",black+1);
                black = black+1;
                break;
            case 'r':
                printf("%d",black-1);
                black = black-1;
                break;
            case 'u':
                printf("%d",black+3);
                black = black+3;
                break;
            case 'd':
                printf("%d",black-3);
                black = black-3;
                break;
      }
    printf("\n");
    return 0;
}





Hmily 发表于 2016-3-29 10:07

你真不是人,用windbg写还原脚本。。。

丶懒喵喵 发表于 2016-3-28 23:35

加油楼主!

梦游枪手 发表于 2016-3-28 23:35

先留名,再看{:301_1009:}

知足zz 发表于 2016-3-28 23:39

前排支持懒死老师

wzxkk123 发表于 2016-3-28 23:46

{:301_972:} 才看到.. 有好多疑问的..

wzxkk123 发表于 2016-3-28 23:48

原来是加花了.. 我说呢.. 到一定的地方代码就变了{:301_971:}

lies2014 发表于 2016-3-28 23:58

谢谢,学习中……先加分再说!

丶小明 发表于 2016-3-29 00:07

谢谢楼主分享经验

天蝎浪花 发表于 2016-3-29 00:10

哇噻,答案出来了,赶紧围观学习,不过好像看不太懂{:1_890:}

chenjingyes 发表于 2016-3-29 00:24

原来是加花了.. 我说呢{:1_937:}
页: [1] 2 3 4 5 6 7 8 9 10
查看完整版本: 吾爱破解安全大赛cm1分析