Doug’s Projects

Source code for some of these projects is available at https://bitbucket.org/Douggem just log in with the credentials given in the resume!

ObRegisterCallbacks research

I reversedObRegisterCallbacks and the structures it creates in order to research a solution to Anti-cheat kernel modules protecting game processes.  You can see the results here:

https://douggemhax.wordpress.com/2015/05/27/obregistercallbacks-and-countermeasures/

SKiDE

SKiDE is a script development tool targeted at Arma 3.  It has common IDE features such as syntax highlighting and auto completion, but it also has some Arma specific features like enumeration of script threads and script variables.  It will also, if connected to the game, show the value of variables in a script if the user hovers the mouse over the variable.  SKiDE is still early in development but is already complete enough to be a very strong tool for script development as it offers the user a real-time glimpse into the execution of their scripts.

Peacekeeper Drone

Peacekeeper is an application that acts as a radar for Real Virtuality games.  It reads memory from the target game, finds and parses data structures in the game’s memory, and presents it to the user in the form of a map.  It also allows a small degree of object manipulation, such as changing the user’s weapon properties.  For example, the user can increase the damage his weapon does, eliminate the weapon’s recoil, or make his handgun shoot missiles.

Source available at https://bitbucket.org/Douggem/peacekeeper by logging in with the credentials included in the resume.

Continue reading

Fallout76 server anti-cheat check strings

These are dumped from the binary that launched with the game.  They are unreferenced in the client and appear to be optimized out of later versions, but they may give some insight into the checks the FO76 server does on client data.

At least *some* of these checks were not implemented or not enforced in early versions of the game, as I was able to teleport with impunity up through early this year when I quit playing the game.  But it appears that they at least had a framework available to do these checks.  When I first started hacking FO76 I was actually impressed by the client/server architecture, as I was unable to do things like edit inventory, stamina, health, etc. as the server would send an entity snapshot and reset my changes.  Editing things like storage space in those crates you can put down appeared to work on the client, but the server would reject putting things into the stash that was full even though my client believed it was not full.

The only interesting things I was able to do before I quit trying were teleport/flying, stupid aimbot, infinite ammo, and adjusting the size of the magazine loaded into the current weapon.  The magazine thing was most useful, as I could fire things like the double barrel shotgun without having to reload, but it only worked some of the time.  I’m guessing that there’s peer to peer functionality in the game and the handling of NPCs is offloaded to clients, so if I was in an area by myself the infinite ammo would work, but if another PC was nearby it sometimes wouldn’t, I think because their client became authoritative over the area.

The strings appear to be in the form of:

Check name
Check class name or something
Check description
Check relevant values

InvalidHitNoSourcePlayer
hit.inv.no-source-player
Invalid: Source player does not exist

InvalidHitInvalidMergedHits
hit.inv.invalid-merged-hits
Invalid: Invalid merged hit requests. Unable to unmerge, ignored

InvalidHitTemporary
hit.inv.temporary-hit
Invalid: Hit data is temporary and can’t request damaging hits from the server

InvalidHitInvalidated
hit.inv.invalidated
Invalid: Hit data was invalidated

InvalidHitNoTarget
hit.inv.no-target
Invalid: Target Actor does not exist on the host

InvalidHitBatchMergeCountTooHigh
hitbatch.inv.merge-count-too-high
Invalid: Trying to unmerge hitbatch. Merged hit count too high
count
max-count

InvalidHitBatchNotEnoughCountsPerHit
hitbatch.inv.invalid-count-size
Invalid: Trying to unmerge hitbatch. Number of hits doesn’t match the number of merged counts
hit-size
merged-size

InvalidHitBatchNotEnoughUniquePerMerged
hitbatch.inv.invalid-unique-data-size
Invalid: Trying to unmerge hitbatch. Number of merged hits doesn’t match the amount of unique data
merged-hit-count
unique-data-size

InvalidFriendlyHitNoTarget
friendlyhit.inv.no-target
Invalid: No target

InvalidFriendlyHitNoPerk
friendlyhit.inv.no-perk
Invalid: Client doesn’t have, Apply Friendly Hit, Perk

InvalidFriendlyHitTargetNotSameTeam
friendlyhit.inv.target-not-same-team
Invalid: Target is not in the same team as the client

InvalidFriendlyHitFireRate
friendlyhit.inv.fire-rate-too-high
Invalid: Client is requesting faster than fire rate
elapsedtime-last-shot
weapon-id

SuspectFriendlyHitFireRate
friendlyhit.sus.fire-rate-too-high
Suspect: Client is requesting faster than fire rate
elapsedtime-last-shot
weapon-id

InvalidHitEventNoTarget
hitevent.inv.no-target
Invalid: Requested target doesn’t exist or is an Actor
target-id

InvalidHitEventInvalidSource
hitevent.inv.invalid-source
Invalid: Requested source parameter is not a valid event source
source-id

InvalidHitEventNoProjectile
hitevent.inv.invalid-projectile
Invalid: Requested projectile parameter is not a projectile
projectile-id

InvalidHavokImpulseNoTarget
havok.inv.no-target
Invalid: Requested target doesn’t exist or isn’t a REFR

InvalidHavokImpulseInvalidProjectile
havok.inv.no-projectile
Invalid: Requested projectile doesn’t exist or isn’t a BGSProjectile

InvalidHavokImpulseImpulseVector
havok.inv.bad-vector
Invalid: Impulse direction not a unit vector

InvalidHavokImpulseBadProjectile
havok.inv.bad-projectile
Invalid: Client’s current weapon doesn’t shot projectiles that were part of an impulse request

InvalidHavokImpulseInvalidated
havok.inv.invalidated
Invalid: Validation failed for havok impulse. Impulse ignored

InvalidHavokImpulseFireRate
havok.inv.fire-rate-too-high
Invalid: Client is requesting faster than fire rate
elapsedtime-last-shot
weapon-id

SuspectHavokImpulseFireRate
havok.sus.fire-rate-too-high
Suspect: Client is requesting faster than fire rate
elapsedtime-last-shot
weapon-id

InvalidDamageDestructInvalid
dmgdestructable.inv.invalid
Invalid: Request invalid, ignored

InvalidDamageDestructNoTarget
dmgdestructable.inv.no-target
Invalid: Requested target doesn’t exist or is an Actor
target-id

InvalidDamageDestructNoForm
dmgdestructable.inv.no-destructable
Invalid: Requested target doesn’t contain a BGS destructible object form
target-id

InvalidDamageDestructIgnorePlayerDamage
dmgdestructable.inv.ignore-damage
Invalid: Requested target is ignoring player damage
target-id

InvalidDamageDestructCantBeDamaged
dmgdestructable.inv.cant-be-damaged
Invalid: Requested target can’t be damaged
target-id

InvalidDamageDestructFireRate
dmgdestructable.inv.fire-rate-too-high
Invalid: Client is requesting faster than fire rate
elapsedtime-last-shot
weapon-id

SuspectDamageDestructFireRate
dmgdestructable.sus.fire-rate-too-high
Suspect: Client is requesting faster than fire rate
elapsedtime-last-shot
weapon-id

InvalidAmmoRequirements
ammo.inv.not-using-ammo
Invalid: Client is requesting shot damage but isn’t using ammo
weapon-id
suspect-count
god-mode

SuspectAmmoRequirments
ammo.sus.not-using-ammo
Suspect: Client is requesting shot damage but isn’t using ammo
weapon-id
suspect-count
god-mode

InvalidWeaponMissing
weapon.inv.missing
Invalid: Client fired weapon is not in client’s inventory
weapon-id

SuspectWeaponMissingThrown
weapon.sus.missing-thrown
Suspect: Client requesting a thrown weapon but doesn’t have it equipped
weapon-id

SuspectWeaponNotCurrent
weapon.sus.not-current
Suspect: Client fired weapon is not the client’s current weapon
weapon-id
current-weapon-id

SuspectWeaponDuringUnarmed
weapon.sus.unarmed
Suspect: Client requested damage with a weapon but is currently doing an unarmed attack
current-weapon-id

InvalidFireIdDifferentWeapons
fireid.inv.different-weapons
Invalid: Client is shooting different weapons with the same FireID
weapon-id
weapon-id

InvalidFireIdSuspectInfractionsTooHigh
fireid.inv.infraction-count-too-high
Invalid: Client suspect infractions too high
suspect-count

SuspectFireIdTooManyShotsFired
fireid.sus.shot-count-too-high
Suspect: Client is shooting more projectiles per shot than possible
hit-count
max-hit-count

InvalidVATSHit
vathit.inv.vats-not-activated
Invalid: Client requesting a VATS hit while outside of VATS
elapsed-time-deactivated-vats
max-elapsed-time

SuspectVATSHit
vathit.sus.vats-not-activated
Suspect: Client requesting a VATS hit while outside of VATS
elapsed-time-deactivated-vats
max-elapsed-time

InvalidVATSCriticalHitWithoutFlags
vatcritical.inv.invalid-flags
Invalid: Client requesting critical VATS attack without VATS or Critical flag

InvalidVATSCriticalHitWithoutActivation
vatcritical.inv.critical-not-activated
Invalid: Client requesting critical VATS attack without having activated VATS Critical recently
elapsed-time-activated-critical-vats
max-elapsed-time

SuspectVATSCriticalHitWithoutActivation
vatcritical.sus.critical-not-activated
Suspect: Client requesting critical VATS attack without having activated VATS Critical recently
elapsed-time-activated-critical-vats
max-elapsed-time

InvalidVATSCriticalActivation
vatcritical.inv.no-charge
Invalid: VATS Critical activation requested without enough charge or banked vats
vats-charge
vats-banked

InvalidCreateProjectileAmmoCount
createprojectile.inv.no-ammo
Invalid: Client is requesting to create a projectile with no ammo
weapon-id

InvalidCreateProjectilePosition
createprojectile.inv.position
Invalid: Client trying to create a projectile too far away from the actor
sqrd-distance
weapon-id

InvalidCreateProjectileNoShooter
createprojectile.inv.no-shooter
Invalid: Shooter creating server authoritative projectile doesn’t exist
shooter-id

InvalidCreateProjectileNoWeapon
createprojectile.inv.no-weapon
Invalid: Requested weapon for creating server authoritative projectile doesn’t exist

InvalidCreateProjectileFireRate
createprojectile.inv.fire-rate-too-high
Invalid: Client is requesting faster than fire rate
elapsedtime-last-shot
weapon-id

SuspectCreateProjectileFireRate
createprojectile.sus.fire-rate-too-high
Suspect: Client is requesting faster than fire rate
elapsedtime-last-shot
weapon-id

SuspectCreateProjectilePosition
createprojectile.sus.position
Suspect: Client trying to create a projectile too far away from the actor
sqrd-distance
weapon-id

InvalidHitDataType
hitdata.inv.type
Invalid: HitData initialization type is invalid
type

