吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 7330|回复: 17
收起左侧

[CTF] 学校OJ的一道简单的CTF reverse题

  [复制链接]
默之然i 发表于 2019-12-7 21:04
本帖最后由 默之然i 于 2019-12-7 21:23 编辑

楼主的学校信安协会搞了个OJ出来
作为信安大一新生的萌新,我对关于计算机方面的知识都非常感兴趣
于是就尝试了一下

在那些题目中,在我看来最有趣的就是这个逆向(更有趣的不会做T_T):
这是我第一次做reverse,第一次,所以遇到了很多坑
————————————————————————

题目描述

题目描述

程序打开界面

程序打开界面

先查壳,区段数据什么的都不懂,就只看是什么壳
   image3.png
一看——UPX压缩壳
这个简单,之前ximo在教程里脱壳过pushad对应popad,或者esp定律法,再不行就单步跟踪
楼主图省事,就直接从上往下拉,过一个个jmp以及call
image4.png
再一个jump就到OEP了
image5.png

然后LordPE dump,再ImportREC走起
输入OEP偏移地址,然后AutoSearch
image6.png
出来了个Yes,很开心,然后就FixDump。
OK后尝试直接执行程序,程序崩了……

仿佛想起了什么,于是把Size改大,果然……
多了两个yes
那之前的错误就是没把这两个dll一起import了
image7.png
之后就show Invalid——右键——delete thunks,
把那些Invaid都删掉,再FixDump就完事了,程序也正常运行。
(原谅楼主连一个简单的压缩壳都搞不定,
楼主距离上一次看ximo教程已经一年半了,忘了很多,
这次写的这么详细也希望其他一起学习的小伙伴们能避坑)
——————————————————
接下来就日常拖到IDA里,再一个反编译
image8.png
几乎都是这种类型的反编译代码查找字符串也没结果,有点奇怪。
那就只好再把程序拖到OD里,定位关键代码
拖入,右键——中文搜索引擎——智能搜索,找到了这个
image9.png
这几个字符串被调用的距离都挺近,
随便记住一个地址,然后到IDA里鼠标滚轮,手动到达包含这个地址的函数
image10.png
[C] 纯文本查看 复制代码
int __cdecl sub_415280(int a1, int a2)
{
  size_t v2; // eax@5
  char v4; // [sp+0h] [bp-134h]@1
  char v5; // [sp+Ch] [bp-128h]@1
  char Buf; // [sp+D0h] [bp-64h]@11
  unsigned int i; // [sp+100h] [bp-34h]@4
  FILE *File; // [sp+10Ch] [bp-28h]@9
  char v9; // [sp+118h] [bp-1Ch]@1
  unsigned int v10; // [sp+130h] [bp-4h]@1
  int savedregs; // [sp+134h] [bp+0h]@1

  memset(&v5, 0xCCu, 0x128u);
  v10 = (unsigned int)&savedregs ^ __security_cookie;
  sub_411208(dword_41C008);
  ((void (__cdecl *)(const char *, char))sub_41137A)("Input Your Flag:\n", v4);
  sub_41137F("%19s", (unsigned int)&v9);
  if ( a1 != 2 )
  {
    ((void (__cdecl *)(const char *, char))sub_41137A)("Input error!\n", v4);
    exit(1);
  }
  sub_41137A("%s\n", *(_DWORD *)(a2 + 4));
  for ( i = 0; ; ++i )
  {
    v2 = j_strlen(*(const char **)(a2 + 4));
    if ( i >= v2 )
      break;
    *(_BYTE *)(*(_DWORD *)(a2 + 4) + i) += i;
  }
  if ( !j_strcmp("fmcj2y~{", *(const char **)(a2 + 4)) )
  {
    fopen(*(const char **)(a2 + 4), "r");
    File = (FILE *)sub_411212();
    if ( File )
    {
      fgets(&Buf, 40, File);
      sub_411212();
      if ( j_strlen(&Buf) != 32 || j_strlen(&Buf) % 2 == 1 )
        exit(1);
      sub_4113B1(&Buf, (int)dword_41A4E0);
      if ( sub_4113B6(dword_41A4E0) )
        sub_41137A("flag{%s}", &Buf);
      else
        sub_41137A("Input Error!\n");
    }
    else
    {
      sub_41137A("Input Error!\n");
    }
  }
  else
  {
    sub_41137A("Input Error!\n");
  }
  sub_411235(&savedregs, dword_4154C4);
  sub_4111E0();
  return sub_411212();
}


