add Rust ScrapHacks prototype and network sniffer/parser

This commit is contained in:
Daniel S. 2023-05-07 21:36:20 +02:00
parent 58407ecc9f
commit 63962c95cc
27 changed files with 5008 additions and 0 deletions

View file

@ -0,0 +1,2 @@
[build]
target = "i686-pc-windows-msvc"

1624
tools/remaster/scraphacks_rs/Cargo.lock generated Normal file

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,45 @@
[package]
name = "scraphacks_rs"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[profile.release]
debug = 0
[lib]
crate-type = ["dylib"]
[features]
console=[]
[dependencies]
anyhow = "1.0.68"
comma = "1.0.0"
custom-print = "0.1.0"
derivative = "2.2.0"
detour3 = "0.1.0"
discord-sdk = "0.3.2"
futures = "0.3.25"
hex = "0.4.3"
iced-x86 = "1.18.0"
mlua = { version = "0.8.7", features = ["luajit", "vendored", "macros", "serialize", "mlua_derive"] }
nom = "7.1.3"
nom-greedyerror = "0.5.0"
nom-supreme = "0.8.0"
nom_locate = "4.1.0"
num-traits = "0.2.15"
once_cell = "1.17.0"
parse_int = "0.6.0"
pelite = "0.10.0"
region = "3.0.0"
rhexdump = "0.1.1"
rustc-hash = "1.1.0"
shadow-rs = "0.21.0"
struct_layout = "0.1.0"
tokio = "1.24.2"
viable = "0.2.0"
winsafe = { version = "0.0.15", features = ["kernel", "user", "dshow"] }
[build-dependencies]
shadow-rs = "0.21.0"

View file

@ -0,0 +1,11 @@
[[source]]
url = "https://pypi.org/simple"
verify_ssl = true
name = "pypi"
[packages]
[dev-packages]
[requires]
python_version = "3.9"

View file