InvalidVATSHitChanceTooHigh
vathitchance.inv.chance-too-high
Invalid: Client is requesting to create a projectile with a VATS hit chance that is higher than possible
hit-chance
max-hit-chance

InvalidVATSHitChanceNotInVATS
vathitchance.inv.player-not-in-vats
Invalid: Client is requesting to create a projectile with a VATS hit chance but is not currently in VATS

SuspectVATSHitChanceTooHigh
vathitchance.sus.chance-too-high
Suspect: Client is requesting to create a projectile with a VATS hit chance that is higher than possible
hit-chance
max-hit-chance

InvalidTargetCell
cell.inv.target-cell
Invalid: Client is requesting hit on a target that isn’t spatially relevant

SuspectTargetCell
cell.sus.target-cell
Suspect: Client is requesting hit on a target that isn’t spatially relevant

InvalidMeleeAttackPosition
melee.inv.attack-position
Invalid: Client’s melee attack position is too far from server’s player actor replicant
distance

InvalidMeleeHit
melee.inv.melee-hit
Invalid: Melee request contains a target, but it could not pass the validation, flag it as invalid, it’s probably from cheater

SuspectMeleeHit
melee.sus.melee-hit
Suspect: Attacking something without AIProcess or out of rewind scope, it might happen with high ping

InvalidLimbDamageTarget
limb.inv.limb-target
Invalid: The Client is requesting to damage a limb the target doesn’t have
target-id
limb

SuspectLimbDamageMaybeFake
limb.sus.maybe-fake
Suspect: The Client is VERY likely faking the limb it damaged
hit-precentage
limb

SuspectLimbDamageVeryPossiblyFake
limb.sus.possibly-fake
Suspect: The Client MIGHT be faking the limb it damaged
hit-precentage
limb

InvalidAimbotDetected
aimbot.inv.detected
Invalid: The Client is very likely using an aim bot
detect-precent
max-precent

SuspectAimbotDetected
aimbot.sus.detected
Suspect: The Client is very likely using an aim bot
detect-precent
max-precent

InvalidKeycodeNoTarget
keycode.inv.no-target
Invalid: No target
keypad-id

InvalidKeycodeSpam
keycode.inv.spam
Invalid: Spam Trap
time-ms
min-time-ms

InvalidKeycodeNotaObjectRefr
keycode.inv.target-no-obj-refr
Invalid: Not a keypad. Target has no object reference
keypad-id

InvalidKeycodeNotFurniture
keycode.inv.target-not-furniture
Invalid: Not a keypad. Target is not furniture
keypad-id

InvalidKeycodeNotaKeypad
keycode.inv.target-not-keypad
Invalid: Not a keypad
keypad-id

InvalidKeycodeDistance
keycode.inv.distance-too-high
Invalid: Distance between player and keypad too high
distance
max-distance

InvalidKeycodeInteraction
keycode.inv.interaction-failed
Invalid: Failed interaction check

InvalidLockPickingNoTarget
lockpick.inv.no-target
Invalid: No target

InvalidLockPickingSpam
lockpick.inv.spam
Invalid: Spam trap
retry-time-ms

InvalidLockPickingTargetNotALock
lockpick.inv.target-not-a-lock
Invalid: Target not a lock.
lock-id

InvalidLockPickingTargetNotLocked
lockpick.inv.target-not-locked
Invalid: Target no locked
lock-id

InvalidLockPickingTargetDestroyed
lockpick.inv.target-destroyed
Invalid: Target is destroyed
lock-id

InvalidLockPickingTargetLockBroken
lockpick.inv.target-broken
Invalid: Target is broken
lock-id

InvalidLockPickingNoPicksOrKey
lockpick.inv.no-picks-or-key
Invalid: Player has no picks or keys

InvalidLockPickingInvalidSkill
lockpick.inv.skill-too-low
Invalid: Player’s skill is too low
lock-level
player-level

InvalidLockPickingDistance
lockpick.inv.distance-too-high
Invalid: Distance between player and lock too high
distance
max-distance

InvalidHolotape
holotape.inv.not-a-holotape
Invalid: Player tried to play holotape with invalid
form-id

InvalidHolotapeMissing
holotape.inv.missing-holotape
Invalid: Player tried to play holotape they do not have or are not in a terminal
form-id

InvalidHolotapeNotLoaded
holotape.inv.not-loaded
Invalid: Failed to find loaded holotape
form-id

InvalidTradeBeginSpam
trade.begin.inv.spam
Invalid: Spam trap
retry-time-ms

InvalidTradeCreatePlayerNotTrading
trade.create.inv.player-not-trading
Invalid: Player is not trading with buyer
buyer-id

InvalidTradeCreateTargetNotTrading
trade.create.inv.vendor-not-trading
Invalid: Vendor is not trading with player
buyer-id

InvalidTradeCreateInvalidItem
trade.create.inv.invalid-item
Invalid: Invalid item handle
item-id

InvalidTradeCreateOwnerlessItem
trade.create.inv.ownerless-item
Invalid: Ownerless item
item-id
item-server-id

InvalidTradeCreateUnexpectedOwner
trade.create.inv.unexpected-owner
Invalid: Unexpected owner
owner-id
item-id

InvalidTradeAcceptPlayerNotTrading
trade.accept.inv.player-not-trading
Invalid: Player is not trading with vendor
buyer-id

InvalidTradeAcceptVendorNotTrading
trade.accept.inv.vendor-not-trading
Invalid: Vendor is not trading with player
buyer-id

InvalidTradeAcceptOfferDoesntMatch
trade.accept.inv.offer-no-match
Invalid: Offer does not match

InvalidTradeAcceptInvalidItem
trade.accept.inv.invalid-item
Invalid: Invalid item handle
item-id

InvalidTradeAcceptOwnerlessItem
trade.accept.inv.ownerless-item
Invalid: Ownerless item
item-id
item-server-id

InvalidTradeAcceptUnexpectedOwner
trade.accept.inv.unexpected-owner
Invalid: Unexpected owner
owner-id
item-id

InvalidTradeRejectPlayerNotTrading
trade.reject.inv.player-not-trading
Invalid: Player is not trading with vendor
buyer-id

InvalidTradeRejectVendorNotTrading
trade.reject.inv.vendor-not-trading
Invalid: Vendor is not trading with player
buyer-id

InvalidTradeRejectInvalidItem
trade.reject.inv.invalid-item
Invalid: Invalid item handle
item-id

InvalidTradeRejectOwnerlessItem
trade.reject.inv.ownerless-item
Invalid: Ownerless item
item-id
item-server-id

InvalidTradeRejectUnexpectedOwner
trade.reject.inv.unexpected-owner
Invalid: Unexpected owner
owner-id
item-id

InvalidTradeDeletePlayerNotTrading
trade.delete.inv.player-not-trading
Invalid: Player is not trading with vendor
buyer-id

InvalidTradeDeleteVendorNotTrading
trade.delete.inv.vendor-not-trading
Invalid: Vendor is not trading with player
buyer-id

InvalidTradeDeleteOfferDoesntMatch
trade.delete.inv.offer-no-match
Invalid: Offer does not match

InvalidTradeDeleteInvalidItem
trade.delete.inv.invalid-item
Invalid: Invalid item handle
item-id

InvalidTradeDeleteOwnerlessItem
trade.delete.inv.ownerless-item
Invalid: Ownerless item
item-id
item-server-id

InvalidTradeDeleteUnexpectedOwner
trade.delete.inv.unexpected-owner
Invalid: Unexpected owner
owner-id
item-id

InvalidTradeItemRequestPlayerNotTrading
trade.item.request.inv.player-not-trading
Invalid: Player is not trading with buyer
buyer-id

InvalidTradeItemRequestVendorNotTrading
trade.item.request.inv.vendor-not-trading
Invalid: Vendor is not trading with player
buyer-id

InvalidTradeItemRequestInvalidItem
trade.item.request.inv.invalid-item
Invalid: Invalid item handle
item-id

InvalidTradeItemRequestOwnerlessItem
trade.item.request.inv.ownerless-item
Invalid: Ownerless item
item-id
item-server-id

InvalidTradeItemRequestUnexpectedOwner
trade.item.request.inv.unexpected-owner
Invalid: Unexpected owner
owner-id
item-id

InvalidNetworkPathSpam
networkpath.inv.spam
Invalid: Spam trap
retry-time-ms

InvalidNetworkPathDifferentSpace
networkpath.inv.goal-different-space
Invalid: Target and player in different spaces

InvalidNetworkPathBadCellOrWorld
networkpath.inv.invalid-cell-or-world
Invalid: Invalid cellid and/or worldid

InvalidNetworkPathDistanceTooHigh
networkpath.inv.distance-too-high
Invalid: Attempting to pathfind distance too high
distance
max-distance

InvalidQuestActionToggleInvalid
quest.inv.toggled-quest-not-on
Invalid: Player tried to toggle tracking a quest they are not on
quest-id

InvalidTargetingMapNukeRegion
targetingmap.inv.non-nuke-region
Invalid: Player tried to launch a nuke at a non-nukable region (already tested bounds on client)

InvalidWordGuessSpam
wordguess.inv.spam
Invalid: Spam trap
retry-time-ms

InvalidWordGuessPlayerNotHacking
wordguess.inv.player-not-hacking
Invalid: Player isn’t playing the hacking game

InvalidWordGuessInvalid
wordguess.inv.guess-invalid
Invalid: Invalid guess
guess-index

InvalidBonusGuessSpam
bonusguess.inv.spam
Invalid: Spam trap
retry-time-ms

InvalidBonusGuessPlayerNotHacking
bonusguess.inv.player-not-hacking
Invalid: Player isn’t playing the hacking game

InvalidBonusGuessInvalid
bonusguess.inv.guess-invalid
Invalid: Invalid bonus guess
guess-index

InvalidBonusGuessAlreadyUsed
bonusguess.inv.already-used
Invalid: Bonus guess already used
guess-index

InvalidTerminalRefr
terminal.inv.no-refr
Invalid: Missing terminal ref

InvalidTerminalForm
terminal.inv.no-form
Invalid: Missing terminal form

InvalidTerminalInteractionDistance
terminal.inv.distance
Invalid: Attempted to access terminal out of range
distance
max-distance

InvalidTerminalInteractionLocked
terminal.inv.locked
Invalid: Attempted to access data of a locked terminal
terminal-id

InvalidTerminalInteractionSubMenu
terminal.inv.invalid-sub-menuInvalid: Attempted to access invalid sub menu
menu-id
terminal-id

InvalidVendorPurchaseNoItem
vendor.inv.zero-item-count
Invalid: Zero item count
item-id

InvalidVendorPurchaseNoItemValue
vendor.inv.zero-item-value
Invalid: Zero item value
item-id

