14道Buuoj Re记录

CrackRTF

  1. 拉到IDA反编译一下,找到主函数main0,发现需要输入两次password,第一次输入的password在追加一个字符串以后还要进行一次加密操作(这里的encrypt是我自行修改的名称)
  2. 进入这个函数,首先发现了关键的hash操作,查一下这个函数 CryptCreateHash

文档中有

1
2
3
4
5
6
7
BOOL CryptCreateHash(
HCRYPTPROV hProv,
ALG_ID Algid,
HCRYPTKEY hKey,
DWORD dwFlags,
HCRYPTHASH *phHash
);

所以,第二个参数无符号数0x8004应当指哈希算法的类型,点击页面提供的超链 ALG_ID 可以发现这个值意味着使用的哈希算法是SHA1

  1. 所以基本的逻辑应该是Destination+”@DBApp”经过SHA1后得到string1
  2. 由于上文代码中可以确认Destination转成int之后是大于100000的,所以可以通过爆破得到Destination的值,python爆破代码如下:
1
2
3
4
5
6
7
8
9
10
import hashlib
s = '@DBApp'
Destination = 100000
while True:
dcats = str(Destination) + s
hashVal = hashlib.sha1(dcats.encode()).hexdigest()
if hashVal.upper() == "6E32D0943418C2C33385BC35A1470250DD8923A9":
print(Destination, dcats)
break
Destination += 1

