Skip to content

Reverse部分

之前从没碰过 Re(看着难,本人懒,于是鸽),这一次趁着 Viking 的招新摸一下(以题促学?),

感谢 @Vior gaygay 领我入门😘

因为是速成,其实也没专门看过逆向的书什么的(Google Everything),所以把 wp 写啰嗦点

承接上文,这篇文章不太会涉及原理了哈哈,主要是来自 Web 手的 IDA 的愤怒

某种意义上来说这也是个人 IDA 工具使用的过程经验

提一嘴:

  • IDA 版本 IDA_Pro_v7.5 (建议支持正版😁)
  • Kali 下 upx -d [filename] 脱壳

1. 小白の初探1(签到)

Description:

孙悟空再次被压于五指山下,1000年来动弹不得,任凭风吹雨打,心力交瘁。一日正午恍惚间只见远处走来一人,未待看清,顷刻山崩地裂山河改道飞沙走石,五指山竟然裂开了,孙悟空从碎石中飞一般的蹦出,奔向来人,跪地流泪道“请问来者何人”,来人颔首微笑“IDA! yyds!!!”,说罢开着挖掘机远去。

Attachment: exe

  • IDA F5大法

首先注意这是一个 PE+ 文件,需要 IDA 64 位打开(用 32 位也会叫你换的,签到题就无脑上 IDA 了,也可以先用 Linux 命令$ file [filename]康康)(后面有兴趣了还可以做个右键菜单自动调用)

进来长这样,

image-20210327114147557

这里就不直接无脑 F5 了,因为是题已经做了一轮下来才写的 wp,利用这张图说一下整个界面(第一次用 IDA 的时候看着一堆英文,而且打开之前还往往有各种弹窗确认信息,可惜我已经没了当初的那种探索欲,其实多用用就渐渐摸熟了)

看下界面结构,分成上面的菜单以及工具栏(彩色方块区大概是对程序的分析,看看紧跟的注释就好),

然后是 3 个 Window(从图上请寻找 Funtion window, IDA View-A, Output window),Graph overview 不在此列(可以发现这个窗口是没法让光标过去的),每个 Window 窗口可以继续往里面塞 subview,刚刚提到光标也是为了说明新开的 subview 会塞进当前光标所在窗口,而刚进去默认是聚焦在 IDA View-A(后面我称这个窗口为主视图),所以按 F5 的时候会在最大的窗口切换视图,

下面还有个 Python 终端供你使用(这里还可以涉及到 IDA python2 和 python3的问题)

我目前主要用到的视图都是 IDA View(Disassembly 反汇编), Pseudocode(伪代码 F5), Strings window(字符串 Shift + F12), Hex view(16进制视图)

我刚开始注意到 Function name 是 Vior 提示我可以点进去看下 exit, main, start 等等函数(if exist),然后按 x 键交叉引用跳转的方法后面再说,总之双击函数名的时候主视图相应会跳转到该函数所在代码区,双击变量则会跳到对应数据区(伪代码双击变量会新开一个 stack of [function] 的视图)

比如本题你可以看到 main 函数下面就有 getflag(void),双击之:image-20210327125936993

IDA 很人性化的会用分号 ; 给出注释(在光标所在行按 ; 键可以添加自己的注释),右侧那个灰色的东东就是 aVikingW1ecomeT 这个字符数组存放的字符串,说到这儿,你可以把鼠标移动到 aVikingW1ecomeT 上面查看详细信息:image-20210327130708104

像这样,你只要看过一点点 C 语言就能明白这是个字符数组

额,让我们回到正题 F5 :image-20210327131226316

这个伪代码可以说是十分清晰了,你可以通过双击去进一步查看函数的具体内容:image-20210327131445692

不止是在伪代码界面,IDA View也可以这样双击函数(蓝色的)或变量然后在当前视图下追踪,你看过了之后还可以点击工具栏处的左箭头←回到上一处视角,

正好这里又可以提到 Vior 教我的技巧,按 Tab 键可以在 Pseudocode 和 IDA View 视图进行对应跳转,

还有能让事情变得更清晰的:image-20210327132216420

可以看到原本为整数 92 的地方变成了其对应的 Ascii 字符 \ (此处两道反斜线的原因是转义,不过实际我在运行这个 exe 文件的时候发现输入一道或两道都会打印出 flag),在该处右键,IDA 为我们提供了几种转化选择,按需取用即可,也可以让光标停留在这里按快捷键(if exist)直接转化,比如此处可以鼠标点一下让光标停在 92 的位置,然后按下 r 键转化成对应字符