InvalidVendorPurchaseNotEnoughCaps
vendor.inv.not-enough-caps
Invalid: Requested purchase without enough caps
cost
balance

InvalidBarterContainerClose
barter.inv.barter-clear-not-using
Invalid: Attempted to clear a barter container this player was not using

InvalidWorkshopReferenceNotFound
workshop.inv.reference-not-found
Invalid: Reference not found
item-form-id
message-name

InvalidWorkshopReservation
workshop.inv.reference-not-reserved
Invalid: Reference was not reserved
workshop-form-id
item-form-id
message-name

InvalidWorkshopOutOfBuildArea
workshop.inv.out-of-build-area
Invalid: The position is not within the buid area
workshop-form-id
item-form-id
message-name

InvalidWorkshopActivationDistance
workshop.inv.activation-distance
Invalid: The activation distance is bigger than expected
distance
allowed-distance
message-name

InvalidWorkshopReference
workshop.inv.not-workshop-item
Invalid: The reference is not a workshop item
item-form-id
message-name

InvalidWorkshopOwner
workshop.inv.not-owner
Invalid: The player is not the owner
item-form-id
message-name

InvalidWorkshopBlueprintNotFound
workshop.inv.blueprint-not-found
Invalid: The blueprint was not found
workshop-form-id
message-name

InvalidWorkshopRecipeNotFound
workshop.inv.recipe-not-found
Invalid: The recipe was not found
workshop-form-id
recipe-id
message-name

InvalidWorkshopContestDistance
workshop.inv.contest-distance
Invalid: The contest distance is bigger than expected
distance
allowed-distance

InvalidWorkshopContestRef
workshop.inv.contested-workshop-not-found
Invalid: The contested workshop wasn’t found
workshop-form-id

InvalidWorkshopPlayerTooFarToActivate
workshop.inv.player-too-far-activation
Invalid: The player is too far from the workshop to activate it
workshop-form-id

InvalidWorkshopReserveDistanceTooFar
workshop.inv.reserve-distance
Invalid: The reservation distance is bigger than expected
workshop-form-id

InvalidEquipItemInvalid
equip.inv.item-invalid
Invalid: Invalid item handle
handle-id

InvalidEquipOwnerlessItem
equip.inv.ownerless-item
Invalid: Ownerless item
item-id
handle-id

InvalidEquipUnexpectedOwner
equip.inv.unexpected-owner
Invalid: Unexpected owner of item
owner-id
item-id

InvalidUnequipItemInvalid
unequip.inv.item-invalid
Invalid: Invalid item handle
handle-id

InvalidUnequipOwnerlessItem
unequip.inv.ownerless-item
Invalid: Ownerless item
item-id
handle-id

InvalidUnequipUnexpectedOwner
unequip.inv.unexpected-owner
Invalid: Unexpected owner of item
owner-id
item-id

InvalidDropItemInvalid
dropitem.inv.item-invalid
Invalid: Invalid item handle
handle-id

InvalidDropItemOwnerlessItem
dropitem.inv.ownerless-item
Invalid: Ownerless item
item-id
handle-id

InvalidDropItemUnexpectedOwner
dropitem.inv.unexpected-owner
Invalid: Unexpected owner of item
owner-id
item-id

InvalidDropItemUndroppable
dropitem.inv.undroppable
Invalid: Specified undroppable item
item-id
handle-id

InvalidTransferItemInvalidSource
transferitem.inv.no-source
Invalid: Invalid source when transferring
source-id
item-handle-id

InvalidTransferItemInvalidDestination
transferitem.inv.no-destination
Invalid: Invalid dest containers when transferring
dest-id
item-handle-id

InvalidTransferItemDistance
transferitem.invalid.distance-too-high
Invalid: Attempted to access container out of range
distance
max-distance

InvalidTransferItemInvalidSourcePowerArmorOwner
transferitem.invalid.unexpected-source-pa-owner
Invalid:Invalid source power armor owner

InvalidTransferItemInvalidDestPowerArmorOwner
transferitem.invalid.unexpected-dest-pa-owner
Invalid:Invalid dest power armor owner

InvalidTransferItemRequestInventoryItemFail
transferitem.invalid.request-inv-item-fail
Invalid:Request inventory item fail
item-handle-id

InvalidTransferItemCanBeTransferredFail
transferitem.invalid.can-be-transferred-fail
Invalid:Item can be transferred fail
item-handle-id

InvalidTransferInvalidItemTypeForContainer
transferitem.invalid.wrong-item-type
Invalid:Wrong item type for container

InvalidTransferInvalidDestContainer
transferitem.invalid.bad-dest-container
Invalid:bad destination container

InvalidTransferInvalidItem
transferitem.invalid.bad-item
Invalid:bad item

InvalidTransferWrongContainerFormId
transferitem.invalid.bad-container-formid
Invalid:wrong formid on container
form-id

InvalidTransferExceedsContainerMaxWeight
transferitem.invalid.exceeds-weight-limit
Invalid:item would cause container to exceed its weight limit
message

InvalidTransferSharedQuestItem
transferitem.invalid.shared-quest-item
Invalid:attempting to remove/transfer a shared quest item
item-formid
dst-formid

InvalidScrapItemInvalid
scrapitem.inv.item-invalid
Invalid: Invalid item handle
item-id

InvalidScrapItemOwnerlessItem
scrapitem.inv.ownerless-item
Invalid: Ownerless item
item-id

InvalidScrapItemUnexpectedOwner
scrapitem.inv.unexpected-owner
Invalid: Unexpected owner of item
owner-id
item-id

InvalidScrapDistance
scrapitem.inv.distance-too-high
Invalid: Attempted to access furniture out of range
distance
max-distance

InvalidScrapItemNotFurniture
scrapitem.inv.not-furniture
Invalid: Refr isn’t valid furniture
workbench-id

InvalidScrapItemScrapNotAllowed
scrapitem.inv.scrap-not-allowed
Invalid: Furniture doesn’t allow scrapping
furniture-id

InvalidScrapCantScrapItem
scrapitem.inv.cant-scrap-item
Invalid: Cannot scrap item handle
item-id

InvalidScrapCountTooHigh
scrapitem.inv.scrap-count-too-high
Invalid: Request to scrap item count higher than possible
count
item-id

InvalidEmote
emote.inv.invalid-emote
Invalid: Invalid emote
emote-id

InvalidEmoteMod
emote.inv.invalid-emote-mod
Invalid: Invalid EmoteMod
emote-mod

InvalidEmoteLocked
emote.inv.emote-locked
Invalid: Invalid Requested locked emote
emote-id

SuspectPerkCardChangeListTooLarge
perkcardchange.sus.list-too-large
Suspect: m_perkCardList suspiciously large
size

SuspectPerkCardChangeScrapTooLarge
perkcardchange.sus.scrap-too-large
Suspect: m_perkCardsToScrap suspiciously large at
size

SuspectPerkCardChangeCombineTooLarge
perkcardchange.sus.combine-too-large
Suspect: m_perkCardsToCombine suspiciously large at
size

InvalidConstructObjectNoWorkbench
contructobject.inv.no-workbench
Invalid: Trying to craft without being at a workbench

InvalidConstructObjectDistance
contructobject.inv.distance-too-far
Invalid: Trying to craft too far away from workbench
distance
max-distance

InvalidConstructObjectInvalidFormId
contructobject.inv.bad-form-id
Invalid: Invalid form ID
form-id

InvalidConstructObjectEligibleLevel
contructobject.inv.bad-eligible-level
Invalid: Ineligible item instance level
item-level

InvalidConstructObjectPlayerLevel
contructobject.inv.player-level-too-low
Invalid: Character level is too low to craft item instance level
player-level
item-level

InvalidConstructObjectItemInvalid
contructobject.inv.item-invalid
Invalid: Invalid item handle
item-id

InvalidConstructObjectItemOwnerlessItem
contructobject.inv.ownerless-item
Invalid: Ownerless item
item-id

InvalidConstructObjectItemUnexpectedOwner
contructobject.inv.unexpected-owner
Invalid: Unexpected owner of item
owner-id
item-id

InvalidInventorySyncDistance
inventorysync.inv.distance
Invalid: Requested access to a container too far away from the player
distance
max-distance

InvalidInventorySyncUnauthorized
inventorysync.inv.unauthorized-access
Invalid: Requested unauthorized access to Workshop container
container-id

InvalidInventorySyncLocked
inventorysync.inv.locked-container
Invalid: Requested access to a locked container
container-id

InvalidPathGuideEffectSpam
pathguideeffect.inv.spam
Invalid: Spam trap
retry-time-ms

InvalidPathGuideEffectNoPerk
pathguideeffect.inv.no-perk
Invalid: Attempt to use vans, but doesn’t have perk

InvalidPlayerCheating
playercheating.inv.player-cheating
Invalid: Player detected cheating

InvalidAvatarChangedNullForm
avatarchanged.inv.null-form
Invalid: Null Avatar form

InvalidAvatarChangedLockedAvatar
avatarchanged.inv.locked-avatar
Invalid: Attempting to equip a locked avatar
form-id

InvalidEntitlementMappingRefresh
entitlement.inv.mapping-refresh
Invalid: Requested illegal entitlement mappings refresh

InvalidPickupItemBadItem
pickupitem.invalid.invalid-item
Invalid: invalid pickup item
item-id

InvalidPickupItemDisabledItem
pickupitem.invalid.disabled-item
Invalid: item disabled

InvalidPickupItemDeletedItem
pickupitem.invalid.deleted-item
Invalid: item deleted

InvalidPickupItemDistance
pickupitem.invalid.distance-too-high
Invalid: pickup item distance fail
distance
max-distance

InvalidReconMarkDistance
reconmark.inv.distance-too-high
Invalid: Recon mark distance fail
distance
max-distance

InvalidReconMarkNoWeapon
reconmark.inv.no-weapon
Invalid: Current weapon not valid

InvalidReconMarkInvalidWeapon
reconmark.inv.invalid-weapon
Invalid: Current weapon does not have instance data
weapon-id

InvalidReconMarkNoScope
reconmark.inv.no-scope
Invalid: Current weapon does not have a scope

InvalidReconMarkNoEnchantments
reconmark.inv.no-enchantments
Invalid: Current weapon does not have enchantments (no recon enchantment)

InvalidReconMarkNoAttachment
reconmark.inv.no-recon-attachment
Invalid: Current weapon does not have recon enchantment

InvalidButtonPressBadMessageForm
buttonpress.invalid.bad-message-form
Invalid: messageform is null

InvalidButtonPressBadButtonIndex
buttonpress.invalid.bad-button-index
Invalid: Bad button index
button-index

InvalidButtonPressBadDefaultIndex
buttonpress.invalid.bad-default-indexInvalid: Bad default index
default-index

InvalidCRCCheckValue
crccheck.inv.incorrect-value
Invalid: Masterfile crc check value incorrect
value
master-value

