发表于 2022-11-23 12:45

申请ID:b1gcat【申请通过】

>1、申请ID:b1gcat                              
2、个人邮箱:admistrator@foxmail.com
3、原创技术文章:某线下比赛一道ctf逆向分析


昨天参加了一场ctf拿到了一道re题目,解题过程分享给大家,本次解题使用了ida、angr

下载题目(bits为64elf文件,core为14字节乱码)
```bash
b1gcat@b1gcat  bits  ls -l
total 32
-rw-rw-r--@ 1 b1gcatstaff102962 152021 bits
-rw-rw-r--@ 1 b1gcatstaff   142 152021 code
```

运行bits(实际,bits是运行 在服务器上的,需要交互式输入answer)

```
└─# ./bits
Your number is 4037157444
Your answer: 111
Thanks for coming!
```

将bits拖入die,无壳;(图略)
查看是否存在preinit、init等处理
```
└─# r2 -e bin.cache=true bits
-- Find hexpairs with '/x a0 cc 33'
> aaa
Analyze all flags starting with sym. and entry0 (aa)
Analyze all functions arguments/locals
Analyze function calls (aac)
Analyze len bytes of instructions for references (aar)
Finding and parsing C++ vtables (avrr)
[ Trap, trying to execute on non-executable memory
Type matching analysis for all functions (aaft)
Propagate noreturn information (aanr)
Use -AA or aaaa to perform additional experimental analysis.
> iee

vaddr=0x00000bf0 paddr=0x00000bf0 hvaddr=0x00201d20 hpaddr=0x00001d20 type=init
vaddr=0x00000bb0 paddr=0x00000bb0 hvaddr=0x00201d28 hpaddr=0x00001d28 type=fini

2 entrypoints
> pdg @ 0x00000bf0

// WARNING: Removing unreachable block (ram,0x00000b88)
// WARNING: Removing unreachable block (ram,0x00000b94)

void entry.init0(void)

{
    return;
}
```
看起来基本上是一个中规中矩的逆向 happy。

拖入IDA,F5后很输入逻辑很清晰(不上图了,图显示不了):
```
int v5; // eax
unsigned int v6; // BYREF
unsigned int randkey; //
unsigned int codelen; //
unsigned int size_4; //
void *code; //
void *s; //
FILE *stream; //
unsigned __int64 v13; //

v13 = __readfsqword(0x28u);
if ( ptrace(PTRACE_TRACEME, 0LL, 1LL, 0LL) < 0 )// 反调试,需要的话可以直接nop掉
{
    puts("Do not trace me!");
    exit(0);
}
v3 = getpid();
v4 = getsid(v3);
if ( v4 != getppid() )                        // //反调试,nop掉即可
{
    puts("Do not trace me!");
    exit(1);
}
code = 0LL;
s = 0LL;
randkey = sub_BFA();                        // 随机数生成
stream = fopen("code", "rb");               // 打开14字节的乱码文件
if ( !stream )
{
    fwrite("Could not open file code!\n", 1uLL, 0x1AuLL, stderr);
    exit(1);
}
fseek(stream, 0LL, 2);
codelen = ftell(stream);
fseek(stream, 0LL, 0);
code = malloc(codelen);
if ( fread(code, codelen, 1uLL, stream) != 1 )
{
    fwrite("Error reading file code\n", 1uLL, 0x18uLL, stderr);
    exit(1);
}
size_4 = sub_C31(randkey, codelen, (__int64)code);
printf("Your number is %u\n", size_4);
printf("Your answer: ");
__isoc99_scanf("%d", &v6);
v5 = sub_C31(v6, codelen, (__int64)code);
if ( size_4 == v5 )
{
    puts("Congrats!");
    s = malloc(0x40uLL);
    memset(s, 0, 0x40uLL);
    stream = fopen("flag.txt", "rb");
    fgets((char *)s, 64, stream);
```
分析下代码逻辑:
>1、生成随机数,随机数randkey和code(固定)作为输入传入加密函数sub_C31中并返回一个加密后的结果y1,并输出"you number is: y";
2、我们输入一个inputkey,inputkey和 code(固定)作为输入传入加密函数sub_C31中并返回一个加密后的结果y2
3、看来只要y1==y2就行了

**结论:通过分析如果y1=y2,那么inputkey和randkey必然相等,但是randkey是无法预测的,只能从加密函数sub_C31分析依据解密解果推导输入的随机数即可。**

