pk8900 发表于 2019-12-17 20:01

CTF攻防世界Reverse018_zorropub逆向分析【原创】

本帖最后由 pk8900 于 2019-12-17 22:16 编辑

0x00:前言
好久没有发贴了,现在论坛里大神好多啊,看完大神的帖子,真不知道该写点什么,只能水一贴了,写一下昨天逆向一个CFT的过程和思路吧。

0x01:程序32/64位识别
CTF题目没有说明,只提供一个附件,下载后,发现无扩展名,估计是Linux程序,于是用十六进制编辑器(Uedit)打开查看,在文件头部发现 ELF 字符,确定为Linux程序,再往下查看,发现“/lib64/ld-linux-x86-64.so”字符,确认为64位Linux程序。
说一下Linux程序识别的方法,因为Linux系统中文件并非是通过扩展名来标识文件类型的,所以linux程序在WINDOWS系统里只能通过查看文件内容进行确定,(估计有相关的工具,之前下载了一个,发现不好用,就放弃了。)通过十六进制编辑器查看更方便一些。
linux程序32位和64位的区别如图:


我认为通过查看字符串资源:/lib/ld-linux.so或 /lib64/ld-linux-x86-64.so 来区分更方便一些。也可以用Linux的 FILE 命令查看,这个CTF程序为64位程序,那就用IDA64打开进行分析。

