Buuoj-逆向刷题记录

Buu-2019红帽杯-easyRe

基本信息:64位 ELF文件

分析关键代码

在字符串窗口发现You found me!!!,通过查找其交叉引用找到关键函数
image.png

__int64 sub_4009C6()
{
  __int64 result; // rax
  int i; // [rsp+Ch] [rbp-114h]
  __int64 v2; // [rsp+10h] [rbp-110h]
  __int64 v3; // [rsp+18h] [rbp-108h]
  __int64 v4; // [rsp+20h] [rbp-100h]
  __int64 v5; // [rsp+28h] [rbp-F8h]
  __int64 v6; // [rsp+30h] [rbp-F0h]
  __int64 v7; // [rsp+38h] [rbp-E8h]
  __int64 v8; // [rsp+40h] [rbp-E0h]
  __int64 v9; // [rsp+48h] [rbp-D8h]
  __int64 v10; // [rsp+50h] [rbp-D0h]
  __int64 v11; // [rsp+58h] [rbp-C8h]
  char v12[13]; // [rsp+60h] [rbp-C0h] BYREF
  char v13[4]; // [rsp+6Dh] [rbp-B3h] BYREF
  char v14[19]; // [rsp+71h] [rbp-AFh] BYREF
  char v15[32]; // [rsp+90h] [rbp-90h] BYREF
  int v16; // [rsp+B0h] [rbp-70h]
  char v17; // [rsp+B4h] [rbp-6Ch]
  char v18[72]; // [rsp+C0h] [rbp-60h] BYREF
  unsigned __int64 v19; // [rsp+108h] [rbp-18h]

  v19 = __readfsqword(0x28u);
  qmemcpy(v12, "Iodl>Qnb(ocy", 12);
  v12[12] = 127;
  qmemcpy(v13, "y.i", 3);
  v13[3] = 127;
  qmemcpy(v14, "d`3w}wek9{iy=~yL@EC", sizeof(v14));
  memset(v15, 0, sizeof(v15));
  v16 = 0;
  v17 = 0;
  sub_4406E0(0LL, v15, 37LL);
  v17 = 0;
  if ( sub_424BA0(v15) == 36 )
  {
    for ( i = 0; i < (unsigned __int64)sub_424BA0(v15); ++i )
    {
      if ( (unsigned __int8)(v15[i] ^ i) != v12[i] )
      {
        result = 4294967294LL;
        goto LABEL_13;
      }
    }
    sub_410CC0("continue!");
    memset(v18, 0, 0x40uLL);
    v18[64] = 0;
    sub_4406E0(0LL, v18, 64LL);
    v18[39] = 0;
    if ( sub_424BA0(v18) == 39 )
    {
      v2 = sub_400E44(v18);
      v3 = sub_400E44(v2);
      v4 = sub_400E44(v3);
      v5 = sub_400E44(v4);
      v6 = sub_400E44(v5);
      v7 = sub_400E44(v6);
      v8 = sub_400E44(v7);
      v9 = sub_400E44(v8);
      v10 = sub_400E44(v9);
      v11 = sub_400E44(v10);
      if ( !(unsigned int)sub_400360(v11, off_6CC090) )
      {
        sub_410CC0("You found me!!!");
        sub_410CC0("bye bye~");
      }
      result = 0LL;
    }
    else
    {
      result = 4294967293LL;
    }
  }
  else
  {
    result = 0xFFFFFFFFLL;
  }
LABEL_13:
  if ( __readfsqword(0x28u) != v19 )
    sub_444020();
  return result;
}

通过查看sub_4009C6()的堆栈,猜测v12、v13、v14应该是一个连续的数组
image.png

sub_424BA0()

看着sub_424BA0()在代码中的使用,猜测应该是strlen()
image.png
如上图中,有一个for对数据做异或,我们通过分析已经知道了v12的数据内容,那么直接跟0~35进行异或,得到提示`Info:The first four chars are `flag``

v12 = 'Iodl>Qnb(ocy' + chr(127) + 'y.i' + chr(127) + 'd`3w}wek9{iy=~yL@EC'

for i in range(36):
    print(chr(ord(v12[i]) ^ i), end='')

sub_400E44()

