Skip to content

Commit 14f3fa4

Browse files
committed
Merge branch 'release/0.3.4'
2 parents a3ff7cc + 3bd23da commit 14f3fa4

15 files changed

Lines changed: 164 additions & 16 deletions

File tree

Dockerfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ RUN curl -o platform-tools.zip https://dl.google.com/android/repository/platform
8383
RUN pip3 install frida-tools==13.7.1
8484
RUN npm ci --only=production --ignore-scripts
8585
# If I don't do this the binding is missing
86-
RUN npm i frida
86+
RUN npm rebuild frida
8787
RUN chmod +x run.sh
8888

8989
ENTRYPOINT ["./run.sh"]

README.md

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,8 @@ The usage of the `DROIDGROUND_NUM_TEAMS` variable slightly changes the behaviour
117117

118118
This allows to share the same DroidGround instance with multiple teams in challenges where the flag can be exfiltrated via a network request. This **massively reduces deploy costs of DroidGround** for CTF competitions.
119119

120+
Furthermore, if the value is set to `-1` it will enable the so-called **Unlimited Teams** mode. In this mode a button to generate a new **_team token_** will be available in the _Overview_ page. All the `DROIDGROUND_TEAM_TOKEN_<N>` variables are ignored if this mode is enabled.
121+
120122
## 🧩 Use Cases
121123

122124
Here are some ways DroidGround can be used:
@@ -181,10 +183,17 @@ After that you may just run the following:
181183
```sh
182184
git clone https://github.com/SECFORCE/droidground.git
183185
cd droidground
184-
npm run install
185-
```
186186

