180822 逆向-网鼎杯(2-1)

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/whklhhhh/article/details/81950438

Reverse

martricks

main函数中接收输入以后将input和一个字符串异或一下存入savedregs中
这里写图片描述
这里7*(i_23/7)+i_23%7这种写法实际上还是i_23,只不过表示成了第x行y列的形式(7个元素/行)

两个数组分别存放在了savedregs-192和savedregs-128中

这里写图片描述
下面两层嵌套循环,中间进行了一个累加的运算
关键是箭头所指向的积的累加

实际上就是矩阵相乘

相乘结果异或后与数组6010a0比较

理解各个变量以后也可以通过逆矩阵的方式求解,不过矩阵归根结底还是线性方程,用Z3照抄跑一下就能出来,更省事XD

from z3 import *
t1 = [115, 111, 109, 101, 32, 108, 101, 103, 101, 110, 100, 115, 32, 114, 32, 116, 111, 108, 100, 44, 32, 115, 111, 109, 101, 32, 116, 117, 114, 110, 32, 116, 111, 32, 100, 117, 115, 116, 32, 111, 114, 32, 116, 111, 32, 103, 111, 108, 100]
t2 = [170, 122, 36, 10, 168, 188, 60, 252, 130, 75, 81, 82, 94, 28, 130, 31, 121, 186, 181, 227, 67, 4, 253, 172, 16, 181, 99, 189, 141, 231, 53, 217, 211, 232, 66, 109, 113, 90, 9, 84, 233, 159, 76, 220, 162, 175, 17, 135, 148]
flag = [BitVec("flag%d"%i, 8) for i in range(49)]
m1 = [0 for i in range(49)]
m2 = [0 for i in range(49)]

i_23 = 23
for i in range(49):
    m1[i_23] = flag[i]^i_23
    m2[i] = t1[i_23]^i
    i_23 = (i_23+13)%49
a = 3
b = 4
c = 5
d = 41
s = Solver()
for i in range(7):
    for j in range(7):
        sum = 0
        for k in range(7):
            sum += m2[7*c+b]*m1[7*a+c]
            c = (c+5)%7
        # print(sum)
        s.add(sum==t2[d]^d)
        d = (d+31)%49
        b = (b+4)%7
    a = (a+3)%7
c = (s.check())
f = ""
if(c==sat):
    m=s.model()
    # print("flag",end='\n')
    for i in range(49):
        f += (chr(m[flag[i]].as_long()))
    print(f)

give_a_try

GUI程序就不能直接跟着main函数走,而是要找到按钮的点击函数
按照MFC的套路
CreateDialogParamA(dword_404060, 1000, 0, sub_401223, 0);
这玩意儿的第四个参数就是回调函数拉

if ( (unsigned __int16)a3 == 1001 )
{
GetDlgItemTextA(a1, 1002, &Str, 255);
sub_401103(&Str);
}

然后找到取输入的地方,看下一个处理输入的函数sub_401103
这里写图片描述
判断长度以后,将输入的ASCII累加与某个变量异或作为种子
然后逐字符运算求解

刚开始被哈希吓到了,后来想了一想,求和的抗碰撞性极差,实际上对于42个字符的累加和穷举空间只有32*42~127*42=3990个字节,对于计算机来说非常小

因此应该通过已知flag的前4个字符来穷举种子

异或的那个数初值为0,查看交叉引用发现在TLS_callback里
这里写图片描述
可以看到结构完全被破坏,显然是大量花指令的功劳

取其中一段为例分析

pizza:004020CA                 call    loc_4020D0
pizza:004020CA ; ---------------------------------------------------------------------------
pizza:004020CF                 db 5
pizza:004020D0 ; ---------------------------------------------------------------------------
pizza:004020D0
pizza:004020D0 loc_4020D0:                             ; CODE XREF: pizza:004020CAj
pizza:004020D0                 add     dword ptr [esp], 6
pizza:004020D4                 retn
pizza:004020D5 ; ---------------------------------------------------------------------------
pizza:004020D5                 cmp     dword_40406C, 0

call到4020d0后,会在栈中压入一个ret address指向4020ca–下一条指令的地址
然后4020d0对这个ret address加了6个字节,于是移到了4020cf+6=4020d5,即跳过了花指令

由于retn和call的组合会使得IDA无法分析函数结构,因此抗静态分析的效果还是不错的
尤其是如果像OD使用线性反汇编,会完全被那些脏字节干扰,使得整片区域的反汇编都错误

对于去花可以直接用IDC脚本,运行即可呈现出干净的指令