注意,这里哈希值的字符串中的字母是默认小写的,所以upper一下和IDA中的值对应即可。最终得到Destination值为 123321

  1. 通过password1后还有一个password2,看一下就是输入长度为6的字符,然后追加Destination,再经过一个函数进行处理,处理函数和上文的哈希类似。

    不过算法变成了MD5
  2. 如果 MD5(str+Destination) 结果为27019e688a4e62a649fd99cadaafdb4e,还要经过 sub_40100F函数对输入的字符串str进行判断
  3. 进入该函数,首先定位一个名为AAA的资源文件,采集文件信息并通过函数 sub_401005 这里的lpString就是前文提到的str也就是我们第二次输入的password,查看微软文档中SizeofResource和LockResource的return值可知,lpBuffer是指向资源文件第一个字节的指针,nNumberOfBytesToWrite为资源文件的字节数,进入该函数

  4. 基本的逻辑就是从AAA文件中取值与str进行循环异或,因为需要最终成功创建一个rtf文件,sub_40100F才能返回1,才能通过验证。

  5. 因为我们需要的结果字符串str(password2)只有6位,所以只需要和AAA的前六位进行异或就可以拿到RTF文件的前六位了,这里需要通过 Resource Hacker 来获取AAA前六个字节的数据

  6. RTF文件前六位{\rtf1

1
2
3
4
5
6
rtf = "{\\rtf1"
AAA = [0x05, 0x7D, 0x41, 0x15, 0x26, 0x01]
passwd2 = ""
for i in range(6):
passwd2 += chr(ord(rtf[i]) ^ AAA[i])
print(passwd2)

结果为 ~!3a@0

  1. 通过验证,自动创建dbapp.rtf文件,打开即有Flag{N0_M0re_Free_Bugs}

Youngter-drive

参考

  1. 直接拖进IDA,符号不可见,可能是加壳了,用Exeinfo PE 查壳

  2. UPX工具脱壳,用法参考

1
upx -d Youngter-drive.exe  

  1. 脱壳后重新用IDA即可正常反编译

  2. 找到main函数

  3. 查阅 docs.microsoft 可知,CreateThread 函数用于在调用函数的虚拟地址空间创建一个线程,第三个参数是线程的起始地址

  4. 查看第一个线程的起始地址StartAddress,最终发现报栈指针错误(如果无法反编译需要按以下步骤调整栈指针)
    a. 跳转到该函数地址,(直接在函数窗口搜索即可),在IDA-View可以看到

b. 为了能看到栈指针的值并做修改,需要 option->General->Disassembly->Stack pointer 勾选即可

c. 然后就发现刚才的IDA-View显示栈指针

d. 将负值修正为0即可

  1. 但这里不需要,因为我们已经可以反编译到源码了,其中a1根据代码上文可知是Source。

    a2是后面要提到的线程2的递减指针。该函数对字符数组off_418000进行操作,该字符数组内容为 “QWERTYUIOPASDFGHJKLZXCVBNMqwertyuiopasd”
  2. 基本的逻辑如果字母小于’a’或大于’z’就减去38,否则就减去96

  3. 另一个线程一直在做递减操作,不改变字符串,进程交替工作,导致奇数位字符串经过线程1的处理,偶数位字符串经过线程2不变

  4. 最后有一个判断

  5. off_418004字符数组的内容是 “TOiZiZtOrYaToUwPnToBsOaOapsyS” ,所以上文Source也即a1的变换后的值就是这个。
  6. 再回到加密函数,我们整理好逻辑为:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class Solution {
private:
string source = "TOiZiZtOrYaToUwPnToBsOaOapsyS";
string off_418000 = "QWERTYUIOPASDFGHJKLZXCVBNMqwertyuiopasd";
public:
void getFlag() {
string ans = "";
for (int i = 0; i < source.size(); i++) {
if (i % 2 == 0) {
ans += source[i];
}
else {
if (source[i] < 'a')
ans += char(off_418000.find_first_of(char(source[i])) + 96);
else
ans += char(off_418000.find_first_of(char(source[i])) + 38);
}
}
cout << ans;
}
};

输出为ThisisthreadofwindowshahaIsES,实际上少一位,尝试得到E,flag{ThisisthreadofwindowshahaIsESE}

[ACTF新生赛2020] easyre

  1. 用IDA打开时,提示文件被损坏,有可能被加壳,用Exeinfo PE查看是UPX,所以用upx工具脱壳,重新拿到IDA即可
  2. 找到main函数
  3. 可以双击 data_start 看看数据

  4. 结合main函数代码,flag就是遍历v4从data中找打对应下标再加1

1
2
3
4
5
6
7
8
9
10
11
12
13
class Solution {
private:
string v4 = "*F'\"N,\"(I?+@";
string data = "~}|{zyxwvutsrqponmlkjihgfedcba`_^]\\[ZYXWVUTSRQPONMLKJIHGFEDCBA@?>=<;:9876543210/.-,+*)(\x27&%$# !\"";
public:
void getFlag() {
string flag = "";
for (int i = 0; i <= 11; i++) {
flag += data.find(v4[i]) + 1;
}
cout << "flag{" << flag << "}";
}
};
  1. flag{U9X_1S_W6@T?}

[ACTF新生赛2020]rome

  1. 无壳,直接IDA反编译,找到main函数,内有关键函数 func()

  2. 可以看到首先确定了flag的格式,为ACTF{}(这里是BUU所以flag格式实际还是flag{},可以暂不考虑),另外第一个while实际上在做移位,而我们也没有必要非得逆着写代码,只要从0~127遍历字符,遇到变换后和v12中对应字符相等的就追加到flag字符串即可

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
class Solution {
private:
string v12 = "Qsw3sj_lz4_Ujw@l";
public:
void getFlag() {
string flag = "";
for (char v : v12) {
for (int i = 0; i < 128; ++i) {
char tmp = '0';
if (i > '@' && i <= 'Z') {
tmp = (i - 51) % 26 + 65;
}
else if (i > '`' && i <= 'z') {
tmp = (i - 79) % 26 + 97;
}
else {
tmp = i;
}
if (v == tmp) {
flag += char(i);
break;
}
}
}
cout << "flag{" << flag << "}" << endl;
}
};
  1. 得到flag{Cae3ar_th4_Gre@t}

[GUET-CTF2019]re

  1. 用Exeinfo PE查壳发现UPX,upx工具脱壳,拉进IDA
  2. main函数如下
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
int __cdecl main(int argc, const char **argv, const char **envp)
{
// [COLLAPSED LOCAL DECLARATIONS. PRESS KEYPAD CTRL-"+" TO EXPAND]
v5 = __readfsqword(0x28u);
v4[0] = 0LL;
v4[1] = 0LL;
v4[2] = 0LL;
v4[3] = 0LL;
printf_0((__int64)"input your flag:");
scanf((__int64)"%s", v4); // flag
if ( encrypt((char *)v4) )
printf((__int64)"Correct!");
else
printf((__int64)"Wrong!");
result = 0;
if ( __readfsqword(0x28u) != v5 )
sub_443550();
return result;
}
  1. 我根据功能对函数名进行了猜测与修改,进入加密函数后

    一直到a1[31],不过缺少了a1[6],没有从其他地方找到跟a1[6]相关的数据,只能进行爆破,c++代码如下,最终结果为 flag{e165421110ba03099a1c039337}
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
39
40
41
42
43
44
45
46
47
class Solution {
public:
void getFlag() {
vector<int> a1(32, 0);
a1[0] = 166163712 / 1629056;
a1[1] = 731332800 / 6771600;
a1[2] = 357245568 / 3682944;
a1[3] = 1074393000 / 10431000;
a1[4] = 489211344 / 3977328;
a1[5] = 518971936 / 5138336;
a1[7] = 406741500 / 7532250;
a1[8] = 294236496 / 5551632;
a1[9] = 177305856 / 3409728;
a1[10] = 650683500 / 13013670;
a1[11] = 298351053 / 6088797;
a1[12] = 386348487 / 7884663;
a1[13] = 438258597 / 8944053;
a1[14] = 249527520 / 5198490;
a1[15] = 445362764 / 4544518;
a1[17] = 174988800 / 3645600;
a1[16] = 981182160 / 10115280;
a1[18] = 493042704 / 9667504;
a1[19] = 257493600 / 5364450;
a1[20] = 767478780 / 13464540;
a1[21] = 312840624 / 5488432;
a1[22] = 1404511500 / 14479500;
a1[23] = 316139670 / 6451830;
a1[24] = 619005024 / 6252576;
a1[25] = 372641472 / 7763364;
a1[26] = 373693320 / 7327320;
a1[27] = 498266640 / 8741520;
a1[28] = 452465676 / 8871876;
a1[29] = 208422720 / 4086720;
a1[30] = 515592000 / 9374400;
a1[31] = 719890500 / 5759124;
string flag = "";
for (int i = 0; i < a1.size(); ++i) {
flag += a1[i];
}
for (int i = 0; i < 128; ++i) {
if (isalnum(i) || isalpha(i)) {
flag[6] = char(i);
cout << flag << endl;
}
}
}
};

相册

  1. apk文件拿到jadx反编译,搜索mail,这里调用的方法是加载了lib的

  2. 将apk文件解压,将lib中的libcore.so文件拖到IDA64查看字符串,发现几个base64编码

  3. 解码MTgyMTg0NjUxMjVAMTYzLmNvbQ==时,有18218465125@163.com,这就是flag

[BJDCTF2020]easy

OD 动态调试
修改EIP的值

  1. 拉到IDA,没有找打main函数中有什么关键信息,最后有个ques函数比较可疑,没有被调用过,其地址为0x401520
  2. 拖进OD,step over (F8) 到jmp指令处,双击地址将其地址改为ques的地址
  3. F9运行得到
  4. 所以flag{HACKIT4FUN}

[ACTF新生赛2020]usualCrypt

  1. 输入字符串后,首先对字符串进行Base64编码(这里的base是人为修改的,因为进入该函数能发现明显的Base64特征)。然后判断结果是否与byte_40E0E4相等

  2. 结果字符串数组为zMXHz3TIgnxLxJhFAdtZn2fFk3lYCrtPC2l9,注意前面还有个7Ah

  3. 另外,这个base函数中有两处修改
  4. 第一次修改为:交换byte_40E0AA和byte_40E0A0两个数组[6,15)处的值

  5. 容易得到交换结果为 ABCDEFQRSTUVWXYPGHIJKLMNOZabcdefghijklmnopqrstuvwxyz0123456789+/ 这就是修改后的编码表

  6. 第二次修改为

  7. 是小写就减32,大写就加32,大小写字母ASCII差值为32,所以逻辑就是大小写转换。 变表Base64解码 参考 解密代码如下

1
2
3
4
5
6
7
import base64
c = "zMXHz3TIgnxLxJhFAdtZn2fFk3lYCrtPC2l9"
A0 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
changed = "ABCDEFQRSTUVWXYPGHIJKLMNOZabcdefghijklmnopqrstuvwxyz0123456789+/"
c = c.swapcase()
flag = base64.b64decode(c.translate(str.maketrans(changed, A0)))
print(flag)
  1. 结果为flag{bAse64_h2s_a_Surprise}

[MRCTF2020]Transform

  1. main函数如下,首先输入字符串,可以推断其长度为33,原始字符串数组先用40F040做索引生成新数组再,与40F040进行异或需要与40F0E0相等
  2. 40F040及40F0E0处的数据情况如下,两者异或再索引回原来的值即为flag,后者可以用hex视图复制

  3. 解题代码为

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Solution {
private:
vector<int> f1 = { 9, 0x0A, 0x0F, 0x17, 7, 0x18, 0x0C, 6, 1, 0x10, 3, 0x11, 0x20, 0x1D, 0x0B, 0x1E, 0x1B, 0x16, 4, 0x0D, 0x13, 0x14, 0x15, 0x2, 0x19, 5, 0x1F, 8, 0x12, 0x1A, 0x1C, 0x0E, 0 };
string f2 = "gy{.u+<RSyW^]B{-*fB~LWyAk~e<\\EobM";
public:
void getFlag() {
vector<char> flag(33, '0');
for (int i = 0; i <= 32; ++i) {
char c = f1[i] ^ f2[i];
flag[f1[i]] = c;
}
for (char f : flag) {
cout << f;
}
}
};
  1. 结果为 MRCTF{Tr4nsp0sltiON_Clp93r_1s_3z}

[WUSTCTF2020]level1

  1. 题目给了一个二进制文件,一个文本文件,将二进制文件拖入IDA,进入main函数
  2. 所以逻辑很简单,就是将输入的flag,按奇偶数位对原始字符进行修改,解题代码如下
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
#include <iostream>
#include <fstream>
#include <string>
#include <vector>
#include <sstream>
using namespace std;
class Solution {
private:
vector<long> c;
public:
void getFlag() {
ifstream output("output.txt", ios::in);
string tmp;
while (getline(output, tmp)) {
stringstream ss(tmp);
long tmpInt;
ss >> tmpInt;
c.push_back(tmpInt);
}
for (int i = 1; i <= 19; ++i) {
if (i % 2 != 0) {
c[i-1] = c[i - 1] >> i;
}
else {
c[i - 1] = c[i - 1] / i;
}
cout << char(c[i - 1]);
}
}
};
  1. 输出为 ctf2020{d9-dE6-20c}

[WUSTCTF2020]level2

  1. exeinfo PE查到UPX壳,用UPX工具脱壳后拿到IDA

  2. 找到main函数,IDA-View有

  3. wctf2020{Justupx-d}

[GWCTF 2019]xxor

  1. 找到main函数
  2. 参考
  3. 这里dword_601078是取了每个v6元素四个字节中的后两个字节中的值,dword_60107c则是取了每个v6元素四个字节中前两个字节的值
  4. 为什么dword——601078那一条语句没有lodword函数却依然和有lodword函数的结果相同,这是由于dword_601078这个变量只能存储2个字节的数据,因此在读取v6[j]时只能读取其前两个字节,那么效果其实就跟lodword一样了
  5. sub_400686中对输入的数据两个两个一组进行异或加密
  6. 最后判断
  7. a2的值为2,2,3,4
  8. 解题代码如下
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
class Solution {
public:
void getFlag() {
vector<long long> v7(6, 0);
v7[0] = 0xDF48EF7E;
v7[1] = 550153460;
v7[5] = 0x84F30420;
v7[2] = (2225223423LL + 4201428739LL + 1121399208LL) / 2;
v7[3] = v7[2] - 2225223423LL;
v7[4] = v7[2] - 1121399208LL;
for (long long v : v7) {
cout << v << endl;
}
vector<int> a2 = { 2,2,3,4 };
unsigned int v3, v4;
long long v5;
for (int j = 0; j <= 4; j += 2) {
v3 = v7[j];
v4 = v7[j + 1];
v5 = 1166789954ULL * 0x40ULL;
for (int i = 0; i <= 0x3F; ++i) {
v4 -= (v3 + v5 + 20) ^ ((v3 << 6) + a2[2]) ^ ((v3 >> 9) + a2[3]) ^ 0x10;
v3 -= (v4 + v5 + 11) ^ ((v4 << 6) + a2[0]) ^ ((v4 >> 9) + a2[1]) ^ 0x20;
v5 -= 1166789954;
}
v7[j] = v3;
v7[j + 1] = v4;
}
for (int i = 0; i < 6; ++i) {
cout << hex << v7[i];
}
}
};
  1. 要注意防止溢出,原来IDA中可获取的变量类型尽量不变,否则,输出的结果会因为变量类型来得到非期望的值
  2. 输出为 666c61677b72655f69735f6772656174217d,转ASCII结果为 flag{re_is_great!}

[HDCTF2019]Maze

参考

  1. 查壳发现UPX,脱壳后拿到IDA分析
  2. 找到start,发现main函数不能反编译
  3. jnz到下一行代码,相当于没做跳转。另外call调用的地址找不到,可以推断出这段代码添加了花指令。
  4. 利用OD将jnz这段指令nop掉

OD nop指令

  1. 原有代码段可参照上文
  2. 将可执行文件拖入OD,找到对应地址。双击将其用nop填充

  3. 修改后如下图
  4. 这里需要注意,此处在内存中的修改不会自动保存到可执行文件中,需要右键,将修改保存到可执行文件

  5. 右键弹出的窗口,并保存到文件,这时就可以发现,保存的文件是exe

  6. 将新文件拖入IDA,发现修改完成

  1. [续前节]不过这里的main函数还是不能反编译。后面这个call指令,不能全部nop,因为后面那个东西可能是有效代码。D键将其先转换为字节数据
  2. 先将第一个数据nop掉再转成代码,进行尝试。这里用OD再nop掉 db E8 ,方法相同,再保存到可执行文件重新用IDA打开
  3. 发现IDA自动将OD修改完的数据转成代码,main函数可以正常反编译了

  4. 查看8078和807C两个变量,发现其值分别为7和0,看到了上面的迷宫。所以adsw分别代表左右下上,8078代表行,807C代表列。
1
2
3
4
5
6
7
*******+**
******* **
**** **
** *****
** **F****
** ****
**********
  1. 可以整理到7行10列的迷宫,走法为ssaaasaassdddw,正好14步,符合main中for循环的设定,所以 flag{ssaaasaassdddw}

[BJDCTF2020]BJD hamburger competition

参考

  1. 是一个unity做的游戏,这里直接用 dnspy Github地址 dnspy Github加速站地址 下载dnspy的release版本,看源码
  2. 在BJD hamburger competition_Data\Managed文件夹中找到Assembly-CSharp.dll,这是程序的源码。将其拖入dnspy中
  3. 搜索字符串CTF,找到关键的类
  4. 发现只要str的sha1值等于 DD01903921EA24941C26A48F2CEC24E0BB0E8CC7 就满足条件,最后的flag就是str的MD5值,先将SHA1值拿到 SHA1解密 得到1001,再生成1001的MD5得到 b8c37e33defde51cf91e1e03e51657da,但是提交不通过。进入MD5函数查看
  5. X2转大写,Substring取前20位,所以
1
s= 'b8c37e33defde51cf91e1e03e51657da'.upper()[0:20]
  1. 结果为B8C37E33DEFDE51CF91E,所以flag{B8C37E33DEFDE51CF91E}


----------- 本文结束 -----------




0%