InvalidResurrectMsgCantFastTravel
resurrect.inv.cant-fast-travel
Invalid: Player trying to respawn fast travel but can’t

InvalidResurrectMsgRespawnTimerNotUp
resurrect.inv.repawn-timer-not-up
Invalid: Respawn timer has not elapsed

InvalidResurrectMsgInvalidTravelMarker
resurrect.inv.travel-marker-invalid
Invalid: Player trying to respawn at invalid travel marker

InvalidResurrectMsgPlayerNotDead
resurrect.inv.player-not-dead
Invalid: Player trying to respawn but isn’t dead

InvalidResurrectMsgInvalidLocationType
resurrect.inv.invalid-location-type
Invalid: Player trying to respawn with bad location type

InvalidResurrectMsgInvalidState
resurrect.inv.invalid-resurrect-state
Invalid: Player trying to respawn with bad resurrect state

InvalidResurrectMsgMarkerNotDiscovered
resurrect.inv.marker-not-discovered
Invalid: Player trying to respawn at undiscovered marker

InvalidResurrectMsgScriptBlockFastTravel
resurrect.inv.script-blocking-fast-travel
Invalid: Player trying to respawn with fast travel, but script is currently blocking it

InvalidResurrectMsgFastTravelEncumbered
resurrect.inv.fast-travel-encumbered
Invalid: Player trying to respawn while encumbered. Not using encumbered fast travel

InvalidResurrectMsgCellNotEligible
resurrect.inv.cell-not-eligible
Invalid: Player trying to respawn with fast travel while in cell not eligible for fast travel

InvalidResurrectMsgCantAffordFastTravel
resurrect.inv.not-enough-caps
Invalid: Player trying to respawn with fast travel without enough caps

InvalidResurrectMsgFastTravelDisabled
resurrect.inv.fast-travel-disabled
Invalid: Player trying to respawn with fast travel while in restricted area

InvalidResurrectMsgInChargen
resurrect.inv.player-in-chargen
Invalid: Player trying to respawn with fast travel while in char gen

InvalidResurrectMsgUnownedWorkshop
resurrect.inv.unowned-workshop
Invalid: Player trying to respawn with fast travel to workshop they don’t own

InvalidQuickplayQuestNoQuickMatch
quickplay.inv.no-quick-match
Invalid: Player requesting to opt in to a quick play quest that doesn’t have a quickplay match
quest-id

InvalidQuickplayQuestNoQuickplayContext
quickplay.inv.no-quick-context
Invalid: Player requesting to opt in to a quick play quest that doesn’t have a quickplay context
quest-id

InvalidQuickplayQuestInvalidQuest
quickplay.inv.invalid-quest
Invalid: Player requesting to opt in to a quick play quest that doesn’t exist
quest-id

InvalidQuickplayQuestNotRunning
quickplay.inv.quest-not-running
Invalid: Player requesting to opt in to a quick play quest that isn’t running
quest-id

InvalidQuickplayQuestAlreadyInQP
quickplay.inv.already-in-quick-play
Invalid: Player requesting to opt in to a quick play quest but is already in a quickplay quest
quest-id

InvalidQuickplayQuestBadVisitorType
quickplay.inv.disallowed-visitor-type
Invalid: Player requesting to opt in to a quick play quest that doesn’t allow their visitor type
quest-id

InvalidQuickplayQuestCantOptJoin
quickplay.inv.cant-optionally-join
Invalid: Player requesting to opt in to a quick play quest that doesn’t allow the player to optionally join
quest-id

InvalidQuickplayQuestCantOptIn
quickplay.inv.cant-opt-in
Invalid: Player requesting to opt in to a quick play quest that the player can’t opt in
quest-id

InvalidActivateRefDistance
activateref.inv.distance-too-high
Invalid: Distance between player and ref
distance
max-distance

InvalidPlayerSpeedResolved
motionvalidation.speed.sus.detected
Invalid: Player failed to pass server side speed validation, but it’s something server could solve, it will teleport player back.

InvalidPlayerSpeedCantResolve
motionvalidation.speed.inv.detected
Invalid: Player failed to pass server side speed validation, but it’s something server couldn’t solve, it will disconnect player

InvalidPlayerCollisionResolved
motionvalidation.collision.sus.detected
Invalid: Player failed to pass server side collision validation, but it’s something server could solve, it will teleport player back.

InvalidPlayerCollisionCantResolve
motionvalidation.collision.inv.detected
Invalid: Player failed to pass server side collison validation, but it’s something server couldn’t solve, it will disconnect player

InvalidPlayerSupportResolved
motionvalidation.support.sus.detected
Invalid: Player failed to pass server side support validation, but it’s something server could solve, it will teleport player back.

InvalidPlayerSupportCantResolve
motionvalidation.support.inv.detected
Invalid: Player failed to pass server side support validation, but it’s something server couldn’t solve, it will disconnect player

InvalidPlayerJumpResolved
motionvalidation.jump.sus.detected
Invalid: Player failed to pass server side jump validation, but it’s something server could solve, it will teleport player back.

InvalidPlayerJumpCantResolve
motionvalidation.jump.inv.detected
Invalid: Player failed to pass server side jump validation, but it’s something server couldn’t solve, it will disconnect player

ClientReportedBadAppUsageIDA
antitamper.badapp.ida
Invalid: Player detected with IDA resident in memory by window name

ClientReportedBadAppUsageWinDbg
antitamper.badapp.windbg
Player detected with WinDbg resident in memory by window name

ClientReportedBadAppUsageX64dbg
antitamper.badapp.x64dbg
Player detected with X64dbg resident in memory by window name

ClientReportedBadAppUsageX96dbg
antitamper.badapp.x96dbg
Player detected with X96dbg resident in memory by window name

ClientReportedBadAppUsageCheatEngine
antitamper.badapp.cheatengine
Player detected with CheatEngine resident in memory by window name

ClientReportedBadAppUsageInteractiveDisassembler
antitamper.badapp.intereactivedisassembler
Player detected with InteractiveDisassembler resident in memory by window name

ClientReportedBadAppUsageMemoryHackingSoftware
antitamper.badapp.memoryhackingsoftware
Player detected with MemoryHackingSoftware resident in memory by window name

ClientReportedDebuggerAttachedIsDebuggerPresent
antitamper.debugger.isdebuggerpresent
Player detected with attached debugger (IsDebuggerPresent)

ClientReportedDebuggerAttachedCheckRemoteDebuggerPresent
antitamper.debugger.checkremotedebuggerpresent
Player detected with attached debugger (CheckRemoteDebuggerPresent)

ClientReportedDebuggerAttachedOutputDebugString
antitamper.debugger.outputdebugstring
Player detected with attached debugger (OutputDebugString)

ClientReportedBadAppUsageCheatEngineByProcessName
antitamper.badapp.cheatengine
Player detected with CheatEngine resident in memory by process name

ClientReportedBadAppUsageX64dbgByProcessName
antitamper.badapp.x64dbg
Player detected with X64dbg resident in memory by process name

ClientReportedBadAppUsageMemoryHackingSoftwareByProcessName
antitamper.badapp.memoryhackingsoftware
Player detected with MemoryHackingSoftware resident in memory by process name

ClientReportedBadAppUsageIDAByProcessName
antitamper.badapp.ida
Invalid: Player detected with IDA resident in memory by process name

ClientReportedBadAppUsageWinDbgByProcessName
antitamper.badapp.windbg
Player detected with WinDbg resident in memory by process name

ClientReportedBadDllUsageCheatEngineSpeedhack
antitamper.baddll.speedhack
Player detected with CheatEngine’s speedhack DLL resident in process memory

AlertPlayerTeleportAttemptMaxCountExceeded
requestteleport.alert.exceeds-max
Invalid:item would cause container to exceed its weight limit
count
max-count-allowed
message

How to get caught by Fallout’s anti-cheat

You probably aren’t hacking Fallout 76 because you probably aren’t playing it at all.  But if the new Battle Royale seems attractive to you here are a few easy and simple ways to get caught by their anti-cheat.

EnumWindowsTask

What it does:

Looks for windows with titles that contain ‘bad word’ substrings

How it does it:

download (1)

Each of the three primary anti-cheat scans are implemented as IOTasks, created, dispatched, and destructed with each iteration of the anti-cheat scanning loop.  The EnumWindowsTask is the first.  The scan is started by IOManager::ExecuteIOTask, which looks something like this:

download (2)

On line 12 you can see the indirect call to the EnumWindowsTask object that leads to the actual scan.  That indirect call lands in a function that calls EnumWindow with the scanning callback

download

The scanning callback gets the Window Text and converts it to ASCII from widechar.  Then, it does some string encoding.  Yeah encoding, not decoding.

download (3)

I know it looks weird, IDA didn’t quite realize that those long ass constants are actually pointers.

download (4)

So the usual suspects – Cheat Engine, IDA, and I think Spiro’s memory hacking thing.  Which is kind of weird to see in 2019 but whatever.

But you might be asking yourself, “Why are they encoding the strings they’re searching for?”.  Well, they’re encoding them so that they can turn around and *decode* them.

download (5)

Get used to this, it happens a lot in the Fallout76 anti-cheat.  It *appears* that they meant to store the offending string in an encoded form and decode it at runtime, but they’ve botched it.  Instead, the strings are stored in the clear, encoded, then decoded for comparison.

Each decoded string is passed through a case insensitive strstr against the title of the window that’s been passed by the EnumWindow function.  If there’s a match, a variable is set that is used later in the function to mark you as a dirty, filthy cheater.

Also, some of the strings are built on the stack instead of going through the encoding/decoding stuff.  So on top of the previous verboten window titles, you can also get flagged for “IDA – ” (the IDA window title if you’ve got a project active), WInDbg, x64dbg, and x96dbg (which is a launcher for x32dbg/x64dbg, I think).

download (6)

If you’re lucky enough to get detected by the galaxy brains at Bethesda who don’t realize people use debuggers and IDA as part of their jobs and might accidentally leave one open when launching Fallout, you’ll get marked as a cheater.  Each bad window title has its own value that’s added to the cheater flag.

download (7)

EnumToolsTask

What it does:

Looks for debuggers

How it does it:

EnumToolsTask is created and dispatched in the same way as the EnumWindowsTask.  The scanning function is mostly nothing out of the ordinary – it checks IsDebuggerPresent, CheckRemoteDebuggerPresent, and calls OutputDebugString and checks GetLastError.  If a debugger is attached the error code shouldn’t change during the call to OutputDebugString.  But if there’s no debugger, the error code will be changed. At least, according to this blog from 2008 https://www.veracode.com/blog/2008/12/anti-debugging-series-part-ii  It makes sense to me but I actually couldn’t get it to work in a test application, the error code didn’t change without a debugger attached.  So who knows, I’m a boomer that’s not good with computers so I probably botched it.

download (8)

EnumLibrariesTask