#include <idc.idc>


static matchBytes(StartAddr, Match) 
{ 
auto Len, i, PatSub, SrcSub; 
Len = strlen(Match);

while (i < Len) 
{ 
   PatSub = substr(Match, i, i+1); 
   SrcSub = form("%02X", Byte(StartAddr)); 
   SrcSub = substr(SrcSub, i % 2, (i % 2) + 1); 

   if (PatSub != "?" && PatSub != SrcSub) 
   { 
    return 0; 
   } 

   if (i % 2 == 1) 
   { 
    StartAddr++; 
   } 
   i++; 
}

return 1; 
}


static main() 
{ 
   auto Addr, Start, End, Condition, junk_len, i;

Start = 0x402000; 
End = 0x4020f4;
Condition = "E801000000??83042406C3";
junk_len = 11;

for (Addr = Start; Addr < End; Addr++) 
{ 
   if (matchBytes(Addr, Condition)) 
   { 
    for (i = 0; i < junk_len; i++) 
    {
     PatchByte(Addr, 0x90); 
     MakeCode(Addr); 
     Addr++; 
    } 
   } 

}

AnalyzeArea(Start, End); 
Message("Clear Fake-Jmp Opcode Ok "); 
} 

这里面还做了两个反调试:
1. NtSetInformationThread这个函数,当第三个参数为0x11(ThreadHideFromDebugger)时,可以去掉所有调试器
2. NtQueryInformationProcess通过第三个参数0x7,可以返回调试端口到某个变量中。之后可以通过判断该变量是否为0来反调。
本题中,如果该变量非0则不会将第二个tls_callback挂上SEH链,使得后续过程不会触发

该变量为0则会将0x4020f7赋给0x404042,即seh链的第二个触发函数
这里写图片描述

再过去看该函数,同样一大堆花
里面做的事也很简单,通过NtQueryInformationProcess来反调,未被调试器附加时则会将0x40406c赋为0x31333337,然后对entry point–0x401000处取了一个字节来异或校验,本意应该是反调试器的断点(会将该字节改为0xcc)

所以最终0x40406c的值就是0x31333359
静态分析这么复杂,其实完全可以直接挂上调试器去看该内存的值
由于这两个反调试都仅在TLS中触发,所以挂上调试器的瞬间是没有影响的

得到值以后即可进行爆破种子
得到种子3681以后,就通过随机数向后逐字节爆破flag啦


#include <stdio.h>
#include <stdlib.h>
int cal(long long ori)
{
    int i;
    unsigned long long b=ori;
    for(i=0;i<16;i++)
    {
        //printf("%x\n", b);
        b = (b * b)%0xFAC96621;
    }
    return (ori*b)%0xFAC96621;
}
int t[] = {0x63b25af1, 0xc5659ba5, 0x4c7a3c33, 0xe4e4267, 0xb611769b, 0x3de6438c, 0x84dba61f, 0xa97497e6, 0x650f0fb3, 0x84eb507c, 0xd38cd24c, 0xe7b912e0, 0x7976cd4f, 0x84100010, 0x7fd66745, 0x711d4dbf, 0x5402a7e5, 0xa3334351, 0x1ee41bf8, 0x22822ebe, 0xdf5cee48, 0xa8180d59, 0x1576dedc, 0xf0d62b3b, 0x32ac1f6e, 0x9364a640, 0xc282dd35, 0x14c5fc2e, 0xa765e438, 0x7fcf345a, 0x59032bad, 0x9a5600be, 0x5f472dc5, 0x5dde0d84, 0x8df94ed5, 0xbdf826a6, 0x515a737a, 0x4248589e, 0x38a96c20, 0xcc7f61d9, 0x2638c417, 0xd9beb996};

int main()
{
    int i, seed;
    int r;
    char ch;
    //3681
    /*for(seed=0;seed<=5292;seed++)
    {
        srand(seed^0x31333359);
        r = cal('f'*rand());
        //printf("%x\n", r);

        if(r==0x63B25AF1){
            printf("%d\n", seed);
            printf("%x\n", cal('l'*rand()));
            break;
        }
    }
    */
    srand(3681^0x31333359);
    for(i=0;i<42;i++)
    {
        r = rand();
        //printf("%x\n", r);
        for(ch=32;ch<127;ch++)
        {
            if(cal(ch*r)==t[i])
                {
                    printf("%c", ch);
                    break;
                }
        }
        if(ch==127){printf("error");break;}

    }

    return 0;
}

阅读更多
换一批

没有更多推荐了,返回首页