@ -0,0 +1,242 @@
{
"id": "Outskirts - 07:41:13",
"title": "Scrapland savegame",
"data": {
"CTFFinishPlayerLoosesTextCad": "Mission_CTFAgainstBankers_RemoteMessage3",
"CTFFinishPlayerWinsTextCad": "Mission_CTFAgainstBankers_RemoteMessage2",
"CTFStartTextCad": "Mission_CTFAgainstBankers_RemoteMessage1",
"CTFOnDeathSpawnTime": "5.0",
"CTFFriendProfile": "BankersCTFFriends",
"CTFEnemyProfile": "BankersCTFEnemies",
"CTFFriendHead": "Functionary",
"CTFFriendType": "Functionary",
"CTFEnemyType": "BankDirector",
"CTFNumFriends": "5",
"CTFNumEnemies": "5",
"CTFFlags": "5",
"CombatFriendProfile": "ArrangeBankersCombatFriends",
"CombatFriendType": "Police",
"CombatNumFriends": "4",
"CombatNumEnemies": "5",
"PlayerWinsCombat": "1",
"OnVictory": "import SaveGame; SaveGame.QWayPoint();import MissionsFuncs; MissionsFuncs.SetNextMission(\"Mission_BackFromMortalRace\", \"GamblinDen\");Scrap.SetSaveVar(\"Map\", \"Levels/GamblinDen\");import SaveGame; SaveGame.QLoad(\"Levels/GamblinDen\");Scrap.SetSaveVar('MapPress_BureauExists','1')",
"OnAbort": "import MissionsFuncs; MissionsFuncs.EndOfSuperDeal('SuperDeal_Faliure_SystemMessage1')",
"CombatFinishPlayerLoosesTextCad": "SuperDeal_First_RemoteMessage3",
"CombatFinishPlayerWinsTextCad": "SuperDeal_First_RemoteMessage2",
"CombatStartTextCad": "SuperDeal_First_RemoteMessage1",
"CombatEnemyProfile": "SuperDealFirstElite",
"CombatEnemyTypeHead": "CrazyGambler",
"CombatEnemyType": "BankDirector",
"CombatDeaths": "5",
"SuperDealType": "",
"IgorFirstContactMissionState": "TalkToMercenaries",
"Stats.MadHunter": "10",
"Stats.Nurse.Dazed": "2",
"Stats.BankMaster": "6758",
"Bank.Circuit.36": "0",
"Bank.Circuit.35": "0",
"Bank.Circuit.34": "0",
"Bank.Circuit.33": "0",
"Bank.Circuit.32": "0",
"Bank.Circuit.31": "0",
"Bank.Circuit.30": "0",
"Bank.Circuit.29": "0",
"Bank.Circuit.28": "0",
"Bank.Circuit.27": "0",
"Bank.Circuit.26": "0",
"Bank.Circuit.25": "0",
"Bank.Circuit.24": "0",
"Bank.Circuit.23": "0",
"Bank.Circuit.22": "0",
"Bank.Circuit.21": "0",
"Bank.Circuit.20": "0",
"Bank.Circuit.19": "0",
"Bank.Circuit.18": "0",
"Bank.Circuit.17": "0",
"Bank.Circuit.16": "0",
"Bank.Circuit.15": "0",
"Bank.Circuit.14": "0",
"Bank.Circuit.13": "0",
"Bank.Circuit.12": "0",
"Bank.Circuit.11": "0",
"Bank.Circuit.10": "0",
"Bank.Circuit.9": "0",
"Bank.Circuit.8": "0",
"Bank.Circuit.7": "0",
"Bank.Circuit.6": "0",
"Bank.Circuit.5": "0",
"Bank.Circuit.4": "0",
"Bank.Circuit.3": "0",
"Bank.Circuit.2": "0",
"Bank.Circuit.1": "0",
"Bank.Circuit.0": "0",
"Stats.Mosquito": "116",
"PoliceBossAtTownHall": "0",
"Stats.Parking": "18",
"Police.FicusDeath": "1",
"CostumeAtPolice": "0",
"Hangar.HangarShip10": "SLifeBoat<-<-<-<-<-0,0,0,0,0,0,1<-30<-0,0,0,0,0,0",
"Hangar.iHangarShips": "8",
"AutoSaveGameOnLoad": "0",
"GameAct": "3rdMurder",
"BankDebt": "0",
"Map": "Levels/Outskirts",
"Mission.Library": "Mission_DestroyBadDebtors",
"EnergyBarActive": "1",
"SpecialActionActive": "2",
"CrazyWing.Status": "1",
"Conversor.ActiveConversors": "1",
"Hangar.iHangarShip": "0",
"Player.NumLives": "100",
"Hangar.shipsToEditList": "['SPoli1', 'SPoli2', 'SPoli3', 'SPoli4', 'SPoli5', 'SPoliBoss1', 'SMerc1', 'SMerc2', 'SMerc3', 'SMayor1', 'SBanker1', 'SBankMaster1', 'SBishop1', 'SArchbishop1', 'SFunc1', 'SBerto1', 'SBetty1', 'SHump1', 'SBoss1', 'SPoli4']",
"Hangar.availableEnginesList": "['MPOLI4', 'MPOLI5', 'MPOLIBOSS1', 'MPOLI2', 'MBERTO1', 'MBETTY1', 'MPOLI1', 'MMERC1', 'MMERC2', 'MPOLI3', 'MMAYOR1', 'MFUNC1', 'MBANKER1', 'MBANKMASTER1', 'MBISHOP1', 'MARCHBISHOP1', 'MHUMP1', 'MBOSS1', 'MMERC3']",
"Hangar.availableWeaponsList": "['Vulcan', 'Devastator', 'Swarm', 'Inferno', 'Tesla', 'ATPC', 'Swarm', 'Devastator']",
"Hangar.availableUpgradesList": "[\"VulcanUpgrade1\", \"VulcanUpgrade2\", \"DevastatorUpgrade1\", \"DevastatorUpgrade2\", \"SwarmUpgrade1\", \"SwarmUpgrade2\", \"InfernoUpgrade1\", \"InfernoUpgrade2\", \"TeslaUpgrade1\", \"TeslaUpgrade2\", \"ATPCUpgrade1\", \"ATPCUpgrade2\"]",
"JackInTheBox.Active": "1",
"Parked.Police": "['SPOLI1', 'SPOLI2', 'SPOLI3', 'SPOLI4']",
"Parked.Mercs": "['SMERC1', 'SMERC2']",
"Parked.TownHall": "['SFUNC1']",
"Parked.Bank": "['SBANKER1']",
"Parked.Press": "['SBERTO1', 'SBETTY1', 'SHUMP1']",
"Parked.Temple": "['SBISHOP1']",
"PoliceBlueprints.Ships": "['SPoli2', 'SPoli3', 'SPoli5', 'SPoliBoss1']",
"PoliceBlueprints.Engines": "['MPoli2', 'MPoli3', 'MPoli4', 'MPoli5', 'MPoliBoss1']",
"PressBlueprints.Ships": "['SBerto1', 'SBetty1', 'SHump1']",
"PressBlueprints.Engines": "['MBerto1', 'MBetty1', 'MHump1']",
"MayorAtGambinDen": "0",
"PolicesAtGambinDen": "1",
"PoliceBossAtPolice": "0",
"CrazyGamblerAtGambinDen": "1",
"FunctionaryTwinsAtGambinDen": "1",
"BankersAtGambinDen": "0",
"BishopsAtGambinDen": "0",
"GameSkill": "0",
"CreateBertos": "0",
"MercsHelpDtritus": "0",
"RobotsControlledByBoss": "0",
"Player.InfiniteLives": "0",
"PrevMap": "Levels/GamblinDen",
"Spawn": "DM_Player_Spawn_GamblinDen",
"Char": "Dtritus",
"ComeFrom": "DoorElevator",
"AlarmActive": "0",
"AlarmStatus": "0.0",
"Money": "2147483647",
"Challenge": "1",
"Challenge.Type": "",
"Challenge.Foe": "",
"Challenge.NumEnemies": "",
"Challenge.Money": "0",
"Revenge.Type": "",
"Revenge.Foe": "",
"Revenge.NumEnemies": "",
"Revenge.LastWinner": "",
"Mission.Map": "Outskirts",
"BadReputation": "3",
"GameplayTime": "27673.5857997",
"LonelyMercInGamblinDen": "0",
"LonelyMercLairActive": "1",
"LonelyMercDataActive": "0",
"ComSatsMissionsMapsFinished": "[]",
"Conversor.AvailableChars": "['Police', 'Nurse', 'BankDirector', 'Desktop', 'Sentinel', 'Gear', 'Bishop', 'Messenger', 'Functionary', 'Betty', 'Berto', 'BankMaster', 'Dtritus']",
"Batteries": "5",
"AcBatteries": "2",
"PrimaryMissionDesc": "Ich muss den Bankdirektor besuchen. Er treibt gerade ausstehende Kreditzahlungen mit seinem Kampfraumschiff ein. Ich sehe mal, ob ich ihm helfen kann.",
"SecondaryMissionDesc": "",
"TakePhotoMsg": "0",
"Race.Num": "3",
"Race.FirstTime": "0",
"Race.Profile": "Pilot",
"Combat.FirstTime": "1",
"Combat.Profile": "Rookie",
"Traffic.AcShips": "[\"SPoli6\", \"SMerc1\",\"SMerc2\",\"SBanker1\"]",
"IsSecondMission": "0",
"CrazyDeal.1.Var": "Stats.Dtritus",
"CrazyDeal.1.Tgt": "5",
"CrazyDeal.2.Var": "Stats.Parking",
"CrazyDeal.2.Tgt": "3",
"CrazyDeal.3.Var": "Stats.Battery",
"CrazyDeal.3.Tgt": "5",
"SuperDeal.Map": "FZ",
"SuperDeal.Library": "SuperDeal_Second",
"SuperDeal.Type": "MortalRace",
"CrazyWing.List": "['Sentinel', 'Betty', 'CrazyGambler', 'Functionary', 'Bishop']",
"Journalist.Humphrey_Defaut": "NoPlace",
"Journalist.Betty_Defaut": "NoPlace",
"Journalist.Berto_Defaut": "Press",
"Conversor.FirstConversion": "0",
"Conversor.FirstPossession": "0",
"WindowsError": "0",
"Orbit.Decontaminated": "yes",
"MercFriends.MercenaryA_Smartie": "0",
"MercFriends.MercenaryC_Brutus": "0",
"MercFriends.MercenaryB_Dumber": "0",
"StdShipAIProfile": "Elite",
"usrShip.Ammo00": "1000.0",
"usrShip.Ammo01": "500.0",
"usrShip.Ammo02": "1500.0",
"usrShip.AcWeap": "6",
"Parking.Desolate": "0",
"Hangar.HangarShip1": "SBoss1<-MBOSS1<-MBOSS1<-<-<-15,15,15,15,15,15,1<-187<-1,8,6,9,11,3",
"Hangar.HangarShip2": "",
"Hangar.HangarShip3": "",
"Hangar.HangarShip4": "",
"Hangar.HangarShip5": "",
"Hangar.HangarShip6": "",
"Hangar.HangarShip7": "",
"Hangar.HangarShip8": "",
"Hangar.HangarShip9": "",
"Hangar.HangarShipAux": "SLifeBoat<-<-<-<-<-0,0,0,0,0,0,1<-50<-0,0,0,0,0,0",
"Hangar.DestroyedShips": "[0, 0, 0, 0, 0, 0, 0, 0, 0, 0]",
"NewBluePrintAvaliable": "11",
"DebugSave": "1",
"OutMusicRelax": "141",
"MayorAtTownHall": "1",
"GamblinMusic": "1",
"OutMusicAction": "122",
"Stats.Traffic": "767",
"Stats.OutPolice": "110",
"MapPress_HiAlarm": "0",
"MapPress_BureauExists": "1",
"Stats.PossessBerto": "1",
"Stats.ConvertIntoDtritus": "1",
"Stats.WinHumphreyRace": "1",
"Stats.Possession": "57",
"Stats.Betty": "49",
"Stats.Killer": "119",
"Stats.Jump": "1",
"Stats.Jump.Police": "1",
"Stats.Bishop": "10",
"Stats.Battery": "0",
"Stats.Dtritus": "0",
"Stats.Race.Press": "1",
"Stats.TotalRaces.Press": "1",
"BankMasterAtBank": "1",
"DM_ExtraLife_00": "-60",
"DM_ExtraLife_01": "-60",
"DM_ExtraLife_02": "-60",
"DM_ExtraLife_03": "-60",
"DM_ExtraLife_04": "16294.7919922",
"Stats.TempleLife": "4",
"Mission_CatchTrurl_Data": "[]",
"Mission_CatchTrurl_MapsPos": "[]",
"Mission_CatchTrurl_NumMapsDropped": "0",
"Mission_CatchTrurl_NumMapsTaken": "0",
"GDB.BishopsMsg": "1",
"Stats.GDB": "6",
"LonelyMercActive": "0",
"Stats.Messenger": "2",
"MortalRaceRace": "[((71611.59375, 18231.6992188, 93232.796875), 422.337219238), ((45388.4140625, 14599.3476563, 79622.6640625), 400.984222412), ((25194.9804688, 18783.4863281, 59759.296875), 404.390136719), ((-433.664245605, 26340.1289063, 34561.0898438), 409.718261719), ((-38229.3671875, 26457.5292969, 679.068054199), 449.472442627), ((-107464.132813, 19331.875, 3288.328125), 528.452758789), ((-113911.117188, 14331.4462891, 40812.9414063), 558.054199219), ((-102532.132813, 11236.1474609, 75072.375), 630.567077637), ((-58177.6289063, 6282.20654297, 74209.3515625), 673.615478516), ((-24157.5449219, 7054.30419922, 43223.1679688), 630.510681152), ((33550.1445313, 15480.2402344, 41122.5820313), -55.4565696716), ((56201.6054688, 15587.5126953, 24649.8496094), 23.7488441467), ((26343.9511719, 22077.8789063, -32317.0292969), 90.5086135864), ((-13835.4755859, 26276.8730469, -31975.1582031), 145.932754517), ((-29244.3652344, 26745.4667969, -2544.81738281), -892.995666504), ((-23808.9570313, 27246.9980469, 32018.1816406), -819.383483887), ((21584.3066406, 29452.4667969, 41221.6171875), -822.313781738), ((54026.796875, 24611.7421875, 42694.0898438), -802.188171387), ((95732.015625, 16516.8085938, 36323.546875), -872.699890137), ((113450.46875, 12325.5195313, 77796.75), -969.003662109)]",
"MortalRaceWaypoints": "20",
"MortalRaceRacers": "['ArchBishop', 'BankDirector', 'Functionary', 'MercenaryA', 'MercenaryB']",
"MortalRaceRacersProfile": "MortalRace",
"MortalRaceFightingFreezeControlAim": "4",
"MortalRaceRespawnTime": "5.0",
"MortalRaceStartTextCad": "Mission_WinMortalRace_RemoteMessage1",
"MortalRaceFinishPlayerLoosesTextCad": "Mission_WinMortalRace_RemoteMessage2",
"MortalRaceFinishPlayerLoosesTextFoe": "Messenger",
"MortalRaceFinishPlayerWinsTextCad": "Mission_WinMortalRace_RemoteMessage3",
"MortalRaceFinishPlayerWinsTextFoe": "Messenger",
"MortalRaceAutoRestart": "1"
}
}