2. 小白の初探2

Description:

小明发现有人把flag加密了一下下

Attachment:

babyEncrypted.exe

  • 追踪函数

说真的,这道题困住我的时间比后面有的题还长,基础的知识缺了点

这次是个 32 位 PE 文件(not PE+),用 IDA 32-bit 或 64-bit 都可以打开,但是只有用 32 位打开的时候才能 decompile 查看伪代码,所以你懂的

下面⏩到思路是在 Function name 找到 exit 函数,然后光标停在上面按 x 查看交叉引用:image-20210327134841757

这样做的原因是你从刚进来的 start 函数查看的话是难以找到程序主逻辑的(直接按 F5 你也看不到主程序处的伪代码),这里的技巧就是「寻找出口」,在交叉引用窗口双击对应位置可以跳转到调用函数的代码区:image-20210327135331336

这里 2 个黄色的 exit 就是其所有的调用了,顺便你可能发现窗口有点不一样,这是因为我把 IDA View-A 这个子视图单独从窗口中拖出来然后最大化了,至于怎么塞回去就别问我了。。。😅(目前只知道在菜单栏处的 Windows 可以 reset desktop 重置为刚打开时的桌面,这也意味着你开的伪代码或字符串视图会 GG)

现在 F5 回归题目:image-20210327140601049

在这里因为我已经操作过一会儿了,v1 前的 int 和 25 处的 fixed 都是新增的,

逻辑也较为清楚,主要是两个 if 判断以及中间夹着的子函数,这种 sub_ 开头的也是函数,但它们不是像 scanf 和 printf 这种 C 语言库里本就有的函数(可能是别人自己编写的),总不至于就着编译出来的程序反向把别人的函数名都还原出来吧,戳进第一个 if 里的函数看逻辑:image-20210327141619364

主要也就是这个循环看不懂,当时没整明白在干甚么,其实搜一下 *(_BYTE *) 就差不多能理解的(注意是Google 英文结果🙏)image-20210327142255396

总之,这个函数返回了参数 a1 的长度,整个 if 语句就在检测我们输入的字符串长度是否为 25

后面两处函数:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
//if ( sub_401460((int)v1) != 25 )
//    exit(-1);
//sub_401487(v1);
int __cdecl sub_401487(int a1)
{
  int result; // eax
  int i; // [esp+10h] [ebp-4h]

  for ( i = 0; ; ++i )
  {
    result = sub_401460(a1);
    if ( result <= i )
      break;
    *(_BYTE *)(i + a1) ^= 7u;
    *(_BYTE *)(i + a1) -= 2;
  }
  return result;
}


//if ( sub_4014DD(v1) )
//    exit(-1);
//puts("\nGreat!Now u know the flag!");
BOOL __cdecl sub_4014DD(char *Str2)
{
  return strcmp("_id^zs2o2s02Vn24b5h2V|5px", Str2) != 0;
}

不说那么多了,贴一下 C 语言写的反向解 flag 代码:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
#include <stdio.h>
#include <string.h>

int main(){
    printf("Fight!\n");
    char v1[26] = "_id^zs2o2s02Vn24b5h2V|5px";
    for(int i=0;i<=26;i++){
        v1[i] += 2;
        v1[i] ^= 7u;
    }
    printf("%s", v1);
    return 0;
}

3. 小精灵

Description:

在那山的那边海的那边有一群elf

Attachment:

easyElf

建议了解一下 ELF 文件

这个题用 IDA 64-bit 分析,依然可以点通过左侧的 main 函数进去按 F5, 不过此处我换一种方式:

Shift + F12 打开字符串窗口(第一题也可以这样做)

image-20210327144342702

当时做的时候好像还不知道可以从左边的 main 进,无论你身处何方,Strings window 窗口都会为你展现当前程序所有的字符串,在这里用 Ctrl + F 搜索想寻找的字符串更容易,本题很清晰,可以看到一串 Base64 躺在那儿,解开就是 flag

4. baby_pyc

Attachment:

easypy.pyc

这是后面上新的一道 baby 题,pyc 程序逆向直接扔到在线工具网站上就可以拿到 python 源码

5. 好多flag啊

Description:

你猜猜 你猜我猜不猜 你猜我猜你猜不猜

注意:当输入的和输出的完全一致,此时输入的才是正确的flag哦