进入函数发现base64字符表集合,应该是base64没跑了
image.png
这里将v18进行十次base64编码后,与off_6CC090传入sub_400360()中
image.png
跟进off_6CC090发现是一串base64,经过十次解码得到一个链接
image.png
image.png
但是在链接文章中没有找到线索

sub_400D35()

看了网上师傅wp才知道关键函数在这,base64数据下面有一块数据被sub_400D35()调用
image.png
通过查看sub_400D35()的交叉引用,发现是来自.fini段调用了sub_400D35()
.fini:此节区包含了可执行的指令,是进程终止代码的一部分。程序正常退出时,系统将安排执行这里的代码。
所以文章链接的意思,是让我们不要通过常规方式找关键函数吗?😲
image.png
sub_400D35()内容如下:

unsigned __int64 sub_400D35()
{
  unsigned __int64 result; // rax
  unsigned int v1; // [rsp+Ch] [rbp-24h]
  int i; // [rsp+10h] [rbp-20h]
  int j; // [rsp+14h] [rbp-1Ch]
  unsigned int v4; // [rsp+24h] [rbp-Ch]
  unsigned __int64 v5; // [rsp+28h] [rbp-8h]

  v5 = __readfsqword(0x28u);
  v1 = sub_43FD20(0LL) - qword_6CEE38;
  for ( i = 0; i <= 1233; ++i )
  {
    sub_40F790(v1);
    sub_40FE60();
    sub_40FE60();
    v1 = sub_40FE60() ^ 0x98765432;
  }
  v4 = v1;
  if ( ((unsigned __int8)v1 ^ byte_6CC0A0[0]) == 102 && (HIBYTE(v4) ^ (unsigned __int8)byte_6CC0A3) == 103 )
  {
    for ( j = 0; j <= 24; ++j )
      sub_410E90((unsigned __int8)(byte_6CC0A0[j] ^ *((_BYTE *)&v4 + j % 4)));
  }
  result = __readfsqword(0x28u) ^ v5;
  if ( result )
    sub_444020();
  return result;
}

这里就很好分析了,有两个异或,byte_6CC0A0数组直接在堆栈中找到对应数据。byte_6CC0A0与v4[j % 4]异或,而前面根据提示,前四位是flag。所以:

byte_6CC0A0[0] ^ v4[0%4] = 'f';
byte_6CC0A0[1] ^ v4[1%4] = 'l';
byte_6CC0A0[2] ^ v4[2%4] = 'a';
byte_6CC0A0[3] ^ v4[3%4] = 'g';

这样就得出v4 = [38, 89, 65, 49]
最终脚本,得出flag

byte_6cc0a0 = [0x40, 0x35, 0x20, 0x56, 0x5d, 0x18, 0x22, 0x45, 0x17, 0x2f, 0x24, 0x6e, 0x62, 0x3c, 0x27, 0x54, 0x48, 0x6c, 0x24, 0x6e, 0x72, 0x3c, 0x32, 0x45, 0x5b]
flag = 'flag'
v4 = list()
for i in range(4):
    v4.append((byte_6cc0a0[i] ^ ord(flag[i])))

for i in range(25):
    print(chr(byte_6cc0a0[i] ^ v4[i % 4]), end='')

Buu-CrackRTF

首先查基本信息:无壳,Win32 console程序
image.png

分析关键代码

主函数

使用IDA查看,分析主函数内容如下:
基本逻辑已经有了,但是有几个函数还不确定是什么操作
image.png

主要函数分析

sub_40100A()

首先能能确定的是传入的参数是
参数1: 用户输入六位数+拼接六位数
参数2: v3=12
参数3: 空字符串首地址String1
image.png
在这个函数中,可以看到将参数1放到了CryptCreateHash中,这个函数是wincrypt.h中的,用于创建并返回哈希句柄
CryptCreateHash的第二个参数指定算法类型,IDA中的0x8004u表示SHA1算法,具体参考:https://docs.microsoft.com/en-us/windows/win32/seccrypto/alg-id
随后在21-22行中将哈希值拼接到lpString1中,就是mian中传入的String1
所以sub_40100A()的作用是将传入的数据进行sha1计算
结合mian中对输入的限制(六位数,atoi后必须大于100000),所以应该是六位数字+@DBApp的组合,这个很好爆破

import hashlib

