吾爱破解 - LCG - LSG |安卓破解|病毒分析|www.52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

搜索
查看: 6179|回复: 42

[CTF] [网鼎杯 2020 青龙组]singal(补充)

  [复制链接]
Ginobili 发表于 2020-10-29 14:37

这个题目方法有很多,目的是练习算法的分析和vm练习,尝试了静态直接逆算法和动态静态结合的方法

方法1

拖入IDA找到main函数

int __cdecl main(int argc, const char **argv, const char **envp)
{
  int v4; // [sp+18h] [bp-1D4h]@1

  __main();
  qmemcpy(&v4, &unk_403040, 0x1C8u);//拷贝opcode表
  vm_operad(&v4, 114);//关键函数
  puts("good,The answer format is:flag {}");
  return 0;
}

找到关键函数

int __cdecl vm_operad(int *a1, int a2)
{
  int result; // eax@2
  char v3[100]; // [sp+13h] [bp-E5h]@4
  char v4[100]; // [sp+77h] [bp-81h]@5
  char v5; // [sp+DBh] [bp-1Dh]@5
  int v6; // [sp+DCh] [bp-1Ch]@1
  int v7; // [sp+E0h] [bp-18h]@1
  int v8; // [sp+E4h] [bp-14h]@1
  int v9; // [sp+E8h] [bp-10h]@1
  int v10; // [sp+ECh] [bp-Ch]@1

  v10 = 0;
  v9 = 0;
  v8 = 0;
  v7 = 0;
  v6 = 0;
  while ( 1 )
  {
    result = v10;
    if ( v10 >= a2 )
      return result;
    switch ( a1[v10] )                          // a1是opcode  v10是索引
    {
      case 10:                                  // 输入点
        read(v3);
        ++v10;
        break;
      case 1:
        v4[v7] = v5;
        ++v10;
        ++v7;
        ++v9;
        break;
      case 2:
        v5 = a1[v10 + 1] + v3[v9];
        v10 += 2;
        break;
      case 3:
        v5 = v3[v9] - LOBYTE(a1[v10 + 1]);
        v10 += 2;
        break;
      case 4:
        v5 = a1[v10 + 1] ^ v3[v9];
        v10 += 2;
        break;
      case 5:
        v5 = a1[v10 + 1] * v3[v9];
        v10 += 2;
        break;
      case 6:
        ++v10;
        break;
      case 7:
        if ( v4[v8] != a1[v10 + 1] )            // 最终判断 a1->v5->v4
        {
          printf("what a shame...");
          exit(0);
        }
        ++v8;
        v10 += 2;
        break;
      case 11:
        v5 = v3[v9] - 1;
        ++v10;
        break;
      case 12:
        v5 = v3[v9] + 1;
        ++v10;
        break;
      case 8:
        v3[v6] = v5;
        ++v10;
        ++v6;
        break;
      default:
        continue;
    }
  }
}

可以判断这是一个vm逆向,分析逻辑,输入点是case 10最后的判断位置是case 7。v4由v5得到,且必经过case 1,v5由opcode经过运算得到,所以我们可以模拟算法,先找出每次switch使用哪个索引值v10和整个过程中进行比较的opcode v4。
逆向算法如下