View file

@ -0,0 +1,3 @@
fn main() -> shadow_rs::SdResult<()> {
shadow_rs::new()
}

View file

@ -0,0 +1,4 @@
impo
cargo b -r
cp D:/devel/Git_Repos/Scrapland-RE/tools/remaster/scraphacks_rs/target/i686-pc-windows-msvc/release/scraphacks_rs.dll E:/Games/Steam/steamapps/common/Scrapland/lib/ScrapHack.pyd
# x32dbg E:/Games/Steam/steamapps/common/Scrapland/Bin/Scrap.unpacked.exe "-debug:10 -console"

View file

@ -0,0 +1,16 @@
# Notes
## Snippets
Map name: `Scrap.GetLangStr("Station_" + Scrap.GetLevelPath()[7:])`
## Run
`steam://run/897610/`
## Signatures
- World pointer: `a3 *{'} e8 ? ? ? ? 6a 00 68 *"World initialized"`
- print: `6a0068 *{"Scrap engine"} 6a?e8 $'`
- console handler: `68 *{"import Viewer"} e8 $'`
- DirectX Device: ``

View file

@ -0,0 +1,34 @@
import subprocess as SP
import shutil as sh
import json
from pathlib import Path
import psutil
import os
import sys
os.environ['DISCORD_INSTANCE_ID']='1'
SP.check_call(["cargo","b","-r"])
info=[json.loads(line) for line in SP.check_output(["cargo","b", "-r" ,"-q","--message-format=json"]).splitlines()]
dll_path=None
for line in info:
if line.get('reason')=="compiler-artifact" and ("dylib" in line.get("target",{}).get("crate_types",[])):
dll_path=Path(line['filenames'][0])
sh.copy(dll_path,"E:/Games/Steam/steamapps/common/Scrapland/lib/ScrapHack.pyd")
if "--run" not in sys.argv[1:]:
exit(0)
os.startfile("steam://run/897610/")
pid=None
while pid is None:
for proc in psutil.process_iter():
try:
if proc.name()=="Scrap.exe":
pid=proc.pid
except:
pass
print(f"PID: {pid:x}")
if "--dbg" in sys.argv[1:]:
SP.run(["x32dbg","-p",str(pid)])
# cp D:/devel/Git_Repos/Scrapland-RE/tools/remaster/scraphacks_rs/target/i686-pc-windows-msvc/release/scraphacks_rs.dll E:/Games/Steam/steamapps/common/Scrapland/lib/ScrapHack.pyd
# x32dbg E:/Games/Steam/steamapps/common/Scrapland/Bin/Scrap.unpacked.exe "-debug:10 -console"

View file

@ -0,0 +1,16 @@
#include <stdint.h>
struct HashTable {
uint32_t num_slots;
struct HashTableEntry **chains;
};
struct HashTableEntry {
void *data;
const char *name;
HashTableEntry *next;
};
struct World {
void** VMT;
HashTable *entities;
};

View file

@ -0,0 +1,7 @@
enum FilePatch {
}
pub struct Config {
file_patches: FxHashMap<PathBuf,FilePatch>
}

View file