我们看下sub_C31
```
__int64 __fastcall sub_C31(unsigned int input, int codelen, char *a3)
{
int v3; // eax
int v4; // eax
unsigned __int8 v7; //
unsigned __int8 v8; //
unsigned __int8 v9; //
unsigned __int8 v10; //
unsigned __int8 v11; //
char v12; //
int v13; //
unsigned int v14; //
unsigned int v15; //
int v16; //
int v17; //
int i; //

v17 = 0;
v16 = 0;
v15 = 0;
v14 = 0;
v13 = 0;
for ( i = 0; i < codelen; ++i )
{
    v7 = a3;
    if ( (v7 & 1) != 0 )
    {
      input ^= dword_202020;
      v13 = ((_BYTE)v13 + 1) & 0xF;
    }
    v8 = v7 >> 1;
    v3 = v8 & 3;
    if ( v3 == 2 )
    {
      v15 = dword_202020 & 0xAABBCCDD;
      v13 = ((_BYTE)v13 + 1) & 0xF;
      v9 = v8 >> 2;
    }
    else if ( v3 == 3 )
    {
      input += v14 + v15;
      v15 = 0;
      v14 = 0;
      v9 = v8 >> 2;
    }
    else
    {
      if ( v3 == 1 )
      {
      v14 = dword_202020 | 0xABCDABCD;
      v13 = ((_BYTE)v13 + 1) & 0xF;
      }
      v9 = v8 >> 2;
    }
    if ( (v9 & 1) != 0 )
      input = ~input;
    v10 = v9 >> 1;
    if ( (v10 & 1) != 0 )
      input ^= (((input << 16) ^ input) >> 16) ^ (input << 16);
    v11 = v10 >> 1;
    v4 = v11 & 3;
    if ( v4 == 2 )
    {
      v17 = dword_202020 - 539034144;
      v13 = ((_BYTE)v13 + 1) & 0xF;
      v12 = v11 >> 2;
    }
    else if ( v4 == 3 )
    {
      input += v16 + v17;
      v17 = 0;
      v16 = 0;
      v12 = v11 >> 2;
    }
    else
    {
      if ( v4 == 1 )
      {
      v16 = 539034132 * dword_202020;
      v13 = ((_BYTE)v13 + 1) & 0xF;
      }
      v12 = v11 >> 2;
    }
    if ( (v12 & 1) != 0 )
      input = input - (input & 7) - (input & 7) + 7;
}
return input;
}
```
好家伙有点长,大循环 套小循环,先大橘关分析下:
>1)dword_202020是固定数组,可以认为是常量
2)**整个循环计算是依据code来驱动的,也就是说,code不变循环流程不变 (很关键)**
3)最后返回变形(加密)后的input

**分析下,这么多循环逆向出来太阳估计下山了,想用angr直接 躺平但是angr遇到循环怂得一B基本跑不出结果,怎么办?**

一个声音响起上述分析中如果加密的序列就不会变,那么把**序列输出**出来是不是就更加简化流程,而且还去掉了循环了吗,先试试?