Nice! 核心代码找到了,接下来就是对它进行分析了
仔细观察一下,发现这个V9变量就是程序提示输入flag的变量,这个变量就是个幌子
image11.png
下文没有任何一个地方用到这个V9,被坑了。
而是要满足这个a1==0才能不被Exit
image12.png

a1变量是由上一个函数调用的,于是我就逐级查看上一层函数,发现……
image13.png

“argv””argc”似曾相识,好像就是C/C++ main函数的传入参数
再联想一下这道reverse的题目“Argument”
于是在调试选项里加入了一个参数 “flag”(随便加的字符串)
单步走,发现成功过了第一个if,并输出了传入的参数

接下来就是对传入参数进行简单的加密,然后在下面的if里对加密后的字符串进行比对
image14.png
这样看的太难受了,就等价替换了一下
:IDA提示的类型并不一定真就是那个类型
(例如一个char*,IDA就误以为是int,可能是因为两者所占用的内存空间是一样的),
还是得具体问题具体分析这个问题坑了我好久的时间……
[C] 纯文本查看 复制代码
char *a2;
char* str = a2 + 4;

for (int i = 0; i < strlen(str); ++i)
        str[i] += i;


嫌a2+4太烦,就把a2+4替换成str了,不影响,
下面那个strcmp也是比对的a2+4
然后着手编写解密代码,这个倒是挺简单的
[C] 纯文本查看 复制代码
#include <stdio.h>
#include <cstring>
int main()
{
        const char* str = "fmcj2y~{";
        for (int i = 0; i < strlen(str); ++i)
                printf("%c", str[i] - i);

        return 0;
}


解出flag.txt
image15.png

解出后就动态调试验证一下,传入参数为flagflag.txt
(为什么前面多了一个“flag”,因为上面是a2+4,随便在”flag.txt”前面加了4个字符),
发现还是过不去,于是就尝试只传入flag.txt,然后就过了?!
【黑人问号】原来IDA不可全信,我还是太天真了……