@ -0,0 +1,94 @@
use std::{num::NonZeroU32, thread::JoinHandle, time::SystemTime};
use crate::{cdbg, ceprintln, cprint, cprintln};
use anyhow::{bail, Result};
use discord_sdk::{
activity::{ActivityBuilder, Assets, PartyPrivacy, Secrets},
registration::{register_app, Application, LaunchCommand},
wheel::Wheel,
Discord, DiscordApp, Subscriptions,
};
const APP_ID: discord_sdk::AppId = 1066820570097930342;
const STEAM_APP_ID: u32 = 897610;
pub struct Client {
pub discord: discord_sdk::Discord,
pub user: discord_sdk::user::User,
pub wheel: discord_sdk::wheel::Wheel,
}
impl Client {
pub fn run() -> Result<JoinHandle<Result<()>>> {
let rt = tokio::runtime::Builder::new_multi_thread()
.enable_all()
.build()?;
register_app(Application {
id: APP_ID,
name: Some("Scrapland Remastered".to_owned()),
command: LaunchCommand::Steam(STEAM_APP_ID),
})?;
Ok(std::thread::spawn(move || rt.block_on(Self::run_async())))
}
async fn run_async() -> Result<()> {
let (wheel, handler) = Wheel::new(Box::new(|err| {
ceprintln!("Encountered an error: {}", err);
}));
let mut user = wheel.user();
let discord = Discord::new(
DiscordApp::PlainId(APP_ID),
Subscriptions::ACTIVITY,
Box::new(handler),
)?;
user.0.changed().await?;
let user = match &*user.0.borrow() {
discord_sdk::wheel::UserState::Connected(user) => user.clone(),
discord_sdk::wheel::UserState::Disconnected(err) => {
ceprintln!("Failed to connect to Discord: {err}");
bail!("{}", err);
}
};
let uid = user.id;
cprintln!(
"Logged in as: {user}#{discriminator}",
user = user.username,
discriminator = user
.discriminator
.map(|d| d.to_string())
.unwrap_or_else(|| "????".to_owned())
);
let mut activity = ActivityBuilder::new()
.state("Testing")
.assets(Assets::default().large("scrap_logo", Some("Testing")))
.timestamps(Some(SystemTime::now()), Option::<SystemTime>::None)
.details("Testing ScrapHack");
if false {
// (SCRAP.is_server()||SCRAP.is_client())
let players = 1;
let capacity = 32;
activity = activity
.instance(true)
.party(
"Testt",
NonZeroU32::new(players),
NonZeroU32::new(capacity),
if false {
PartyPrivacy::Private
} else {
PartyPrivacy::Public
}
)
.secrets(Secrets {
r#match: Some("MATCH".to_owned()), // Use server_ip+port
join: Some("JOIN".to_owned()), // Use server_ip+port
spectate: Some("SPECTATE".to_owned()), // Use server_ip+port
});
}
discord.update_activity(activity).await?;
loop {
if let Ok(req) = wheel.activity().0.try_recv() {
cprintln!("Got Join request: {req:?}");
}
}
Ok(())
}
}

View file

@ -0,0 +1,96 @@
#![feature(abi_thiscall)]
#![feature(c_variadic)]
mod discord;
mod lua;
mod mem;
mod parser;
mod scrap;
use std::ffi::{c_char, c_void, CString};
use anyhow::Result;
use crate::mem::search;
use crate::scrap::SCRAP;
use shadow_rs::shadow;
use winsafe::{co::{MB, CS, WS}, prelude::*, HWND, WNDCLASSEX, RegisterClassEx, WString};
shadow!(build);
custom_print::define_macros!({cprint, cprintln, cdbg}, fmt, |value: &str| {crate::scrap::SCRAP.print(value)});
custom_print::define_macros!({ceprint, ceprintln}, fmt, |value: &str| {crate::scrap::SCRAP.print_c(0x800000,value)});
#[allow(clippy::single_component_path_imports)]
pub(crate) use {cdbg, cprint, cprintln};
#[warn(clippy::single_component_path_imports)]
pub(crate) use {ceprint, ceprintln};
#[repr(C)]
#[derive(Debug)]
struct PyMethodDef {
name: *const c_char,
func: *const (*const c_void, *const c_void),
ml_flags: i32,
doc: *const c_char,
}
#[repr(C)]
#[derive(Debug)]
struct PyModuleDef {
name: *const c_char,
methods: *const PyMethodDef,
}
fn init_py_mod() {
let py_init_module: fn(
*const c_char, // name
*const PyMethodDef, // methods
*const c_char, // doc
*const (), // passthrough
i32, // module_api_version
) -> *const () =
unsafe { std::mem::transmute(search("68 *{\"Scrap\" 00} e8 ${'}", 1, None).unwrap_or_default()) };
let name = CString::new("ScrapHack").unwrap_or_default();
let desc = CString::new("ScrapHack Rust version").unwrap_or_default();
let methods: &[PyMethodDef] = &[PyMethodDef {
name: 0 as _,
func: 0 as _,
ml_flags: 0,
doc: 0 as _,
}];
assert!(
!py_init_module(name.as_ptr(), methods.as_ptr(), desc.as_ptr(), 0 as _, 1007).is_null()
);
}
#[no_mangle]
pub extern "system" fn initScrapHack() {
#[cfg(feature = "console")]
unsafe {
AllocConsole();
}
std::panic::set_hook(Box::new(|info| {
ceprintln!("ScrapHacks: {info}");
HWND::DESKTOP
.MessageBox(&format!("{info}"), "ScrapHacks error", MB::ICONERROR)
.unwrap();
std::process::exit(1);
}));
init_py_mod();
print_version_info();
cprintln!("{SCRAP:#x?}");
}
#[no_mangle]
pub extern "system" fn DllMain(_inst: isize, _reason: u32, _: *const u8) -> u32 {
1
}
fn print_version_info() {
cprintln!(
"{} v{} ({} {}), built for {} by {}.",
build::PROJECT_NAME,
build::PKG_VERSION,
build::SHORT_COMMIT,
build::BUILD_TIME,
build::BUILD_TARGET,
build::RUST_VERSION
);
}

View file

