PicoCTF - Java Script Kiddie
Background
1
2
3const fruits = ["Banana", "Orange", "Lemon", "Apple", "Mango"]; const citrus = fruits.slice(1, 3); # output: Orange,Lemon
JavaScript Uint8Array.from() Method
1
2
3
4
5
6
7let 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()
1
2
3let text = String.fromCharCode(65); console.log(text) # output: A
Source code
:::spoiler Source Code
<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可以參考,但1這一部影片討論的非常詳細,再搭配自己寫的script就可以尻出原本的圖檔
- 首先題目的頁面剛載入的時候,會直接傳送一個bytes的矩陣,內涵720個bytes,也就是Code:
5-8
在做的事情(記得善用browser內建的debugger和觀察network封包) - 接著,如果甚麼都不輸入,他會直接執行assemble_png這個function(也就是Code:
10-29
),這個function簡單來說就是descrable這些bytes,可以想像他用封包傳送過來的是已經打亂的bytes,所以他現在要還原 - 具體怎麼還原可以用debugger跟一下,反正他的做法簡單來說就是他的原圖檔中每一行做輪轉,就像下圖一樣,我只有截第一行,其他以此類推,所以我們要怎麼回推回去呢?只要寫過一點Misc/修復png檔之類的題目的人應該對magic header不陌生,反正前面都長,那我們就直接回推到最一開始的狀態就好
- 而輪轉的次數介於0-9之間,所以可以像2的作法一樣,用看的,或是用1的script(當然我有做一點修改,見註一),而我們輸入的key長度是16,就是代表16行各自需要輪轉幾次回來,這樣我們就可以拿到一張正常的圖片
- 問題:如果看其他人的WP,因為圖片不一樣,所以當然scramble bytes也會不一樣,但其他人好像沒有多個候選key的問題,也就是同一行中,在
0-9
的區間有超過一個一樣的bytes,這樣到底怎麼知道要輪轉幾次才會回到一開始的地方,比方說0x08這一行,應該要是0x00,但有多達三個0x00,這樣要怎麼知道輪轉2/3/4次?我自己的做法會是直接當成一個候選的key然後都try try看,以這個例子來說,就會多達3*4*3=36種組合
註一: 修改的地方在於前面的known_bytes,因為Walkthru的作法是前8bytes對照png前面的magic header,而後8bytes就看最後面的IEND block,但因為Code:24-26
的while loop會把多餘的padding截掉,也就是說圖檔的bytes length不會和一開始打亂後的一樣,這樣在輪轉讓就沒辦法alignment,所以我就直接參考2的作法,也就是全部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寫入圖片檔中
1 |
|
:::
:::spoiler 找key
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
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直接找,輸出如下
1 |
|
有了候選的key後就可以用script 3自動填入,另外要提醒的是要先把key變成51081803ghi63640
,也就是把重複的地方變成ghi,然後填入重複的次數
Key: 5108180345363640
QRCode:
Flag:
picoCTF{066cad9e69c5c7e5d2784185c0feb30b}