#include <stdio.h>
#include <string.h>
unsigned char opcode[] =
{
  0x0A, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x10, 0x00,
  0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00,
  0x05, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x04, 0x00,
  0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00,
  0x05, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0x00,
  0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
  0x08, 0x00, 0x00, 0x00, 0x0B, 0x00, 0x00, 0x00, 0x01, 0x00,
  0x00, 0x00, 0x0C, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00,
  0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00,
  0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00,
  0x08, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x21, 0x00,
  0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0B, 0x00, 0x00, 0x00,
  0x08, 0x00, 0x00, 0x00, 0x0B, 0x00, 0x00, 0x00, 0x01, 0x00,
  0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00,
  0x08, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x20, 0x00,
  0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
  0x51, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x04, 0x00,
  0x00, 0x00, 0x24, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
  0x0C, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x0B, 0x00,
  0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00,
  0x02, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x02, 0x00,
  0x00, 0x00, 0x25, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
  0x02, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x00, 0x08, 0x00,
  0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x41, 0x00, 0x00, 0x00,
  0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x20, 0x00,
  0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00,
  0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x05, 0x00,
  0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00,
  0x02, 0x00, 0x00, 0x00, 0x25, 0x00, 0x00, 0x00, 0x01, 0x00,
  0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00,
  0x08, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x20, 0x00,
  0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
  0x41, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x0C, 0x00,
  0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00,
  0x22, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x3F, 0x00,
  0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x34, 0x00, 0x00, 0x00,
  0x07, 0x00, 0x00, 0x00, 0x32, 0x00, 0x00, 0x00, 0x07, 0x00,
  0x00, 0x00, 0x72, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00,
  0x33, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x18, 0x00,
  0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0xA7, 0xFF, 0xFF, 0xFF,
  0x07, 0x00, 0x00, 0x00, 0x31, 0x00, 0x00, 0x00, 0x07, 0x00,
  0x00, 0x00, 0xF1, 0xFF, 0xFF, 0xFF, 0x07, 0x00, 0x00, 0x00,
  0x28, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x84, 0xFF,
  0xFF, 0xFF, 0x07, 0x00, 0x00, 0x00, 0xC1, 0xFF, 0xFF, 0xFF,
  0x07, 0x00, 0x00, 0x00, 0x1E, 0x00, 0x00, 0x00, 0x07, 0x00,
  0x00, 0x00, 0x7A, 0x00, 0x00,
};
int *a = (int *)opcode;

int __cdecl vm_operad(int *opcode, int a2)
{
  int order[114] = {};//执行顺序
  char flag[100]; // [esp+13h] [ebp-E5h]
  char v4[100]; // [esp+77h] [ebp-81h]
  char v5; // [esp+DBh] [ebp-1Dh]
  int m; // [esp+DCh] [ebp-1Ch]
  int z; // [esp+E0h] [ebp-18h]
  int y; // [esp+E4h] [ebp-14h]
  int x; // [esp+E8h] [ebp-10h]
  int i; // [esp+ECh] [ebp-Ch]
  int s = 0;
  i = 0;
  x = 0;
  y = 0;
  z = 0;
  m = 0;
  while (1)
  {
    if ( i >= a2 )//继续循环
      break;
    switch ( opcode[i] )
    {
      case 1:
        v4[z] = v5;
        ++i;
        ++z;
        ++x;
        break;
      case 2:
        v5 = opcode[i + 1] + flag[x];
        i += 2;
        break;
      case 3:
        v5 = flag[x] - opcode[i + 1];
        i += 2;
        break;
      case 4:
        v5 = opcode[i + 1] ^ flag[x];
        i += 2;
        break;

      case 5:
        v5 = opcode[i + 1] * flag[x];
        i += 2;
        break;
      case 6:
        ++i;
      case 7:
        v4[y] = opcode[i + 1];
        printf("%#x, ",v4[y]);//打印比较的opcode
        ++y;
        i += 2;
        break;
      case 8:
        flag[m] = v5;
        ++i;
        ++m;
        break;
      case 10:
        scanf("%s",flag);//输入点
        ++i;
        break;
      case 11:
        v5 = flag[x] - 1;
        ++i;
        break;
      case 12:
        v5 = flag[x] + 1;
        ++i;
        break;
    }
    //printf("%d, ",i);//打印每次使用的索引
  }
}

int main()
{
    vm_operad(a,114);
    return 0;
}

得到

  char order[100] = {1, 3, 4, 6, 7, 9, 10, 12, 13, 15, 16, 17, 18, 19, 20, 22, 23, 25, 26, 28, 29, 30, 31, 32, 33, 35, 36, 38, 39, 41, 42, 44, 45, 46, 47, 48, 49, 51, 52, 54, 55, 57, 58, 60, 61, 63, 64, 66, 67, 69, 70, 72, 73, 75, 76, 78, 79, 81, 82, 83, 84, 86, 88, 90, 92, 94, 96, 98, 100, 102, 104, 106, 108, 110, 112, 114};
  unsigned char v4[] = {0x22, 0x3f, 0x34, 0x32, 0x72, 0x33, 0x18, 0xffffffa7, 0x31, 0xfffffff1, 0x28, 0xffffff84, 0xffffffc1, 0x1e, 0x7a};

确定了中间结果根据算法写逆向算法,注意:正向的算法逆回去需要从后往前来走

