# PicoCTF - Java Script Kiddie ## Background [JavaScript Array slice()](https://www.w3schools.com/jsref/jsref_slice_array.asp) > ```javascript > const fruits = ["Banana", "Orange", "Lemon", "Apple", "Mango"]; > const citrus = fruits.slice(1, 3); > # output: Orange,Lemon > ``` [JavaScript Uint8Array.from() Method](https://www.geeksforgeeks.org/javascript-uint8array-from-method/) > ```javascript > let array = Uint8Array.from('45465768654323456'); > console.log(array); > # output: Uint8Array(17) [ > 4, 5, 4, 6, 5, 7, 6, > 8, 6, 5, 4, 3, 2, 3, > 4, 5, 6 > ] > ``` [JavaScript String.fromCharCode()](https://www.w3schools.com/jsref/jsref_fromcharcode.asp) > ```javascript > let text = String.fromCharCode(65); > console.log(text) > # output: A > ``` ## Source code :::spoiler Source Code ```htmlmixed= <html> <head> <script src="jquery-3.3.1.min.js"></script> <script> 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; } </script> </head> <body> <center> <form action="#" onsubmit="assemble_png(document.getElementById('user_in').value)"> <input type="text" id="user_in"> <input type="submit" value="Submit"> </form> <img id="Area" src=""/> </center> </body> </html> ``` ::: ## Recon 這一題很難,應該說scramble的方式很難看出來,而且他結合了web(js)/reverse/misc的各種概念在裡面,討論的WP也比較少,沒有甚麼script可以參考,但[^pico-reverse-java-script-kiddle-wp-walkthru]這一部影片討論的非常詳細,再搭配自己寫的script就可以尻出原本的圖檔 1. 首先題目的頁面剛載入的時候,會直接傳送一個bytes的矩陣,內涵720個bytes,也就是Code:`5-8`在做的事情(記得善用browser內建的debugger和觀察network封包) 2. 接著,如果甚麼都不輸入,他會直接執行assemble_png這個function(也就是Code:`10-29`),這個function簡單來說就是descrable這些bytes,可以想像他用封包傳送過來的是已經打亂的bytes,所以他現在要還原 3. 具體怎麼還原可以用debugger跟一下,反正他的做法簡單來說就是他的原圖檔中每一行做輪轉,就像下圖一樣,我只有截第一行,其他以此類推,所以我們要怎麼回推回去呢?只要寫過一點Misc/修復png檔之類的題目的人應該對magic header不陌生,反正前面都長,那我們就直接回推到最一開始的狀態就好 ![](https://hackmd.io/_uploads/rJ37YsIe6.png) 4. 而輪轉的次數介於0-9之間,所以可以像[^pico-java-script-kiddle-wp]的作法一樣,用看的,或是用[^pico-reverse-java-script-kiddle-wp-walkthru]的script(當然我有做一點修改,見註一),而我們輸入的key長度是16,就是代表16行各自需要輪轉幾次回來,這樣我們就可以拿到一張正常的圖片 ![](https://hackmd.io/_uploads/Sk3wHi8ep.png) 5. 問題:如果看其他人的WP,因為圖片不一樣,所以當然scramble bytes也會不一樣,但其他人好像沒有多個候選key的問題,也就是同一行中,在`0-9`的區間有超過一個一樣的bytes,這樣到底怎麼知道要輪轉幾次才會回到一開始的地方,比方說0x08這一行,應該要是0x00,但有多達三個0x00,這樣要怎麼知道輪轉2/3/4次?我自己的做法會是直接當成一個候選的key然後都try try看,以這個例子來說,就會多達3\*4\*3=36種組合 ![](https://hackmd.io/_uploads/rk9K_sUea.png) 註一: 修改的地方在於前面的known_bytes,因為Walkthru的作法是前8bytes對照png前面的magic header,而後8bytes就看最後面的IEND block,但因為Code:`24-26`的while loop會把多餘的padding截掉,也就是說圖檔的bytes length不會和一開始打亂後的一樣,這樣在輪轉讓就沒辦法alignment,所以我就直接參考[^pico-java-script-kiddle-wp]的作法,也就是全部16bytes都依照第一列的16bytes當作對照,也就是`[0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0x00, 0x00, 0x00, 0x0D, 0x49, 0x48, 0x44, 0x52]`,而這樣的結果就是有多達36種組合會出現,所以我才會寫一個自動填入候選key的script,這樣省了一點時間 ## Exploit - Reverse / Javascript / Misc :::spoiler 把bytes寫入圖片檔中 ```python ct = [87, 130, 78, 188, 0, 84, 26, 157, 143, 239, 249, 82, 248, 212, 239, 82, 195, 80, 1, 207, 13, 6, 1, 0, 119, 243, 73, 193, 78, 36, 133, 108, 85, 0, 0, 14, 0, 186, 68, 0, 0, 222, 0, 243, 0, 24, 174, 163, 126, 0, 133, 252, 137, 177, 121, 10, 0, 0, 0, 237, 73, 63, 0, 100, 96, 20, 3, 224, 59, 171, 16, 114, 0, 0, 0, 69, 0, 68, 68, 147, 137, 179, 110, 112, 74, 121, 238, 65, 1, 0, 156, 0, 155, 0, 95, 120, 0, 233, 226, 40, 78, 194, 248, 44, 84, 0, 208, 13, 41, 72, 138, 59, 164, 98, 71, 0, 209, 0, 99, 176, 97, 120, 202, 0, 135, 192, 54, 101, 64, 252, 81, 71, 205, 10, 243, 133, 30, 22, 125, 237, 3, 93, 90, 42, 73, 221, 25, 114, 243, 0, 116, 22, 4, 3, 59, 75, 188, 119, 169, 221, 161, 184, 178, 2, 73, 73, 231, 45, 14, 99, 102, 153, 166, 178, 206, 54, 127, 84, 240, 191, 220, 10, 163, 81, 64, 206, 128, 132, 102, 197, 72, 127, 239, 253, 78, 93, 8, 22, 239, 207, 146, 111, 143, 239, 27, 243, 28, 0, 173, 159, 196, 48, 247, 28, 84, 98, 63, 52, 171, 214, 214, 26, 233, 254, 65, 106, 111, 59, 73, 255, 148, 111, 103, 91, 20, 206, 222, 70, 252, 199, 161, 124, 245, 188, 102, 81, 159, 119, 174, 51, 190, 243, 55, 243, 156, 249, 124, 125, 2, 143, 191, 27, 119, 139, 126, 88, 18, 247, 171, 227, 72, 66, 54, 251, 0, 80, 171, 146, 113, 173, 4, 79, 211, 216, 214, 122, 119, 115, 225, 45, 24, 54, 44, 76, 43, 253, 5, 235, 104, 248, 96, 8, 229, 200, 75, 64, 233, 217, 23, 87, 40, 254, 187, 107, 181, 200, 181, 233, 181, 81, 231, 171, 165, 82, 254, 196, 239, 51, 43, 114, 170, 73, 249, 50, 114, 201, 138, 64, 11, 203, 155, 192, 249, 226, 35, 188, 156, 223, 40, 217, 67, 75, 100, 45, 93, 102, 169, 13, 34, 197, 80, 175, 210, 128, 137, 201, 167, 45, 140, 82, 171, 56, 212, 17, 126, 113, 139, 229, 127, 223, 181, 15, 0, 116, 221, 186, 219, 230, 56, 233, 31, 15, 249, 74, 119, 152, 44, 41, 226, 60, 35, 253, 172, 97, 32, 137, 233, 165, 35, 181, 104, 80, 217, 56, 186, 205, 212, 15, 64, 81, 230, 230, 153, 62, 251, 251, 47, 151, 141, 108, 32, 25, 65, 11, 253, 119, 201, 147, 243, 11, 31, 247, 233, 54, 126, 217, 136, 141, 191, 226, 137, 213, 131, 239, 100, 145, 151, 150, 119, 124, 159, 203, 190, 63, 18, 170, 210, 175, 122, 223, 223, 114, 124, 59, 93, 245, 177, 100, 15, 57, 63, 239, 165, 144, 13, 149, 32, 198, 39, 52, 53, 113, 97, 91, 186, 76, 91, 74, 207, 133, 208, 0, 245, 241, 245, 73, 122, 193, 223, 159, 82, 175, 241, 159, 231, 205, 24, 92, 75, 11, 247, 77, 55, 170, 7, 95, 127, 143, 96, 207, 242, 142, 153, 226, 242, 93, 163, 110, 185, 26, 188, 4, 178, 102, 159, 97, 53, 58, 186, 172, 239, 6, 78, 215, 65, 156, 90, 150, 112, 205, 73, 76, 149, 163, 159, 242, 45, 147, 16, 210, 49, 254, 82, 126, 200, 30, 62, 190, 230, 2, 86, 171, 181, 197, 185, 132, 170, 153, 82, 191, 154, 235, 147, 55, 57, 92, 252, 48, 207, 118, 191, 170, 253, 53, 127, 94, 143, 122, 230, 254, 154, 151, 186, 55, 160, 132, 126, 57, 183, 217, 129, 181, 95, 255, 35, 223, 50, 70, 77, 107, 100, 203, 17, 61, 163, 17, 227, 147, 182, 184, 79, 126, 239, 28, 115, 159, 254, 111, 90, 250, 14, 206, 185, 137, 187, 141, 231, 211, 241, 249, 39, 99, 131, 95, 210, 50, 147, 241, 95, 127, 103, 239, 113, 165, 223, 164, 245, 35, 231, 132, 166, 220, 241, 207, 67, 178, 148, 29, 156, 94, 194, 74, 222, 110, 0, 243, 107, 158, 173, 214, 210, 249, 84, 66, 107, 40, 0, 203, 138, 164, 0, 241, 9, 109, 147, 207, 85, 29, 204, 0] f = open('./PicoCTF/Web/Java Script Kiddie/cipher_flag.png', 'wb') pt = b'' count = 0 for i in range(len(ct)): pt += bytes([ct[i]]) f.write(pt) f.close() ``` ::: :::spoiler 找key ```python= ct = [87, 130, 78, 188, 0, 84, 26, 157, 143, 239, 249, 82, 248, 212, 239, 82, 195, 80, 1, 207, 13, 6, 1, 0, 119, 243, 73, 193, 78, 36, 133, 108, 85, 0, 0, 14, 0, 186, 68, 0, 0, 222, 0, 243, 0, 24, 174, 163, 126, 0, 133, 252, 137, 177, 121, 10, 0, 0, 0, 237, 73, 63, 0, 100, 96, 20, 3, 224, 59, 171, 16, 114, 0, 0, 0, 69, 0, 68, 68, 147, 137, 179, 110, 112, 74, 121, 238, 65, 1, 0, 156, 0, 155, 0, 95, 120, 0, 233, 226, 40, 78, 194, 248, 44, 84, 0, 208, 13, 41, 72, 138, 59, 164, 98, 71, 0, 209, 0, 99, 176, 97, 120, 202, 0, 135, 192, 54, 101, 64, 252, 81, 71, 205, 10, 243, 133, 30, 22, 125, 237, 3, 93, 90, 42, 73, 221, 25, 114, 243, 0, 116, 22, 4, 3, 59, 75, 188, 119, 169, 221, 161, 184, 178, 2, 73, 73, 231, 45, 14, 99, 102, 153, 166, 178, 206, 54, 127, 84, 240, 191, 220, 10, 163, 81, 64, 206, 128, 132, 102, 197, 72, 127, 239, 253, 78, 93, 8, 22, 239, 207, 146, 111, 143, 239, 27, 243, 28, 0, 173, 159, 196, 48, 247, 28, 84, 98, 63, 52, 171, 214, 214, 26, 233, 254, 65, 106, 111, 59, 73, 255, 148, 111, 103, 91, 20, 206, 222, 70, 252, 199, 161, 124, 245, 188, 102, 81, 159, 119, 174, 51, 190, 243, 55, 243, 156, 249, 124, 125, 2, 143, 191, 27, 119, 139, 126, 88, 18, 247, 171, 227, 72, 66, 54, 251, 0, 80, 171, 146, 113, 173, 4, 79, 211, 216, 214, 122, 119, 115, 225, 45, 24, 54, 44, 76, 43, 253, 5, 235, 104, 248, 96, 8, 229, 200, 75, 64, 233, 217, 23, 87, 40, 254, 187, 107, 181, 200, 181, 233, 181, 81, 231, 171, 165, 82, 254, 196, 239, 51, 43, 114, 170, 73, 249, 50, 114, 201, 138, 64, 11, 203, 155, 192, 249, 226, 35, 188, 156, 223, 40, 217, 67, 75, 100, 45, 93, 102, 169, 13, 34, 197, 80, 175, 210, 128, 137, 201, 167, 45, 140, 82, 171, 56, 212, 17, 126, 113, 139, 229, 127, 223, 181, 15, 0, 116, 221, 186, 219, 230, 56, 233, 31, 15, 249, 74, 119, 152, 44, 41, 226, 60, 35, 253, 172, 97, 32, 137, 233, 165, 35, 181, 104, 80, 217, 56, 186, 205, 212, 15, 64, 81, 230, 230, 153, 62, 251, 251, 47, 151, 141, 108, 32, 25, 65, 11, 253, 119, 201, 147, 243, 11, 31, 247, 233, 54, 126, 217, 136, 141, 191, 226, 137, 213, 131, 239, 100, 145, 151, 150, 119, 124, 159, 203, 190, 63, 18, 170, 210, 175, 122, 223, 223, 114, 124, 59, 93, 245, 177, 100, 15, 57, 63, 239, 165, 144, 13, 149, 32, 198, 39, 52, 53, 113, 97, 91, 186, 76, 91, 74, 207, 133, 208, 0, 245, 241, 245, 73, 122, 193, 223, 159, 82, 175, 241, 159, 231, 205, 24, 92, 75, 11, 247, 77, 55, 170, 7, 95, 127, 143, 96, 207, 242, 142, 153, 226, 242, 93, 163, 110, 185, 26, 188, 4, 178, 102, 159, 97, 53, 58, 186, 172, 239, 6, 78, 215, 65, 156, 90, 150, 112, 205, 73, 76, 149, 163, 159, 242, 45, 147, 16, 210, 49, 254, 82, 126, 200, 30, 62, 190, 230, 2, 86, 171, 181, 197, 185, 132, 170, 153, 82, 191, 154, 235, 147, 55, 57, 92, 252, 48, 207, 118, 191, 170, 253, 53, 127, 94, 143, 122, 230, 254, 154, 151, 186, 55, 160, 132, 126, 57, 183, 217, 129, 181, 95, 255, 35, 223, 50, 70, 77, 107, 100, 203, 17, 61, 163, 17, 227, 147, 182, 184, 79, 126, 239, 28, 115, 159, 254, 111, 90, 250, 14, 206, 185, 137, 187, 141, 231, 211, 241, 249, 39, 99, 131, 95, 210, 50, 147, 241, 95, 127, 103, 239, 113, 165, 223, 164, 245, 35, 231, 132, 166, 220, 241, 207, 67, 178, 148, 29, 156, 94, 194, 74, 222, 110, 0, 243, 107, 158, 173, 214, 210, 249, 84, 66, 107, 40, 0, 203, 138, 164, 0, 241, 9, 109, 147, 207, 85, 29, 204, 0] known_bytes = [0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0x00, 0x00, 0x00, 0x0D, 0x49, 0x48, 0x44, 0x52] possible_key_values = [] for key_pos in range(16): known_byte = known_bytes[key_pos] current_block = 0 key_possiblities = [] possible_key_values.append(key_possiblities) target_indices = [index for index, b in enumerate(ct) if b == known_byte] for possible_key_value in range(10): byte_index = (((current_block + possible_key_value) * 16) % len(ct)) + key_pos if byte_index in target_indices: key_possiblities.append(possible_key_value) print(possible_key_values) ``` ::: :::spoiler 自動填入可能的key ```python! from selenium import webdriver from selenium.webdriver.common.by import By import time from tqdm import trange PATH = './chromedriver.exe' driver = webdriver.Chrome() driver.get('https://jupiter.challenges.picoctf.org/problem/17205/?#') img = driver.find_element(By.ID, "Area") ct = '51081803ghi63640' pt_guess = [] for i in range(10): for j in range(10): pt_guess.append(ct.replace('g', hex(j)[2:]).replace('h', hex(i)[2:])) for i in trange(len(pt_guess)): element = driver.find_element(By.ID, 'user_in') click = driver.find_element(By.XPATH, "/html/body/center/form/input[2]") element.send_keys(pt_guess[i]) click.click() time.sleep(0.01) driver.refresh() ``` ::: 首先,可以先用script 1,把資料寫到一個file中,然後用看的或是用script 2直接找,輸出如下 ```bash [[5], [1], [0], [8], [1], [8], [0], [3], [2, 3, 4], [3, 4, 5, 6], [2, 3, 4], [6], [3], [6], [4], [0]] ``` 有了候選的key後就可以用script 3自動填入,另外要提醒的是要先把key變成`51081803ghi63640`,也就是把重複的地方變成ghi,然後填入重複的次數 --- Key: `5108180345363640` QRCode: ![](https://hackmd.io/_uploads/r16cCqIxT.png) Flag: `picoCTF{066cad9e69c5c7e5d2784185c0feb30b}` ## Reference [^pico-java-script-kiddle-wp]:[ [網頁漏洞] Javascript - 修正圖檔 ](https://ithelp.ithome.com.tw/articles/10250802) [^pico-reverse-java-script-kiddle-wp-walkthru]:[ PicoCTF Walkthru [101] - Java Script Kiddie (JavaScript debugging/reversing) ](https://youtu.be/ltLA-q3FLvI?si=_jtjyl6mwsT0mnVV)