YinkoShield

Knowledge Center / Mobile runtime attacks / mobile runtime attacks · 2026·02

Runtime memory manipulation — process-memory rewriting

A process's .text segment normally maps as read-execute, no write. Two paths flip the protection and let an attacker rewrite executing code: /proc/[pid]/mem direct write (requires same-uid or root) and mprotect-then-memcpy (requires code execution inside the process). The class includes GameGuardian-family tooling and is the engine behind sophisticated runtime hooking. The detection signal is in-memory hash drift against the bytes on disk.

[ runtime memory manipulation — text segment rewrite ] .text read-execute · rewrite target application code, libart, libc .rodata read-only · string constants .data read-write · globals [heap] read-write · malloc, ART heap [stack] read-write · thread frames // the two write paths path A · /proc/[pid]/mem open("/proc/9421/mem", O_RDWR); lseek(fd, target_addr, SEEK_SET); write(fd, payload, size); requires same-uid or root path B · mprotect + memcpy mprotect(addr, len, PROT_READ | PROT_WRITE | PROT_EXEC); memcpy(addr, payload, len); substrate signal: code.integrity { text_segment_hash_drift · address · expected_sha · observed_sha }
A process's .text segment is normally read-execute. Two write paths — /proc/[pid]/mem direct write, and mprotect + memcpy — let an attacker rewrite executing code.

1. Mechanism

The Linux memory model maps each segment of an ELF binary at load time with specific protection bits. The .text segment — the executable code — is loaded as PROT_READ | PROT_EXEC. The absence of PROT_WRITE is the platform’s first line of defence against running code modifying itself.

Two paths convert that read-execute mapping into a writable one:

  • /proc/[pid]/mem direct write [1]. The mem file exposes the process’s address space as a regular file. With O_RDWR access an attacker can lseek to a target address in the .text segment and write() arbitrary bytes; the kernel bypasses the segment’s protection bits for this path. Same-UID is necessary but not sufficient on stock Android: Yama ptrace_scope (typically 1 on shipping Android) and SELinux MAC rules deny ptrace and /proc/<pid>/mem write to untrusted_app even at same UID across packages. The path is open in practice on rooted devices, debuggable builds, or shared-UID intra-package contexts.
  • mprotect + memcpy [2]. From within the process, code can call mprotect(addr, len, PROT_READ | PROT_WRITE | PROT_EXEC) to add write permission to a region, then memcpy the payload, then optionally mprotect it back to read-execute. Requires code execution in the process — typically via library injection. SELinux denies execmem for untrusted_app on stock AOSP, so a PROT_EXEC mapping that becomes PROT_WRITE | PROT_EXEC is denied — both classic mprotect(W|X) and the safer rename-to-r---then-r-x cycle trip the policy. The path opens with root, a debuggable build, or in-process injection from a shared-UID helper that holds execmem.

Privilege summary: cross-process address-space access on stock, non-rooted Android from a side-loaded peer of a release- build app is denied by SELinux + Yama. Both paths assume an already-compromised privilege context (rooted device, debuggable repackaged build, or in-process injection from a shared-UID helper). OWASP MASTG documents this class under code tampering [3] and notes that GameGuardian and similar runtime-modification toolchains are built around exactly these primitives — running on rooted gaming devices, not on stock banking-app contexts.

2. Where in the runtime it operates

Both paths leave detectable artefacts:

  • The /proc/[pid]/mem path is observable from outside the process by anyone with the relevant privileges; from inside, the process can hash its own native .text segments (in loaded .so libraries — these are stable read-execute mappings whose bytes correspond to file offsets, modulo ASLR) and notice when the in-memory bytes diverge from the on-disk shared object. ART JIT/AOT-compiled regions are distinct from native .text and managed by the runtime; .text hashing applies to native libraries, not to dex bytecode (dex is a different representation and is not directly comparable to the in-memory ART artefacts).
  • The mprotect path is observable by reading /proc/self/maps and looking for unexpected rwx (read-write-execute) mappings — most legitimate code segments are r-x. Caveat: ART’s JIT compiler legitimately creates rwx-transitioning regions during JIT compilation (write the compiled code, then re-protect to r-x), so a raw rwx count is not a clean signal — the substrate attributes the mapping to the host process’s expected JIT layout vs unexpected mappings.

3. Which checkpoints it bypasses

  • Play Integrity / App Attest. The attestation chain is unaffected by what an attacker did to the running app’s memory. The attacker is inside the process; the chain authenticates the device and the calling app’s signature, not the in-memory state.
  • FIDO2 / EMV / hardware attestation. All produce valid responses for whatever the running code asks. If the running code has been rewritten to ask for the wrong thing, the responses are still valid for that request.

4. Which signals make it observable

code.integrity. The Trusted Runtime Primitive periodically and at sensitive moments hashes critical regions of its own .text segment and compares the hashes to expected values computed at build time. Drift between the in-memory hash and the on-disk hash is the signature.

The substrate also enumerates rwx-protected mappings — any mapping with all three bits set is unusual on a healthy process.

5. Evidence Token shape when observed

The following example is illustrative; field names, type values, and schema are defined in YEI-001 §4 (available through the spec-access process).

{
  "ev": [{
    "ts":   "2026-06-15T10:23:14Z",
    "class": "code.integrity",
    "type":  "text_segment.hash_drift",
    "data": {
      "lib":           "libapp.so",
      "region":        ".text:0x1f200..0x1f400",
      "expected_sha":  "9a2c…b1",
      "observed_sha":  "e431…77",
      "rwx_mappings":  1
    }
  }]
}

The combination of hash drift + an unexpected rwx mapping (net of expected ART JIT regions) is high-confidence evidence of in-process memory rewriting. Execution Evidence Infrastructure (EEI) — the device-identity infrastructure layer for banking and payments — signs the pair so the operator’s verifier reads them together, against a build-time manifest of expected hashes and expected JIT-region shapes.

6. Cross-references

7. External references

[1] Linux man-pages. proc(5) — /proc/[pid]/mem. man7.org/linux/man-pages/man5/proc.5.html. Cited 2026-02-12.

[2] Linux man-pages. mprotect(2). man7.org/linux/man-pages/man2/mprotect.2.html. Cited 2026-02-12.

[3] OWASP MASTG. Code Tampering and Anti-Tampering. mas.owasp.org/MASTG/Android/0x05j-Testing-Resiliency-Against-Reverse-Engineering/. Cited 2026-02-12.