You mean you somehow avoided a load. But what if the constant was already placed in a register ? Also how could you pinpoint the reference to your constant in the machine code ? I'm quite profane about all this.
Not OP, but often one uses an easily identifiable dummy pattern like 0xC0DECA57 or 0xDEADBEEF which can be substituted without also messing up the machine code.
You use a label.
Say you set a value for some reason. Later you have to check IF it is set. If the condition needs to be checked many times you replace it with the code (rather than set a value to check some place). If you need to check if something is still true repeatedly you replace the condition check with no-ops when it isn't true.
Also funny are insanely large loop unrolls with hard coded valued. You could make a kind of rainbow table of those.
Also on modern chips you must wait quite a number of cycles before executing modified code or endure a catastrophic performance hit. This is ok for loops and stuff, but makes a lot of the really clever stuff pointless.
The debuggers software breakpoints _are_ self-modifying code :)
- assumes x86_64
- makes the invalid assumption that functions get compiled into a contiguous range of bytes (I’m not aware of any compiler that violates that, but especially with profile-guided optimization or compilers that try to minimize program size, that may not be true, and there is nothing in the standard that guarantees it)
- assumes (as the article acknowledges) that “to determine the length of foo(), we added an empty function, bar(), that immediately follows foo(). By subtracting the address of bar() from foo() we can determine the length in bytes of foo().”. Even simple “all functions align at cache lines” slightly violates that, and I can see a compiler or a linker move the otherwise unused bar away from foo for various reasons.
- makes assumptions about the OS it is running on.
- makes assumptions about the instructions that its source code gets compiled into. For example, in the original example, a sufficiently smart compiler could compile
void foo(void) {
int i=0;
i++;
printf("i: %d\n", i);
}
as void foo(void) {
printf("1\n");
}
or maybe even void foo(void) {
puts("1");
}
Changing compiler flags can already break this program.Also, why does this example work without flushing the instruction cache after modifying the code?
This has burned me before while writing a binary packer for Android.
I mean that is to be very much expected, unless someone comes up with a programming language that fully embraces the concept.
> Changing compiler flags can already break this program.
That's not the point of the article.
The only way to do this now on macOS is remapping whole pages as JIT. This makes it quite a challenge but still it might work…
Also, I spent a moderately successful internship at Microsoft working on dynamic assemblies. I never got deep enough into that to fully understand when and how customers where actually using it.
https://learn.microsoft.com/en-us/dotnet/fundamentals/reflec...
belter•1mo ago
akdas•1mo ago
timewizard•1mo ago
rkeene2•1mo ago
[0] https://man.openbsd.org/mprotect.2
Retr0id•1mo ago
mananaysiempre•1mo ago
[1] https://developer.apple.com/documentation/apple-silicon/port...
saagarjha•1mo ago