终于来到最后一关了。
反编译出的代码告诉我,
此程序会读取当前目录下名为“fmcj2y~{”的文件,
读取其中的数据,使用此数据进行某种操作,
然后再对这种操作返回出来的某种变量进行验证,最后输出flag。
注意文件中的数据一定是32个字节,不然就exit
也就是说,实际上“fmcj2y~{”文件存放的,就是真正的flag,
所以我必须通过这两个函数,将flag解出来
image16.png

对文件读取的数据进行操作的伪代码
[C] 纯文本查看 复制代码
int __cdecl sub_414E50(char *Str, int a2)
{
  size_t v2; // eax@2
  char v4; // [sp+Ch] [bp-D8h]@1
  int v5; // [sp+D0h] [bp-14h]@17
  int i; // [sp+DCh] [bp-8h]@1

  memset(&v4, 0xCCu, 0xD8u);
  sub_411208(dword_41C008);
  *(&dword_41A078 + 8) = 167;
  *(&dword_41A078 + 9) = 222;
  *(&dword_41A078 + 10) = 218;
  *(&dword_41A078 + 11) = 70;
  *(&dword_41A078 + 12) = 171;
  *(&dword_41A078 + 13) = 46;
  *(&dword_41A078 + 14) = 255;
  *(&dword_41A078 + 15) = 219;
  for ( i = 0; ; i += 2 )
  {
    v2 = j_strlen(Str);
    if ( i >= v2 )
      break;
    if ( Str[i] >= 48 && Str[i] <= 57 )
    {
      *(_DWORD *)(a2 + 4 * (i / 2)) = Str[i] - 48;
    }
    else
    {
      if ( Str[i] < 97 || Str[i] > 102 )
      {
        sub_41137A("Input Error!\n");
        exit(0);
      }
      *(_DWORD *)(a2 + 4 * (i / 2)) = Str[i] - 87;
    }
    *(_DWORD *)(a2 + 4 * (i / 2)) *= 16;
    if ( Str[i + 1] >= 48 && Str[i + 1] <= 57 )
    {
      v5 = Str[i + 1] - 48;
    }
    else
    {
      if ( Str[i + 1] < 97 || Str[i + 1] > 102 )
      {
        sub_41137A("Input Error!\n");
        exit(0);
      }
      v5 = Str[i + 1] - 87;
    }
    *(_DWORD *)(a2 + 4 * (i / 2)) += v5;
  }
  return sub_411212();
}


对返回的字符串进行验证的伪代码
[C] 纯文本查看 复制代码
int __cdecl sub_411D90(int a1)
{
  char v2; // [sp+Ch] [bp-CCh]@1
  int i; // [sp+D0h] [bp-8h]@1

  memset(&v2, 0xCCu, 0xCCu);
  sub_411208(dword_41C008);
  for ( i = 0; i < 16 && *(_DWORD *)(a1 + 4 * i) + 1 == dword_41A078[i]; ++i )
    ;
  return sub_411212();
}

这样的两份伪代码单独看也看不出什么东西,
但是一结合就发现,两者搭配的十分默契。
对操作后的字符串每个字符值加一,并与内存中的值一一进行比较,
如果32个字符全对,则通过验证

不要认为验证部分for循环那里是个分号,就以为这个没事不用管,
楼主当初也是这么想的,
点击return处的sub_411212(),无法查看反编译的伪代码,
进反汇编窗口也看不出什么东西,
想了想唯一有用的也就这个了

再编写解密代码之前,
我们还得去内存中把dword_41A078[0] ~dword_41A078[7]的值找出来
image17.png

解密代码
[C] 纯文本查看 复制代码
void  decrypt()
{
        int Str[32];
        int secretCode[16] = { 0x50, 0xC6, 0xF1, 0xE4, 0xE3, 0xE2, 0x9A, 0xA1,
                167, 222, 218, 70, 171, 46, 255, 219 };

        for (int i = 0; i < 32; i += 2)
        {
                //加了两个for循环遍历穷举
                for (Str[i] = '0'; Str[i] <= 'f'; Str[i]++)
                {
                        for (Str[i + 1] = '0'; Str[i + 1] <= 'f'; Str[i + 1]++)
                        {
                                //本质上仍然是加密代码,只是使用加密代码来碰撞生成 加密前的明文
                                //同时为了代码更好的理解,将判断条件内的数字全部替换为ASCII码
                                int tmpNum;
                                if (Str[i] >= '0' && Str[i] <= '9')
                                        tmpNum = Str[i] - '0';
                                else
                                {
                                        //将exit位置的代码全部替换为continue
                                        if (Str[i] < 'a' || Str[i] > 'f')
                                                continue;
                                        tmpNum = Str[i] - 'a' + 10;
                                }
                                tmpNum *= 16;
                                if (Str[i + 1] >= '0' && Str[i + 1] <= '9')
                                        tmpNum += Str[i + 1] - '0';
                                else
                                {
                                        if (Str[i + 1] < 'a' || Str[i + 1] > 'f')
                                                continue;
                                        tmpNum += Str[i + 1] - 'a' + 10;
                                }
                                //将验证部分编写在这里
                                if (tmpNum + 1 == secretCode[i / 2])
                                        printf("%d --> %c%c\n", i / 2, Str[i], Str[i + 1]);
                        }
                }
        }
}


解密结果
image18.png

最后构建文件
image19.png
在控制台中运行,即可得到flag:flag{4fc5f0e3e2e199a0a6ddd945aa2dfeda}
image20.png
——————————————————
自己挖的大坑
当初我以为这个程序的流程是:
1. 输入flag
2. 程序进行解密,将真正的flag解密出来
3. 如果输入的flag与解密出来的flag相同,则输出flag

在我刚准备reverse这个程序时,
吾爱的win7虚拟机我还没下载好,
没配置好环境,没办法脱壳,
(不知道为什么我在win10脱壳失败,可能是我太菜了QAQ)
就想着,不脱壳,直接执行到核心代码,
解出flag,花最少的时间,得更多的分数

于是,我就神挡杀神佛挡杀佛
哪里不行改哪里
把不能满足条件的jnz改成jz
把自己觉得没用的strcmp  nop掉
然后屡战屡败,还一直觉得应该是我修改姿势不对,
下次一定可以
于是浪费了一天时间

直到52pojie的win7虚拟机下载好,
脱壳、丢进IDA后才发现,
原来自己一直是在以头抢地尔
这道题目真正改变了我的想法,
原来一山更有一山高,学到了
—————————————————
这是我根据对伪代码的理解,写出来的程序源代码

[C] 纯文本查看 复制代码
#include <stdio.h>
#include <Windows.h>

int secretNum[16] = { 0x50, 0xC6, 0xF1, 0xE4, 0xE3, 0xE2, 0x9A, 0xA1 };

void dealWithBuf(char* buf, char* returnedData)
{
        secretNum[8] = 167;
        secretNum[9] = 222;
        secretNum[10] = 218;
        secretNum[11] = 70;
        secretNum[12] = 171;
        secretNum[13] = 46;
        secretNum[14] = 255;
        secretNum[15] = 219;
        int v5;

        for (int i = 0; i < strlen(buf); i += 2)
        {
                if (buf[i] >= '0' && buf[i] <= '9')
                {
                        returnedData[4 * (i / 2)] = buf[i] - '0';
                }
                else
                {
                        if (buf[i] < 97 || buf[i] > 102)
                        {
                                printf("Input Error!\n");
                                exit(0);
                        }
                        returnedData[4 * (i / 2)] = buf[i] - 87;
                }
                returnedData[4 * (i / 2)] *= 16;

                if (buf[i + 1] >= 48 && buf[i + 1] <= 57)
                {
                        v5 = buf[i + 1] - 48;
                }
                else
                {
                        if (buf[i + 1] < 97 || buf[i + 1] > 102)
                        {
                                printf("Input Error!\n");
                                exit(0);
                        }
                        v5 = buf[i + 1] - 87;
                }
                returnedData[4 * (i / 2)] += v5;
        }
}


bool checkBuf(char* returnedData)
{
        int i;
        for (i = 0; i < 16 && returnedData[4 * i] + 1 == secretNum[i]; ++i)
                ;
        return i == 16;
}

int main(int argc, char* argv[])
{
        char v9[20];
        char Buf[40];

        FILE* File;
        printf("Input Your Flag:\n");
        scanf("%19s", v9);

        if (argc != 2)
        {
                printf("Input error!\n");
                exit(1);
        }

        printf("%s\n", argv[0]);
        for (int i = 0; i < strlen(argv[0]); ++i)
                argv[0][i] += i;

        if (!strcmp("fmcj2y~{", argv[0]))
        {
                File = fopen(argv[0], "r");
                if (File)
                {
                        fgets(Buf, 40, File);
                        if (strlen(Buf) != 32 || strlen(Buf) % 2 == 1)
                                exit(1);
                        char returnedStr[40];
                        dealWithBuf(Buf, returnedStr);
                        if (checkBuf(returnedStr))
                                printf("flag{%s}", &Buf);
                        else
                                printf("Input Error!\n");
                }
                else
                        printf("Input Error!\n");
        }
        else
                printf("Input Error!\n");

        return 0;
}


搞定!
——————————————————
以上内容是我对我这第一次reverse程序的过程以及一点总结。
这次发帖也应该是我第一次在吾爱发主题帖
如果有什么不足之处,还请多多包涵,欢迎指出存在的问题

REVERSE-argument.zip

11.54 KB, 下载次数: 6, 下载积分: 吾爱币 -1 CB

reverse文件

免费评分

参与人数 6威望 +1 吾爱币 +11 热心值 +6 收起 理由
Hmily + 1 + 7 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
nanzhi + 1 我很赞同!
蛰火 + 1 + 1 我很赞同!
smile5 + 1 用心讨论,共获提升!
FleTime + 1 + 1 用心讨论,共获提升!
dongfang155 + 2 + 1 用心讨论,共获提升!

查看全部评分

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

IMF 发表于 2019-12-7 21:28
楼主很厉害,一起学习
dongfang155 发表于 2019-12-7 21:32
Fdecade 发表于 2019-12-8 00:10
1129909657 发表于 2019-12-8 10:38
6766666666666666666
冰露㊣神 发表于 2019-12-8 13:38
加油楼主
Devil太初 发表于 2019-12-8 13:50
作为大二的我对于这些还是一知半解
sq178178 发表于 2019-12-8 15:08
下载论坛附件下载论坛附件
头像被屏蔽
hua111 发表于 2019-12-9 10:00
提示: 作者被禁止或删除 内容自动屏蔽
psumnmn 发表于 2019-12-9 11:40
LZ瞎弄.弄一天成这样.也不容易.支持一下.
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

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

GMT+8, 2024-11-16 17:46

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

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