View hint:

flag是viking{}包着的哦

Attachment:

whichisflag.exe

!下面的这个脚本有一点点问题,

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
import re

with open("whichisflag.exe","r",encoding='utf-8',errors='ignore') as f:
    data = f.readlines()

pattern = re.compile(r"viking\{.*?\}")
flags = re.findall(pattern, str(data))

for flag in flags:
   v2 = ord(flag[22]) - ord(flag[8]) + 29
   if flag == flags[v2 - 1]:
       print(flag)

"""
$ python search.py
viking{CSE+/TI5FQHJr6Hz97}
viking{vwH2EUZKy9zzK4B3X/}
"""
# 我还不清楚为啥返回了2个flag,里面只有1个是正确的

我也不知道为啥我想先把脚本放前面,不习惯可以先看看后面的分析

试着阐明脚本:

  • 对 exe 文件的读取
  • utf-8 编码读取(其实不加 encoding 参数也可以,自动识别成 gbk 编码)
  • 忽略读取中的报错,这里是指 UnicodeDecodeError(这个必须加,因为是强行读取的 exe ,直接以文本形式 readlines 会读到无法解码的字符报错退出,我们忽略掉这个报错,有效的字符串信息依然会被读取,等待下一步正则提取)
  • 至于 with open() as:readline()readlines() 这些还可以去搜索一下
  • 编写正则提取所有的 flag
  • 这个正则的匹配模式(pattern)比较简单,可以了解的是 .*? 大法
  • str() 对读取的 data 进行强制类型转换,因为 readlines() 方法返回的是列表
  • 依照程序逻辑找到正确的 flag
  • v2 的赋值 还有 if 判定是根据提供的程序写的

PE+ 文件,IDA 64-bit 打开直接进入的是 main 函数窗口

Shift + F12 可以在字符串窗口看到一堆 viking{} 包裹的 flag,不过我不太清楚 IDA 怎么批量导出