int __cdecl vm_decode(int *opcode,int len_114)
{
  char order[100] = {1, 3, 4, 6, 7, 9, 10, 12, 13, 15, 16, 17, 18, 19, 20, 22, 23, 25, 26, 28, 29, 30, 31, 32, 33, 35, 36, 38, 39, 41, 42, 44, 45, 46, 47, 48, 49, 51, 52, 54, 55, 57, 58, 60, 61, 63, 64, 66, 67, 69, 70, 72, 73, 75, 76, 78, 79, 81, 82, 83, 84, 86, 88, 90, 92, 94, 96, 98, 100, 102, 104, 106, 108, 110, 112, 114};
  unsigned char v4[] = {0x22, 0x3f, 0x34, 0x32, 0x72, 0x33, 0x18, 0xffffffa7, 0x31, 0xfffffff1, 0x28, 0xffffff84, 0xffffffc1, 0x1e, 0x7a};
  unsigned char flag[100] = {}; // [esp+13h] [ebp-E5h]
  int v5; // [esp+DBh] [ebp-1Dh]
  int m; // [esp+DCh] [ebp-1Ch]
  int z; // [esp+E0h] [ebp-18h]
  int x; // [esp+E8h] [ebp-10h]
  int i; // [esp+ECh] [ebp-Ch]
  x = 15;
  z = 15;
  m = 15;
  for(int k=strlen(order) - 1;k>=0;k--)//从后往前
  {
    i = order[k];
    switch ( opcode[i] )
    {
      case 1:
        --x;
        --z;
        v5 = v4[z];
        break;
      case 2:
        flag[x] = v5 - opcode[i + 1];
        break;
      case 3:
        flag[x] = v5 + opcode[i + 1];
        break;
      case 4:
        flag[x] = v5 ^ opcode[i + 1];
        break;
      case 5:
        flag[x] = v5 / opcode[i + 1];
        break;
      case 6:
        break;
      case 8:
        v5 = flag[--m];
        break;
      case 11:
        flag[x] = v5 + 1;
        break;
      case 12:
        flag[x] = v5 - 1;
        break;
    }
  }
  printf("%s",flag);
  return 0;
}

得到flag{757515121f3d478}

方法2

使用动态调试的方法得到opcode和算法。
回到vm算法,可以把断点断在case 7 的cmp上,这样可以根据eax和edx的值一步一步定位使用的opcode值和算法
在这里插入图片描述
在这里插入图片描述

地址是004016E2,动态调试,使用od修改jz为jmp观察这个位置的寄存器数据为0x22
在这里插入图片描述

依次运行得到v4的值
unsigned char v4[] = {0x22, 0x3f, 0x34, 0x32, 0x72, 0x33, 0x18, 0xffffffa7, 0x31, 0xfffffff1, 0x28, 0xffffff84, 0xffffffc1, 0x1e, 0x7a};
下面分析算法,用ida从输入点进行分析,下面举第一个字符的例子:
1.首先走到case10读取字符串
在这里插入图片描述
2.走到case4执行抑或操作抑或的对象是第一个字符
在这里插入图片描述
在这里插入图片描述
3.再往下到case8赋值操作
在这里插入图片描述
4.再往后到了case3,相减操作操作数是5
在这里插入图片描述
在这里插入图片描述
5.再往下到case1 case1是每个opcode的进行判段前的赋值,在这里结束第一个字符进入第二个字符
在这里插入图片描述
这五步的计算结果就是0x22 = (flag[0] ^ 0xa) - 5
依次动态调试分析出15个算法分别为

0x22 = (flag[0] ^ 0xa) - 5

0x3f = (flag[1] ^ 0x20) * 3

0x34 = (flag[2] - 2) - 1

0x32 = (flag[3] + 1) ^ 4

0x72 = (flag[4] * 3) - 0x21

0x33 = (flag[5] - 1) - 1 

0x18 = (flag[6] ^ 9) - 0x20

0xffffffa7 = (flag[7] + 0x51) ^ 0x24

0x31 = (flag[8] +1 ) - 1

0xfffffff1 = (flag[9] * 2) + 0x25

0x28 = (flag[10] + 0x36) ^ 0x41

0xffffff84 = (flag[11] + 0x20) * 1

0xffffffc1 = (flag[12] *3) + 0x25

0x1e = (flag[13] ^ 9) - 0x20

0x7a = (flag[14] + 0x41) + 1 

可以写脚本得到每个的值拼接

