欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

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

程序员文章站 2022-05-19 14:12:37
...

Reverse

martricks

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

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

180822 逆向-网鼎杯(2-1)
下面两层嵌套循环,中间进行了一个累加的运算
关键是箭头所指向的积的累加

实际上就是矩阵相乘

相乘结果异或后与数组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
180822 逆向-网鼎杯(2-1)
判断长度以后,将输入的ASCII累加与某个变量异或作为种子
然后逐字符运算求解

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

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

异或的那个数初值为0,查看交叉引用发现在TLS_callback里
180822 逆向-网鼎杯(2-1)
可以看到结构完全被破坏,显然是大量花指令的功劳

取其中一段为例分析

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链的第二个触发函数
180822 逆向-网鼎杯(2-1)

再过去看该函数,同样一大堆花
里面做的事也很简单,通过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;
}