F5 伪代码如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
int __cdecl main(int argc, const char **argv, const char **envp)
{
  int result; // eax
  char Str[6]; // [rsp+20h] [rbp-60h] BYREF
  char v5; // [rsp+26h] [rbp-5Ah]
  char v6; // [rsp+28h] [rbp-58h]
  char v7; // [rsp+29h] [rbp-57h]
  char v8; // [rsp+2Ah] [rbp-56h]
  char v9; // [rsp+36h] [rbp-4Ah]

  _main();
  printf(aFlag);
  gets(Str);
  if ( strlen(Str) <= 0x1A )    //;这里转Decimal的值是26
  {
    if ( 2 * (v9 - v6) == 3 * (v7 - v8)
      && v7 - v8 == v5 - 97
      && Str[0] == 118  //;v
      && Str[1] == 105  //;i
      && Str[2] == 107  //;k
      && v5 == 123 )    //;{
    {
      which_is_flag(Str);
    }
    else
    {
      printf("flag is wrong");
    }
    getchar();
    result = 0;
  }
  else
  {
    printf("flag is too long");
    result = 0;
  }
  return result;
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
__int64 __fastcall which_is_flag(char *a1)
{
  int v2; // [rsp+2Ch] [rbp-4h]

  v2 = a1[22] - a1[8] + 29;
  puts("yes, this is a flag:");
  switch ( v2 )
  {
    case 1:
      printf(" viking{Ldr9gJO8zSCALUzR0J}");
      break;
    case 2:
      printf(" viking{gJQ9sdM9GHIt5sp5QZ}");
      break;
    case 3:
      printf(" viking{1dKg4mTLLyL/7kkTtm}");
      break;
    case 4:
      printf(" viking{2n0frh0oZs2bCqCqea}");
      break;
    case 5:
      printf(" viking{h7fcJ7LL9SXVgP6MW2}");
      break;
    case 6:
      printf(" viking{+WmIXtnN6Dnkgw86Hi}");
      break;
    //...共666个case
    default:
      return 0i64;
  }
  return 0i64;
}

v5 - v9 这几个变量没弄清楚,双击会跳到 stack of main,数据是 undefined,可能需要动态调试才看得到具体值,不过此题我们只取 which_is_flag(Str)就够了

6. 玩个小游戏吧~

Description:

动态调试也很重要哇!

Attachment:

bird.exe

  • Local Windows 动态调试

这个题可以先玩玩这个小游戏有个整体感知,

进 IDA 32-bit 分析,可以从函数 printf 找交叉引用,这里我选用字符串视图可以让读者看出更多东西(maybe)

image-20210327224647969

双击跳过来再找交叉引用:image-20210327225030569

可以看到 printf 在这里也被调用了,我们在这里整个断点(按 F2 键,或是从工具栏点;正常情况下看到这儿的人大部分都会点调试

image-20210327225401730

然后按 F9 键或者菜单栏 Debugger 准备调试

选择 Local Windows Debugger 并确认运行,然后 IDA 进入调试页面,并且题目所给 exe 待运行,

再按 F9 就开始继续调试进程,也就是游戏开始到游戏结束至断点处再暂停进程(其实不打断点也行,后面会有一个等待输入的地方,程序依然会停在那儿,可以在那里再修改)

因为这个程序的逻辑原因,每次继续调试都会马上等待,只需要一直 F9 让小鸟('@')坠地就可以

这里我讲不太清楚了,不过没关系,方法才是重点

image-20210327232701129

此时程序已经过了打印 Score 的地方,紧接着是等待输入 choice,

图中是断点处即打印 Score 的地方生成的伪代码

这时怎么找到 Your choice 所对应的代码区呢?

不推荐强开 Strings Window,可能是因为处于动调的原因,有很多字符串需要索引,所以生成十分慢

我是根据打印 Score 所处函数的交叉引用,一番操作来到下图:image-20210327234155263

一切准备就绪,控制台输入 1 (赋给 v3) 但不要急着按回车,相信 Score: 0 是过不了后面的判定的,于是我们在 if 处设下断点,准备直接修改变量的值绕过去,

image-20210327234952645

果然,byte_408078 存着我们的分数,双击之可以在 IDA View-EIP 跳转到其所在地址,

接下来我们就需要在 Hex View 视图中直接修改其数据,

image-20210327235508399

此时我通过右键选择 Synchronize with Hex View-1,使得两处窗口「同步化」,无论在哪个窗口改变光标所在位置,另一对应窗口也会相应标识出,可以看到 Hex View-1 中高亮部分就是我们需要修改的数据区,

让光标来到第一个 00 处,按下 F2 开始修改,完成后再按一次保存修改,本题中我们需要改为一个能通过 if 语句的整数值对应的 16 进制值,我取了 0x18 -> 24 的原因可以看看后面的代码逻辑。

再后面同样的方法过 v2 就可,只要满足下图这几步逻辑就可,毕竟这个生成 flag 的函数有点复杂,不太好反解

image-20210328000829364

7. 注册码是多少

Description:

小明想白嫖软件,但是小美只肯告诉他用户名,不肯告诉他注册码。 用户名:Viking flag是viking{注册码}

Attachment:

crake.exe

  • upx 脱壳

很有意思的一道题,我独自摸索了很久,这是看雪 《加密与解密》第四版上的一道 crackme 题目,摸索的过程也就是在网上不断搜 注册机、crackme 这一类题(有几种类型),还有就是一些比较有特征的陌生函数名(特别指 GetDlgItemTextA )

省略了一些前置摸索,通往 flag 的过程可能显得有点突兀

这个题的特点是:用户名和与其对应的特征序列码,我们需要破解得到根据用户名而生成的序列码

这已经和实战中的破解搭上边了,私以为可以对照 Burp Suite 的注册机生成 license 的机制

8. Game

Description:

你听说过gdb吗

View Hint:

程序貌似被加壳了...

Attachment:

easyGame2

9. func

Description:

小ViKinger用这个加密程序加密了一段字符串,但是把解密程序弄丢了... 加密后的字符串:0x29,0x23,0x18,0x23,0x31,0x21,0xc8,0x13,0x32,0x3f,0x7a,0x22,0x24,0x2e,0x7a,0x27,0x38,0x67,0x2e,0x3e,0x70,0x22,0x22,0x26,0x3f,0x39,0xcb 密钥:0xba2759??

Attachment:

func.exe

10. 诸神抛弃的代码

堕落的窥视者,停下你的脚步!这里是诸神厌弃之地,被遗弃的代码永远沉睡于此。

你惊醒它们了。

本题为动态分数,解题人越多分数越低

给hint的hint:hint是告诉你做题不要充钱,但是由于泄露了额外的信息,因此hint扣分较高

Unlock Hint for 80 points: 我没换

Attachment:

codes_abandoned.exe

11. baby_data_structure

12. Do_you_love_CPP

后话

Back to top