@ -0,0 +1,204 @@
use std::{path::PathBuf, sync::Arc};
use crate::{
cprintln,
mem::{get_module, get_modules},
parser::Cmd,
};
use anyhow::{anyhow, bail, Result};
use detour3::GenericDetour;
use mlua::{prelude::*, Variadic};
use pelite::pattern;
use pelite::pe32::{Pe, PeObject};
use rustc_hash::FxHashMap;
use winsafe::{prelude::*, HINSTANCE};
struct Ptr(*const ());
impl LuaUserData for Ptr {
fn add_methods<'lua, M: LuaUserDataMethods<'lua, Self>>(methods: &mut M) {
methods.add_meta_method(LuaMetaMethod::ToString, |_, this, _: ()| {
Ok(format!("{:p}", this.0))
});
methods.add_method("read", |_, this, (size,): (usize,)| {
let addr = this.0 as u32;
let ptr = this.0 as *const u8;
let info = region::query(ptr).map_err(mlua::Error::external)?;
let end = info.as_ptr_range::<()>().end as u32;
let size = ((end - addr) as usize).min(size);
if !info.is_readable() {
return Err(LuaError::external(anyhow!("No read permission on page")));
}
let data = unsafe { std::slice::from_raw_parts(ptr, size) };
Ok(data.to_vec())
});
methods.add_method("write", |_, this, data: Vec<u8>| {
let data = data.as_slice();
let addr = this.0 as *const u8;
unsafe {
let handle = region::protect_with_handle(
addr,
data.len(),
region::Protection::READ_WRITE_EXECUTE,
)
.map_err(mlua::Error::external)?;
std::ptr::copy(data.as_ptr(), addr as *mut u8, data.len());
drop(handle);
};
Ok(())
});
// methods.add_method("hook", |_, this, func: LuaFunction| -> LuaResult<()> {
// let addr = this.0;
// cprintln!("Hook: {func:?} @ {addr:p}");
// let dt = unsafe { GenericDetour::<extern "thiscall" fn(*const (), (u32,u32,u32)) -> u32>::new(std::mem::transmute(addr), hook_func) }.unwrap();
// Err(LuaError::external(anyhow!("TODO: hook")))
// });
}
}
// extern "thiscall" fn hook_func(this: *const (), args: (u32,u32,u32)) -> u32 {
// return 0;
// }
pub(crate) fn init() -> Result<Lua> {
let lua = unsafe { Lua::unsafe_new() };
{
let globals = lua.globals();
globals.set("scan", lua.create_function(lua_scan)?)?;
globals.set("print", lua.create_function(lua_print)?)?;
globals.set("hook", lua.create_function(lua_hook)?)?;
globals.set("imports", lua.create_function(lua_imports)?)?;
globals.set(
"ptr",
lua.create_function(|_, addr: u32| Ok(Ptr(addr as _)))?,
)?;
globals.set(
"ptr",
lua.create_function(lua_alloc)?,
)?;
}
Ok(lua)
}
fn lua_val_to_string(val: &LuaValue) -> LuaResult<String> {
Ok(match val {
LuaNil => "Nil".to_owned(),
LuaValue::Boolean(b) => format!("{b}"),
LuaValue::LightUserData(u) => format!("{u:?}"),
LuaValue::Integer(i) => format!("{i}"),
LuaValue::Number(n) => format!("{n}"),
LuaValue::String(s) => (s.to_str()?).to_string(),
LuaValue::Table(t) => {
let mut vals = vec![];
for res in t.clone().pairs() {
let (k, v): (LuaValue, LuaValue) = res?;
vals.push(format!(
"{k} = {v}",
k = lua_val_to_string(&k)?,
v = lua_val_to_string(&v)?
));
}
format!("{{{vals}}}", vals = vals.join(", "))
}
LuaValue::Function(f) => format!("{f:?}"),
LuaValue::Thread(t) => format!("{t:?}"),
LuaValue::UserData(u) => format!("{u:?}"),
LuaValue::Error(e) => format!("{e:?}"),
})
}
fn lua_alloc(lua: &Lua, size: usize) -> LuaResult<Ptr> {
let data = vec![0u8;size].into_boxed_slice();
Ok(Ptr(Box::leak(data).as_ptr() as _))
}
fn lua_hook(lua: &Lua, (addr, func): (u32, LuaFunction)) -> LuaResult<()> {
cprintln!("Hook: {func:?} @ {addr:08x}");
Err(LuaError::external(anyhow!("TODO: hook")))
}
fn lua_imports(lua: &Lua, (): ()) -> LuaResult<()> {
Err(LuaError::external(anyhow!("TODO: imports")))
}
fn lua_print(lua: &Lua, args: Variadic<LuaValue>) -> LuaResult<()> {
let msg: Vec<String> = args
.into_iter()
.map(|v| lua_val_to_string(&v))
.collect::<LuaResult<Vec<String>>>()?;
cprintln!("{}", msg.join(" "));
Ok(())
}
#[derive(Debug)]
enum ScanMode {
MainModule,
Modules(Vec<String>),
All,
}
impl FromLua<'_> for ScanMode {
fn from_lua<'lua>(lua_value: LuaValue<'lua>, lua: &'lua Lua) -> LuaResult<Self> {
match &lua_value {
LuaValue::Nil => Ok(ScanMode::MainModule),
LuaValue::Boolean(true) => Ok(ScanMode::All),
LuaValue::Table(t) => Ok(ScanMode::Modules(FromLua::from_lua(lua_value, lua)?)),
_ => Err(LuaError::FromLuaConversionError {
from: lua_value.type_name(),
to: "scan_mode",
message: None,
}),
}
}
}
fn lua_scan(lua: &Lua, (pattern, scan_mode): (String, ScanMode)) -> LuaResult<LuaTable> {
let pat = pattern::parse(&pattern).map_err(mlua::Error::external)?;
let mut ret = FxHashMap::default();
let modules = match scan_mode {
ScanMode::MainModule => vec![get_module(None).map_err(mlua::Error::external)?],
ScanMode::Modules(modules) => modules
.into_iter()
.map(|m| get_module(Some(m)))
.collect::<Result<_>>()
.map_err(mlua::Error::external)?,
ScanMode::All => get_modules().map_err(mlua::Error::external)?,
};
'outer: for module in modules {
let regions = region::query_range(module.image().as_ptr(), module.image().len())
.map_err(mlua::Error::external)?;
for region in regions {
let Ok(region)=region else {
continue 'outer;
};
if !region.is_readable() {
continue 'outer;
}
}
let h_module = unsafe { HINSTANCE::from_ptr(module.image().as_ptr() as _) };
let module_name = PathBuf::from(
h_module
.GetModuleFileName()
.map_err(mlua::Error::external)?,
)
.file_name()
.unwrap()
.to_str()
.unwrap()
.to_owned();
if let Ok(res) = crate::mem::scan(&pat, &module) {
if !res.is_empty() {
let res: Vec<Vec<Ptr>> = res
.into_iter()
.map(|res| res.into_iter().map(|a| Ptr(a as _)).collect())
.collect();
ret.insert(module_name, res);
}
};
}
lua.create_table_from(ret.into_iter())
}
pub(crate) fn exec(chunk: &str) -> Result<()> {
Ok(init()?.load(chunk).set_name("ScrapLua")?.exec()?)
}

View file

@ -0,0 +1,94 @@
use std::str::FromStr;
use anyhow::anyhow;
use anyhow::bail;
use anyhow::Result;
use pelite::pattern::parse;
use pelite::pattern::save_len;
use pelite::pattern::Atom;
use pelite::pe32::{Pe, PeView};
use winsafe::co::TH32CS;
use winsafe::prelude::*;
use winsafe::HINSTANCE;
use winsafe::HPROCESSLIST;
pub(crate) struct Pattern(Vec<Atom>, usize);
impl Pattern {
pub(crate) fn set_index(mut self, idx: usize) -> Self {
self.1 = idx;
self
}
}
impl FromStr for Pattern {
type Err = anyhow::Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Ok(Self(parse(s)?, 0))
}
}
impl Pattern {
pub(crate) fn scan(&self, module: Option<String>) -> Result<u32> {
let pe = get_module(module)?;
let scan = pe.scanner();
let mut save = vec![0u32; save_len(&self.0)];
if !scan.finds(&self.0, 0..u32::MAX, &mut save) {
bail!("Pattern not found");
}
save.get(self.1)
.ok_or_else(|| anyhow!("Result index out of range"))
.and_then(|r| pe.rva_to_va(*r).map_err(|e| e.into()))
}
}
pub(crate) fn get_modules() -> Result<Vec<PeView<'static>>> {
let mut res = vec![];
let pid = std::process::id();
let mut h_snap = HPROCESSLIST::CreateToolhelp32Snapshot(TH32CS::SNAPMODULE, Some(pid))?;
for module in h_snap.iter_modules() {
res.push(unsafe { PeView::module(module?.hModule.as_ptr() as *const u8) });
}
Ok(res)
}
pub(crate) fn get_module(module: Option<String>) -> Result<PeView<'static>> {
let hmodule = HINSTANCE::GetModuleHandle(module.as_deref())?;
Ok(unsafe { PeView::module(hmodule.as_ptr() as *const u8) })
}
pub(crate) fn scan(pat: &[Atom], pe: &PeView) -> Result<Vec<Vec<u32>>> {
let mut ret = vec![];
let scan = pe.scanner();
let mut m = scan.matches(pat, 0..u32::MAX);
let mut save = vec![0u32; save_len(pat)];
while m.next(&mut save) {
ret.push(
save.iter()
.map(|rva| pe.rva_to_va(*rva).map_err(|e| e.into()))
.collect::<Result<Vec<u32>>>()?,
);
}
Ok(ret)
}
pub(crate) fn search(pat: &str, idx: usize, module: Option<String>) -> Result<u32> {
pat.parse::<Pattern>()?.set_index(idx).scan(module)
}
fn addr_info(addr: u32) -> Result<()> {
let pid = std::process::id();
let mut h_snap = HPROCESSLIST::CreateToolhelp32Snapshot(TH32CS::SNAPMODULE, Some(pid))?;
for module in h_snap.iter_modules() {
let module = module?;
let module_name = module.szModule();
if module_name.to_lowercase() == "kernel32.dll" {
continue;
}
let mod_range =
unsafe { module.modBaseAddr..module.modBaseAddr.offset(module.modBaseSize as isize) };
println!("{module_name}: {mod_range:?}");
// let module = unsafe { PeView::module(module.modBaseAddr as *const u8) };
}
Ok(())
}

