Page Menu
Home
Sealhub
Search
Configure Global Search
Log In
Files
F10360467
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Flag For Later
Size
20 KB
Referenced Files
None
Subscribers
None
View Options
diff --git a/android/Dockerfile b/android/Dockerfile
index adc494c..2813e82 100644
--- a/android/Dockerfile
+++ b/android/Dockerfile
@@ -1,30 +1,32 @@
FROM pre_android/ready
# Set up node
RUN npm install -g n && n install 22.14.0 && n use 22.14.0 && hash -r
+RUN node --version
+
# Set up httptoolkit-server
RUN git clone https://github.com/httptoolkit/httptoolkit-server /httptoolkit-server
WORKDIR /httptoolkit-server
RUN git checkout 5c60a70b08d30126639484314f5b5619a388b026 \
&& npm i && npm run build:src
# Set up proxy_cache_thing
ADD proxy_cache_thing /proxy_cache_thing
WORKDIR /proxy_cache_thing
RUN npm i && npm run build
ADD entrypoint.sh /entrypoint.sh
ARG PROXY_PORT
ARG SERVER_PORT
ARG MONITORING_API_PORT
ARG LOOPBACK_PORT
ENV PROXY_PORT=${PROXY_PORT}
ENV SERVER_PORT=${SERVER_PORT}
ENV MONITORING_API_PORT=${MONITORING_API_PORT}
ENV LOOPBACK_PORT=${LOOPBACK_PORT}
ENTRYPOINT /entrypoint.sh
diff --git a/android/code/index.mjs b/android/code/index.mjs
index 3fc1ba3..bb5c961 100644
--- a/android/code/index.mjs
+++ b/android/code/index.mjs
@@ -1,64 +1,76 @@
import { WebSocketServer } from "ws";
import child_process from "child_process";
import fs from "fs";
import { send_notification } from "./notifications.mjs";
async function spawnPromise(program, args) {
return new Promise((resolve, reject) => {
let output = "";
const process = child_process.spawn(program, args);
process.stdout.on("data", (data) => {
output += data;
});
process.stderr.on("data", (data) => {
output += data;
});
process.on("close", (code) => {
resolve({ output, code });
});
});
}
const wss = new WebSocketServer({ port: 3000 });
//maybe check output of child processes and send errors in some way
wss.on("connection", (ws) => {
ws.on("message", async (dataBuf) => {
let data = dataBuf.toString();
if (data === "screenshot") {
await spawnPromise("bash", ["/conf/screenshot.sh"]);
ws.send(fs.readFileSync("/screenshot.png"));
} else if (data.includes("touch")) {
const dataSplit = data.split(" ");
await spawnPromise("bash", [
"/conf/touch.sh",
dataSplit[1],
dataSplit[2],
]);
} else if (data === "back") {
await spawnPromise("bash", ["/conf/back.sh"]);
} else if (data === "home") {
await spawnPromise("bash", ["/conf/home.sh"]);
} else if (data === "install") {
const res = await spawnPromise("bash", ["/conf/install.sh"]);
send_notification(
res.code === 0,
"Installing the application",
res.output
);
} else if (data.includes("drag")) {
const dataSplit = data.split(" ");
await spawnPromise("bash", [
"/conf/drag.sh",
dataSplit[1],
dataSplit[2],
dataSplit[3],
dataSplit[4],
]);
+ } else if (data.startsWith("setcoord")) {
+ const dataSplit = data.split(" ");
+ const res = await spawnPromise("bash", [
+ "/conf/set_geo.sh",
+ dataSplit[1],
+ dataSplit[2],
+ ]);
+ send_notification(
+ res.code === 0,
+ "Setting the moch location",
+ res.output
+ );
}
});
ws.on("close", (_) => {
ws.close();
});
});
diff --git a/android/conf/set_geo.sh b/android/conf/set_geo.sh
new file mode 100644
index 0000000..5de2ea8
--- /dev/null
+++ b/android/conf/set_geo.sh
@@ -0,0 +1,13 @@
+#!/bin/bash
+ko=$((
+ echo "auth $(cat ~/.emulator_console_auth_token)"
+ echo "geo fix $1 $2 0.0"
+ sleep 0.2
+ ) | telnet localhost 5554 | grep KO: );
+
+if [ -z "${ko}" ]; then
+ exit 0
+else
+ echo "$ko"
+ exit 1;
+fi
diff --git a/android/conf/start_culebra.sh b/android/conf/start_culebra.sh
index 6ceeda8..d638ea7 100644
--- a/android/conf/start_culebra.sh
+++ b/android/conf/start_culebra.sh
@@ -1,15 +1,22 @@
rm -f /opt/android-sdk-linux/.android/avd/virtual_dev.avd/*.lock
adb start-server
-emulator -avd virtual_dev -writable-system -no-window -no-audio &
-adb wait-for-device
-adb emu avd snapshot load configured
+cat > /simple.gpx << EOF
+<?xml version="1.0" encoding="UTF-8"?>
+<gpx version="1.1" creator="ChatGPT" xmlns="http://www.topografix.com/GPX/1/1">
+ <wpt lat="37.422" lon="-122.084">
+ <name>Simple Waypoint</name>
+ </wpt>
+</gpx>
+EOF
+
+emulator -grpc 5556 -avd virtual_dev -snapshot configured -no-window -no-audio -debug all,-adb,-gles1emu,-gles,-mtport,-metrics,-memory,-car,-tvremote &
adb wait-for-device
export PATH=$PATH:/root/culebraDependencies
cd /root/culebra
./culebratester2 start-server &
#wait for the server to start
while ! curl http://localhost:9987/v2/uiDevice/screenshot > /dev/null 2> /dev/null; do
sleep 0.1
-done
\ No newline at end of file
+done
diff --git a/docker-compose.yaml b/docker-compose.yaml
index 1505ce7..0b693af 100644
--- a/docker-compose.yaml
+++ b/docker-compose.yaml
@@ -1,58 +1,59 @@
services:
android:
build:
context: ./android/
args:
PROXY_PORT: "45459"
SERVER_PORT: "45456"
LOOPBACK_PORT: "10000"
MONITORING_API_PORT: "10001"
container_name: httptoolkit_server
container_name: android
sysctls:
- net.ipv6.conf.all.disable_ipv6=1
cap_add:
- NET_ADMIN
devices:
- /dev/kvm
networks:
- rent_gen_android
ports:
- 45456:45459 # This cannot change
- 45457:45459 # This cannot change
- 10001:10001 # api port
- 3000:3000 # android server port
- 3001:3001 # Notifications server
+ - 5556:5556 # emulator grpc port
volumes:
- $PWD/shared_buffer:/shared_buffer
- $PWD/android/conf:/conf
- $PWD/certificates:/certificates
- $PWD/android/code:/code
httptoolkit_ui:
build:
context: ./httptoolkit_ui/
args:
# The ip / hostname using which,
# the browser can reach this docker session
DOCKER_HOST: "127.0.0.1"
container_name: httptoolkit_ui
networks:
- rent_gen_android
ports:
- 9080:9080
http_server:
build: ./http_server/
container_name: http_server
networks:
- rent_gen_android
volumes:
- $PWD/http_server/code:/code
- $PWD/shared_buffer:/shared_buffer
- $PWD/log:/log
ports:
- 8080:8080
networks:
rent_gen_android:
driver: bridge
diff --git a/http_server/code/index.html b/http_server/code/index.html
index 83ab631..ef48e0f 100644
--- a/http_server/code/index.html
+++ b/http_server/code/index.html
@@ -1,327 +1,332 @@
<!DOCTYPE html>
<html lang="en">
<head style="height: 100vh">
<meta charset="UTF-8" />
<title>Rentgen android</title>
<script src="/htmx.js"></script>
<style>
main {
display: flex;
}
.log-section {
height: auto;
width: 400px;
overflow: auto;
display: flex;
flex-direction: column;
margin-left: 20px;
}
.screen {
display: inline-block;
cursor: pointer;
}
.screen-buttons {
display: flex;
justify-content: space-around;
margin-top: 5px;
gap: 10px;
}
.screen-buttons button {
font-size: 1.1rem;
padding: 10px 20px;
width: 100%;
cursor: pointer;
background-color: transparent;
}
.screen-buttons button:hover {
background-color: aqua;
}
#clicks-log {
font-family: Menlo, Consolas, Monaco, Liberation Mono, Lucida Console,
monospace;
}
.tab {
border: 1px solid #ccc;
background-color: #f1f1f1;
}
/* Style the buttons that are used to open the tab content */
.tab button {
background-color: inherit;
float: left;
border: none;
outline: none;
cursor: pointer;
padding: 14px 16px;
transition: 0.3s;
}
/* Change background color of buttons on hover */
.tab button:hover {
background-color: #ddd;
}
/* Create an active/current tablink class */
.tab button.active {
background-color: #ccc;
}
.tabcontent.active {
display: flex;
flex-grow: 1;
}
/* Style the tab content */
.tabcontent {
display: none;
padding: 6px 12px;
border: 1px solid #ccc;
border-top: none;
}
html,
body, main{
width: 100%;
height: 100%;
overflow: hidden;
margin: 0;
}
main {
display:flex;
flex-direction: row;
align-items: stretch;
}
#logs-tab {
overflow: auto;
text-wrap: wrap;
}
.screen-section {
display: flex;
flex-direction: column;
}
.screen-section #screen, .screen-section.screen_buttons {
flex-grow: 0;
}
.tab-section {
display: flex; flex-direction: column; flex-grow: 1;
}
<!-- TODO: A notification system -->
#resp {
display: none;
}
#upload_form {
display:flex;
flex-direction: column;
}
#upload_form button, #upload_form label {
border: 2px solid #ccc;
background-color: #f1f1f1;
cursor: pointer;
padding: 3px 10px;
transition: 0.3s;
}
#upload_form button:hover, #upload_form label:hover {
background-color: #ddd;
}
#notifications {
width: 40%;
margin-left: 60%;
position: absolute;
}
</style>
</head>
<body>
<div id="notifications"></div>
<div id="resp" style="display: none;"></div>
<main>
<section class="screen-section" >
<img
id="screen"
alt="android screen"
src=""
draggable="false"
class="screen"
style="flex-grow: 0"
/>
<div class="screen-buttons" style="flex-grow: 0">
<button class="screen-buttons-home">home</button>
<button class="screen-buttons-back">back</button>
</div>
<form id="upload_form" hx-post="/upload_apk" enctype="multipart/form-data" hx-target="#resp">
<label id="upload_input" for="app">Select file
<input type="file" id="app" name="app" accept=".apk" required multiple/>
</label>
<button type="submit">Install the app</button>
</form>
</section>
<div class="tab-section" >
+ <form id="set_coords" hx-post="/setcoord" hx-target="#resp">
+ <input type="text" name="lon"/>
+ <input type="text" name="lat"/>
+ <button type="submit">Submit coords</button>
+ </form>
<div class="tab">
<button class="tablinks active" onclick="open_tab(event, 'httptoolkit-tab')">HttpToolkit UI</button>
<button class="tablinks" onclick="open_tab(event, 'logs-tab')">Logs</button>
</div>
<div class="tabcontent" id="logs-tab">
<p id="clicks-log" class="log-section" ></p>
<p id="traffic-log" class="log-section"></p>
</div>
<div class="tabcontent active" id="httptoolkit-tab">
<iframe id="httptoolkit-frame" style="flex-grow: 1;" src="http://localhost:9080/" title="httptoolkit"></iframe>
</div>
</div>
</main>
<script>
function open_tab(evt, tab_name) {
let i, tabcontent, tablinks;
// Get all elements with class="tabcontent" and hide them
tabcontent = document.getElementsByClassName("tabcontent");
for (i = 0; i < tabcontent.length; i++) {
if (tabcontent[i].id != tab_name) {
tabcontent[i].classList.remove("active");
} else {
tabcontent[i].classList.add("active");
}
}
// Get all elements with class="tablinks" and remove the class "active"
tablinks = document.getElementsByClassName("tablinks");
for (i = 0; i < tablinks.length; i++) {
tablinks[i].classList.remove("active")
}
// Show the current tab, and add an "active" class to the button that opened the tab
evt.currentTarget.classList.add("active");
}
</script>
<script>
var screen = document.getElementById("screen");
var clicksLog = document.getElementById("clicks-log");
const homeButton = document.querySelector(".screen-buttons-home");
const backButton = document.querySelector(".screen-buttons-back");
let lastTouch = new Date().getTime();
const calculateElapsedTime = (last) => {
const currentTouch = new Date().getTime();
const elapsedTime = currentTouch - lastTouch;
const elapsedSec = Math.round(elapsedTime / 1000);
lastTouch = currentTouch;
return elapsedSec;
};
const waitToLog = (clickInfoText) => {
const clickInfo = document.createElement("span");
const waitInfo = document.createElement("span");
waitInfo.textContent = `await wait(${calculateElapsedTime(
lastTouch
)});`;
clicksLog.appendChild(waitInfo);
clickInfo.textContent = clickInfoText;
clicksLog.appendChild(clickInfo);
};
const registerClick = ({ path, logText, body }) => {
const clicksLog = document.getElementById("clicks-log");
const span = document.createElement("span");
waitToLog(logText);
fetch(path, {
method: "POST",
headers: {
"Content-Type": "application/x-www-form-urlencoded",
},
...(body ? { body } : {}),
});
};
homeButton.addEventListener("click", () =>
registerClick({ path: "home", logText: "await homeButton();" })
);
backButton.addEventListener("click", () =>
registerClick({ path: "back", logText: "await backButton();" })
);
async function displayImage() {
try {
const response = await fetch("screen");
const blob = await response.blob();
screen.src = URL.createObjectURL(blob);
} catch (error) {
console.error("Error fetching image: ", error);
}
}
let isDragging = false;
let startDraggingPosX = 0;
let endDraggingPosX = 0;
let startDraggingPosY = 0;
let endDraggingPosY = 0;
const screenSize = [320, 640];
const handleDraggStart = (e) => {
e.preventDefault();
isDragging = true;
startDraggingPosX = e.offsetX;
startDraggingPosY = e.offsetY;
};
screen.addEventListener("mousedown", handleDraggStart);
document.addEventListener("mouseup", (e) => {
endDraggingPosX = e.offsetX;
endDraggingPosY = e.offsetY;
if (
isDragging &&
(Math.abs(endDraggingPosY - startDraggingPosY) > 10 ||
Math.abs(endDraggingPosX - startDraggingPosX) > 10)
) {
registerClick({
path: "drag",
logText: `await drag({x:${startDraggingPosX},y:${startDraggingPosY}},{x:${e.offsetX},y:${e.offsetY}});`,
body: `startX=${startDraggingPosX}&startY=${startDraggingPosY}&endX=${e.offsetX}&endY=${e.offsetY}`,
});
} else if (e.target === screen) {
const phoneX = event.offsetX;
const phoneY = event.offsetY;
if (phoneX <= screenSize[0] && phoneY <= screenSize[1])
registerClick({
path: "touch",
logText: `await click(${phoneX}, ${phoneY});`,
body: `x=${phoneX}&y=${phoneY}`,
});
}
isDragging = false;
});
async function sleep(time) {
return new Promise((resolve) => setTimeout(resolve, time));
}
async function screenshot_loop() {
var before;
while (true) {
before = performance.now();
await displayImage();
while (performance.now() - before < ___screenshotDelayMs___)
await sleep(50);
}
}
screenshot_loop();
</script>
<script src="/trafficLog.js"></script>
<script src="/notifications.js"></script>
</body>
</html>
diff --git a/http_server/code/index.mjs b/http_server/code/index.mjs
index 26c6d4a..386054e 100644
--- a/http_server/code/index.mjs
+++ b/http_server/code/index.mjs
@@ -1,112 +1,123 @@
import express from "express";
import { readFile } from "node:fs/promises";
import {
guardedScreenshot,
android_websocket,
waitFullBoot,
} from "./screenshot.mjs";
import { execSync } from "node:child_process";
import fileUpload from "express-fileupload";
const device_size_x = 320;
const device_size_y = 640;
const app = express();
app.use(express.urlencoded({ extended: false }));
app.use(express.static("/code/dist"));
console.log("Waiting for full boot...");
await waitFullBoot();
console.log("Boot detected! activating endpoints");
//GET
app.get("/favicon.ico", function (req, res) {
res.sendFile("/code/favicon.ico");
});
app.get("/htmx.js", function (req, res) {
res.sendFile("/code/node_modules/htmx.org/dist/htmx.min.js");
});
app.get("/trafficLog", async function (req, res) {
res.sendFile("/log/trafficLog");
});
app.get("/screen", async function (req, res) {
await guardedScreenshot();
res.sendFile("/code/screenshot.png");
});
app.get("/", async function (req, res) {
let fileData = (await readFile("/code/index.html")).toString();
fileData = fileData.replace(
"___screenshotDelayMs___",
process.env.screenshotDelayMs
);
res.setHeader("Content-Type", "text/html");
res.setHeader("Content-Disposition", "inline");
res.send(fileData);
});
//POST
app.post("/back", function (req, res) {
android_websocket.send(`back`);
res.sendStatus(200);
});
// default options
app.use(fileUpload());
app.post("/upload_apk", async function (req, res) {
if (!req.files || Object.keys(req.files).length === 0) {
return res.status(400).send("No files were uploaded.");
}
execSync("rm -rf /shared_buffer/*");
if (Array.isArray(req.files.app)) {
for (const [idx, file] of req.files.app.entries()) {
let uploadPath = "/shared_buffer/app" + idx + ".apk";
await file.mv(uploadPath);
}
} else {
let uploadPath = "/shared_buffer/app" + 0 + ".apk";
await req.files.app.mv(uploadPath);
}
android_websocket.send(`install`);
res.send("Files uploaded!");
});
app.post("/home", function (req, res) {
android_websocket.send(`home`);
res.sendStatus(200);
});
app.post("/touch", function (req, res) {
const x = parseInt(req.body.x);
const y = parseInt(req.body.y);
if (isNaN(x) || isNaN(y) || x > device_size_x || y > device_size_y) {
res.send(
`the query params must be x <= ${device_size_x}, y <= ${device_size_y}\n`
);
} else {
android_websocket.send(`touch ${x} ${y}`);
res.sendStatus(200);
}
});
app.post("/drag", function (req, res) {
const body = req.body;
const startX = Number(body.startX);
const startY = Number(body.startY);
const endX = Number(body.endX);
const endY = Number(body.endY);
android_websocket.send(`drag ${startX} ${startY} ${endX} ${endY}`);
res.sendStatus(200);
});
+app.post("/setcoord", function (req, res) {
+ console.log(req.body);
+ console.log(req.body.lat);
+ console.log(req.body.lon);
+ const lat = Number(req.body.lat);
+ const lon = Number(req.body.lon);
+ console.log(lat, lon);
+ android_websocket.send(`setcoord ${lat} ${lon}`);
+ res.sendStatus(200);
+});
+
app.listen(8080, () => console.log("Listening in port 8080"));
diff --git a/pre_android/Dockerfile b/pre_android/Dockerfile
index 95efdde..a74afaf 100644
--- a/pre_android/Dockerfile
+++ b/pre_android/Dockerfile
@@ -1,9 +1,9 @@
FROM runmymind/docker-android-sdk:ubuntu-standalone-20230511
ENV PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/opt/android-sdk-linux/cmdline-tools/latest/bin:/opt/android-sdk-linux/cmdline-tools/tools/bin:/opt/android-sdk-linux/tools/bin:/opt/android-sdk-linux/build-tools/32.0.0:/opt/android-sdk-linux/platform-tools:/opt/android-sdk-linux/emulator:/opt/android-sdk-linux/bin
RUN sdkmanager --channel=2 "system-images;android-30;google_apis;x86_64" \
&& echo no | avdmanager create avd -n virtual_dev -b google_apis/x86_64 -k "system-images;android-30;google_apis;x86_64" \
- && apt-get update && apt-get install -y iproute2 iputils-ping npm git
+ && apt-get update && apt-get install -y iproute2 iputils-ping npm git libxml2-utils telnet
CMD bash /preconf/docker-entrypoint.sh
diff --git a/pre_android/preconf/docker-entrypoint.sh b/pre_android/preconf/docker-entrypoint.sh
index 149ae85..a56a68d 100644
--- a/pre_android/preconf/docker-entrypoint.sh
+++ b/pre_android/preconf/docker-entrypoint.sh
@@ -1,11 +1,14 @@
adb start-server
emulator -avd virtual_dev -writable-system -no-window -no-audio &
bash /preconf/install_culebra.sh
+# Save the ad id while the emulator is still up
+su root cat /data/data/com.google.android.gms/shared_prefs/adid_settings.xml | xmllint --xpath 'string(//map/string[@name="adid_key"])' - > /adid
+
adb emu avd snapshot save configured
adb emu kill
#to let the host know it finished installing
install -m 777 /dev/null /preconf/finished
tail -f /dev/null
File Metadata
Details
Attached
Mime Type
text/x-diff
Expires
Sat, Nov 8, 06:42 (1 d, 18 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
1032032
Default Alt Text
(20 KB)
Attached To
Mode
R134 rentgen-android
Attached
Detach File
Event Timeline
Log In to Comment