吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 2117|回复: 2
收起左侧

[会员申请] 申请ID:b1gcat【申请通过】

[复制链接]
吾爱游客  发表于 2022-11-23 12:45

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

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

下载题目(bits为64elf文件,core为14字节乱码)

 b1gcat@b1gcat  bits  ls -l
total 32
-rw-rw-r--@ 1 b1gcat  staff  10296  2 15  2021 bits
-rw-rw-r--@ 1 b1gcat  staff     14  2 15  2021 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'
[0x00000af0]> aaa
[x] Analyze all flags starting with sym. and entry0 (aa)
[x] Analyze all functions arguments/locals
[x] Analyze function calls (aac)
[x] Analyze len bytes of instructions for references (aar)
[x] Finding and parsing C++ vtables (avrr)
[[ESIL] Trap, trying to execute on non-executable memory
[x] Type matching analysis for all functions (aaft)
[x] Propagate noreturn information (aanr)
[x] Use -AA or aaaa to perform additional experimental analysis.
[0x00000af0]> iee
[Constructors]
vaddr=0x00000bf0 paddr=0x00000bf0 hvaddr=0x00201d20 hpaddr=0x00001d20 type=init
vaddr=0x00000bb0 paddr=0x00000bb0 hvaddr=0x00201d28 hpaddr=0x00001d28 type=fini

2 entrypoints
[0x00000af0]> 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; // [rsp+0h] [rbp-40h] BYREF
  unsigned int randkey; // [rsp+4h] [rbp-3Ch]
  unsigned int codelen; // [rsp+8h] [rbp-38h]
  unsigned int size_4; // [rsp+Ch] [rbp-34h]
  void *code; // [rsp+10h] [rbp-30h]
  void *s; // [rsp+18h] [rbp-28h]
  FILE *stream; // [rsp+20h] [rbp-20h]
  unsigned __int64 v13; // [rsp+28h] [rbp-18h]

  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; // [rsp+13h] [rbp-1Dh]
  unsigned __int8 v8; // [rsp+13h] [rbp-1Dh]
  unsigned __int8 v9; // [rsp+13h] [rbp-1Dh]
  unsigned __int8 v10; // [rsp+13h] [rbp-1Dh]
  unsigned __int8 v11; // [rsp+13h] [rbp-1Dh]
  char v12; // [rsp+13h] [rbp-1Dh]
  int v13; // [rsp+14h] [rbp-1Ch]
  unsigned int v14; // [rsp+18h] [rbp-18h]
  unsigned int v15; // [rsp+1Ch] [rbp-14h]
  int v16; // [rsp+20h] [rbp-10h]
  int v17; // [rsp+24h] [rbp-Ch]
  int i; // [rsp+28h] [rbp-8h]

  v17 = 0;
  v16 = 0;
  v15 = 0;
  v14 = 0;
  v13 = 0;
  for ( i = 0; i < codelen; ++i )
  {
    v7 = a3[i];
    if ( (v7 & 1) != 0 )
    {
      input ^= dword_202020[v13];
      v13 = ((_BYTE)v13 + 1) & 0xF;
    }
    v8 = v7 >> 1;
    v3 = v8 & 3;
    if ( v3 == 2 )
    {
      v15 = dword_202020[v13] & 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[v13] | 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[v13] - 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];
        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; // [rsp+13h] [rbp-1Dh]
  unsigned __int8 v8; // [rsp+13h] [rbp-1Dh]
  unsigned __int8 v9; // [rsp+13h] [rbp-1Dh]
  unsigned __int8 v10; // [rsp+13h] [rbp-1Dh]
  unsigned __int8 v11; // [rsp+13h] [rbp-1Dh]
  char v12; // [rsp+13h] [rbp-1Dh]
  int v13; // [rsp+14h] [rbp-1Ch]
  unsigned int v14; // [rsp+18h] [rbp-18h]
  unsigned int v15; // [rsp+1Ch] [rbp-14h]
  int v16; // [rsp+20h] [rbp-10h]
  int v17; // [rsp+24h] [rbp-Ch]
  int i; // [rsp+28h] [rbp-8h]

  v17 = 0;
  v16 = 0;
  v15 = 0;
  v14 = 0;
  v13 = 0;
  for ( i = 0; i < bufsize; ++i )
  {
    v7 = buf[i];
    if ( (v7 & 1) != 0 )
    {
      key ^= dword_202020[v13];
      printf("key ^= dword_202020[%d];\n", v13);
      v13 = ((_BYTE)v13 + 1) & 0xF;
    }
    v8 = v7 >> 1;
    v3 = v8 & 3;
    if ( v3 == 2 )
    {
      v15 = dword_202020[v13] & 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[v13] | 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[v13] - 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];
        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[0];
    key = ~key;
    key ^= (((key << 16) ^ key) >> 16) ^ (key << 16);
    key ^= dword_202020[3];
    key = key - (key & 7) - (key & 7) + 7;
    key = ~key;
    key ^= (((key << 16) ^ key) >> 16) ^ (key << 16);
    key += 1256252113;
    key ^= dword_202020[6];
    key += 636006435;
    key += 0;
    key ^= dword_202020[7];
    key ^= (((key << 16) ^ key) >> 16) ^ (key << 16);
    key = key - (key & 7) - (key & 7) + 7;
    key ^= dword_202020[10];
    key = ~key;
    key += 1908961232;
    key = key - (key & 7) - (key & 7) + 7;
    key ^= dword_202020[12];
    key ^= (((key << 16) ^ key) >> 16) ^ (key << 16);
    key += 0;
    key = key - (key & 7) - (key & 7) + 7;
    key ^= dword_202020[14];
    key ^= (((key << 16) ^ key) >> 16) ^ (key << 16);
    key = key - (key & 7) - (key & 7) + 7;
    key ^= dword_202020[1];
    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[7];
    key += 371756133;
    key = ~key;
    key = key - (key & 7) - (key & 7) + 7;
    key ^= dword_202020[8];
    key ^= (((key << 16) ^ key) >> 16) ^ (key << 16);
    key = key - (key & 7) - (key & 7) + 7;
    很起来很nice,虽然也是很长不过流程更清晰了,现在呢手工逆感觉还是很难,比如最后一个方程想半天也没办法手工逆向(数学知识不扎实), 还是用angr躺吧, 因此我把序列写了个小程序,再用angr跑。
    ```
    #include <stdio.h>

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

int calc(unsigned int key)
{
key ^= dword_202020[0];
key = ~key;
key ^= (((key << 16) ^ key) >> 16) ^ (key << 16);
key ^= dword_202020[3];
key = key - (key & 7) - (key & 7) + 7;
key = ~key;
key ^= (((key << 16) ^ key) >> 16) ^ (key << 16);
key += 1256252113;
key ^= dword_202020[6];
key += 636006435;
key += 0;
key ^= dword_202020[7];
key ^= (((key << 16) ^ key) >> 16) ^ (key << 16);
key = key - (key & 7) - (key & 7) + 7;
key ^= dword_202020[10];
key = ~key;
key += 1908961232;
key = key - (key & 7) - (key & 7) + 7;
key ^= dword_202020[12];
key ^= (((key << 16) ^ key) >> 16) ^ (key << 16);
key += 0;
key = key - (key & 7) - (key & 7) + 7;
key ^= dword_202020[14];
key ^= (((key << 16) ^ key) >> 16) ^ (key << 16);
key = key - (key & 7) - (key & 7) + 7;
key ^= dword_202020[1];
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[7];
key += 371756133;
key = ~key;
key = key - (key & 7) - (key & 7) + 7;
key ^= dword_202020[8];
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[1])
p = angr.Project(sys.argv[1], load_options={"auto_load_libs": True})

'''
带参数:
flag_chars = [claripy.BVS('flag_%d' % i, 8) for i in range(32)]
flag = claripy.Concat(*flag_chars)

#对于 c++ 的程序,如果调用了 c++ 的函数,使用 full_init_state
state = p.factory.full_init_state(add_options=angr.options.unicorn, args=[sys.argv[1], 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[0].solver.eval(flag, cast_to=bytes)
  # print(sm.found[0].posix.dumps(sys.stdout.fileno())
    print(sm.found[0].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_FILLUNCONSTRAINED{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_FILLUNCONSTRAINED{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
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - LCG - LSG ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2024-11-24 05:39

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表