View file

@ -0,0 +1,177 @@
// use crate::{cdbg, ceprintln, cprint, cprintln};
use std::path::PathBuf;
use std::str::FromStr;
use anyhow::{anyhow, Result};
use nom::branch::alt;
use nom::bytes::complete::{take_till, take_while1};
use nom::character::complete::{digit1, hex_digit1};
use nom::character::streaming::char;
use nom::combinator::{eof, opt, rest};
use nom::sequence::{separated_pair, tuple};
use nom::{IResult, Parser};
use nom_locate::LocatedSpan;
use nom_supreme::error::ErrorTree;
use nom_supreme::final_parser::final_parser;
use nom_supreme::tag::complete::{tag, tag_no_case};
use nom_supreme::ParserExt;
use pelite::pattern::{self, Atom};
type Span<'a> = LocatedSpan<&'a str>;
type ParseResult<'a, 'b, T> = IResult<Span<'a>, T, ErrorTree<Span<'b>>>;
#[derive(Debug, Clone)]
pub enum Cmd {
Imports,
Read(u32, usize),
ReadPE(u32, usize),
Write(u32, Vec<u8>),
Disams(u32, usize),
Info(Option<u32>),
Script(PathBuf),
Unload,
ScanModule(Vec<Atom>, Option<String>),
Lua(String),
}
impl FromStr for Cmd {
type Err = anyhow::Error;
fn from_str(s: &str) -> Result<Self> {
match parse(s) {
Ok(cmd) => Ok(cmd),
Err(err) => Err(anyhow!("{}", err)),
}
}
}
fn ws(input: Span) -> ParseResult<()> {
take_while1(|c: char| c.is_whitespace())
.value(())
.context("Whitepace")
.parse(input)
}
//
/// Test
fn hex_bytes(input: Span) -> ParseResult<Vec<u8>> {
hex_digit1
.map_res_cut(hex::decode)
.context("Hex string")
.parse(input)
}
fn num(input: Span) -> ParseResult<usize> {
digit1
.map_res_cut(|n: Span| parse_int::parse(&n))
.context("Number")
.parse(input)
}
fn address(input: Span) -> ParseResult<u32> {
tag_no_case("0x")
.precedes(hex_digit1)
.recognize()
.map_res_cut(|addr: Span| parse_int::parse::<u32>(&addr))
.context("Memory address")
.parse(input)
}
fn parse_read_pe(input: Span) -> ParseResult<Cmd> {
tag("read_pe")
.precedes(ws)
.precedes(separated_pair(address, ws, num.opt()))
.map(|(addr, size)| Cmd::ReadPE(addr, size.unwrap_or(0x100)))
.parse(input)
}
fn parse_read(input: Span) -> ParseResult<Cmd> {
tag("read")
.precedes(ws)
.precedes(separated_pair(address, ws, num.opt()))
.map(|(addr, size)| Cmd::Read(addr, size.unwrap_or(0x100)))
.parse(input)
}
fn parse_disasm(input: Span) -> ParseResult<Cmd> {
tag("disasm")
.precedes(ws)
.precedes(separated_pair(address, ws, num.opt()))
.map(|(addr, size)| Cmd::Disams(addr, size.unwrap_or(50)))
.parse(input)
}
fn parse_write(input: Span) -> ParseResult<Cmd> {
tag("write")
.precedes(ws)
.precedes(separated_pair(address, ws, hex_bytes))
.map(|(addr, data)| Cmd::Write(addr, data))
.parse(input)
}
fn parse_info(input: Span) -> ParseResult<Cmd> {
tag("info")
.precedes(eof)
.value(Cmd::Info(None))
.or(tag("info")
.precedes(ws)
.precedes(address)
.map(|addr| Cmd::Info(Some(addr))))
.parse(input)
}
fn parse_scan(input: Span) -> ParseResult<Cmd> {
let (input, _) = tag("scan").parse(input)?;
let (input, module) =
opt(tuple((char(':'), take_till(|c: char| c.is_whitespace())))).parse(input)?;
let module = module.map(|(_, module)| module.fragment().to_string());
let (input, _) = ws.parse(input)?;
let (input, pattern) = rest
.map_res(|pat: Span| pattern::parse(&pat))
.parse(input)?;
Ok((input, Cmd::ScanModule(pattern, module)))
}
fn parse_unload(input: Span) -> ParseResult<Cmd> {
tag("unload").value(Cmd::Unload).parse(input)
}
fn parse_imports(input: Span) -> ParseResult<Cmd> {
tag("imports").value(Cmd::Imports).parse(input)
}
fn parse_lua(input: Span) -> ParseResult<Cmd> {
tag("lua")
.precedes(ws)
.precedes(rest)
.map(|s| Cmd::Lua(s.fragment().to_string()))
.parse(input)
}
fn parse_script(input: Span) -> ParseResult<Cmd> {
tag("script")
.precedes(ws)
.precedes(rest)
.map(|s| Cmd::Script(PathBuf::from(s.fragment())))
.parse(input)
}
fn parse(input: &str) -> Result<Cmd, ErrorTree<Span<'_>>> {
final_parser(
alt((
parse_imports,
parse_unload,
parse_scan,
parse_info,
parse_write,
parse_read,
parse_read_pe,
parse_script,
parse_disasm,
parse_lua,
))
.context("command"),
)(Span::new(input))
}

View file