187-
There is a `postinstall` script which will also try to build it, so if something's missing you'll know pretty soon.
187+
# Install without running scripts
188+
npm install --ignore-scripts
189+
# Rebuild frida to get the bindings
190+
npm rebuild frida
191+
# Build companion app
192+
npm run companion
193+
# Get scrcpy
194+
npx fetch-scrcpy-server 3.1
195+
npm run scrcpy
196+
```
188197

189198
After that you just need to set the **env** variables and then run `npm run dev` and you'll be good to go. Happy dev mode!
190199

package-lock.json

Lines changed: 8 additions & 8 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "droidground",
3-
"version": "0.3.3",
3+
"version": "0.3.4",
44
"type": "module",
55
"author": "Angelo Delicato",
66
"scripts": {

src/client/api/rest.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import {
1010
GetFilesRequest,
1111
GetFilesResponse,
1212
IGenericResultRes,
13+
NewTeamResponse,
1314
StartActivityRequest,
1415
StartBroadcastRequest,
1516
StartExploitAppRequest,
@@ -122,6 +123,11 @@ class RESTManager {
122123
return res;
123124
}
124125

126+
async addNewTeam(): Promise<AxiosResponse<NewTeamResponse>> {
127+
const res = await http.post<NewTeamResponse>(E.NEW_TEAM);
128+
return res;
129+
}
130+
125131
async getAttackSurface(debugToken: string): Promise<AxiosResponse<CompanionAttackSurface>> {
126132
const res = await http.get<CompanionAttackSurface>(E.ATTACK_SURFACE, { headers: { Authorization: debugToken } });
127133
return res;
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
import { useEffect, useState } from "react";
2+
import toast from "react-hot-toast";
3+
import { RESTManagerInstance } from "@client/api/rest";
4+
5+
interface IModalProps {
6+
dialogRef: React.RefObject<HTMLDialogElement | null>;
7+
}
8+
9+
export const AddNewTeamModal: React.FC<IModalProps> = ({ dialogRef }) => {
10+
const [requestResult, setRequestResult] = useState<string>("");
11+
12+
useEffect(() => {
13+
const handleClose = () => {
14+
setRequestResult("");
15+
};
16+
17+
const dialog = dialogRef.current;
18+
if (dialog) {
19+
dialog.addEventListener("close", handleClose);
20+
}
21+
22+
return () => {
23+
if (dialog) {
24+
dialog.removeEventListener("close", handleClose);
25+
}
26+
};
27+
}, []);
28+
29+
const getTeamToken = async () => {
30+
try {
31+
const res = await RESTManagerInstance.addNewTeam();
32+
setRequestResult(`New Team Token generated: ${res.data.teamToken}`);
33+
} catch (e) {
34+
console.error(e);
35+
toast.error("Error while generating new Team Token.");
36+
}
37+
};
38+
39+
return (
40+
<dialog ref={dialogRef} className="modal">
41+
<div className="modal-box max-w-3xl">
42+
<h3 className="font-bold text-lg mb-4">Get Team Token</h3>
43+
<p className="mb-4">
44+
This will allow you to get a new Team Token which is <b>required</b> to install and run <i>exploit apps</i>{" "}
45+
and for the <i>Exploit Server</i> as well.
46+
</p>
47+
<div className="space-y-4">
48+
{/* Result */}
49+
{requestResult.length > 0 && (
50+
<div className="flex flex-col justify-between mb-4 gap-4">
51+
<span className="font-semibold">Output</span>
52+
<div className="mockup-code w-full hide-before max-h-96 overflow-y-auto p-4">
53+
<pre className="text-accent text-wrap wrap-break-word break-all">
54+
<code>{requestResult}</code>
55+
</pre>
56+
</div>
57+
</div>
58+
)}
59+
60+
{/* Submit */}
61+
<div className="flex justify-end">
62+
<input onClick={getTeamToken} className="btn btn-primary" value="Get Token" />
63+
</div>
64+
</div>
65+
</div>
66+
<form method="dialog" className="modal-backdrop">
67+
<button>close</button>
68+
</form>
69+
</dialog>
70+
);
71+
};

src/client/components/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,4 @@ export { StartBroadcastModal } from "@client/components/StartBroadcastModal";
44
export { StartServiceModal } from "@client/components/StartServiceModal";
55
export { InstallExploitAppModal } from "@client/components/InstallExploitAppModal";
66
export { StartExploitAppModal } from "@client/components/StartExploitAppModal";
7+
export { AddNewTeamModal } from "@client/components/AddNewTeamModal";

src/client/layout/Header.tsx

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -154,7 +154,13 @@ const Navbar: React.FC = () => {
154154
.filter(i => i.routeEnabled)
155155
.map(item => (
156156
<li key={item.to}>
157-
<button className="justify-between" onClick={() => navigate(item)}>
157+
<button
158+
className="justify-between"
159+
onClick={() => {
160+
(document.activeElement as HTMLElement | null)?.blur();
161+
navigate(item);
162+
}}
163+
>
158164
{item.label}
159165
</button>
160166
</li>

src/client/views/Overview.tsx

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import { MdSpaceDashboard } from "react-icons/md";
77
import { RESTManagerInstance } from "@client/api/rest";
88
import { BugreportzStatusResponse } from "@shared/api";
99
import { useAPI } from "@client/context/API";
10-
import { StartActivityModal, StartBroadcastModal, StartServiceModal } from "@client/components";
10+
import { AddNewTeamModal, StartActivityModal, StartBroadcastModal, StartServiceModal } from "@client/components";
1111

1212
export const Overview: React.FC = () => {
1313
const { featuresConfig, deviceInfo } = useAPI();
@@ -17,6 +17,7 @@ export const Overview: React.FC = () => {
1717
});
1818
const isPowerMenuEnabled = featuresConfig.shutdownEnabled || featuresConfig.rebootEnabled;
1919
// Dialogs
20+
const addNewTeamDialogRef = useRef<HTMLDialogElement | null>(null);
2021
const startActivityDialogRef = useRef<HTMLDialogElement | null>(null);
2122
const startBroadcastReceiverDialogRef = useRef<HTMLDialogElement | null>(null);
2223
const startServiceDialogRef = useRef<HTMLDialogElement | null>(null);
@@ -120,6 +121,9 @@ export const Overview: React.FC = () => {
120121
* Modals *
121122
***************/}
122123

124+
{/* New Team Modal */}
125+
<AddNewTeamModal dialogRef={addNewTeamDialogRef} />
126+
123127
{/* Start Activity Modal */}
124128
<StartActivityModal dialogRef={startActivityDialogRef} />
125129

@@ -202,6 +206,22 @@ export const Overview: React.FC = () => {
202206
</div>
203207
</div>
204208

209+
{featuresConfig.unlimitedTeams && (
210+
<div className="flex w-full justify-between items-center">
211+
<p>
212+
Get <b>Team Token</b>
213+
</p>
214+
<div className="join">
215+
<button
216+
className="btn btn-info join-item rounded-r-md"
217+
onClick={() => addNewTeamDialogRef.current?.showModal()}
218+
>
219+
Get
220+
</button>
221+
</div>
222+
</div>
223+
)}
224+
205225
{featuresConfig.startActivityEnabled && (
206226
<div className="flex w-full justify-between items-center">
207227
<p>

src/server/api/controller.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import {
2020
GetFilesResponse,
2121
IGenericErrRes,
2222
IGenericResultRes,
23+
NewTeamResponse,
2324
StartActivityRequest,
2425
StartBroadcastRequest,
2526
StartExploitAppRequest,
@@ -70,6 +71,19 @@ class APIController {
7071
}
7172
};
7273

74+
newTeam: RequestHandler = async (req: Request, res: Response<NewTeamResponse | IGenericErrRes>) => {
75+
Logger.info(`Received ${req.method} request on ${req.path}`);
76+
try {
77+
const singleton = ManagerSingleton.getInstance();
78+
const teamToken = singleton.addTeam();
79+
80+
res.json({ teamToken }).end();
81+
} catch (error: any) {
82+
Logger.error(`Error generating new Team Token: ${error}`);
83+
res.status(500).json({ error: "An error occurred while generating a new Team Token" }).end();
84+
}
85+
};
86+
7387
restartApp: RequestHandler = async (req: Request, res: Response<IGenericResultRes | IGenericErrRes>) => {
7488
Logger.info(`Received ${req.method} request on ${req.path}`);
7589
try {

0 commit comments

Comments
 (0)