What it does:

Enumerates modules in the game process and executable names of other processes and checks their names against a whitelist and blacklist of substrings

How it does it:

EnumLibrariesTask is started the same way as the other two.  It starts off with a call to CreateToolhelp32Snapshot with flag TH32CS_SNAPPROCESS, so we have an idea of what it’s going to be doing.

download (9)

String Obfuscation

This particular scan makes heavy use of string obfuscation.  One of the first things you’ll encounter in the function is a bunch of strange values being dumped onto the stack

download (13)

Followed by a routine that manipulates them and then runs strstr on the manipulated value

download (14)

It’s obvious that the garbage values on the stack are actually encoded strings.  If you try to decode the first value, 0xEB79CC25 and XOR it with the key, 0x64, you’ll get junk instead of valid ASCII.  The Fallout devs cleverly only use the least significant byte of each 4 byte wide value as a character in the string.  So take the least significant byte of each of these values and XOR them against the key to get the decoded string.  In this case, “Cheat Engine.exe“.

download (15)

Yes, I once wrote a GUI tool to do XOR encoding/decoding.  I’m a Windoze user and I don’t know how to use the terminal.

Naughty Executables

I glossed over what the string is compared against.  The snapshot handle is passed into the function getProcessInfo which calls Process32First (don’t worry, there’s another function that calls Process32Next) and fills out a structure with process information, to include the .exe file name of the process’ main module.

download (16)

download (17)

The .exe file name is what the decoded string in the previous example is being compared against.  So FO76 enumerates processes and checks that none of them are Cheat Engine.  And if one of them IS…well you know what happens.

The next two obfuscated strings are “cheatengine-i386.exe” and “cheatengine-x86_64.exe“.  After our obfuscated strings,  we have a few strings in clear text – “x64dbg.exe“, “MHS.exe“, “IDA.exe“, and “windbg.exe“.  The same software that the window scan was trying to detect above.  If one of these executables is found to be running on your system, you get flagged.

download (18)

Whitelisted Libraries

Uh oh

download (19)

CreateToolhelp32Snapshot called with TH32CS_SNAPMODULE.  I think you all know what’s going to happen next.  At least, I *thought* I knew what was going to happen next – I expected FO76 to enumerate modules in the current process and flag for any blacklisted module.  And that’s what we encounter to begin with.  Obfuscated strings start us out here, as before.  The first encoded string is “speedhack-x86_64.dll“, a dll injected by Cheat Engine to achieve its speedhack functionality.

download (20)

The next obfuscated string is “speedhack-i386.dll“, the same thing but 32 bit.  If either of these modules is found in the FO76 process, you’re flagged as a cheater.

download (21)

But that’s the end of the DLL blacklist.  Only two entries?  Well, what we see next are names of modules that are probably not from cheating software.

download (22)

The modulepath is strstr’d against these plaintext strings – “ntdll“, “KERNEL32“, etc. and if it is NOT a match, FO76 keeps comparing against more and more benign modules.  As we fall through this search we see a few obfuscated strings –

download (23)

Here we see “vivoxsdk_x64“, which is a VOIP library for games or something.  As we go down (and we go down for a looooong time) we notice a trend – short strings are stored as literals in the module, VERY short strings are built on the stack, and longer strings are obfuscated.

When we get waaaaaaaaaaay down there to the last few module names, they fall into a new category – the string literal is passed into a function that encodes the string, and the result of that is passed into a function that both decodes the string and runs strstr against it.  What I think is happening is the optimizer has pretty much ruined Bethesda’s attempt at obfuscating the strings their anti-cheat uses and, for some reason, gave up toward the end of the function.

download (24)

 

If the module gets to the last module in the whitelist (of which there are over 200), “fraps64“, and isn’t a match…you’re marked as a dirty hecker.

download (25)

The path of the dirty module is stuffed into a std::string which is then put into a SuspiciousModuleLoadedMsg object and sent over to the good old boys at Bethesda.

download (27)

We don’t know, however, whether Bethesda bans for this.  Each detection vector sets a unique value in the ‘cheater’ variable that is eventually sent to Bethesda, so it’s possible they don’t ban for bad modules.  But if your module is named “WastedLandsHax42069.dll” or something that will stick out to whoever hopefully manually reviews your report, you’re probably in for a bad time.

Conclusion

To get caught by Bethesda’s anti-cheat, you need to:

  1. Use a debugger that doesn’t hide itself
  2. Use Cheat Engine, Jiro’s memory hacking software, IDA, or Windbg
  3. Use Cheat Engine’s speedhack functionality or have a detectable module running in the game process that doesn’t disguise itself as a whitelisted module

It’s worth noting that I’ve only looked at what stood out to me and what interacts with the set_cheat_found function, so it’s possible there’s some more leet anti-cheat stuff in there I haven’t come across.  But so far it looks like Bethesda isn’t doing a great job detecting cheaters in their newest game – which probably isn’t a big deal so far because not only is no one playing it, but the client-server architecture is pretty good and there’s not a ton of juicy hacky stuff you can do anyway.

But, if the Battle Royale mode catches on then that changes.  Suddenly teleporting and aimbotting become a problem and people are going to cheat more.  And if that happens, Todd Howard is going to need to step up the anti-cheat game if he wants to keep the game full of legitimate players that enjoy playing the game.

ObRegisterCallbacks and countermeasures

Because anti-virus softwares were hardhooking the Windows kernel to intercept WinAPI calls when processes were opening files and stuff, Microsoft implemented official and supported methods for receiving notifications for certain events and acting on them.  For instance, when a user launches an executable file, an anti-virus software can be alerted and can scan the file for malicious signatures before launching it.  Or, as is the case in the context of the research that led to this posting, an anti-cheat software can be alerted when a process is attempting to open a handle to the protected game and deny that handle privileges.

This is achieved by registering a callback function for a given event in the kernel using the function ObRegisterCallbacks.  You can see its MSDN article here https://msdn.microsoft.com/en-us/library/windows/hardware/ff558692%28v=vs.85%29.aspx

The short of it is, if you fill out some information regarding what you want to watch and what function you want called when the kernel needs to alert you and pass it to that function, your callback will be called at the appropriate time and you can take appropriate action.  The function returns a handle that you can later use to unregister the callback using ObUnRegisterCallbacks.  This is necessary if your driver unloads because if you left your callback in place and unloaded your driver the callback would probably cause a nice big bugcheck.

This is very annoying when anti-cheats such as Battleye and EasyAntiCheat leverage the callback system to lock down a process and prevent cheaters from reading or manipulating the process memory.  There already exist numerous ways  of countering the callbacks, such as implementing reading/writing functions in your own driver that don’t rely on the standard Windows method of reading from a file.  I wanted to do something a little cleaner, though, and hopefully enable non-modified software access to the protected process.

The Undocumented Structures

The callbacks are obviously stored in kernel objects and there MUST exist some way to either manipulate these objects or resolve a handle to pass to ObUnRegisterCallbacks.  Then we can just disable the callbacks and read and write to our heart’s content, right?  Well, sort of.  To begin with, I dug into ObRegisterCallbacks to figure out exactly what was going on.

The function begins by allocating some memory.

The first argument is a pointer to the OB_CALLBACK_REGISTRATION structure that describes how the callback is to be installed.  If the structure version starts with 0x100 and the registration count is greater than zero, it allocates a buffer the size of 0x20 + OperationCount * 64 + the altitude string length.  It’s very easy, at this point, to start getting some information on the object that is going to be used to store the callback information.  There will be a header of size 0x20 and a series of objects of size 64.  So already we can start figuring out our structures.

For the sake of brevity (and since I already put these structures into IDA) I’m going to go ahead and give you the structure definitions here:


typedef struct _CALLBACK_ENTRY_ITEM {
LIST_ENTRY EntryItemList;
OB_OPERATION Operations;
CALLBACK_ENTRY* CallbackEntry; // Points to the CALLBACK_ENTRY which we use for ObUnRegisterCallback
POBJECT_TYPE ObjectType;
POB_PRE_OPERATION_CALLBACK PreOperation;
POB_POST_OPERATION_CALLBACK PostOperation;
__int64 unk;
}CALLBACK_ENTRY_ITEM, *PCALLBACK_ENTRY_ITEM;

typedef struct _CALLBACK_ENTRY{
 __int16 Version;
 char buffer1[6];
 POB_OPERATION_REGISTRATION RegistrationContext;
 __int16 AltitudeLength1;
 __int16 AltitudeLength2;
 char buffer2[4];
 WCHAR* AltitudeString;
 CALLBACK_ENTRY_ITEM Items; // Is actually an array of CALLBACK_ENTRY_ITEMs that are also in a doubly linked list
}CALLBACK_ENTRY, *PCALLBACK_ENTRY;

Where the 0x20 length header is the beginning of the CALLBACK_ENTRY and the 64 size structure is the CALLBACK_ENTRY_ITEM.  While the CALLBACK_ENTRY_ITEM in the CALLBACK_ENTRY could be accessed as an array of CALLBACK_ENTRY_ITEMs because they’re right next to each other, the kernel doesn’t do this and walks through the linked list instead, and they are only next to each other out of convenience.

ObRegisterCallback then works on filling out these structs using data from the OB_CALLBACK_REGISTRATION object passed in the first argument.  Curiously, when filling out the CALLBACK_ENTRY_ITEMs, it obtains a pointer to the last QWORD of the entry and sets its members according to negative offsets from that point.

Once a CALLBACK_ENTRY_ITEM has been filled out, it’s passed to ObpInserCallbackByAltitude which is exactly what it sounds like.  If you’re not familiar with the Altitude, it’s just a numeric value indicating the order that callbacks should be called.  Lower numbers are called first, higher numbers are called last.  When you insert a callback, the callback is inserted into the linked list according to its altitude value.  If a callback with the same altitude is already in the list, the new callback is not inserted and instead ObpInsertCallbackByAltitude returns a value of STATUS_FLT_INSTANCE_ALTITUDE_COLLISION indicating a collision.  Given that Microsoft’s supported altitudes go up to 430,000 it is unlikely a collision would occur in the wild by chance.  https://msdn.microsoft.com/en-us/library/windows/hardware/ff549689%28v=vs.85%29.aspx

Finding Registered Callbacks

But where does it put the callback objects?  Well, it’s passed in ObpInsertCallbackByAltitude’s first parameter.  Sort of.  The first parameter is to an OBJECT_TYPE structure which is considered opaque by Microsoft but, unlike our other structures in this post, can be dumped with dt in kd.  Its definition is:

typedef struct _OBJECT_TYPE {
 LIST_ENTRY TypeList;
 UNICODE_STRING Name;
 VOID* DefaultObject; 
 UCHAR Index;
 unsigned __int32 TotalNumberOfObjects;
 unsigned __int32 TotalNumberOfHandles;
 unsigned __int32 HighWaterNumberOfObjects;
 unsigned __int32 HighWaterNumberOfHandles;
 OBJECT_TYPE_INITIALIZER TypeInfo;
 EX_PUSH_LOCK TypeLock;
 unsigned __int32 Key;
 LIST_ENTRY CallbackList; // A linked list of CALLBACK_ENTRY_ITEMs, which is what we want!
}OBJECT_TYPE, *POBJECT_TYPE;

These objects are global variables, such as PsProcessType and PsThreadType (the only two object types that allow callbacks on their creation or copying).  Note: Those global variables are OBJECT_TYPE**’s, not just single indirection pointers, so beware.

How did ObRegisterCallbacks know which object to use?  It’s passed in the OB_CALLBACK_REGISTRATION.  Or, at least, one of its substructures.  So it gets that handy LIST_ENTRY of Callback items at the end of the object and inserts into that list.  Super convenient!

After the callbacks are inserted ObRegisterCallbacks does some stuff, fires off an APC, whatever.  It doesn’t matter, we have what we’re interested in: The callback structures and how they’re registered and unregistered.  Speaking of unregistering, you know the handle that ObRegisterCallbacks returns?  It’s just a pointer to the CALLBACK_ENTRY structure, the one that is effectively a 0x20 byte header.  But how do we get to that structure?  After all, the OBJECT_TYPE which we will have to parse to find callbacks only has the CALLBACK_ENTRY_ITEMs in a linked list.  Conveniently, each CALLBACK_ENTRY_ITEM has a pointer to its parent CALLBACK_ENTRY, giving us a ‘handle’ with which to unregister it.

So armed with this information, we can just grab PsProcessType and PsThreadType, parse its Callback List, find each entry item’s parent entry, and unregister them with ObUnRegisterCallbacks by passing the address of the entry as the handle!  Bazinga!

Unregistering the callbacks using ObUnRegisterCallbacks and reading memory from a protected process

The Problem

Sort of.  This works just fine and unregisters the callback.  Unfortunately, when the driver that actually owns the callback goes to unregister it itself when it unloads or whatever, it passes a now invalid handle to ObUnRegisterCallbacks.  Why is this a problem?

ObUnRegisterCallbacks

When we unregister the callback, the memory of the entry is freed.  So, when the parent driver goes to unregister the now invalid handle pointer to freed memory, bugchecks happen.  Unless this is an acceptable price to pay for unregistering those callbacks, something must be done.

My short term solution is to just do some good old fashioned DKOM.  On Windows 7, as far as I can tell, those callbacks are not monitored by Patchguard.  Simply put, all one has to do is overwrite the PreCallback and PostCallback function pointers in the CALLBACK_ENTRY_ITEM to dummy functions either located in one’s own driver or elsewhere.  This solution seemed to work fine and could even be reversed.  Your driver could disable the callbacks, load its cheat into the target process, then replace the callbacks through DKOM.  This is not an option when using ObUnRegisterCallbacks because the parent driver of the original callbacks would not have a handle to your newly added callbacks.

Things to consider

An anti-virus or anti-cheat software may occasionally attempt to re-register its callback and see if the return value is success or STATUS_FLT_INSTANCE_ALTITUDE_COLLISION.  A collision would indicate that the callback was still in place, while success would indicate that it had been removed.  While an anti-virus has little recourse if this occurs, an anti-cheat software could see this as a sign of tampering and ban the user or close the game.  The solution to this could be to register your own callback at the same altitude as the callback you’re replacing, meaning the anti-cheat will get the collision value and assume its callback is still in place.

An anti-virus or anti-cheat software could check the integrity of its callback items by walking the list itself.  While this is obviously a bad idea in general, if their objects are being manipulated directly it seems like the most direct countermeasure.  Of course, this would spell disaster if a service pack update came out and suddenly anti-viruses and anti-cheats started bugchecking people’s computers because an opaque structure definition changed.

I feel like this is one of the ‘cleanest’ ways to deal with modern anti-cheats kernel modules.  This allows Ring 3 cheat delivery systems to work again without modification and should be relatively stable.  I have no doubt that a similar solution could be used for PsSetLoadImageNotifyRoutine, which I intend on researching next.  I’m sure that the anti-cheat developers will come up with new, exciting, and clever countermeasures to our countermeasures, and the cat and mouse game will rage on 🙂

Doug “Douggem” Confere

How to make a DayZ Standalone admin menu

Hey fellow DayZ admins!  Keeping cheaters out of your servers can be hard, and we don’t have a lot of official admin tools yet, so I’m releasing info on creating your own admin tool!

Back in November when the BE Kernel Module for SA was going to be in the “next patch”, I said I was going to release admin menu and injector source when it went live. At the time I expected “next patch” to be in a few weeks, not months. In the time since I made that promise the kernel module has improved, breaking the user mode methods I was using to inject (including a really slick modified PBO that callextensioned your admin menu which hid the modified PBO – I was pretty proud of it even if it was stupid!). It’s all kernel now. Since things have changed, I’m having to alter my release. Instead of admin menu source and injector source, I’m going to release basic admin menu source with detailed explanations of how to flesh it out into your own personal admin menu.

The reason for this is because I’d rather enable people to make their own admin menus than just give them admin menus, and also because there are about a hundred 12 year olds waiting for me to release something so they can start selling it on Skype. This way they’ll actually have to do some work themselves to get a working sellable admin menu which I don’t expect any of them to do.

1 – Introduction

Well shit dawg, the Battleye kernel module just went live (at the time when I started writing this)! It’s blocking access to the DayZ process from our external admin menus. What ever will we do guys? In the words of Eugen Harton, no admin menuers anymore

This image has been resized. Click this bar to view the full image. The original image is sized 638×360.


Much like increased police presence in one neighborhood will push criminal activity to another, this hurdle in the way of external admin menus is going to push most people to internal admin menus. It’s possible to make your own driver to read the DayZ process’s memory, but it’s a lot easier to fire up Xenos and inject a DLL. This is ultimately a good thing for heckers, as internal cheats are actually easier than external ones! Reading classes and calling engine functions is much easier when you’re in the process than when you’re in some other process. Instead of writing ReadProcessMemory wrappers and manually dereferencing everything, all you have to do is map out your classes! And for calling engine functions, instead of making a system to write bytecode into the target process to call the function and calling createthread on it, you just have to make a function pointer and call it!

So let’s do that! Let’s create a simple internal admin menu.

1.1 – Internal vs External

An external admin menu is a admin menu made as a second separate program from the game entirely. To get information from the game or to write information to it, we have to get a handle to the process and use ReadProcessMemory and WriteProcessMemory to view and manipulate the memory of the game. The information taken from the game is used to draw radars or ESP overlays, or sometimes a admin menu might just manipulate memory in the game in order to spawn items or teleport the character. Because external cheats can be written in Fisher Price languages like C#, they can be made very quickly. They are also very ‘safe’ in the terms of causing the game to crash – if you’re just reading from the game to draw a radar and you mess something up, you might crash your own program but not the game.

An internal admin menu is code that’s loaded into the game itself instead of being in a separate program. This means you can interface with the memory of the game as if it were in your own program! This makes things very very easy as we don’t have to manually add up offsets and do dereferences through function calls to ReadProcessMemory. We also have added power since we can hook functions in the game and directly call the game’s functions, which can both be done from an external admin menu but it’s much more involved and difficult. But with this ease of use and power comes responsibility – if we done goof too bad we can crash the game. If we try to dereference a bad pointer or write to something we don’t have permission to write to we will cause an exception that, if not handled, will crash our game. This means in early testing when you make some small mistake you can crash over and over again until you figure it out.

Each system has its own advantages and disadvantages, but because of the BE Kernel Module it is much easier to make an internal admin menu than an external, so that is what we will be doing in this tutorial.

This image has been resized. Click this bar to view the full image. The original image is sized 690×535.


An external admin menu is usually its own free-standing program that displays information in its own window

This image has been resized. Click this bar to view the full image. The original image is sized 896×761.


An internal admin menu is loaded into the game and usually displays information inside the game itself

1.2 – Getting object information in an internal admin menu
0xDFCDD8] + 0x13A8] + 4]
That’s the path to your local player in Arma 2 OA. Or, it was a few patches ago. If you’ve made external admin menus you probably understand that notation – you read the value at 0xDFCDD8, then add 0x13A8 to the result, read that, add 4 to the result, and read that.

Why? Why DFCDD8? Why do you add 13A8 to whatever’s there? Well, I’m super bad at explaining this but I’m going to give it a shot. Imagine we make the following class:

PHP Code:
class TheNumbers {
int numberOfNumbers;
bool numbersFilledIn;
bool numbersDeleted;
bool myDogIsCool;
bool cowwaDooty;
float numberCoefficient;
int* numbers;
};

Let’s say we create an instance of that class and fill the members in. That class is going to be located somewhere in memory, right? And it’s going to take up a certain amount of memory, right? How much memory will it take up? Assuming 32 bit an int is going to take up 4 bytes, a bool is going to take up 1 byte, a float is going to take up 4 bytes, and a pointer is going to take up 4 bytes. So we end up with:

1 int: 4 bytes, 4 bools: 4 bytes, 1 float: 4 bytes, 1 pointer: 4 bytes. 4 + 4 +4 + 4 = 16 bytes total.

So if our class is located at memory address 0xD0000, it takes up bytes 0xD0000 through 0xD0010. So if we want to pull something out of that class in memory, say myDogIsCool’s byte, we know that it’s somewhere in those 16 bytes. But how do we figure out which one? Well, assuming the class isn’t re-arranged by the optimizer, it’s pretty straightforward!

Since numberOfNumbers is the first member of the class, it should be the very first thing in the class in memory, right? So if our class is at 0xD0000, numberOfNumbers should be right there at 0xD0000! Since an int is 4 bytes big, the next thing in the class, numbersFilledIn, should be 4 bytes into the class! That means it should be at 0xD0004! So the class looks like this, with the memory address of each member as well as how far it is in the class:

PHP Code:
/* 0xD0000 */ class TheNumbers {
/* 0xD0000 */ int numberOfNumbers; // 0x00
/* 0xD0004 */ bool numbersFilledIn; // 0x04
/* 0xD0005 */ bool numbersDeleted; //0x05
/* 0xD0006 */ bool myDogIsCool; //0x06
/* 0xD0007 */ bool cowwaDooty; //0x07
/* 0xD0008 */ float numberCoefficient; //0x08
/* 0xD000C */ int* numbers; //0x0C
};// Size: 0x10

What if we want to read the number pointed to by numbers? We need to read the pointer and then read the memory pointed to by the pointer. So we would go to the address of TheNumbers, then add the offset of the pointer we want to read(0xC), read the value at the address (read the memory at 0xD000C, which is the pointer), then read the memory address that the pointer points to.