def en_sha1(plain):
    sha1 = hashlib.sha1(plain.encode('utf-8'))
    return sha1.hexdigest()


hash = '6E32D0943418C2C33385BC35A1470250DD8923A9'.lower()
for i in range(100000, 1000000):
    if hash == en_sha1(str(i) + '@DBApp'):
        print(i)
# 输出结构:123321

sub_401019()

这个函数的逻辑跟上面那个基本相符,就是哈希算法类型变了,这次是md5
image.png
但是这一次mian中没有限制六位是数字,所有可见字符的6位数组合爆破是很不现实的,所以只能继续分析

sub_40100F()

image.png
FindResourceA用于获取指定的自定义资源
自定义资源详情参考:https://www.cnblogs.com/gakusei/articles/1352922.html
根据https://blog.csdn.net/singleyellow/article/details/80308789,学习到了自定义资源可以通过ResourceHacker获取
在21行获取资源指针后,进入sub_401005(),在这里面将资源内容与passwd(2)进行异或处理,结果保存在资源内容指针lpBuffer中
image.png
跳出函数后,在sub_40100F()26行中将lpBuffer写入文件dbapp.rtf中
整理下思路:
资源内容 ^ passwd(2) ==> 写入文件dbapp.rtf,那么我们就可以通过dbapp.rtf的内容异或资源内容,从而得到passwd(2)了
资源内容可以通过ResourceHacker获得
image.png
而dbapp.rtf,前面几个字节是固定的,这是用于给程序识别是什么类型的文件,类似于zip的50 4B、png的89 50 4E 47,我们系统里随便找个rtf文件用十六进制查看工具打开
image.png

然后取资源内容和rtf头前六个字节进行异或,得到passwd(2)需要用户输入的那六个字符

rtf_head = ['\x7b', '\x5c', '\x72', '\x74', '\x66', '\x31']
res_head = ['\x05', '\x7d', '\x41', '\x15', '\x26', '\x01']

for i in range(len(rtf_head)):
    print(chr(ord(rtf_head[i]) ^ ord(res_head[i])), end='')
# 输出结果:~!3a@0

GetFlag

运行程序,输入结果
image.png
打开同目录下生成的dbapp.rtf文件就得到flag
image.png

Buu-刮开有奖

定位关键点

首先查壳查信息,无壳,Win32程序
image.png

跟进到WinMain函数,这是Win32的入口函数
image.png

调用了DialogBoxParamA(),该函数是打开一个对话框,其中第四个参数是一个函数,我们跟进这个函数(因为也没有其他可以跟的了)
跟进后,通过57行的调用提示框内容,猜测这个函数应该就是程序核心了。
image.png

分析关键代码

前半部分的代码基本就是注释中描述的样子
image.png

分析sub_4010f0()

接着分析sub_4010f0()函数,参数是v7,0,10。
说实话,硬看代码确实难受。根据网上师傅们的做法,是简单翻译成C代码然后带入参数值执行一遍。
首先看到函数内对a1的操作是一个很明显的数组通过索引获取数据的操作,所以给参数a1修改为char* a1,然后是将数组a1操作的类型转换修改成char*
image.png

然后是将函数内所有数组操作的4*n修改成n,即v6 = *(char*)(4 * i + a1);修改成v6 = *(char*)(i + a1);
这个操作是看了师傅们的WriteUp才知道的,但是具体为什么这样做的细节我的猜想如下:
假设有如下C++语言代码

#include<iostream>

using namespace std;
void print_arr(int* arr, size_t len);

void main(void) {
	int arr[] = { 1,2,3,4,5,6,7,8,9,0 };
	print_arr(arr, sizeof(arr) / sizeof(int));
}

void print_arr(int* arr, size_t len) {
	for (int i = 0; i < len; ++i) {
		cout << arr[i] << endl;
	}
}

其中cout << arr[i] << endl;中arr[i]的操作伪汇编代码如下(这段汇编是Visual Studio2019的调试=》反汇编里的):
image.png
其中eax相当于for中的i变量,ecx是arr的首元素地址,去第eax个元素的操作是mov edx, dword ptr [ecx + eax * 4],这个操作看起来是不是跟IDA生成的伪C代码中的v6 = *(char*)(4 * i + a1);很像,个人感觉是这里翻译出了点问题。当然,这只是我基于浅薄的二进制基础下的猜想- -
最终修改代码如下:

#include<iostream>
using namespace std;
int __cdecl sub_4010F0(char* a1, int a2, int a3)
{
    int result; // eax
    int i; // esi
    int v5; // ecx
    int v6; // edx

    result = a3;
    for (i = a2; i <= a3; a2 = i)
    {
        v5 = i;
        v6 = *(char*)(i + a1);
        if (a2 < result && i < result)
        {
            do
            {
                if (v6 > *(char*)(a1 + result))
                {
                    if (i >= result)
                        break;
                    ++i;
                    *(char*)(v5 + a1) = *(char*)(a1 + result);
                    if (i >= result)
                        break;
                    while (*(char*)(a1 + i) <= v6)
                    {
                        if (++i >= result)
                            goto LABEL_13;
                    }
                    if (i >= result)
                        break;
                    v5 = i;
                    *(char*)(a1 + result) = *(char*)(i + a1);
                }
                --result;
            } while (i < result);
        }
    LABEL_13:
        *(char*)(a1 + result) = v6;
        sub_4010F0(a1, a2, i - 1);
        result = a3;
        ++i;
    }
    return result;
}

int main(void) {
    char arr[] = { 90, 74, 83, 69, 67, 97, 78, 72, 51, 110, 103 };
    sub_4010F0(arr, 0, 10);
    for (int i = 0; i < 11; ++i) {
        cout << arr[i];
    }
    cout << endl;
}
// 输出结果:3CEHJNSZagn

分析sub_401000()

这个函数也可以利用上述的分析方法,但是还有一个可以根据经验猜测出来的技巧。
函数中用到一个数组byte_407830
image.png
双击进入数据段中可以发现值为ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=
(像我这样的菜🐓可能会有个疑问,明明截图中值看到BCDEFGHIJKLMNOPQRSTUVWXYZab...,A哪里来的,其实就在上一行中,41H转换成十进制是65,及时'A'的ASCII码)
image.png
有经验的CTFer,通过这个数组就会联想到base64编码了,通过验证也确定了,sub_401000()就是base64编码函数

#include<iostream>
#include<memory>

using namespace std;
char* __cdecl sub_401000(char* a1, int a2)
{
    int v2; // eax
    int v3; // esi
    size_t v4; // ebx
    char* v5; // eax
    char* v6; // edi
    int v7; // eax
    char* v8; // ebx
    int v9; // edi
    int v10; // edx
    int v11; // edi
    int v12; // eax
    int i; // esi
    char* result; // eax
    char* v15; // [esp+Ch] [ebp-10h]
    char* v16; // [esp+10h] [ebp-Ch]
    int v17; // [esp+14h] [ebp-8h]
    int v18; // [esp+18h] [ebp-4h]
    string byte_407830 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";

    v2 = a2 / 3;
    v3 = 0;
    if (a2 % 3 > 0)
        ++v2;
    v4 = 4 * v2 + 1;
    v5 = (char* )malloc(v4);
    v6 = v5;
    v15 = v5;
    if (!v5)
        exit(0);
    (char*)memset(v5, 0, v4);
    v7 = a2;
    v8 = v6;
    v16 = v6;
    if (a2 > 0)
    {
        while (1)
        {
            v9 = 0;
            v10 = 0;
            v18 = 0;
            do
            {
                if (v3 >= v7)
                    break;
                ++v10;
                v9 = *(unsigned __int8*)(v3 + a1) | (v9 << 8);
                ++v3;
            }       while (v10 < 3);
            v11 = v9 << (8 * (3 - v10));
            v12 = 0;
            v17 = v3;
            for (i = 18; i > -6; i -= 6)
            {
                if (v10 >= v12)
                {
                    *((char*)&v18 + v12) = (v11 >> i) & 0x3F;
                    v8 = v16;
                }
                else
                {
                    *((char*)&v18 + v12) = 64;
                }
                *v8++ = byte_407830[*((char*)&v18 + v12++)];
                v16 = v8;
            }
            v3 = v17;
            if (v17 >= a2)
                break;
            v7 = a2;
        }
        v6 = v15;
    }
    result = v6;
    *v8 = 0;
    return result;
}

void main(void) {
    char str[] = { 'a', 'b', 'c', '\0'};
    cout << sub_401000(str, strlen(str)) << endl;
}
// 输出:YWJ