于是我把加密函数copy出来,然后在每个处理input地方家了打印,以便输出固定的加密序列,修改后的加密函数:
```
__int64 sub_C31(unsigned int key, int bufsize, unsigned char *buf)
{
int v3; // eax
int v4; // eax
unsigned __int8 v7; //
unsigned __int8 v8; //
unsigned __int8 v9; //
unsigned __int8 v10; //
unsigned __int8 v11; //
char v12; //
int v13; //
unsigned int v14; //
unsigned int v15; //
int v16; //
int v17; //
int i; //

v17 = 0;
v16 = 0;
v15 = 0;
v14 = 0;
v13 = 0;
for ( i = 0; i < bufsize; ++i )
{
    v7 = buf;
    if ( (v7 & 1) != 0 )
    {
      key ^= dword_202020;
      printf("key ^= dword_202020[%d];\n", v13);
      v13 = ((_BYTE)v13 + 1) & 0xF;
    }
    v8 = v7 >> 1;
    v3 = v8 & 3;
    if ( v3 == 2 )
    {
      v15 = dword_202020 & 0xAABBCCDD;
      v13 = ((_BYTE)v13 + 1) & 0xF;
      v9 = v8 >> 2;
    }
    else if ( v3 == 3 )
    {
      key += v14 + v15;
      printf("key += %u;\n", v14 + v15);
      v15 = 0;
      v14 = 0;
      v9 = v8 >> 2;
    }
    else
    {
      if ( v3 == 1 )
      {
      v14 = dword_202020 | 0xABCDABCD;
      v13 = ((_BYTE)v13 + 1) & 0xF;
      }
      v9 = v8 >> 2;
    }
    if ( (v9 & 1) != 0 ) {
      key = ~key;
      printf("key=~key;\n");
    }

    v10 = v9 >> 1;
    if ( (v10 & 1) != 0 ) {
      key ^= (((key << 16) ^ key) >> 16) ^ (key << 16);
      printf("key ^= (((key << 16) ^ key) >> 16) ^ (key << 16);\n");
    }
    v11 = v10 >> 1;
    v4 = v11 & 3;
    if ( v4 == 2 )
    {
      v17 = dword_202020 - 539034144;
      v13 = ((_BYTE)v13 + 1) & 0xF;
      v12 = v11 >> 2;
    }
    else if ( v4 == 3 )
    {
      key += v16 + v17;
      printf("key += %u;\n", v16 + v17);
      v17 = 0;
      v16 = 0;
      v12 = v11 >> 2;
    }
    else
    {
      if ( v4 == 1 )
      {
      v16 = 539034132 * dword_202020;
      v13 = ((_BYTE)v13 + 1) & 0xF;
      }
      v12 = v11 >> 2;
    }
    if ( (v12 & 1) != 0 ) {
      key = key - (key & 7) - (key & 7) + 7;
      printf("key = key - (key & 7) - (key & 7) + 7;\n");
    }
}
return key;
}
```
输出序列
```
key ^= dword_202020;
    key = ~key;
    key ^= (((key << 16) ^ key) >> 16) ^ (key << 16);
    key ^= dword_202020;
    key = key - (key & 7) - (key & 7) + 7;
    key = ~key;
    key ^= (((key << 16) ^ key) >> 16) ^ (key << 16);
    key += 1256252113;
    key ^= dword_202020;
    key += 636006435;
    key += 0;
    key ^= dword_202020;
    key ^= (((key << 16) ^ key) >> 16) ^ (key << 16);
    key = key - (key & 7) - (key & 7) + 7;
    key ^= dword_202020;
    key = ~key;
    key += 1908961232;
    key = key - (key & 7) - (key & 7) + 7;
    key ^= dword_202020;
    key ^= (((key << 16) ^ key) >> 16) ^ (key << 16);
    key += 0;
    key = key - (key & 7) - (key & 7) + 7;
    key ^= dword_202020;
    key ^= (((key << 16) ^ key) >> 16) ^ (key << 16);
    key = key - (key & 7) - (key & 7) + 7;
    key ^= dword_202020;
    key += 3791846473;
    key = ~key;
    key = key - (key & 7) - (key & 7) + 7;
    key ^= (((key << 16) ^ key) >> 16) ^ (key << 16);
    key = key - (key & 7) - (key & 7) + 7;
    key = ~key;
    key ^= (((key << 16) ^ key) >> 16) ^ (key << 16);
    key = key - (key & 7) - (key & 7) + 7;
    key ^= (((key << 16) ^ key) >> 16) ^ (key << 16);
    key += 3767795326;
    key = key - (key & 7) - (key & 7) + 7;
    key ^= dword_202020;
    key += 371756133;
    key = ~key;
    key = key - (key & 7) - (key & 7) + 7;
    key ^= dword_202020;
    key ^= (((key << 16) ^ key) >> 16) ^ (key << 16);
    key = key - (key & 7) - (key & 7) + 7;
```

      很起来很nice,虽然也是很长不过流程更清晰了,现在呢手工逆感觉还是很难,比如最后一个方程想半天也没办法手工逆向(数学知识不扎实), 还是用angr躺吧, 因此我把序列写了个小程序,再用angr跑。
      ```
      #include <stdio.h>

unsigned int dword_202020 = {
    0x24DD20CF, 0x3E4F0354, 0x18B2E85F, 0x2F2CAFB8, 0x5810ADCB, 0x42F7FF85, 0x36E0D6C2, 0x5F3EF93F,
    0x7F46E74A, 0x44DDC864, 0x64959795, 0x39413451, 0x5DC36C45, 0x62037E7E, 0x5AEA541F, 0x153F8FAC};


int calc(unsigned int key)
{
    key ^= dword_202020;
    key = ~key;
    key ^= (((key << 16) ^ key) >> 16) ^ (key << 16);
    key ^= dword_202020;
    key = key - (key & 7) - (key & 7) + 7;
    key = ~key;
    key ^= (((key << 16) ^ key) >> 16) ^ (key << 16);
    key += 1256252113;
    key ^= dword_202020;
    key += 636006435;
    key += 0;
    key ^= dword_202020;
    key ^= (((key << 16) ^ key) >> 16) ^ (key << 16);
    key = key - (key & 7) - (key & 7) + 7;
    key ^= dword_202020;
    key = ~key;
    key += 1908961232;
    key = key - (key & 7) - (key & 7) + 7;
    key ^= dword_202020;
    key ^= (((key << 16) ^ key) >> 16) ^ (key << 16);
    key += 0;
    key = key - (key & 7) - (key & 7) + 7;
    key ^= dword_202020;
    key ^= (((key << 16) ^ key) >> 16) ^ (key << 16);
    key = key - (key & 7) - (key & 7) + 7;
    key ^= dword_202020;
    key += 3791846473;
    key = ~key;
    key = key - (key & 7) - (key & 7) + 7;
    key ^= (((key << 16) ^ key) >> 16) ^ (key << 16);
    key = key - (key & 7) - (key & 7) + 7;
    key = ~key;
    key ^= (((key << 16) ^ key) >> 16) ^ (key << 16);
    key = key - (key & 7) - (key & 7) + 7;
    key ^= (((key << 16) ^ key) >> 16) ^ (key << 16);
    key += 3767795326;
    key = key - (key & 7) - (key & 7) + 7;
    key ^= dword_202020;
    key += 371756133;
    key = ~key;
    key = key - (key & 7) - (key & 7) + 7;
    key ^= dword_202020;
    key ^= (((key << 16) ^ key) >> 16) ^ (key << 16);
    key = key - (key & 7) - (key & 7) + 7;
    return key;
}

int main(void) {
    unsigned int x = 0;
    unsigned int y = 0;
    scanf("%u", &x);
    y = calc(x);

    if (y == 514763580) { //**比赛实际服务器返回的number**)
      printf("right\n");
    }else {
      printf("wrong\n");
    }
}
      ```