0x02:分析过程
IDA64加载后,查看函数列表,找到Main函数,F5分析伪代码如下:
int __fastcall main(__int64 a1, char **a2, char **a3)
{
size_t v3; // rax
int v5; //
int v6; //
int i; //
unsigned int seed; //
unsigned int v9; //
char v10; //
char v11; //
char v12; //
char s; //
char s1; //
unsigned __int64 v15; //

v15 = __readfsqword(0x28u);
seed = 0;
puts("Welcome to Pub Zorro!!");
printf("Straight to the point. How many drinks you want?", a2);
__isoc99_scanf("%d", &v5);                  // scanf 获取输入数字 drinks
if ( v5 <= 0 )
{
    printf("You are too drunk!! Get Out!!", &v5);
    exit(-1);
}
printf("OK. I need details of all the drinks. Give me %d drink ids:", (unsigned int)v5);
for ( i = 0; i < v5; ++i )
{
    __isoc99_scanf("%d", &v6);                  // scanf 获取输入数字 drink ids
    if ( v6 <= 16 || v6 > 65535 )               // drink ids 范围:16-65534
    {
      puts("Invalid Drink Id.");
      printf("Get Out!!", &v6);
      exit(-1);
    }
    seed ^= v6;
}
i = seed;
v9 = 0;
while ( i )
{
    ++v9;
    i &= i - 1;
}
if ( v9 != 10 )                               // ids 循环位操作进行计数,最终计数必须为10
{
    puts("Looks like its a dangerous combination of drinks right there.");
    puts("Get Out, you will get yourself killed");
    exit(-1);
}
srand(seed);                                  // 置随机数种子
MD5_Init((__int64)&v10);
for ( i = 0; i <= 29; ++i )
{
    v9 = rand() % 1000;                         // 生成随机数除1000取余
    sprintf(&s, "%d", v9);
    v3 = strlen(&s);
    MD5_Update(&v10, &s, v3);
    v12 = v9 ^ LOBYTE(dword_6020C0);      // V12前30位与6020C0数据异或
}
v12 = 0;                                 // V12第30位添0
MD5_Final(v11, &v10);
for ( i = 0; i <= 15; ++i )
    sprintf(&s1, "%02x", (unsigned __int8)v11);
if ( strcmp(s1, "5eba99aff105c9ff6a1a913e343fec67") )
{
    puts("Try different mix, This mix is too sloppy");
    exit(-1);
}
return printf("\nYou choose right mix and here is your reward: The flag is nullcon{%s}\n", v12);// flag为V12的内容
}
通过静态分析Main函数流程,发现程序写脚本得到FLAG:
1、程序要求输入:Straight to the point. How many drinks you want?
输入数:V5 >=0,且后续V5并没有引用,所以只要随意输入大于0的数就可以。
2、"OK. I need details of all the drinks. Give me %d drink ids:", (unsigned int)v5
输入ids:变量为v6,seed ^= v6;运行后,ids存入seed中,后续作为生成随机数的种子。
3、对IDS进行较验:
i = seed;
v9 = 0;
while ( i )
{
    ++v9;
    i &= i - 1;
}
if ( v9 != 10 )                               // ids 循环位操作进行计数,最终计数必须为10
{
    puts("Looks like its a dangerous combination of drinks right there.");
    puts("Get Out, you will get yourself killed");
    exit(-1);
}
此代码进行了一个自减1并位与操作,核对计数为10,实现就是对输入的ids(16-65534) WORD值进行位较验,如果数字中有正好有10个位是1,则符合要求,由此可知最小可满足要求的数是1023,1023的二进制中低10位全是1,因此满足这一条件的数字应该不少,但可以写代码逐一列出。
4、用上一步的IDS【seed】做为随机数种子,生成30个小于1000的随机数,并分别与LOBYTE(dword_6020C0)异或,结果存到V12中,最后的flag就是V12中的内容(nullcon{%s}\n", v12)。
以上步骤分析完后,总结一下,可以通过写一个程序进行计算。
于是用VS2013写代码如下:
#include "IDA.h"
#include<stdio.h>
#include<stdlib.h>

bool myEnc(int n);//异或还原函数
int main()
{
      int i, j,n;
      for (int y = 16; y < 65534; y++){
                n = 0;
                int m = y;
                while (m)
                {
                        ++n;
                        m &= m - 1;
                        if (n>=11)
                              break;
                }
                if (n==10){
                        if (myEnc(y)){
                              printf("n=%d \n", y);
                              break;
                        }
                }
      }
system("pause");
return 0;
}
bool myEnc(int n){
      srand(n);
      int enclist[] = { 0x3C8, 0x32, 0x2CE, 0x302, 0x7F, 0x1B8, 0x37E, 0x188, 0x349, 0x27F, 0x5E, 0x234, 0x354, 0x1A3, 0x96, 0x340, 0x128, 0x2FC, 0x300, 0x28E, 0x126, 0x1B, 0x32A, 0x2F5, 0x15F, 0x368, 0x1EB, 0x79, 0x11D, 0x24E };
      unsigned char flag = { 0 };
      int j;
      for (int i = 0; i < 29; i++)
      {
                j = rand() % 1000;
                flag = j ^ LOBYTE(enclist);
                if (flag>128)
                        return false;//如果字符不可见,返回false
      }
      flag = '\0';
      printf(" %s \n", flag); //打印输出找到的flag
      return true;
}

编译运行,可是没有找到flag,于是对程序反复检查,发现没有错误啊,不行动态调试吧,对CTF程序进行动态调试,扔到虚拟机的64位ubuntu中,用IDA进行远程调试,输入第2步ids值输入:1023,发现了问题所在:
WINDOWS中,种子为1023时,生成的随机数为: 37959741439368145906571287595385624524884895300......
Linux中,种子为1023时,生成的随机数为:808,14,219,336,499,953,745,120,164,303,30,151,640,588.....
原来在两个系统中种子相同,生成的随机数却不同,看来只能在linux中试一下了,于是在ubuntu中安装了VSCODE进行调试,结果和CTF程序运行一致,找到Flag.
cd "/root/C++/Hello/" && g++ hello.cpp -o hello && "/root/C++/Hello/"hello
sh: 1: pause: not found
nu11c0n_s4yz_x0r1n6_1s_4m4z1ng
n=59306

拼接后得到最终flag为:nullcon{nu11c0n_s4yz_x0r1n6_1s_4m4z1ng}
也就是原程序中输入ids:59306 即可得到flag
至此分析完成。至于程序中关于md5值计算的部分,大致分析应为生成的随机数除1000取余后,组成一个字串,对字串求MD5_32,这部分没进行验证,CTF中用的应该是OPENSSL库中的算法。
附上CTF程序:


yz3781286 发表于 2020-3-30 14:46

@PK8900楼主麻烦看一下   大佬分享的2009文泰刻绘出错补丁的分享没有了   可以重新分享或者发我邮箱吗467183485@qq.com    那边回复不了   我没法发私信只能这样回复了实在是抱歉

yz3781286 发表于 2020-3-31 10:34

@PK8900楼主麻烦看一下   大佬分享的2009文泰刻绘出错补丁的分享没有了   可以重新分享下吗 那边回复不了   我没法发私信只能这样回复了实在是抱歉我百度网盘加好友了

xiaoyou66 发表于 2019-12-17 20:13

前排支持大佬

一人游弋 发表于 2019-12-17 20:14

前排支持大佬

若时光安好 发表于 2019-12-17 20:15

Devil太初 发表于 2019-12-17 20:20

感谢大佬分享,力挺

117882697 发表于 2019-12-17 21:10

看看,学习学习

Mino 发表于 2019-12-17 23:45

膜拜一下大神

huaekinvod 发表于 2019-12-17 23:51

的 FILE 命令查看,这个CTF程

2Burhero 发表于 2019-12-18 02:16

看看试试

xxpl123 发表于 2019-12-18 09:00

跟随学习一下,感谢分享
页: [1] 2 3
查看完整版本: CTF攻防世界Reverse018_zorropub逆向分析【原创】