if判断分析

image.png
通过分析if,我们能得到如下信息:

if ( String[0] == v7[0] + 34		// String[0] = v7[0] + 34 = 51 + 34 = 85 = 'U'
    && String[1] == v10				// String[1] = v10 = 'J'
    && 4 * String[2] - 141 == 3 * v8	// String[2] = (3 * v8 + 141) / 4 = (3 * 69 + 141) / 4 = 87 = 'W'
    && String[3] / 4 == 2 * (v13 / 9)	// String[3] = 2 * (v13 / 9) * 4 = 2 * (90 / 9) * 4 = 'P'
    && !strcmp(v4, "ak1w")				// v4是v18的base64编码值,v18是String[5、6、7],所以v4是String第六七八个字符的base64编码
    && !strcmp(v5, "V1Ax") )			// v5的操作与v4类似,是String第三四五个字符的base64编码
{
    MessageBoxA(hDlg, "U g3t 1T!", "@_@", 0);
}

综上所述,flag是UJWP1jMp

Buu-SimpleRev

用file查看,是elf 64位文件
image.png

找主函数

用IDA64打开,在函数列表找到main
image.png
image.png

关键代码分析

main函数中只做了简单的功能,输入d/D进入Decry(),输入q/Q退出
image.png
跟进Decry()

数据赋值部分

需要注意伪代码是小端序
image.png
join函数是将参数a1和a2拼接起来,然后返回拼接后地址
image.png
后面一个for,认真跟一下流程就知道是大写转小写的
image.png
接下来是通过getchar()循环获取用户输入,然后判断为字母的,进行计算操作
计算操作:str2[v2] = (v1 - 39 - key[v3++ % v5] + 97) % 26 + 97;
image.png

整理思路

  • 用户输入数据
  • 将所有用户输入为字母的,进行计算操作
  • 最后将结果与text变量的值比较,匹配则成功。

解题思路:我们可以将26个字母的所有计算操作结果都枚举出来,然后寻找出text每一位字母对应的明文即可

key1 = 'adsfkndcls'
key2 = 'killshadow'
letter = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
key1_len = len(key1)

flag = ''
for i in range(len(key1)):
    for ch in letter:
        if ((ord(ch) - 39 - ord(key1[i % 10]) + 97) % 26 + 97) == ord(key2[i]):
            flag += ch

print(flag, end='')

Buu-reverse3

程序放到ida中,找到mian函数,然后对能直观看出来的代码进行注释
image.png

目前只有sub_4110BE()函数不确定作用,我们跟进sub_4110BE()
函数中使用了大量的aAbcdefghijklmn数组
image.png
在数据块中查看数组是如下图内容,猜测该函数是base64编码。这一点也可以通过OD单步执行观察出来,用户输入的数据经过一个call后变成了base64
image.png

现在思路就很清晰了,只需要找到IDA中Dest变量的值就可以了。我们通过OD单步执行找到push了我们输入数据的base64值,然后调用strncmp的位置,就可以找到Dest
下图第二个push就是我们要找的数据
image.png
然后python根据IDA分析的算法逆一下就得出flag了

import base64

data = 'e3nifIH9b_C@n@dH'
flag = ''
for i in range(len(data)):
    flag += chr(ord(data[i]) - i)

print(base64.b64decode(flag))
// output: {i_l0ve_you}

Bugku-游戏过关

当所有开关都为关闭时,输出flag
image.png
首先用中文搜索引擎找到输出flag的地方
image.png
双击进入其所在位置,通过观察,发现是个函数
image.png
解题思路:找到一处正常执行的位置,修改跳转地址,直接跳转到这个函数即可打印flag
通过运行程序,发现每次都会打印“n=”,所以我们将打印“n=”的位置修改成jmp 15E940,就可以让其在新的循环中跳转到打印flag的函数
修改前:
image.png
修改后:
image.png
在修改的汇编代码上面一行下个断点,然后让程序运行到断点位置,之后一步步运行打印flag
执行到这个位置会一直循环,我们可以在循环的后一行下个断点,然后f9运行到这个断点处
image.png
运行到断点处就得到flag了
image.png

posted @ 2021-06-24 20:36  Gcker  阅读(243)  评论(0编辑  收藏  举报