diff --git a/picoctf/web/javascriptkiddie/Makefile b/picoctf/web/javascriptkiddie/Makefile new file mode 100644 index 0000000..98627cf --- /dev/null +++ b/picoctf/web/javascriptkiddie/Makefile @@ -0,0 +1,11 @@ +CC = gcc + +CFLAGS = -g -Wall + +jsk_solver: jsk_solver.o + +jsk_solver.o: jsk_solver.c + +.PHONY: clean +clean: + rm -f *.o a.out core jsk_solver diff --git a/picoctf/web/javascriptkiddie/javascriptkiddie.js b/picoctf/web/javascriptkiddie/javascriptkiddie.js new file mode 100644 index 0000000..8038428 --- /dev/null +++ b/picoctf/web/javascriptkiddie/javascriptkiddie.js @@ -0,0 +1,29 @@ +/* +Original JavaScript script that is found after capturing requests via BurpSuite +*/ + +var bytes = []; +$.get("bytes", function(resp) { + bytes = Array.from(resp.split(" "), x => Number(x)); +}); + +function assemble_png(u_in){ + var LEN = 16; + var key = "0000000000000000"; + var shifter; + if(u_in.length == LEN){ + key = u_in; + } + var result = []; + for(var i = 0; i < LEN; i++){ + shifter = key.charCodeAt(i) - 48; + for(var j = 0; j < (bytes.length / LEN); j ++){ + result[(j * LEN) + i] = bytes[(((j + shifter) * LEN) % bytes.length) + i] + } + } + while(result[result.length-1] == 0){ + result = result.slice(0,result.length-1); + } + document.getElementById("Area").src = "data:image/png;base64," + btoa(String.fromCharCode.apply(null, new Uint8Array(result))); + return false; +} \ No newline at end of file diff --git a/picoctf/web/javascriptkiddie/jsk-images/.DS_Store b/picoctf/web/javascriptkiddie/jsk-images/.DS_Store new file mode 100644 index 0000000..25b5f71 Binary files /dev/null and b/picoctf/web/javascriptkiddie/jsk-images/.DS_Store differ diff --git a/picoctf/web/javascriptkiddie/jsk-images/basic-png-format-ex.png b/picoctf/web/javascriptkiddie/jsk-images/basic-png-format-ex.png new file mode 100644 index 0000000..4745584 Binary files /dev/null and b/picoctf/web/javascriptkiddie/jsk-images/basic-png-format-ex.png differ diff --git a/picoctf/web/javascriptkiddie/jsk-images/repeater-bytes.png b/picoctf/web/javascriptkiddie/jsk-images/repeater-bytes.png new file mode 100644 index 0000000..a0f201a Binary files /dev/null and b/picoctf/web/javascriptkiddie/jsk-images/repeater-bytes.png differ diff --git a/picoctf/web/javascriptkiddie/jsk-images/repeater-js.png b/picoctf/web/javascriptkiddie/jsk-images/repeater-js.png new file mode 100644 index 0000000..753fb53 Binary files /dev/null and b/picoctf/web/javascriptkiddie/jsk-images/repeater-js.png differ diff --git a/picoctf/web/javascriptkiddie/jsk_solver.c b/picoctf/web/javascriptkiddie/jsk_solver.c new file mode 100644 index 0000000..5307b03 --- /dev/null +++ b/picoctf/web/javascriptkiddie/jsk_solver.c @@ -0,0 +1,91 @@ +/* +Written by Madalina Stoicov +March 27, 2025 +PicoCTF: Java Script Kiddie +*/ + +#include +#include +#include + + +int main(int argc, char **argv) +{ + // Insert the given bytes array as a string + // This data might differ depending on difference instances of the challenge + char *bytes_as_string = "156 255 80 255 117 10 239 248 152 253 120 232 36 127 116 255 151 235 25 172 215 0 56 102 219 174 30 15 36 188 93 90 249 36 32 45 123 73 191 151 236 241 151 68 144 250 157 130 1 180 20 85 213 2 157 248 68 255 250 13 60 66 249 82 187 157 29 222 29 30 0 252 126 251 95 0 174 72 194 108 29 101 70 21 121 40 26 132 73 119 254 237 73 192 96 219 137 80 89 71 0 145 1 152 0 69 254 71 0 65 68 35 0 0 119 114 13 222 68 119 1 0 78 40 155 111 95 90 164 0 26 2 0 245 186 0 84 0 0 233 145 46 110 49 48 16 78 223 135 64 197 10 0 120 0 243 252 62 144 188 21 61 1 110 148 208 22 114 160 31 156 17 45 59 72 237 74 218 0 8 32 123 136 65 179 150 32 56 206 43 240 9 156 225 69 54 226 158 106 148 62 48 1 232 173 0 239 248 243 206 82 255 241 252 56 55 152 132 108 181 78 254 175 251 60 183 38 231 63 123 204 48 43 13 131 4 113 75 243 215 32 200 144 195 29 233 196 63 3 190 139 207 89 28 107 159 185 101 59 120 121 12 245 116 64 96 250 187 241 234 231 207 213 239 119 191 233 71 205 127 144 40 251 253 173 186 246 10 227 252 202 242 163 74 237 33 75 49 205 74 154 165 126 231 30 231 232 199 118 65 211 98 204 7 250 244 141 155 243 123 82 137 252 35 183 201 132 91 252 37 244 56 188 86 125 103 216 248 215 146 144 149 21 164 233 219 127 127 207 208 30 154 111 203 63 127 141 231 146 5 20 4 81 239 38 36 19 191 63 61 183 223 215 205 210 239 168 135 148 201 39 248 212 191 160 151 116 19 150 99 249 141 111 188 0 225 193 61 73 140 160 56 23 53 48 5 99 100 175 250 125 151 253 12 150 85 41 72 206 97 52 79 88 196 130 26 157 254 185 181 42 146 217 255 24 125 155 88 111 116 167 62 238 36 52 95 57 54 126 233 184 143 46 183 234 73 183 108 163 228 218 233 129 44 169 191 74 0 30 126 245 10 249 245 241 65 191 245 73 209 50 140 26 72 132 223 181 204 200 123 185 186 183 218 175 228 249 75 180 91 229 252 193 203 187 253 52 166 28 117 119 13 238 134 74 227 127 71 251 237 50 191 61 76 230 90 241 178 221 233 202 254 211 228 156 60 202 241 71 49 24 90 187 3 245 247 159 124 157 250 227 18 150 50 49 101 86 235 162 234 57 124 108 116 245 226 190 28 43 129 220 86 245 85 107 38 215 223 119 242 72 140 213 103 209 194 70 30 96 111 204 128 234 55 184 247 205 49 227 5 220 101 80 171 155 217 87 33 26 173 127 187 128 253 215 111 203 54 210 243 29 237 148 204 235 202 131 191 191 211 157 54 147 104 188 87 4 251 25 17 185 219 247 124 135 228 176 223 135 196 157 130 215 206 124 122 136 248 28 23 175 56 104 209 253 47 161 236 61 252 147 140 86 102 185 82 110 231 91 251 245 216 243 254 236 176 127 134 31 135 152 251 90 0 216 127 102 56 99 56 64 204 61 95"; + + int png_bytes[16] = {137, 80, 78, 71, 13, 10, 26, 10, 0, 0, 0, 13, 73, 72, 68, 82}; + + int i; + int count = 0; + + // copy of initial string for counting number of bytes + char *bytes_as_string_counting = strdup(bytes_as_string); + + char *token = strtok(bytes_as_string_counting, " "); + + // count the number of bytes + for (i = 0; token != NULL; i++) + { + count++; + token = strtok(NULL, " "); + } + + // official bytes int array with count number of slots + int bytes[count]; + + // make copy for adding numbers to bytes array + char *bytes_as_string_copy = strdup(bytes_as_string); + + // reassign token + token = strtok(bytes_as_string_copy, " "); + + // iterate again and add numbers to the bytes array + for (i = 0; token != NULL; i++) + { + bytes[i] = atoi(token); + token = strtok(NULL, " "); + } + + // Now for the solving! + + int key[16]; + int shifters[10] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; + int curr_shifter; + int index; + int j; + + // for each space in the key array + for (i = 0; i < 16; i++) + { + + // for each possible shifter + for (j = 0; j < sizeof(shifters); j++) + { + curr_shifter = shifters[j]; + + /* + Calculate the index of bytes based off the original script + Original from JS script: bytes[(((j + shifter) * 16) % bytes.length) + i] + We do not add anything to shifter because we're just iterating through possible shifters + */ + index = ((curr_shifter * 16) % count) + i; + + // if the bytes in the PNG array match our calculated bytes, + // add key shifter to our key array + if (png_bytes[i] == bytes[index]) + { + key[i] = curr_shifter; + break; + } + } + } + + // print the key + for (i = 0; i < 16; i++) + { + printf("%d", key[i]); + } + printf("\n"); +} \ No newline at end of file diff --git a/picoctf/web/javascriptkiddie/writeup.md b/picoctf/web/javascriptkiddie/writeup.md new file mode 100644 index 0000000..4250905 --- /dev/null +++ b/picoctf/web/javascriptkiddie/writeup.md @@ -0,0 +1,139 @@ +# PicoCTF: Java Script Kiddie + +## Context + +We are provided with a website that prompts you to enter some text. We are not given information on what the text should be. Upon submitting that text, we get a broken image link. We are not provided with any site source code. + +## Background Information: PNG images + +Every PNG image contains the same first 8 bytes, known as the "PNG signature": 89 50 4E 47 0D 0A 1A 0A. + +The next chunk of a PNG is the image header (IHDR), which contains information about the image's width, depth, and color information. The image below displays what the IHDR could look like for a basic image. Note that the first 8 bytes of the IHDR are the size of the IHDR chunk and the fact that the chunk is an IHDR. Therefore, these bytes will be consistent throughout all PNGs: 00 00 00 0D 49 48 44 52. + +![Alt text](jsk-images/basic-png-format-ex.png) + +## Vulnerability + +As we are given no source code for this challenge, we may try to get more information by capturing requests via BurpSuite. + +We see that upon sending text in the website's input area, two requests are sent. We capture both of them and send each to the BurpSuite Repeater for further analyzing. + +Upon hitting "Send" in the BurpSuite Repeater for the first request, we see that the HTML contains a JavaScript script. See `javascriptkiddie.js` for the script. It gathers information from /bytes (see next paragraph), calls a function assemble_png, and then pieces together an image. + +![Alt text](jsk-images/repeater-js.png) + +Upon hitting "Send" in the BurpSuite Repeater for the second request, we see that we are directed to /bytes and we get a series of bytes in our response. See the top of `jsk_solver.c` (char* bytes_as_string) for a possible version of the bytes (every instance of the challenge will have a slightly different set of bytes). + +![Alt text](jsk-images/repeater-bytes.png) + +Putting these pieces of information together we gather that the assemble_png function does the following: +1. Set a default key of 0000000000000000 (16 0s) +2. If the inputted key from the user is 16 digits long, keep the default key. Else, make the key the user's inputted key. +3. For every digit in the key, grab 1/16 of the bytes and calculate a new positition for each byte in that chunk depending on the value of the digit in the key. +4. Eventually, this shuffling will rearrange all of the bytes into a result array. + +We realize that we must input the key that will rearrange the bytes to resemble a PNG. + +## Exploitation + +Let's write a script that will find the key we need to use. Please reference jsk_solver.c. + +First we must acknowledge that the bytes sent by the site do not at all resemble a PNG. Recall that the first 8 bytes must be 89 50 4E 47 0D 0A 1A 0A (in decimal: 137 80 78 71 13 10 26 10). The next 8 bytes of any PNG will be 00 00 00 0D 49 48 44 52 (in decimal: 0 0 0 13 73 72 68 82). These are the only parts of the target PNG we know. Note that assemble_png relied on 16-byte chunks, signalling that we only need to know the first 16 bytes of the PNG to be able to make a 16-long key to reassemble our PNG. + +Therefore we can declare that the first 16 bytes of our target PNG are: + +`int png_bytes[16] = {137, 80, 78, 71, 13, 10, 26, 10, 0, 0, 0, 13, 73, 72, 68, 82};` + +I first parsed the bytes to see what length our resulting array would need to be: + +```c + int i; + int count = 0; + + // copy of initial string for counting number of bytes + char *bytes_as_string_counting = strdup(bytes_as_string); + + char *token = strtok(bytes_as_string_counting, " "); + + // count the number of bytes + for (i = 0; token != NULL; i++) + { + count++; + token = strtok(NULL, " "); + } + + // official bytes int array with count number of slots + int bytes[count]; +``` +I then made an int array for our bytes: + +```c + // make copy for adding numbers to bytes array + char *bytes_as_string_copy = strdup(bytes_as_string); + + // reassign token + token = strtok(bytes_as_string_copy, " "); + + // iterate again and add numbers to the bytes array + for (i = 0; token != NULL; i++) + { + bytes[i] = atoi(token); + token = strtok(NULL, " "); + } +``` +To construct the key, I iterated once for each position in the key, and for each iteration iterated through all possible digits. Note that the JS had `shifter = key.charCodeAt(i) - 48;`, letting us know that only digits 0-9 are possible key digits. Through these iterations, if the target bytes in `png_bytes` matched the calculated byte in, `bytes` we added that shifter to the key and proceeded to the next digit in the key. The calculation for which byte to get in `bytes` is based off `assemble_png`'s calculations: + +`result[(j * LEN) + i] = bytes[(((j + shifter) * LEN) % bytes.length) + i]` + +In my script, there is no inner "j" loop because we are manually going through the possible shifters, so my calculations are a bit simpler: + +`index = ((curr_shifter * 16) % count) + i;` + +This is what this whole portion of the script looks like: + +```c + // Now for the solving! + + int key[16]; + int shifters[10] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; + int curr_shifter; + int index; + int j; + + // for each space in the key array + for (i = 0; i < 16; i++) + { + + // for each possible shifter + for (j = 0; j < sizeof(shifters); j++) + { + curr_shifter = shifters[j]; + + /* + Calculate the index of bytes based off the original script + Original from JS script: bytes[(((j + shifter) * 16) % bytes.length) + i] + We do not add anything to shifter because we're just iterating through possible shifters + */ + index = ((curr_shifter * 16) % count) + i; + + // if the bytes in the PNG array match our calculated bytes, + // add key shifter to our key array + if (png_bytes[i] == bytes[index]) + { + key[i] = curr_shifter; + break; + } + } + } +``` +From this script we are able to calculate the key needed for the specific set of bytes we were given! Once entering the key into the website, we are given a QR code. I just scanned it to get the flag, but because we are security-minded people, you should not scan random QR codes and instead use QR code decoder to see where it gets you. + +## Remediation + +This kind of challenge is not something you would find naturally as an exploit, but it shows how easy it can be to reassemble a PNG if all we need are the first 16 bytes. To make this reassembling more difficult, the key could be longer (so we would need to iterate through possible values of width and length, etc.), the unscrambling algorithm could be more complex, or the key could include both letters and numbers (so we would have to iterate through more possible values for the key). + +# Sources/Credits + +Written by Madalina Stoicov + +- https://en.wikipedia.org/wiki/PNG diff --git a/picoctf/web/javascriptkiddie2/Makefile b/picoctf/web/javascriptkiddie2/Makefile new file mode 100644 index 0000000..cf0b84e --- /dev/null +++ b/picoctf/web/javascriptkiddie2/Makefile @@ -0,0 +1,11 @@ +CC = gcc + +CFLAGS = -g -Wall + +jsk2_solver: jsk2_solver.o + +jsk2_solver.o: jsk2_solver.c + +.PHONY: clean +clean: + rm -f *.o a.out core jsk2_solver diff --git a/picoctf/web/javascriptkiddie2/jsk2.js b/picoctf/web/javascriptkiddie2/jsk2.js new file mode 100644 index 0000000..aa09003 --- /dev/null +++ b/picoctf/web/javascriptkiddie2/jsk2.js @@ -0,0 +1,25 @@ +var bytes = []; +$.get("bytes", function(resp) { + bytes = Array.from(resp.split(" "), x => Number(x)); +}); + +function assemble_png(u_in){ + var LEN = 16; + var key = "00000000000000000000000000000000"; + var shifter; + if(u_in.length == key.length){ + key = u_in; + } + var result = []; + for(var i = 0; i < LEN; i++){ + shifter = Number(key.slice((i*2),(i*2)+1)); + for(var j = 0; j < (bytes.length / LEN); j ++){ + result[(j * LEN) + i] = bytes[(((j + shifter) * LEN) % bytes.length) + i] + } + } + while(result[result.length-1] == 0){ + result = result.slice(0,result.length-1); + } + document.getElementById("Area").src = "data:image/png;base64," + btoa(String.fromCharCode.apply(null, new Uint8Array(result))); + return false; +} \ No newline at end of file diff --git a/picoctf/web/javascriptkiddie2/jsk2_images/basic-png-format-ex.png b/picoctf/web/javascriptkiddie2/jsk2_images/basic-png-format-ex.png new file mode 100644 index 0000000..4745584 Binary files /dev/null and b/picoctf/web/javascriptkiddie2/jsk2_images/basic-png-format-ex.png differ diff --git a/picoctf/web/javascriptkiddie2/jsk2_images/burpsuite_bytes.png b/picoctf/web/javascriptkiddie2/jsk2_images/burpsuite_bytes.png new file mode 100644 index 0000000..7121592 Binary files /dev/null and b/picoctf/web/javascriptkiddie2/jsk2_images/burpsuite_bytes.png differ diff --git a/picoctf/web/javascriptkiddie2/jsk2_images/burpsuite_js.png b/picoctf/web/javascriptkiddie2/jsk2_images/burpsuite_js.png new file mode 100644 index 0000000..2707379 Binary files /dev/null and b/picoctf/web/javascriptkiddie2/jsk2_images/burpsuite_js.png differ diff --git a/picoctf/web/javascriptkiddie2/jsk2_solver.c b/picoctf/web/javascriptkiddie2/jsk2_solver.c new file mode 100644 index 0000000..a809af8 --- /dev/null +++ b/picoctf/web/javascriptkiddie2/jsk2_solver.c @@ -0,0 +1,93 @@ +/* +Written by Madalina Stoicov +April 4, 2025 +PicoCTF: Java Script Kiddie 2 +*/ + +#include +#include +#include + + +int main(int argc, char **argv) +{ + // Insert the given bytes array as a string + // This data might differ depending on difference instances of the challenge + char *bytes_as_string = "228 80 39 113 0 148 0 143 252 172 225 6 220 243 68 255 63 0 164 243 13 127 26 30 158 48 174 68 6 119 95 128 200 0 191 28 0 107 1 69 243 234 78 13 176 66 110 174 27 16 188 71 135 228 68 235 100 192 219 0 235 72 229 231 56 148 150 114 141 247 101 254 172 69 171 237 90 192 73 117 43 154 78 2 150 254 129 252 124 0 255 71 174 77 152 91 137 13 1 95 69 59 180 0 164 0 78 233 73 171 255 245 0 197 0 0 133 0 243 10 255 120 0 85 0 153 129 195 164 223 70 170 205 10 48 114 73 28 0 243 154 253 15 130 48 32 220 62 37 0 35 65 0 216 156 170 129 59 90 82 112 104 153 12 45 73 221 105 1 147 32 98 68 221 137 108 66 208 191 111 29 45 30 190 84 239 166 159 118 77 151 164 140 250 34 210 24 45 211 223 164 197 74 30 206 132 127 102 111 36 153 144 9 75 10 206 89 102 188 212 103 191 71 20 156 126 73 179 227 131 33 69 226 223 96 88 214 252 91 248 142 219 199 161 196 98 96 0 59 125 205 3 74 206 60 30 69 210 115 144 166 73 249 122 128 229 153 237 231 205 252 122 71 0 143 190 70 255 103 200 65 136 172 166 227 56 55 178 248 255 245 247 24 170 253 165 156 130 31 95 61 57 148 55 159 255 31 96 26 195 185 96 129 252 233 36 36 92 146 111 113 180 242 210 175 208 29 63 102 246 172 154 248 191 12 248 22 153 140 180 61 151 111 0 138 113 101 89 73 125 184 15 12 8 65 144 72 239 249 63 168 110 239 198 158 57 79 235 35 165 197 248 51 223 107 7 132 217 126 255 169 201 223 33 206 181 158 1 166 55 247 219 91 91 14 248 115 149 105 93 117 130 242 84 226 148 83 84 232 117 181 155 51 31 4 180 252 237 161 195 206 164 175 75 19 245 194 65 186 239 62 253 211 111 130 154 24 254 201 214 194 254 105 173 174 5 159 173 8 180 114 23 202 53 39 243 173 87 59 95 61 69 64 219 122 143 62 8 136 51 98 146 128 67 241 126 216 166 166 234 242 191 207 157 242 53 254 39 78 197 215 197 26 33 176 36 193 113 151 211 131 57 158 179 86 113 139 116 59 100 7 189 164 45 200 251 122 12 223 253 179 103 74 228 254 89 155 83 5 173 32 230 126 201 199 199 86 91 126 114 62 105 91 132 183 188 243 193 77 217 243 167 201 190 123 71 196 110 117 115 76 58 103 61 190 114 58 130 232 110 171 147 243 73 157 202 30 235 240 239 174 224 172 149 53 141 170 206 34 55 212 227 229 152 130 217 221 155 221 102 48 87 109 62 235 107 150 58 64 18 142 4 187 24 70 133 180 175 223 13 22 191 126 255 188 198 73 119 40 156 95 111 160 79 155 223 234 138 98 7 145 127 26 141 174 207 179 31 191 122 251 233 191 79 174 130 160 190 232 244 231 191 225 105 222 159 253 46 37 155 3 212 182 239 250 149 102 196 245 107 225 95 126 186 126 233 226 147 189 51 197 30 206 77 239 128 184 176 255 106 236 145 96 160"; + + /* + The contents of our array of bytes is similar to JSK1's, except we input zeros in between as fillers + */ + int png_bytes[32] = {137, 0, 80, 0, 78, 0, 71, 0, 13, 0, 10, 0, 26, 0, 10, 0, 0, 0, 0, 0, 0, 0, 13, 0, 73, 0, 72, 0, 68, 0, 82}; + + int i; + int count = 0; + + // copy of initial string for counting number of bytes + char *bytes_as_string_counting = strdup(bytes_as_string); + + char *token = strtok(bytes_as_string_counting, " "); + + // count the number of bytes + for (i = 0; token != NULL; i++) + { + count++; + token = strtok(NULL, " "); + } + + // official bytes int array with count number of slots + int bytes[count]; + + // make copy for adding numbers to bytes array + char *bytes_as_string_copy = strdup(bytes_as_string); + + // reassign token + token = strtok(bytes_as_string_copy, " "); + + // iterate again and add numbers to the bytes array + for (i = 0; token != NULL; i++) + { + bytes[i] = atoi(token); + token = strtok(NULL, " "); + } + + // Now for the solving! + + int key[32]; + int shifters[10] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; + int curr_shifter; + int index; + int j; + + // for each space in the key array + for (i = 0; i < 33; i++) + { + // for each possible shifter + for (j = 0; j < sizeof(shifters); j++) + { + curr_shifter = shifters[j]; + + /* + Calculate the index of bytes based off the original script + Original from JS script: bytes[(((j + shifter) * 16) % bytes.length) + i] + We do not add anything to shifter because we're just iterating through possible shifters + */ + index = ((curr_shifter * 16) % count) + i; + + // if the bytes in the PNG array match our calculated bytes, + // add key shifter to our key array + if (png_bytes[i*2] == bytes[index]) + { + key[i*2] = curr_shifter; + break; + } + } + } + + // print the key + for (i = 0; i < 32; i++) + { + printf("%d", key[i]); + } + printf("\n"); +} \ No newline at end of file diff --git a/picoctf/web/javascriptkiddie2/writeup.md b/picoctf/web/javascriptkiddie2/writeup.md new file mode 100644 index 0000000..3d0e686 --- /dev/null +++ b/picoctf/web/javascriptkiddie2/writeup.md @@ -0,0 +1,148 @@ +# PicoCTF: Java Script Kiddie 2 + +## Context + +This is an extension of PicoCTF's Java Script Kiddie. + +We are provided with a website that prompts you to enter some text. We are not given information on what the text should be. Upon submitting that text, we get a broken image link. We are not provided with any site source code. + +## Background Information: PNG images + +Every PNG image contains the same first 8 bytes, known as the "PNG signature": 89 50 4E 47 0D 0A 1A 0A. + +The next chunk of a PNG is the image header (IHDR), which contains information about the image's width, depth, and color information. The image below displays what the IHDR could look like for a basic image. Note that the first 8 bytes of the IHDR are the size of the IHDR chunk and the fact that the chunk is an IHDR. Therefore, these bytes will be consistent throughout all PNGs: 00 00 00 0D 49 48 44 52. + +![Alt text](jsk2_images/basic-png-format-ex.png) + +## Vulnerability + +As we are given no source code for this challenge, we may try to get more information by capturing requests via BurpSuite. + +We see that upon sending text in the website's input area, two requests are sent. We capture both of them and send each to the BurpSuite Repeater for further analyzing. + +Upon hitting "Send" in the BurpSuite Repeater for the first request, we see that the HTML contains a JavaScript script. See `jsk2.js` for the script. It gathers information from /bytes (see next paragraph), calls a function assemble_png, and then pieces together an image. + +![Alt text](jsk2_images/burpsuite_js.png) + +Upon hitting "Send" in the BurpSuite Repeater for the second request, we see that we are directed to /bytes and we get a series of bytes in our response. See the top of `jsk2_solver.c` (char* bytes_as_string) for a possible version of the bytes (every instance of the challenge will have a slightly different set of bytes). + +![Alt text](jsk2_images/burpsuite_bytes.png) + +Putting these pieces of information together we gather that the assemble_png function does the following: +1. Set a default key of 00000000000000000000000000000000 (32 0s) +2. If the inputted key from the user is 16 digits long, keep the default key. Else, make the key the user's inputted key. +3. For every digit in the key, grab 1/16 of the bytes and calculate a new positition for each byte in that chunk depending on the value of the digit in the key. +4. Eventually, this shuffling will rearrange all of the bytes into a result array. + +We realize that we must input the key that will rearrange the bytes to resemble a PNG. + +## Exploitation + +Let's write a script that will find the key we need to use. Please reference jsk2_solver.c. + +First we must acknowledge that the bytes sent by the site do not at all resemble a PNG. Recall that the first 8 bytes must be 89 50 4E 47 0D 0A 1A 0A (in decimal: 137 80 78 71 13 10 26 10). The next 8 bytes of any PNG will be 00 00 00 0D 49 48 44 52 (in decimal: 0 0 0 13 73 72 68 82). These are the only parts of the target PNG we know. Note that assemble_png relied on 16-byte chunks, signalling that we only need to know the first 16 bytes of the PNG to be able to make a 16-long key to reassemble our PNG. + +Therefore we can declare that the first 16 bytes of our target PNG are: + +`int png_bytes[16] = {137, 80, 78, 71, 13, 10, 26, 10, 0, 0, 0, 13, 73, 72, 68, 82};` + +I first parsed the bytes to see what length our resulting array would need to be: + +```c + int i; + int count = 0; + + // copy of initial string for counting number of bytes + char *bytes_as_string_counting = strdup(bytes_as_string); + + char *token = strtok(bytes_as_string_counting, " "); + + // count the number of bytes + for (i = 0; token != NULL; i++) + { + count++; + token = strtok(NULL, " "); + } + + // official bytes int array with count number of slots + int bytes[count]; +``` +I then made an int array for our bytes: + +```c + // make copy for adding numbers to bytes array + char *bytes_as_string_copy = strdup(bytes_as_string); + + // reassign token + token = strtok(bytes_as_string_copy, " "); + + // iterate again and add numbers to the bytes array + for (i = 0; token != NULL; i++) + { + bytes[i] = atoi(token); + token = strtok(NULL, " "); + } +``` +To construct the key, I iterated once for each position in the key, and for each iteration iterated through all possible digits. Note that the JS script had `shifter = Number(key.slice((i*2),(i*2)+1));`, letting us know that only digits 0-9 are possible key digits. We also see that only even-numbered indexes will be selected in the key rearrangement process. Therefore, we will add a character in between every number in `png_bytes`. + +`int png_bytes[32] = {137, 0, 80, 0, 78, 0, 71, 0, 13, 0, 10, 0, 26, 0, 10, 0, 0, 0, 0, 0, 0, 0, 13, 0, 73, 0, 72, 0, 68, 0, 82};` + +When we iterate, we will make sure to calculate `i*2` for the indexes. + +Through these iterations, if the target bytes in `png_bytes` matched the calculated byte in, `bytes` we added that shifter to the key and proceeded to the next digit in the key. The calculation for which byte to get in `bytes` is based off `assemble_png`'s calculations: + +`result[(j * LEN) + i] = bytes[(((j + shifter) * LEN) % bytes.length) + i]` + +In my script, there is no inner "j" loop because we are manually going through the possible shifters, so my calculations are a bit simpler: + +`index = ((curr_shifter * 16) % count) + i;` + +This is what this whole portion of the script looks like: + +```c + // Now for the solving! + + int key[32]; + int shifters[10] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; + int curr_shifter; + int index; + int j; + + // for each space in the key array + for (i = 0; i < 33; i++) + { + // for each possible shifter + for (j = 0; j < sizeof(shifters); j++) + { + curr_shifter = shifters[j]; + + /* + Calculate the index of bytes based off the original script + Original from JS script: bytes[(((j + shifter) * 16) % bytes.length) + i] + We do not add anything to shifter because we're just iterating through possible shifters + */ + index = ((curr_shifter * 16) % count) + i; + + // if the bytes in the PNG array match our calculated bytes, + // add key shifter to our key array + if (png_bytes[i*2] == bytes[index]) + { + key[i*2] = curr_shifter; + break; + } + } + } +``` +From this script we are able to calculate the key needed for the specific set of bytes we were given! Once entering the key into the website, we are given a QR code. I just scanned it to get the flag, but because we are security-minded people, you should not scan random QR codes and instead use a QR code decoder to see where it gets you. + +## Remediation + +This kind of challenge is not something you would find naturally as an exploit, but it shows how easy it can be to reassemble a PNG if all we need are the first 16 bytes. To make this reassembling more difficult, the key could be longer (so we would need to iterate through possible values of width and length, etc.), the unscrambling algorithm could be more complex, or the key could include both letters and numbers (so we would have to iterate through more possible values for the key). + +Through this challenge, we also see how important it might be to use a good scrambling algorithm for encryption purposes. + +# Sources/Credits + +Written by Madalina Stoicov + +- https://en.wikipedia.org/wiki/PNG