Buu-2019红帽杯-easyRe
基本信息:64位 ELF文件
分析关键代码
在字符串窗口发现You found me!!!,通过查找其交叉引用找到关键函数
__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应该是一个连续的数组
sub_424BA0()
看着sub_424BA0()在代码中的使用,猜测应该是strlen()
如上图中,有一个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没跑了
这里将v18进行十次base64编码后,与off_6CC090传入sub_400360()中
跟进off_6CC090发现是一串base64,经过十次解码得到一个链接
但是在链接文章中没有找到线索
sub_400D35()
看了网上师傅wp才知道关键函数在这,base64数据下面有一块数据被sub_400D35()调用
通过查看sub_400D35()的交叉引用,发现是来自.fini段调用了sub_400D35().fini:此节区包含了可执行的指令,是进程终止代码的一部分。程序正常退出时,系统将安排执行这里的代码。
所以文章链接的意思,是让我们不要通过常规方式找关键函数吗???
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程序
分析关键代码
主函数
使用IDA查看,分析主函数内容如下:
基本逻辑已经有了,但是有几个函数还不确定是什么操作
主要函数分析
sub_40100A()
首先能能确定的是传入的参数是
参数1: 用户输入六位数+拼接六位数
参数2: v3=12
参数3: 空字符串首地址String1
在这个函数中,可以看到将参数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
但是这一次mian中没有限制六位是数字,所有可见字符的6位数组合爆破是很不现实的,所以只能继续分析
sub_40100F()
FindResourceA用于获取指定的自定义资源
自定义资源详情参考:https://www.cnblogs.com/gakusei/articles/1352922.html
根据https://blog.csdn.net/singleyellow/article/details/80308789,学习到了自定义资源可以通过ResourceHacker获取
在21行获取资源指针后,进入sub_401005(),在这里面将资源内容与passwd(2)进行异或处理,结果保存在资源内容指针lpBuffer中
跳出函数后,在sub_40100F()26行中将lpBuffer写入文件dbapp.rtf中
整理下思路:
资源内容 ^ passwd(2) ==> 写入文件dbapp.rtf,那么我们就可以通过dbapp.rtf的内容异或资源内容,从而得到passwd(2)了
资源内容可以通过ResourceHacker获得
而dbapp.rtf,前面几个字节是固定的,这是用于给程序识别是什么类型的文件,类似于zip的50 4B、png的89 50 4E 47,我们系统里随便找个rtf文件用十六进制查看工具打开
然后取资源内容和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
运行程序,输入结果
打开同目录下生成的dbapp.rtf文件就得到flag
Buu-刮开有奖
定位关键点
首先查壳查信息,无壳,Win32程序
跟进到WinMain函数,这是Win32的入口函数
调用了DialogBoxParamA(),该函数是打开一个对话框,其中第四个参数是一个函数,我们跟进这个函数(因为也没有其他可以跟的了)
跟进后,通过57行的调用提示框内容,猜测这个函数应该就是程序核心了。
分析关键代码
前半部分的代码基本就是注释中描述的样子
分析sub_4010f0()
接着分析sub_4010f0()函数,参数是v7,0,10。
说实话,硬看代码确实难受。根据网上师傅们的做法,是简单翻译成C代码然后带入参数值执行一遍。
首先看到函数内对a1的操作是一个很明显的数组通过索引获取数据的操作,所以给参数a1修改为char* a1,然后是将数组a1操作的类型转换修改成char*
然后是将函数内所有数组操作的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的调试=》反汇编里的):
其中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
双击进入数据段中可以发现值为ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=
(像我这样的菜??可能会有个疑问,明明截图中值看到BCDEFGHIJKLMNOPQRSTUVWXYZab...,A哪里来的,其实就在上一行中,41H转换成十进制是65,及时‘A‘的ASCII码)
有经验的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判断分析
通过分析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位文件
找主函数
用IDA64打开,在函数列表找到main
关键代码分析
main函数中只做了简单的功能,输入d/D进入Decry(),输入q/Q退出
跟进Decry()
数据赋值部分
需要注意伪代码是小端序
join函数是将参数a1和a2拼接起来,然后返回拼接后地址
后面一个for,认真跟一下流程就知道是大写转小写的
接下来是通过getchar()循环获取用户输入,然后判断为字母的,进行计算操作
计算操作:str2[v2] = (v1 - 39 - key[v3++ % v5] + 97) % 26 + 97;
整理思路
- 用户输入数据
- 将所有用户输入为字母的,进行计算操作
- 最后将结果与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函数,然后对能直观看出来的代码进行注释
目前只有sub_4110BE()函数不确定作用,我们跟进sub_4110BE()
函数中使用了大量的aAbcdefghijklmn数组
在数据块中查看数组是如下图内容,猜测该函数是base64编码。这一点也可以通过OD单步执行观察出来,用户输入的数据经过一个call后变成了base64
现在思路就很清晰了,只需要找到IDA中Dest变量的值就可以了。我们通过OD单步执行找到push了我们输入数据的base64值,然后调用strncmp的位置,就可以找到Dest
下图第二个push就是我们要找的数据
然后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
首先用中文搜索引擎找到输出flag的地方
双击进入其所在位置,通过观察,发现是个函数
解题思路:找到一处正常执行的位置,修改跳转地址,直接跳转到这个函数即可打印flag
通过运行程序,发现每次都会打印“n=”,所以我们将打印“n=”的位置修改成jmp 15E940,就可以让其在新的循环中跳转到打印flag的函数
修改前:
修改后:
在修改的汇编代码上面一行下个断点,然后让程序运行到断点位置,之后一步步运行打印flag
执行到这个位置会一直循环,我们可以在循环的后一行下个断点,然后f9运行到这个断点处
运行到断点处就得到flag了