#coding=utf-8
b=[34, 63, 52, 50,   114, 51, 24, 256-89,  49,256-15, 40,256-124,  256-63,  30,  122]#(负数取了最低位,因此,直接取后面那部分)
flag=[0]*15
flag[0]=(b[0]+5)^0x10
flag[1]=(b[1]//3)^0x20
flag[2]=(b[2]+3)
flag[3]=(b[3]^4)-1
flag[4]=(b[4]+0x21)//3
flag[5]=b[5]+2
flag[6]=(b[6]+0x20)^9
flag[7]=(b[7]^0x24)-0x51
flag[8]=b[8]
flag[9]=(b[9]-0x25)//2
flag[10]=(b[10]^0x41)-0x36
flag[11]=(b[11]-0x20)
flag[12]=(b[12]-0x25)//3
flag[13]=(b[13]+0x20)^9
flag[14]=(b[14]-1)-0x41
result = ""
for i in range(len(flag)):
        result += chr(flag[i])
print(result)

得到flag{757515121f3d478}

另外的思想

关键点在case 7和case1 指向了v4的来源
在case7中a1[v10]等于7,所以a1[v10 + 1]就是opcode中数值为7的下一个数字(int为四个字节)
在这里插入图片描述
可以直接静态分析出使用的opcode
除此之外,希望看点骚操作的师傅请移步李师傅的吾爱论坛还有小弟的论坛简单的符号执行讲解
https://www.52pojie.cn/thread-1176826-1-1.html
https://www.52pojie.cn/thread-1187493-1-1.html

免费评分

参与人数 24威望 +2 吾爱币 +122 热心值 +21 收起 理由
zy349898114 + 1 + 1 鼓励转贴优秀软件安全工具和文档!
burpliu + 1 我很赞同!
最心疼的玩笑 + 1 + 1 热心回复!
封心以绝尘 + 1 + 1 我很赞同!
Jedis + 1 + 1 热心回复!
p1kachuu + 1 谢谢@Thanks!
哭泣泣 + 1 + 1 用心讨论,共获提升!
小橙子Top + 1 + 1 用心讨论,共获提升!
Jetset + 1 + 1 很好地思路
DarkRide + 1 + 1 我很赞同!
活到lao学到lao + 1 + 1 谢谢@Thanks!
siuhoapdou + 1 + 1 用心讨论,共获提升!
soyiC + 1 + 1 谢谢@Thanks!
Cinwa + 1 用心讨论,共获提升!
hzpqvq + 1 谢谢@Thanks!
fengbolee + 1 + 1 用心讨论,共获提升!
tflyr + 2 + 1 谢谢@Thanks!
Hmily + 2 + 100 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
JIAN_ + 1 + 1 用心讨论,共获提升!
duhao1027 + 1 + 1 热心回复!
小朋友呢 + 2 + 1 我很赞同!
w516258928 + 1 热心回复!
wmsuper + 1 + 1 用心讨论,共获提升!
Mr-墨云 + 1 用心讨论,共获提升!

查看全部评分

发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

小朋友呢 发表于 2020-11-2 15:46
楼主,我照着你的代码用python跑了一下,发现下面这个地方我们有点不同
[C] 纯文本查看 复制代码
      case 2:
        flag[x] = v5 - opcode[i + 1]; //逆运算时,这里应该是 - 1 
        break;
      case 3:
        flag[x] = v5 + opcode[i + 1];
        break;
      case 4:
        flag[x] = v5 ^ opcode[i + 1];
        break;
      case 5:
        flag[x] = v5 / opcode[i + 1];
        break;
csw曾哥 发表于 2020-11-4 10:38
不错嘛,感谢分享,之前这道题没做出来,现在看了一下,感觉有点惋惜
熙晓川 发表于 2020-10-29 14:45
panhaihaihai 发表于 2020-10-29 14:47
额,是个不错的操作我挺喜欢的。
一线码农 发表于 2020-10-29 14:50

看不懂。。。占个楼
aa77ss55dd 发表于 2020-10-29 14:53
这操作我喜欢
cydhr 发表于 2020-10-29 16:02
看不懂 但是很羡慕
莫丶小白 发表于 2020-10-29 16:05
完全看不懂。。。好尴尬。
timefy 发表于 2020-10-29 16:34
这个操作666
Baipg 发表于 2020-10-29 23:33
这才是高端操作
我是新手123 发表于 2020-10-30 09:03
这个是C语言逆向汇编?眼熟。。。
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则 警告:本版块禁止灌水或回复与主题无关内容,违者重罚!

快速回复 收藏帖子 返回列表 搜索

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

GMT+8, 2020-11-24 19:57

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

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