编译好gcc -o f fk.c,生成f, 拿出angr模版跑反向推导rnd
```
import angr
import angr
import claripy
import sys


def main():
    print("*** solving ***:", sys.argv)
    p = angr.Project(sys.argv, load_options={"auto_load_libs": True})

    '''
    带参数:
    flag_chars =
    flag = claripy.Concat(*flag_chars)

    #对于 c++ 的程序,如果调用了 c++ 的函数,使用 full_init_state
    state = p.factory.full_init_state(add_options=angr.options.unicorn, args=, flag])
    '''
    state = p.factory.entry_state()

    '''
    添加约束
    for c in flag_chars:
      state.solver.add(c > 32)
      state.solver.add(c < 127)
    '''

    # 在入口准备开始符号执行
    sm = p.factory.simgr(state)

    def good(state):
      stdout_output = state.posix.dumps(sys.stdout.fileno())
      return 'right'.encode() in stdout_output# :boolean

    def bad(state):
      stdout_output = state.posix.dumps(sys.stdout.fileno())
      return 'wrong'.encode() in stdout_output# :boolean

    '''
    遍历可能的路径并找到成功的那条路径
    最终想找到的路径是0x804868B,要避开的路径是0x804869E,这里可以写多个avoid,用[]
    '''
    sm.explore(find=good, avoid=bad)

    if sm.found:
      # print(sm.found.solver.eval(flag, cast_to=bytes)
      # print(sm.found.posix.dumps(sys.stdout.fileno())
      print(sm.found.posix.dumps(sys.stdin.fileno()))
    else:
      print("Not found")
    print("Done")


if __name__ == '__main__':
    main()
```

几秒就 出结果了 。
```
*** solving ***: f
WARNING | 2022-11-22 18:41:02,418 | cle.loader | The main binary is a position-independent executable. It is being loaded with a base address of 0x400000.
WARNING | 2022-11-22 18:41:02,437 | angr.storage.memory_mixins.default_filler_mixin | The program is accessing memory with an unspecified value. This could indicate unwanted behavior.
WARNING | 2022-11-22 18:41:02,437 | angr.storage.memory_mixins.default_filler_mixin | angr will cope with this by generating an unconstrained symbolic variable and continuing. You can resolve this by:
WARNING | 2022-11-22 18:41:02,437 | angr.storage.memory_mixins.default_filler_mixin | 1) setting a value to the initial state
WARNING | 2022-11-22 18:41:02,437 | angr.storage.memory_mixins.default_filler_mixin | 2) adding the state option ZERO_FILL_UNCONSTRAINED_{MEMORY,REGISTERS}, to make unknown regions hold null
WARNING | 2022-11-22 18:41:02,437 | angr.storage.memory_mixins.default_filler_mixin | 3) adding the state option SYMBOL_FILL_UNCONSTRAINED_{MEMORY,REGISTERS}, to suppress these messages.
WARNING | 2022-11-22 18:41:02,437 | angr.storage.memory_mixins.default_filler_mixin | Filling memory at 0x7fffffffffeff9c with 4 unconstrained bytes referenced from 0x401065 (_start+0x5 in f (0x1065))
b'1431956068'
Done
```

bingo:
```
└─# ./bits
Your number is 514763580
Your answer: 1431956068
Congrats!
```

Hmily 发表于 2022-11-23 23:45

I D:b1gcat
邮箱:admistrator@foxmail.com

申请通过,欢迎光临吾爱破解论坛,期待吾爱破解有你更加精彩,ID和密码自己通过邮件密码找回功能修改,请即时登陆并修改密码!
登陆后请在一周内在此帖报道,否则将删除ID信息。

ps:登陆后请把文章整理发布到脱壳破解区。

b1gcat 发表于 2022-11-24 11:38

新人前来报道
页: [1]
查看完整版本: 申请ID:b1gcat【申请通过】