@ -0,0 +1,381 @@
use crate::{
cdbg, ceprintln, cprint, cprintln, lua,
mem::{get_module, scan, search},
parser::Cmd, discord,
};
use anyhow::{bail, Result};
use derivative::Derivative;
use detour3::GenericDetour;
use futures::executor::block_on;
use iced_x86::{Decoder, DecoderOptions, Formatter, Instruction, NasmFormatter};
use once_cell::sync::Lazy;
use pelite::{pe::PeView, pe32::Pe};
use std::{
collections::HashMap,
ffi::{c_char, CStr, CString},
fmt::Debug,
ptr,
thread::JoinHandle,
time::Duration,
};
use winsafe::HINSTANCE;
use winsafe::{co::TH32CS, prelude::*, HPROCESSLIST};
const POINTER_SIZE: usize = std::mem::size_of::<*const ()>();
#[repr(C)]
struct VirtualMethodTable(*const *const ());
impl Debug for VirtualMethodTable {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let mut methods = vec![];
for idx in 0.. {
let ptr = self.get::<()>(idx);
if ptr.is_null()
|| !region::query(ptr)
.map(|r| r.is_executable())
.unwrap_or(false)
{
break;
}
methods.push(ptr);
}
f.debug_tuple("VMT").field(&methods).finish()
}
}
impl VirtualMethodTable {
fn get<T>(&self, offset: usize) -> *const T {
unsafe { self.0.add(POINTER_SIZE * offset).read() as *const T }
}
}
#[derive(Derivative)]
#[derivative(Debug)]
pub struct Scrap {
print: extern "C" fn(u32, *const c_char, u8),
console_detour: GenericDetour<extern "C" fn(*const c_char)>,
world: WorldPointer,
discord_thread_handle: JoinHandle<Result<()>>,
}
#[repr(C)]
#[derive(Debug)]
struct Entity {
vmt: VirtualMethodTable,
}
#[repr(C)]
#[derive(Debug)]
struct HashTableEntry<T> {
data: *const T,
name: *const c_char,
next: *const Self,
}
#[repr(C)]
struct HashTable<T> {
num_slots: u32,
chains: *const *const HashTableEntry<T>,
}
fn try_read<T>(ptr: *const T) -> Option<T> {
(!ptr.is_null()).then(|| unsafe { ptr.read() })
}
impl<T: std::fmt::Debug> std::fmt::Debug for HashTable<T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let mut entries: HashMap<String, Option<T>> = HashMap::default();
for offset in 0..self.num_slots {
let offset = offset as _;
// let chain=vec![];
let mut chain_ptr = unsafe { self.chains.offset(offset).read() };
while !chain_ptr.is_null() {
let entry = unsafe { chain_ptr.read() };
let data = try_read(entry.data);
let key = unsafe { CStr::from_ptr(entry.name) }
.to_str()
.unwrap()
.to_owned();
chain_ptr = entry.next;
entries.insert(key, data);
}
}
f.debug_struct(&format!("HashTable @ {self:p} "))
.field("num_slots", &self.num_slots)
.field("entries", &entries)
.finish()
}
}
#[repr(C)]
struct World {
vmt: VirtualMethodTable,
entities: HashTable<Entity>,
}
impl Debug for World {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("World")
.field("vmt", &self.vmt)
.field("entities", &self.entities)
.finish()
}
}
struct WorldPointer(u32);
impl std::fmt::Debug for WorldPointer {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let ptr = self.ptr();
let world = unsafe { ptr.read() };
f.debug_tuple(&format!("WorldPointer @ {ptr:p} "))
.field(&world)
.finish()
}
}
impl WorldPointer {
fn ptr(&self) -> *const World {
let ptr = self.0 as *const *const World;
unsafe { ptr.read() }
}
fn get_hashtable(&self) {
// let ents = unsafe { self.ptr().read().entities.read() };
// cprintln!("Ents: {ents:?}");
}
}
pub(crate) static SCRAP: Lazy<Scrap> =
Lazy::new(|| Scrap::init().expect("Failed to initialize Scrap data structure"));
impl Scrap {
const PRINT_PATTERN: &str = r#"6a0068 *{"Scrap engine"} 6a?e8 $'"#;
const PY_EXEC: &str = r#"68 *{"import Viewer"} e8 $'"#;
const WORLD_PATTERN: &str = r#"8b 0d *{'} 68 *"CTFFriends""#;
fn init() -> Result<Self> {
let scrap = unsafe {
Self {
world: WorldPointer(search(Self::WORLD_PATTERN, 1, None)? as _),
print: std::mem::transmute(search(Self::PRINT_PATTERN, 1, None)?),
console_detour: GenericDetour::<extern "C" fn(*const c_char)>::new(
std::mem::transmute(search(Self::PY_EXEC, 1, None)?),
Self::console_input,
)?,
discord_thread_handle: discord::Client::run()?,
}
};
unsafe { scrap.console_detour.enable()? }
Ok(scrap)
}
extern "C" fn console_input(orig_line: *const c_char) {
let line = unsafe { CStr::from_ptr(orig_line) }.to_str();
let Ok(line) = line else {
return SCRAP.console_detour.call(orig_line);
};
if let Some(cmd) = line.strip_prefix('$') {
let res = cmd.parse().and_then(|cmd: Cmd| cmd.exec());
if let Err(err) = res {
ceprintln!("Error: {err}");
}
return;
};
SCRAP.console_detour.call(orig_line)
}
pub fn println(&self, msg: &str) {
self.println_c(0x008000, msg)
}
pub fn print(&self, msg: &str) {
self.print_c(0x008000, msg)
}
pub fn print_c(&self, col: u32, msg: &str) {
let col = (col & 0xffffff).swap_bytes() >> 8; // 0xRRGGBB -> 0xBBGGRR
let msg = CString::new(msg.to_string()).unwrap();
(self.print)(col, msg.as_ptr(), 0);
}
pub fn println_c(&self, col: u32, msg: &str) {
let msg = msg.to_owned() + "\n";
self.print_c(col, &msg)
}
}
impl Cmd {
pub(crate) fn exec(&self) -> Result<()> {
let pe = get_module(None)?;
match self {
Cmd::Imports => {
for import in pe.imports()? {
let iat = import.iat()?;
let int = import.int()?;
for (func, imp) in iat.zip(int) {
let imp = imp?;
cprintln!(
"{addr:p}: {name} {imp:?}",
name = import.dll_name()?,
addr = func
);
}
}
}
Cmd::Read(addr, size) => {
let ptr = *addr as *const u8;
let info = region::query(ptr)?;
let end = info.as_ptr_range::<()>().end as u32;
let size = ((end - addr) as usize).min(*size);
if !info.is_readable() {
bail!("No read permission on page");
}
let data = unsafe { std::slice::from_raw_parts(ptr, size) };
cprintln!("{}", &rhexdump::hexdump_offset(data, *addr));
}
Cmd::Disams(addr, size) => {
let ptr = *addr as *const u8;
let info = region::query(ptr)?;
let end = info.as_ptr_range::<()>().end as u32;
let size = ((end - addr) as usize).min(*size);
if !info.is_readable() {
bail!("No read permission on page");
}
let data = unsafe { std::slice::from_raw_parts(ptr, size) };
let mut decoder = Decoder::with_ip(32, data, *addr as u64, DecoderOptions::NONE);
let mut instruction = Instruction::default();
let mut output = String::new();
let mut formatter = NasmFormatter::new();
while decoder.can_decode() {
decoder.decode_out(&mut instruction);
output.clear();
formatter.format(&instruction, &mut output);
cprint!("{:016X} ", instruction.ip());
let start_index = (instruction.ip() - (*addr as u64)) as usize;
let instr_bytes = &data[start_index..start_index + instruction.len()];
for b in instr_bytes.iter() {
cprint!("{:02X}", b);
}
cprintln!(" {}", output);
}
}
Cmd::Write(addr, data) => {
let data = data.as_slice();
let addr = *addr as *const u8;
unsafe {
let handle = region::protect_with_handle(
addr,
data.len(),
region::Protection::READ_WRITE_EXECUTE,
)?;
std::ptr::copy(data.as_ptr(), addr as *mut u8, data.len());
drop(handle);
};
}
Cmd::ReadPE(addr, size) => {
if !region::query(*addr as *const ())?.is_readable() {
bail!("No read permission for 0x{addr:x}");
}
let data = pe.read_bytes(*addr)?;
cprintln!("{}", &rhexdump::hexdump_offset(&data[..*size], *addr));
}
Cmd::Info(None) => {
let regions = region::query_range(ptr::null::<()>(), usize::MAX)?;
for region in regions.flatten() {
cprintln!(
"{:?}: {}",
region.as_ptr_range::<*const ()>(),
region.protection()
);
}
}
Cmd::Info(Some(addr)) => {
let info = region::query(*addr as *const ())?;
cprintln!(
"{:?}: {}",
info.as_ptr_range::<*const ()>(),
info.protection()
);
}
Cmd::ScanModule(pat, module) => {
cprintln!("{:?}", pat);
let mut total_hits = 0;
let mut modules = vec![];
let is_wildcard = matches!(module.as_deref(), Some("*"));
if is_wildcard {
let pid = std::process::id();
let mut h_snap =
HPROCESSLIST::CreateToolhelp32Snapshot(TH32CS::SNAPMODULE, Some(pid))?;
for module in h_snap.iter_modules() {
let module = module?;
let module_name = module.szModule();
let module_addr = module.hModule.as_ptr() as *const u8;
let module = region::query_range(module_addr, module.modBaseSize as usize)?
.all(|m| m.ok().map(|m| m.is_readable()).unwrap_or(false))
.then(|| unsafe { PeView::module(module_addr) });
if let Some(module) = module {
modules.push((module_name, module));
}
}
} else {
let module = HINSTANCE::GetModuleHandle(module.as_deref())?;
let module_name = module.GetModuleFileName()?;
let module_addr = module.as_ptr() as *const u8;
let module = region::query(module_addr)
.map(|m| m.is_readable())
.unwrap_or(false)
.then(|| unsafe { PeView::module(module_addr) });
if let Some(module) = module {
modules.push((module_name, module));
};
}
for (module_name, pe) in modules {
let res = scan(pat, &pe)?;
if res.is_empty() {
continue;
}
total_hits += res.len();
cprintln!("Module: {module_name}");
let sections = pe.section_headers();
for hit in &res {
for (idx, addr) in hit.iter().enumerate() {
let mut section_name = String::from("<invalid address>");
if let Ok(section_rva) = pe.va_to_rva(*addr) {
if let Some(section) = sections.by_rva(section_rva) {
section_name = match section.name() {
Ok(name) => name.to_string(),
Err(name_bytes) => format!("{name_bytes:?}"),
};
} else {
section_name = String::from("<invalid section>");
}
};
if let Ok(region) = region::query(addr) {
cprintln!(
"\t{}: {:?} {} [{}] {:p}",
idx,
region.as_ptr_range::<()>(),
region.protection(),
section_name,
addr
)
}
}
}
}
cprintln!("Results: {total_hits}");
}
Cmd::Lua(code) => {
lua::exec(code)?;
}
Cmd::Script(path) => {
for line in std::fs::read_to_string(path)?.lines() {
line.parse().and_then(|cmd: Cmd| cmd.exec())?;
}
}
other => bail!("Not implemented: {other:?}"),
}
Ok(())
}
}