So we end up with this:
numbers = 0xD0000 + 0xC] which is in a notation you should be familiar with. Now imagine TheNumbers was a game class that someone had partially reversed. We know it resides at 0xD0000 and know the pointer to numbers is at 0xC in the class, but don’t know any of the other members. We would write the class like this:

PHP Code:
/* 0xD0000 */ class TheNumbers {
/* 0xD0000 */ char buffer[0xC]; // 0x00
/* 0xD000C */ int* numbers; //0x0C
};// Size: 0x10

That puts numbers in the correct place even though we don’t know what comes before it. So why? Why do it? Why write out classes like this? It allows us to interact with game classes as if they were our own classes. Take our definition for TheNumbers above, if we want to get the “numbers” value from the pointer in the class, this is all we have to do:

PHP Code:
TheNumbers* globalNumbers = (TheNumbers*)0xD0000;
int numbersValue = *globalNumbers->numbers;

That is, we make a TheNumbers pointer for 0xD0000, then dereference the ‘numbers’ pointer to get the ‘numbers’ value! So with our classes mapped out we can easily dereference members which makes things much, much simpler.

1.3 – Null pointers and crashes, Doug’s system for stability
So let’s take some actual Arma classes and make a real-world example:

PHP Code:
class World {
char buffer[0x13A8];
EntityLink* cameraOn;
};

class EntityLink {
int referenceCount;
Entity* entity;
};

class Entity {
char buffer[0x18];
VisualState* visualState;
};

class VisualState {
char buffer[0x28];
D3DXVECTOR3 position;
};

Awesome! If we know our World pointer is 0xDFCDD8, we can get our local player position super easy!

PHP Code:
World* gameWorld = *(World**)0xDFCDD8; // NOTE: Casting to pointer-pointer and dereferencing because DFCDD8
//isn't world, but a POINTER to world!
D3DXVECTOR3 localPlayerPos = world->cameraOn->entity->visualState->position;

Hot damn hellz yeah! But wait, now I crash all the time! If I inject before I’m all the way in game I crash, and I crash if I die, what’s up?

Muh’ fuggin null pointers my friend

If you try to dereference a pointer that points to a bad place in memory, you will crash. Null pointers (pointers with a value of ‘0’) are always bad and attempting to dereference them will result in a crash. Why is this important? If you try to get your local position as above before your local player is created or at any time where he does not exist, the pointer to him will be null and you will crash when you try to dereference!

So what do we do? Well, we need to check every pointer every time. The naive method is this:

PHP Code:
World* gameWorld = *(World**)0xDFCDD8;
D3DXVECTOR3 position(0, 0, 0);
if(gameWorld != nullptr) {
EntityLink* cameraOn = gameWorld->cameraOn;
if (cameraOn != nullptr) {
Entity* localPlayer = cameraOn->entity;
if (localPlayer != nullptr) {
VisualState* localPlayerVisualState = localPlayer->visualState;
if (visualState != nullptr) {
position = visualState->position;

Alternatively, some people do this:

PHP Code:
World* gameWorld = *(World**)0xDFCDD8;
D3DXVECTOR3 position(0, 0, 0);
if (gameWorld)
if (gameWorld->cameraOn)
if (gameWorld->cameraOn->entity)
if (gameWorld->cameraOn->entity->visualState)
position = gameWorld->cameraOn->entity->visualState->position;

These methods work fine! They’re just very ugly. I, personally, would suggest that if you decide to use this method, instead of nested if statements you would instead combine them into one if statement. It’s ugly too, but you don’t end up on the very right side of your visual studio pane writing code because you’re so deep in if statements.

PHP Code:
World* gameWorld = *(World**)0xDFCDD8;
D3DXVECTOR3 position(0, 0, 0);
if (gameWorld && gameWorld->cameraOn && gameWorld->cameraOn->entity && gameWorld->cameraOn->visualState)
position = gameWorld->cameraOn->entity->visualState->position;

Bonus points if you assign a pointer to shorten that assignment statement:

Quote:
World* gameWorld = *(World**)0xDFCDD8;
D3DXVECTOR3 position(0, 0, 0);
VisualState* localPlayerVisualState = nullptr;
if (gameWorld && gameWorld->cameraOn && gameWorld->cameraOn->entity && localPlayerVisualState = gameWorld->cameraOn->visualState, localPlayerVisualState != nullptr)
position =localPlayerVisualState->position;

Delegating pointer checks

Of course, if you do this everywhere in your code it will get very long and very ugly very quick. I choose to use a different method: I make each class responsible for verifying its own pointer by wrapping dereferences in class functions. Let me show you what I mean:

PHP Code:
class World {
char buffer[0x13A8];
EntityLink* cameraOn;

EntityLink* getCameraOn() {if (!this) return nullptr; else return cameraOn;}
};

class EntityLink {
int referenceCount;
Entity* entity;

Entity* getEntity() { if(!this) return nullptr; else return entity; }
};

class Entity {
char buffer[0x18];
VisualState* visualState;

VisualState* getVisualState() { if(!this) return nullptr; else return visualState; }
};

class VisualState {
char buffer[0x28];
D3DXVECTOR3 position;

D3DXVECTOR3 getPosition() { if(!this) return D3DXVECTOR3(0, 0, 0); else return position; }
};

So to get our local player’s position we do the following:

PHP Code:
World* gameWorld = *(World**)0xDFCDD8; // Ok ya got me, we DO assume this pointer is good!  You can add your own check for that
D3DXVECTOR3 position = gameWorld->getCameraOn()->getEntity()->getVisualState()->getPosition();

If ANY of those pointers are bad, it will pass a nullptr to the next function which ultimately gets to getPosition. When getPosition is passed a null pointer, it returns a null vector! So we don’t have to check any of our pointers manually anymore, they’re all done automatically by our delegated functions!

Using macros to make pointer checking even simpler

But what if my classes are all messed up and I try to dereference a float as a pointer or something? Yeah I do that stuff sometimes too, that’s why I use a few simple macros to assist in my pointer checking. They run IsBadReadPtr on the this pointer and if the pointer IS bad, it bails out.

PHP Code:
#define CHECK_BAD(x) if(IsBadReadPtr(this, sizeof(x))) return
#define CHECK_BAD_NUM(x) if(IsBadReadPtr(this, sizeof(x))) return 0
#define CHECK_BAD_PTR(x) if(IsBadReadPtr(this, sizeof(x))) return nullptr

Used in our original classes, they would look like this:

PHP Code:
class World {
char buffer[0x13A8];
EntityLink* cameraOn;

EntityLink* getCameraOn() {CHECK_BAD_PTR(World); return cameraOn;}
};

class EntityLink {
int referenceCount;
Entity* entity;

Entity* getEntity() { CHECK_BAD_PTR(EntityLink); return entity; }
};

class Entity {
char buffer[0x18];
VisualState* visualState;

VisualState* getVisualState() { CHECK_BAD_PTR(Entity); return visualState; }
};

class VisualState {
char buffer[0x28];
D3DXVECTOR3 position;

D3DXVECTOR3 getPosition() { CHECK_BAD(VisualState) D3DXVECTOR3(0, 0, 0); return position; }
};

You’ll notice since CHECK_BAD leaves an open return statement at the end of the macro, we can use that to return objects or something other than nothing, 0, and nullptr. With this system of delegation and automatic checking of (hopefully!) all dereferences, we can be pretty safe from crashes due to bad pointers!

2 – Let’s Get Started

Prepare your Skypes, it’s copy/paste/resell time
2.1 – Common Classes
There are a few classes we will see recur over and over as we go through the classes we want to pull information from.

ArmaString

PHP Code:
class ArmaString
{
public:
__int32 referenceCount; //0x0000
__int32 Length; //0x0004
char string[1]; //0x0008
char* getString() { CHECK_BAD_PTR(ArmaString); return string; }
}

This is how Arma encapsulates its strings. It’s a very simple object, nothing really exciting here.

AutoArray

PHP Code:
template <class T>
class AutoArray {
public:
T* cArray;
int count;
int max;
};

The AutoArray. These are EVERYWHERE! And is how Bohemia likes to keep arrays of objects. It’s a pointer to an array, a count of how many items are currently in the array, and the max size before the array will have to be resized and reallocated. Using this templated class will make your iteration MUCH easier! You can add your own add, remove, and resize functions to manipulate arrays and do some pretty cool things.

TackLLinks

PHP Code:
template <class C>
class TrackLLinks
{
public:
void* vtable;
__int32 refCount; //0x0004
C* link; //0x0008
char _0x000C[4];

C* getLink() { CHECK_BAD_PTR(TrackLLInks; return link; }
};//Size=0x0010

These encapsulate a pointer to an object and appear quite frequently in Arma classes. They’re typically used to keep a reference to a parent or to the object that contains the link itself.

2.2 – The Entity Class
I would encourage you guys to make separate soldier/vehicle class that share the same base class. However, in the interest of brevity I will not be doing that here and will outline only one “catchall” entity class. We will get enough information from the entity to get its name and position so we can draw it on the screen.

PHP Code:
class Entity
{
public:
char _0x0000[28];
EntityVisualState* visualState; //0x001C
char _0x0020[48];
EntityType* entityType; //0x0050
char _0x0054[560];
BYTE isDead; //0x0284
char _0x0285[175];
EventHandler eventHandlers[38]; //0x0334
char _0x062C[64];
GameVarSpace variables; //0x066C
char _0x0684[184];
AIAgent* agent; //0x073C
char _0x0740[4];
ArmaString* playerName; //0x0744
char _0x0748[168];
__int32 _playerID; //0x07F0
};//Size=0x07F1

You can tell right off by looking at the class that we can get a few things directly from this class. The byte isDead is exactly what it sounds like – if it’s non-zero, the entity is dead. If it’s zero, the entity is a live. We can also grab our player name directly from here so we don’t have to mess with the network manager.

To get our position, we need to look int the EntityVisualState class:

PHP Code:
class EntityVisualState
{
public:
char _0x0000[28];
Vec3 Direction; //0x001C
Vec3 Coordinates; //0x0028
char _0x0034[252];
Vec3 _headCoords; //0x0130 - only in man classes
Vec3 _torsoCoords; //0x013C  - only in man classes

};//Size=0x0148

The coordinate vector is the position at the player’s FEET if it’s a human, so be careful! If you want to draw your ESP markers above a player’s head, take the head coords and add a half a meter or so. Using head and torso coords you can draw a poor man’s skeletal ESP as well, which is what I do.

But how do we know if it’s a man or not? Well, we will look inside the EntityType class

PHP Code:
class EntityType
{
public:
char _0x0000[40];
LODShape* lodShape; //0x0028
char _0x002C[8];
ArmaString* _entityName; //0x0034
char _0x0038[4];
ArmaString* N092957C9; //0x003C
char _0x0040[48];
ArmaString* _entityType; //0x0070

};//Size=0x0074

entityType is “soldier” or “car” etc. while entityName is the class name such as “manParts01” or “zombie02” etc. You can use these strings to if and how to draw an entity. Pro-tip: all entities of the same type share an entityType object, so you could keep track of the objects and store their types in an enum instead of doing string comparisons to speed up your admin menu a little bit.

So we know how to get basic information from entities, but how do we get those entities?

2.2 – The World Class
World stores tons of information on the simulation – entities in the game world, weather information, ALL KINDS of stuff! It’s hella important.

PHP Code:
class World
{
public:
char _0x0000[36];
InGameUI* inGameUI; //0x0024
char _0x0028[1860];
AutoArray<EntityContainer>* fastEntities; //0x076C
GameVarNamespace* missionNamespace; //0x0770
GameVarNamespace* profileNamespace; //0x0774
char _0x0778[740];
EntitiesDistributed mobiles; //0x0A5C
EntitiesDistributed animals; //0x0D20
EntitiesDistributed slowEntities; //0x0FE4
EntityList stationaryEntities; //0x12A8
char _0x1358[52];
EntityList unknown; //0x138C
char _0x143C[708];
EntityLink* cameraOn; //0x1700
char _0x1704[4];
EntityLink* playerOn; //0x1708
EntityLink* realPlayer; //0x170C
char _0x1710[52];
float actualOvercast; //0x1744
float wantedOvercast; //0x1748
char _0x174C[4];
float actualFog; //0x1750
float wantedFog; //0x1754
char _0x1758[4];
float actualFogBase; //0x175C
float wantedFogBase; //0x1760
char _0x1764[8];
float aperture; //0x176C -1 for automatic, otherwise adjust by VERY small increments like 0.001
};

With the important sub classes here:

PHP Code:
class EntitiesDistributed
{
public:
char _0x0000[4];
EntityList nearEntities; //0x0004
EntityList farEntities; //0x00B4
EntityList invisibleNearEntities; //0x0164
EntityList invisibleFarEntities; //0x0214

};//Size=0x02C4

class EntityList
{
public:
char _0x0000[4];
AutoArray<EntityBase*> entities
char _0x0010[160];

};//Size=0x00B0

class InGameUI
{
public:
char _0x0000[32];
EntityLink* targetEntity; //0x0020
char _0x0024[8];
Vec3 cursorCoords; //0x002C
char _0x0038[72];

};//Size=0x0080

class EntityLink
{
public:
char _0x0000[4];
Entity* entity; //0x0004

};//Size=0x0008

class EntityContainer
{
public:
EntityLink* entityLink; //0x0000
char _0x0004[36];
TrackLLinks<EntityType>* entityType; //0x0028

};//Size=0x002C

// EntityList points to a base class of what fastEntities and CameraOn etc. point to
class EntityBase
{
public:
char _0x0000[40];
Entity entity; //0x0028
};

Getting Entities
fastEntities containers other players and vehicles. Zombies, animals, and loot on the ground are contained in the other EntitiesDistributed and EntityLists in the World class. The lists can be iterated through very easily:

PHP Code:
AutoArray<EntityBase*>* nearAnimals = World->animals->nearEntities->entities;
for (int i = 0; i < entities.count; i++)
Entity* entity = entities.cArray[i];
// Then do whatever you want with the entity object!

Add checks for null pointers (Or delegate them!) as appropriate! Since when we made our class we already sorted the entity lists into near, far, visible, invisible, and their types, it should be very easy to just go through the lists you need for your ESP.

Aperture
Aperture is how much light is let through the lens of the “camera’ that you see the game through. -1 makes it automatic, while other values allow you to fine tune it yourself. You can manipulate this value to see during the night time.

Weather
You can change these values if you want, it’s kinda fun.

2.3 – Putting it all together

You’re going to have to do a little work. Flesh out your classes and work out getting information from them safely. Grab tim0n’s source and use his D3D hook and the included W2S by Fatboy. Plug everything together and you should have something made almost entirely by you from the ground up. This will decrease its detectability DRASTICALLY.

3 – Extra Stuff
3.1 – Augmenting weapons and ammo

Hit detection in DayZ is completely client sided. This has the pleasant side effect of trusting whatever your client says with regards to shooting and hitting people. Your client tells the server you swung your first and a frag grenade came out at 2000m/s and exploded next to a dude on the other side of a wall? Sure, why not, client! Let me tell that guy he’s dead!

This means we can change the values of our weapons and ammunition. We can change our ammo’s damage, how it’s effected by gravity, etc. We can make changes to magazine types to make our guns shoot different bullets as well.

PHP Code:
class AmmoType
{
public:
char _0x0000[52];
ArmaString* name; //0x0034
char _0x0038[12];
AmmoType* parent; //0x0044
char _0x0048[40];
ArmaString* type; //0x0070
char _0x0074[188];
float directHit; //0x0130
float indirectHit; //0x0134
float indirectHitRange; //0x0138
char _0x013C[64];
float explosive; //0x017C
float caliber; //0x0180
float deflecting; //0x0184
float deflectionSlowDown; //0x0188
float timeToLive; //0x018C
float airFriction; //0x0190
float coefGravity; //0x0194
float typicalSpeed; //0x0198
char _0x019C[8];
__int32 simulation; //0x01A4
char _0x01A8[664];

};//Size=0x0440

class MagazineType
{
public:
char _0x0000[52];
ArmaString* name; //0x0034
char _0x0038[4];
ArmaString* model; //0x003C
char _0x0040[48];
ArmaString* type; //0x0070
char _0x0074[3276];
__int32 type; //0x0D40
__int32 count; //0x0D44
char _0x0D48[4];
float initSpeed; //0x0D4C i.e. muzzle velocity
char _0x0D50[4];
__int32 quickReload; //0x0D54
char _0x0D58[432];
AmmoType* N09C16026; //0x0F08

};//Size=0x0F0C

So how do we get to our magazine type? Well it’s probably somewhere in the magazine class, but I’ve never actually gotten a gun in this game so I never really looked at it. Here’s another way to find it:

At 0x2817F040 (B9 ? ? ? ? 56 57 FF 74 24 18) is a class VehicleTypeBank, which includes a hash table of all the entity types of everything currently loaded into the game. Included in this table are ammunition types and magazine types!

PHP Code:
class VehicleTypeBank
{
public:
char _0x0000[16];
AutoArray<AutoArray<TrackLLinks<EntityType*>*>> types;

};//Size=0x0044

Magazinetype and Ammotype inherit from a base class shared by EntityType, which means the name and type armastring pointers are in the same place (0x34 and 0x70). So you can iterate through all the types, look for your magazine or ammo by name and make changes appropriately.

Source will be coming soon. This has been sitting in my google drive for a looooooooong time but I never really was motivated to finish it, so I an going ahead and posting it. Will slap together a working source and post in the next week or so

I would encourage you guys to share snippets of your own solutions and classes since I don’t play this game and so don’t really admin menu it.

Simple script injection – OnEachFrame

So I learned about OnEachFrame the other day https://community.bistudio.com/wiki/onEachFrame and thought I’d take a look at it.


As you can see, all it does is take the ArmaString pointer from the argument object and put it in a global GameValue object. That’s it – that’s all OnEachFrame is. It’s a global GameValue object that has its text, if there is any, executed on each frame. So I figured, why not make a really simple executor from it?

</div>
<div>// A2 allocator.  Not used for everything.</div>
<div class="alt2" dir="ltr">typedef void *(WINAPI* A2Malloc)(SIZE_T);
typedef void (WINAPI* A2Free)(void*);
A2Malloc MemAlloc = *(A2Malloc*)0xDBF2A0;
A2Free MemFree = *(A2Free*)0xDBF2A4;
// To get with sig:
/*
DWORD mallocObject = DUtils::findPatternPlusBytes((DWORD)a2oaModule, (DWORD)a2LastModuleByte, "FF 15 ? ? ? ? 8B F8 85 FF 75 54", 2);
MemAlloc = *(A2Malloc*)mallocObject;
MemFree = *(A2Free*)(mallocObject + 4);*/


class ArmaString {
public:
int References;
int StringLength;
char AString[1];

// Call this to create a new ArmaString.  We're basically using it as a constructor without setting up an actual Allocator for A2Malloc
static ArmaString* CreateArmaString(const char* text) {
if (!text) return 0; // you're retarded

int length = strlen(text);
ArmaString *newArmaString = (ArmaString*)MemAlloc(length + 9);
if (!newArmaString) return nullptr;        // Shouldn't happen, if your MemAlloc pointer is wrong you'll crash on the call unless you get REALLY lucky
newArmaString->References = 1;
newArmaString->StringLength = length;
memcpy(&newArmaString->AString, text, length + 1);

return newArmaString;
}
};
// Kept the inheritance model in case you guys want to do things with scalars or some shit.
class GameData {
public:
void* GameDataTypeVTable;
int References;
void* DebugValueVTable;
};

class GameDataString : public GameData {
public:
ArmaString* Data;

// Calling the engine's constructor is going to be easier than manually doing our own.
typedef GameData* (__thiscall* GDConstructor)(GameData* thisptr, ArmaString* initialValue);
static GDConstructor Constructor;
GameDataString(ArmaString* initialValue) {
if (Constructor != nullptr) // If your constructor pointer is null, your object is going to be empty and you're going to crash
Constructor(this, initialValue);
References++;
}
};
// 0x9D51CB.  No unique sig, other ways to find it programatically at runtime
GameDataString::GDConstructor GameDataString::Constructor = (GameDataString::GDConstructor)0x9D51CB;

class GameValue {
public:
void* GameValueVTable;
GameData* Value;
};

GameValue* OnEachFrame = (GameValue*)0xDB0614;

void InjectScript(const char* scriptText) {
ArmaString* script = ArmaString::CreateArmaString(scriptText);
GameDataString* data = new GameDataString(script);
OnEachFrame->Value = data;
}

void ExecuteScriptFile(std::string filePath) {
// execVM "filePath"; onEachFrame{};
// gotta clear the onEachFrame event since I know you tards won't do it yourselves
std::string scriptText = "execVM \"" + filePath + "\"; onEachFrame{};";
InjectScript(scriptText.c_str());
} 


Super simple, able to be done externally without any nasty CreateThread function calls from the outside (pump up the reference count on your strings and GameData objects so they don’t get freed by Arma), and very stable.  Will port to A3 and SA very soon.

DayZ Standalone script function descriptions and addresses

The Real Virtuality engine keeps a sort of catalog of script engine functions, descriptions, and examples in memory while it’s running.  This catalog is walked by the script VM when searching for a script function, and it can be dumped to easily get an enumeration of script engine functions.  This is a dump of the latest catalog for the DayZ Standalone, and can be used to easily find script function addresses to reverse them.  It’s linked as a private paste because it’s so massive and WordPress is weird about formatting: http://privatepaste.com/325702e386/Douggem