View file

@ -0,0 +1 @@
{"rustc_fingerprint":18143952876974389501,"outputs":{"16636649553340150347":{"success":true,"status":"","code":0,"stdout":"___.exe\nlib___.rlib\n___.dll\n___.dll\n___.lib\n___.dll\nC:\\Users\\Earthnuker\\scoop\\persist\\rustup-msvc\\.rustup\\toolchains\\nightly-x86_64-pc-windows-msvc\npacked\n___\ndebug_assertions\nfeature=\"cargo-clippy\"\npanic=\"unwind\"\nproc_macro\ntarget_abi=\"\"\ntarget_arch=\"x86\"\ntarget_endian=\"little\"\ntarget_env=\"msvc\"\ntarget_family=\"windows\"\ntarget_feature=\"fxsr\"\ntarget_feature=\"sse\"\ntarget_feature=\"sse2\"\ntarget_has_atomic\ntarget_has_atomic=\"16\"\ntarget_has_atomic=\"32\"\ntarget_has_atomic=\"64\"\ntarget_has_atomic=\"8\"\ntarget_has_atomic=\"ptr\"\ntarget_has_atomic_equal_alignment=\"16\"\ntarget_has_atomic_equal_alignment=\"32\"\ntarget_has_atomic_equal_alignment=\"64\"\ntarget_has_atomic_equal_alignment=\"8\"\ntarget_has_atomic_equal_alignment=\"ptr\"\ntarget_has_atomic_load_store\ntarget_has_atomic_load_store=\"16\"\ntarget_has_atomic_load_store=\"32\"\ntarget_has_atomic_load_store=\"64\"\ntarget_has_atomic_load_store=\"8\"\ntarget_has_atomic_load_store=\"ptr\"\ntarget_os=\"windows\"\ntarget_pointer_width=\"32\"\ntarget_vendor=\"pc\"\nwindows\n","stderr":""},"4614504638168534921":{"success":true,"status":"","code":0,"stdout":"rustc 1.70.0-nightly (9df3a39fb 2023-04-11)\nbinary: rustc\ncommit-hash: 9df3a39fb30575d808e70800f9fad5362aac57a2\ncommit-date: 2023-04-11\nhost: x86_64-pc-windows-msvc\nrelease: 1.70.0-nightly\nLLVM version: 16.0.2\n","stderr":""},"1185988223601034215":{"success":true,"status":"","code":0,"stdout":"___.exe\nlib___.rlib\n___.dll\n___.dll\n___.lib\n___.dll\nC:\\Users\\Earthnuker\\scoop\\persist\\rustup-msvc\\.rustup\\toolchains\\nightly-x86_64-pc-windows-msvc\npacked\n___\ndebug_assertions\nfeature=\"cargo-clippy\"\npanic=\"unwind\"\nproc_macro\ntarget_abi=\"\"\ntarget_arch=\"x86_64\"\ntarget_endian=\"little\"\ntarget_env=\"msvc\"\ntarget_family=\"windows\"\ntarget_feature=\"fxsr\"\ntarget_feature=\"sse\"\ntarget_feature=\"sse2\"\ntarget_has_atomic\ntarget_has_atomic=\"16\"\ntarget_has_atomic=\"32\"\ntarget_has_atomic=\"64\"\ntarget_has_atomic=\"8\"\ntarget_has_atomic=\"ptr\"\ntarget_has_atomic_equal_alignment=\"16\"\ntarget_has_atomic_equal_alignment=\"32\"\ntarget_has_atomic_equal_alignment=\"64\"\ntarget_has_atomic_equal_alignment=\"8\"\ntarget_has_atomic_equal_alignment=\"ptr\"\ntarget_has_atomic_load_store\ntarget_has_atomic_load_store=\"16\"\ntarget_has_atomic_load_store=\"32\"\ntarget_has_atomic_load_store=\"64\"\ntarget_has_atomic_load_store=\"8\"\ntarget_has_atomic_load_store=\"ptr\"\ntarget_os=\"windows\"\ntarget_pointer_width=\"64\"\ntarget_thread_local\ntarget_vendor=\"pc\"\nwindows\n","stderr":""}},"successes":{}}

View file

@ -0,0 +1,3 @@
Signature: 8a477f597d28d172789f06886806bc55
# This file is a cache directory tag created by cargo.
# For information about cache directory tags see https://bford.info/cachedir/

View file

@ -0,0 +1,3 @@
Signature: 8a477f597d28d172789f06886806bc55
# This file is a cache directory tag created by cargo.
# For information about cache directory tags see https://bford.info/cachedir/