Deploy site
Lonami Exo totufals@hotmail.com
Fri, 12 Feb 2021 23:58:11 +0100
5 files changed,
665 insertions(+),
7 deletions(-)
M
blog/atom.xml
→
blog/atom.xml
@@ -4,12 +4,417 @@ <title>Lonami's Site - My Blog</title>
<link href="https://lonami.dev/blog/atom.xml" rel="self" type="application/atom+xml"/> <link href="https://lonami.dev/blog/"/> <generator uri="https://www.getzola.org/">Zola</generator> - <updated>2021-02-07T00:00:00+00:00</updated> + <updated>2021-02-12T00:00:00+00:00</updated> <id>https://lonami.dev/blog/atom.xml</id> <entry xml:lang="en"> + <title>Writing our own Cheat Engine: Exact Value scanning</title> + <published>2021-02-12T00:00:00+00:00</published> + <updated>2021-02-12T00:00:00+00:00</updated> + <link href="https://lonami.dev/blog/woce-2/" type="text/html"/> + <id>https://lonami.dev/blog/woce-2/</id> + <content type="html"><p>This is part 2 on the <em>Writing our own Cheat Engine</em> series.</p> +<p>New around here? Skim over <a href="/blog/woce-1">part 1</a> to learn what this series is all about.</p> +<p>In the introduction, we spent a good deal of time enumerating all running processes just so we could find out the pid we cared about. With the pid now in our hands, we can do pretty much anything to its corresponding process.</p> +<p>It's now time to read the process' memory and write to it. If our process was a single-player game, this would enable us to do things like setting a very high value on the player's current health pool, making us invencible. This technique will often not work for multi-player games, because the server likely knows your true current health (the most you could probably do is make the client render an incorrect value). However, if the server is crappy and it trusts the client, then you're still free to mess around with your current health.</p> +<p>Even if we don't want to write to the process' memory, reading is still very useful. Maybe you could enhance your experience by making a custom overlay that displays useful information, or something that makes noise if it detects the life is too low, or even simulating a keyboard event to automatically recover some mana when you're running low.</p> +<p>Be warned about anti-cheat systems. Anything beyond a basic game is likely to have some protection measures in place, making the analysis more difficult (perhaps the values are scrambled in memory), or even pinging the server if it detects something fishy.</p> +<p><strong>I am not responsible for any bans!</strong> Use your brain before messing with online games, and don't ruin the fun for everyone else. If you get caught for cheating, I don't want to know about it.</p> +<p>Now that all <a href="https://www.urbandictionary.com/define.php?term=script%20kiddie">script kiddies</a> have left the room, let's proceed with the post.</p> +<h2 id="exact-value-scanning">Exact Value scanning</h2> +<details open><summary>Cheat Engine Tutorial: Step 2</summary> +<blockquote> +<p>Now that you have opened the tutorial with Cheat Engine let's get on with the next step.</p> +<p>You can see at the bottom of this window is the text Health: xxx. Each time you click 'Hit me' your health gets decreased.</p> +<p>To get to the next step you have to find this value and change it to 1000</p> +<p>To find the value there are different ways, but I'll tell you about the easiest, 'Exact Value': First make sure value type is set to at least 2-bytes or 4-bytes. 1-byte will also work, but you'll run into an easy to fix problem when you've found the address and want to change it. The 8-byte may perhaps works if the bytes after the address are 0, but I wouldn't take the bet. Single, double, and the other scans just don't work, because they store the value in a different way.</p> +<p>When the value type is set correctly, make sure the scantype is set to 'Exact Value'. Then fill in the number your health is in the value box. And click 'First Scan'. After a while (if you have a extremely slow pc) the scan is done and the results are shown in the list on the left</p> +<p>If you find more than 1 address and you don't know for sure which address it is, click 'Hit me', fill in the new health value into the value box, and click 'Next Scan'. Repeat this until you're sure you've found it. (that includes that there's only 1 address in the list.....)</p> +<p>Now double click the address in the list on the left. This makes the address pop-up in the list at the bottom, showing you the current value. Double click the value, (or select it and press enter), and change the value to 1000.</p> +<p>If everything went ok the next button should become enabled, and you're ready for the next step.</p> +<p>Note: If you did anything wrong while scanning, click &quot;New Scan&quot; and repeat the scanning again. Also, try playing around with the value and click 'hit me'</p> +</blockquote> +</details> +<h2 id="our-first-scan">Our First Scan</h2> +<p>The Cheat Engine tutorial talks about &quot;value types&quot; and &quot;scan types&quot; like &quot;exact value&quot;.</p> +<p>The <strong>value types</strong> will help us narrow down <em>what</em> we're looking for. For example, the integer type <code>i32</code> is represented in memory as 32 bits, or 4 bytes. However, <code>f32</code> is <em>also</em> represented by 4 bytes, and so is <code>u32</code>. Or perhaps the 4 bytes represent RGBA values of a color! So any 4 bytes in memory can be interpreted in many ways, and it's up to us to decide which way we interpret the bytes in.</p> +<p>When programming, numbers which are 32-bit wide are common, as they're a good (and fast) size to work with. Scanning for this type is often a good bet. For positive numbers, <code>i32</code> is represented the same as <code>u32</code> in memory, so even if the value turns out to not be signed, the scan is likely to work. Focusing on <code>i32</code> will save us from scanning for <code>f32</code> or even other types, like interpreting 8 bytes for <code>i64</code>, <code>f64</code>, or less bytes like <code>i16</code>.</p> +<p>The <strong>scan types</strong> will help us narrow down <em>how</em> we're looking for a value. Scanning for an exact value means what you think it does: interpret all 4 bytes in the process' memory as our value type, and check if they exactly match our value. This will often yield a lot of candidates, but it will be enough to get us started. Variations of the exact scan include checking for all values below a threshold, above, in between, or even just… unknown.</p> +<p>What's the point of scanning for unknown values if <em>everything</em> in memory is unknown? Sometimes you don't have a concrete value. Maybe your health pool is a bar and it nevers tell you how much health you actually have, just a visual indicator of your percentage left, even if the health is not stored as a percentage. As we will find later on, scanning for unknown values is more useful than it might appear at first.</p> +<p>We can access the memory of our own program by guessing random pointers and trying to read from them. But Windows isolates the memory of each program, so no pointer we could ever guess will let us read from the memory of another process. Luckily for us, searching for &quot;read process memory winapi&quot; leads us to the <a href="https://docs.microsoft.com/en-us/windows/win32/api/memoryapi/nf-memoryapi-readprocessmemory"><code>ReadProcessMemory</code></a> function. Spot on.</p> +<pre><code class="language-rust" data-lang="rust">pub fn read_memory(&amp;self, addr: usize, n: usize) -&gt; io::Result&lt;Vec&lt;u8&gt;&gt; { + todo!() +} +</code></pre> +<p>Much like trying to dereference a pointer pointing to released memory or even null, reading from an arbitrary address can fail for the same reasons (and more). We will want to signal this with <code>io::Result</code>. It's funny to note that, even though we're doing something that seems wildly unsafe (reading arbitrary memory, even if the other process is mutating it at the same time), the function is perfectly safe. If we cannot read something, it will return <code>Err</code>, but if it succeeds, it has taken a snapshot of the memory of the process, and the returned value will be correctly initialized.</p> +<p>The function will be defined inside our <code>impl Process</code>, since it conveniently holds an open handle to the process in question. It takes <code>&amp;self</code>, because we do not need to mutate anything in the <code>Process</code> instance. After adding the <code>memoryapi</code> feature to <code>Cargo.toml</code>, we can perform the call:</p> +<pre><code class="language-rust" data-lang="rust">let mut buffer = Vec::&lt;u8&gt;::with_capacity(n); +let mut read = 0; + +// SAFETY: the buffer points to valid memory, and the buffer size is correctly set. +if unsafe { + winapi::um::memoryapi::ReadProcessMemory( + self.handle.as_ptr(), + addr as *const _, + buffer.as_mut_ptr().cast(), + buffer.capacity(), + &amp;mut read, + ) +} == FALSE +{ + Err(io::Error::last_os_error()) +} else { + // SAFETY: the call succeeded and `read` contains the amount of bytes written. + unsafe { buffer.set_len(read as usize) }; + Ok(buffer) +} +</code></pre> +<p>Great! But the address space is somewhat large. 64 bits large. Eighteen quintillion, four hundred forty-six quadrillion, seven hundred forty-four trillion, seventy-three billion, seven hundred nine million, five hundred fifty-one thousand, six hundred sixteen<sup class="footnote-reference"><a href="#1">1</a></sup> large. You gave up reading that, didn't you? Anyway, 18'446'744'073'709'551'616 is a <em>big</em> number.</p> +<p>I am not willing to wait for the program to scan over so many values. I don't even have 16 <a href="https://en.wikipedia.org/wiki/Orders_of_magnitude_(data)">exbibytes</a> of RAM installed on my laptop yet<sup class="footnote-reference"><a href="#2">2</a></sup>! What's up with that?</p> +<h2 id="memory-regions">Memory regions</h2> +<p>The program does not actually have all that memory allocated (surprise!). Random-guessing an address is extremely likely to point out to invalid memory. Reading from the start of the address space all the way to the end would not be any better. And we <strong>need</strong> to do better.</p> +<p>We need to query for the memory regions allocated to the program. For this purpose we can use <a href="https://docs.microsoft.com/en-us/windows/win32/api/memoryapi/nf-memoryapi-virtualqueryex"><code>VirtualQueryEx</code></a>.</p> +<blockquote> +<p>Retrieves information about a range of pages within the virtual address space of a specified process.</p> +</blockquote> +<p>We have enumerated things before, and this function is not all that different.</p> +<pre><code class="language-rust" data-lang="rust">fn memory_regions(&amp;self) -&gt; io::Result&lt;winapi::um::winnt::MEMORY_BASIC_INFORMATION&gt; { + let mut info = MaybeUninit::uninit(); + + // SAFETY: the info structure points to valid memory. + let written = unsafe { + winapi::um::memoryapi::VirtualQueryEx( + self.handle.as_ptr(), + std::ptr::null(), + info.as_mut_ptr(), + mem::size_of::&lt;winapi::um::winnt::MEMORY_BASIC_INFORMATION&gt;(), + ) + }; + if written == 0 { + Err(io::Error::last_os_error()) + } else { + // SAFETY: a non-zero amount was written to the structure + Ok(unsafe { info.assume_init() }) + } +} +</code></pre> +<p>We start with a base address of zero<sup class="footnote-reference"><a href="#3">3</a></sup> (<code>std::ptr::null()</code>), and ask the function to tell us what's in there. Let's try it out, with the <code>impl-debug</code> crate feature in <code>Cargo.toml</code>:</p> +<pre><code class="language-rust" data-lang="rust">dbg!(process.memory_regions()); +</code></pre> +<pre><code>&gt;cargo run +Compiling memo v0.1.0 + +error[E0277]: `winapi::um::winnt::MEMORY_BASIC_INFORMATION` doesn't implement `std::fmt::Debug` + --&gt; src\main.rs:185:5 + | +185 | dbg!(process.memory_regions()); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `winapi::um::winnt::MEMORY_BASIC_INFORMATION` cannot be formatted using `{:?}` because it doesn't implement `std::fmt::Debug` +</code></pre> +<p>That's annoying. It seems not everything has an <code>impl std::fmt::Debug</code>, and <a href="https://github.com/retep998/winapi-rs/issues/548#issuecomment-355278090">you're supposed to send a PR</a> if you want it to have debug, even if the <code>impl-debug</code> feature is set. I'm surprised they don't auto-generate all of this and have to rely on manually adding <code>Debug</code> as needed? Oh well, let's get rid of the feature and print it out ourselves:</p> +<pre><code>eprintln!( + &quot;Region: + BaseAddress: {:?} + AllocationBase: {:?} + AllocationProtect: {:?} + RegionSize: {:?} + State: {:?} + Protect: {:?} + Type: {:?}&quot;, + region.BaseAddress, + region.AllocationBase, + region.AllocationProtect, + region.RegionSize, + region.State, + region.Protect, + region.Type, +); +</code></pre> +<p>Hopefully we don't need to do this often:</p> +<pre><code>&gt;cargo run + Compiling memo v0.1.0 + Finished dev [unoptimized + debuginfo] target(s) in 0.60s + Running `target\debug\memo.exe` + +Region: + BaseAddress: 0x0 + AllocationBase: 0x0 + AllocationProtect: 0 + RegionSize: 65536 + State: 65536 + Protect: 1 + Type: 0 +</code></pre> +<p>Awesome! There is a region at <code>null</code>, and the <code>AllocationProtect</code> of zero indicates that &quot;the caller does not have access&quot; when the region was created. However, <code>Protect</code> is <code>1</code>, and that is the <em>current</em> protection level. A value of one indicates <a href="https://docs.microsoft.com/en-us/windows/win32/memory/memory-protection-constants"><code>PAGE_NOACCESS</code></a>:</p> +<blockquote> +<p>Disables all access to the committed region of pages. An attempt to read from, write to, or execute the committed region results in an access violation.</p> +</blockquote> +<p>Now that we know that the first region starts at 0 and has a size of 64 KiB, we can simply query for the page at <code>(current base + current size)</code> to fetch the next region. Essentially, we want to loop until it fails, after which we'll know there are no more pages<sup class="footnote-reference"><a href="#4">4</a></sup>:</p> +<pre><code class="language-rust" data-lang="rust">pub fn memory_regions(&amp;self) -&gt; Vec&lt;winapi::um::winnt::MEMORY_BASIC_INFORMATION&gt; { + let mut base = 0; + let mut regions = Vec::new(); + let mut info = MaybeUninit::uninit(); + + loop { + // SAFETY: the info structure points to valid memory. + let written = unsafe { + winapi::um::memoryapi::VirtualQueryEx( + self.handle.as_ptr(), + base as *const _, + info.as_mut_ptr(), + mem::size_of::&lt;winapi::um::winnt::MEMORY_BASIC_INFORMATION&gt;(), + ) + }; + if written == 0 { + break regions; + } + // SAFETY: a non-zero amount was written to the structure + let info = unsafe { info.assume_init() }; + base = info.BaseAddress as usize + info.RegionSize; + regions.push(info); + } +} +</code></pre> +<p><code>RegionSize</code> is:</p> +<blockquote> +<p>The size of the region beginning at the base address in which all pages have identical attributes, in bytes.</p> +</blockquote> +<p>…which also hints that the value we want is &quot;base address&quot;, not the &quot;allocation base&quot;. With these two values, we can essentially iterate over all the page ranges:</p> +<pre><code class="language-rust" data-lang="rust">dbg!(process.memory_regions().len()); +</code></pre> +<pre><code>&gt;cargo run + Compiling memo v0.1.0 + Finished dev [unoptimized + debuginfo] target(s) in 0.63s + Running `target\debug\memo.exe` + +[src\main.rs:189] process.memory_regions().len() = 367 +</code></pre> +<p>That's a lot of pages!</p> +<h2 id="protection-levels">Protection levels</h2> +<p>Let's try to narrow the amount of pages down. How many pages aren't <code>PAGE_NOACCESS</code>?</p> +<pre><code class="language-rust" data-lang="rust">dbg!(process + .memory_regions() + .into_iter() + .filter(|p| p.Protect != winapi::um::winnt::PAGE_NOACCESS) + .count()); +</code></pre> +<pre><code>295 +</code></pre> +<p>Still a fair bit! Most likely, there are just a few interleaved <code>NOACCESS</code> pages, and the rest are allocated each with different protection levels. How much memory do we need to scan through?</p> +<pre><code class="language-rust" data-lang="rust">dbg!(process + .memory_regions() + .into_iter() + .filter(|p| p.Protect != winapi::um::winnt::PAGE_NOACCESS) + .map(|p| p.RegionSize) + .sum::&lt;usize&gt;()); +</code></pre> +<pre><code>4480434176 +</code></pre> +<p>Wait, what? What do you mean over 4 GiB? The Task Manager claims that the Cheat Engine Tutorial is only using 2.1 MB worth of RAM! Perhaps we can narrow down the <a href="https://docs.microsoft.com/en-us/windows/win32/memory/memory-protection-constants">protection levels</a> a bit more. If you look at the scan options in Cheat Engine, you will notice the &quot;Memory Scan Options&quot; groupbox. By default, it only scans for memory that is writable, and doesn't care if it's executable or not:</p> +<pre><code class="language-rust" data-lang="rust">let mask = winnt::PAGE_EXECUTE_READWRITE + | winnt::PAGE_EXECUTE_WRITECOPY + | winnt::PAGE_READWRITE + | winnt::PAGE_WRITECOPY; + +dbg!(process + .memory_regions() + .into_iter() + .filter(|p| (p.Protect &amp; mask) != 0) + .map(|p| p.RegionSize) + .sum::&lt;usize&gt;()); +</code></pre> +<p>Each memory protection level has its own bit, so we can OR them all together to have a single mask. When ANDing this mask with the protection level, if any bit is set, it will be non-zero, meaning we want to keep this region.</p> +<p>Don't ask me why there isn't a specific bit for &quot;write&quot;, &quot;read&quot;, &quot;execute&quot;, and there are only bits for combinations. I guess this way Windows forbids certain combinations.</p> +<pre><code>2580480 +</code></pre> +<p>Hey, that's close to the value shown by the Task Manager! A handfull of megabytes is a lot more manageable than 4 entire gigabytes.</p> +<h2 id="actually-running-our-first-scan">Actually running our First Scan</h2> +<p>Okay, we have all the memory regions from which the program can read, write, and execute. Now we also can read the memory in these regions:</p> +<pre><code class="language-rust" data-lang="rust">let regions = process + .memory_regions() + .into_iter() + .filter(|p| (p.Protect &amp; mask) != 0) + .collect::&lt;Vec&lt;_&gt;&gt;(); + +println!(&quot;Scanning {} memory regions&quot;, regions.len()); + +regions.into_iter().for_each(|region| { + match process.read_memory(region.BaseAddress as _, region.RegionSize) { + Ok(memory) =&gt; todo!(), + Err(err) =&gt; eprintln!( + &quot;Failed to read {} bytes at {:?}: {}&quot;, + region.RegionSize, region.BaseAddress, err, + ), + } +}) +</code></pre> +<p>All that's left is for us to scan for a target value. To do this, we want to iterate over all the <a href="https://doc.rust-lang.org/stable/std/primitive.slice.html#method.windows"><code>slice::windows</code></a> of size equal to the size of our scan type.</p> +<pre><code class="language-rust" data-lang="rust">let target: i32 = ...; +let target = target.to_ne_bytes(); + +... + +memory + .windows(target.len()) + .enumerate() + .for_each(|(offset, window)| { + if window == target { + println!( + &quot;Found exact value at [{:?}+{:x}]&quot;, + region.BaseAddress, offset + ); + } + }) +</code></pre> +<p>We convert the 32-bit exact target value to its memory representation as a byte array in <a href="https://doc.rust-lang.org/stable/std/primitive.i32.html#method.to_ne_bytes">native byte order</a>. This way we can compare the target bytes with the window bytes. Another option is to interpret the window bytes as an <code>i32</code> with <code>from_be_bytes</code>, but <code>slice::windows</code> gives us slices of type <code>&amp;[u8]</code>, and <code>from_be_bytes</code> wants an <code>[u8; 4]</code> array, so it's a bit more annoying to convert.</p> +<p>This is enough to find the value in the process' memory!</p> +<pre><code>Found exact value at [0x10000+aec] +Failed to read 12288 bytes at 0x13f8000: Only part of a ReadProcessMemory or WriteProcessMemory request was completed. (os error 299) +Found exact value at [0x14f0000+3188] +Found exact value at [0x14f0000+ac74] +... +Found exact value at [0x10030e000+1816] +Found exact value at [0x7ff8f7b93000+441a] +... +Found exact value at [0x7ff8fb381000+4023] +</code></pre> +<p>The tutorial starts out with health &quot;100&quot;, which is what I scanned. Apparently, there are nearly a hundred of <code>100</code>-valued integers stored in the memory of the tutorial.</p> +<p>Attentive readers will notice that some values are located at an offset modulo 4. In Cheat Engine, this is known as &quot;Fast Scan&quot;, which is enabled by default with an alignment of 4. Most of the time, values are aligned in memory, and this alignment often corresponds with the size of the type itself. For 4-byte integers, it's common that they're 4-byte aligned.</p> +<p>We can perform a fast scan ourselves with <a href="https://doc.rust-lang.org/stable/std/iter/trait.Iterator.html#method.step_by"><code>step_by</code></a><sup class="footnote-reference"><a href="#5">5</a></sup>:</p> +<pre><code class="language-rust" data-lang="rust">memory + .windows(target.len()) + .enumerate() + .step_by(4) + .for_each(...) +</code></pre> +<p>As a bonus, over half the addresses are gone, so we have less results to worry about<sup class="footnote-reference"><a href="#6">6</a></sup>.</p> +<h2 id="next-scan">Next Scan</h2> +<p>The first scan gave us way too many results. We have no way to tell which is the correct one, as they all have the same value. What we need to do is a <em>second</em> scan at the <em>locations we just found</em>. This way, we can get a second reading, and compare it against a new value. If it's the same, we're on good track, and if not, we can discard a location. Repeating this process lets us cut the hundreds of potential addresses to just a handful of them.</p> +<p>For example, let's say we're scanning our current health of <code>100</code> in a game. This gives us over a hundred addresses that point to the value of <code>100</code>. If we go in-game and get hit<sup class="footnote-reference"><a href="#7">7</a></sup> by some enemy and get our health down to, say, <code>99</code> (we have a lot of defense), we can then read the memory at the hundred memory locations we found before. If this second reading is not <code>99</code>, we know the address does not actually point to our health pool and it just happened to also contain a <code>100</code> on the first scan. This address can be removed from the list of potential addresses pointing to our health.</p> +<p>Let's do that:</p> +<pre><code class="language-rust" data-lang="rust">let mut locations = Vec::with_capacity(regions.len()); +// -snip- +locations.push(region.BaseAddress as usize + offset); +// -snip- +let target: i32 = ...; +let target = target.to_ne_bytes(); +locations.retain(|addr| match process.read_memory(*addr, target.len()) { + Ok(memory) =&gt; memory == target, + Err(_) =&gt; false, +}); + +println!(&quot;Now have {} locations&quot;, locations.len()); +</code></pre> +<p>We create a vector to store all the locations the first scan finds, and then retain those that match a second target value. You may have noticed that we perform a memory read, and thus a call to the Windows API, for every single address. With a hundred locations to read from, this is not a big deal, but oftentimes you will have tens of thousands of addresses. For the time being, we will not worry about this inefficiency, but we will get back to it once it matters:</p> +<pre><code>Scanning 98 memory regions +Which exact value to scan for?: 100 +Failed to read 12288 bytes at 0x13f8000: Only part of a ReadProcessMemory or WriteProcessMemory request was completed. (os error 299) +... +Found 49 locations +Which exact value to scan for next?: 99 +Now have 1 locations +</code></pre> +<p>Sweet! In a real-world scenario, you will likely need to perform these additional scans a couple of times, and even then, there may be more than one value left no matter what.</p> +<p>For good measure, we'll wrap our <code>retain</code> in a <code>while</code> loop<sup class="footnote-reference"><a href="#8">8</a></sup>:</p> +<pre><code class="language-rust" data-lang="rust">while locations.len() != 1 { + let target: i32 = ...; + let target = target.to_ne_bytes(); + locations.retain(...); +} +</code></pre> +<h2 id="modifying-memory">Modifying memory</h2> +<p>Now that we have very likely locations pointing to our current health in memory, all that's left is writing our new desired value to gain infinite health<sup class="footnote-reference"><a href="#9">9</a></sup>. Much like there's a call to <code>ReadProcessMemory</code>, there's a different one to <a href="https://docs.microsoft.com/en-us/windows/win32/api/memoryapi/nf-memoryapi-writeprocessmemory"><code>WriteProcessMemory</code></a>. Its usage is straightforward:</p> +<pre><code class="language-rust" data-lang="rust">pub fn write_memory(&amp;self, addr: usize, value: &amp;[u8]) -&gt; io::Result&lt;usize&gt; { + let mut written = 0; + + // SAFETY: the input value buffer points to valid memory. + if unsafe { + winapi::um::memoryapi::WriteProcessMemory( + self.handle.as_ptr(), + addr as *mut _, + value.as_ptr().cast(), + value.len(), + &amp;mut written, + ) + } == FALSE + { + Err(io::Error::last_os_error()) + } else { + Ok(written) + } +} +</code></pre> +<p>Similar to how writing to a file can return short, writing to a memory location could also return short. Here we mimic the API for writing files and return the number of bytes written. The documentation indicates that we could actually ignore the amount written by passing <code>ptr::null_mut()</code> as the last parameter, but it does no harm to retrieve the written count as well.</p> +<pre><code class="language-rust" data-lang="rust">let new_value: i32 = ...; +locations + .into_iter() + .for_each(|addr| match process.write_memory(addr, &amp;new_value) { + Ok(n) =&gt; eprintln!(&quot;Written {} bytes to [{:x}]&quot;, n, addr), + Err(e) =&gt; eprintln!(&quot;Failed to write to [{:x}]: {}&quot;, addr, e), + }); +</code></pre> +<p>And just like that:</p> +<pre><code>Now have 1 location(s) +Enter new memory value: 1000 +Failed to write to [15d8b90]: Access is denied. (os error 5) +</code></pre> +<p>…oh noes. Oh yeah. The documentation, which I totally didn't forget to read, mentions:</p> +<blockquote> +<p>The handle must have <code>PROCESS_VM_WRITE</code> and <code>PROCESS_VM_OPERATION</code> access to the process.</p> +</blockquote> +<p>We currently open our process with <code>PROCESS_QUERY_INFORMATION</code> and <code>PROCESS_VM_READ</code>, which is enough for reading, but not for writing. Let's adjust <code>OpenProcess</code> to accomodate for our new requirements:</p> +<pre><code class="language-rust" data-lang="rust">winapi::um::processthreadsapi::OpenProcess( + winnt::PROCESS_QUERY_INFORMATION + | winnt::PROCESS_VM_READ + | winnt::PROCESS_VM_WRITE + | winnt::PROCESS_VM_OPERATION, + FALSE, + pid, +) +</code></pre> +<p>Behold:</p> +<pre><code>Now have 1 location(s) +Enter new memory value: 1000 +Written 4 bytes to [15d8b90] +</code></pre> +<p><img src="https://user-images.githubusercontent.com/6297805/107829541-3f4f2d00-6d8a-11eb-87c4-e2f2d505afbc.png" alt="Tutorial complete with memo" /></p> +<p>Isn't that active <em>Next</em> button just beautiful?</p> +<h2 id="finale">Finale</h2> +<p>This post somehow ended up being longer than part one, but look at what we've achieved! We completed a step of the Cheat Engine Tutorial <em>without using Cheat Engine</em>. Just pure Rust. Figuring out how a program works and reimplementing it yourself is a great way to learn what it's doing behind the scenes. And now that this code is yours, you can extend it as much as you like, without being constrained by Cheat Engine's UI. You can automate it as much as you want.</p> +<p>And we're not even done. The current tutorial has nine steps, and three additional graphical levels.</p> +<p>In the next post, we'll tackle the third step of the tutorial: Unknown initial value. This will pose a challenge, because with just 2 MiB of memory, storing all the 4-byte aligned locations would require 524288 addresses (<code>usize</code>, 8 bytes). This adds up to twice as much memory as the original program (4 MiB), but that's not our main concern, having to perform over five hundred thousand API calls is!</p> +<h3 id="footnotes">Footnotes</h3> +<div class="footnote-definition" id="1"><sup class="footnote-definition-label">1</sup> +<p>I did in fact use an online tool to spell it out for me.</p> +</div> +<div class="footnote-definition" id="2"><sup class="footnote-definition-label">2</sup> +<p>16 GiB is good enough for my needs. I don't think I'll ever upgrade to 16 EiB.</p> +</div> +<div class="footnote-definition" id="3"><sup class="footnote-definition-label">3</sup> +<p>Every address we query should have a corresponding region, even if it's not allocated or we do not have access. This is why we can query for the memory address zero to get its corresponding region.</p> +</div> +<div class="footnote-definition" id="4"><sup class="footnote-definition-label">4</sup> +<p>Another option is to <a href="https://docs.microsoft.com/en-us/windows/win32/api/sysinfoapi/nf-sysinfoapi-getsysteminfo"><code>GetSystemInfo</code></a> to determine the <code>lpMinimumApplicationAddress</code> and <code>lpMaximumApplicationAddress</code> and only work within bounds.</p> +</div> +<div class="footnote-definition" id="5"><sup class="footnote-definition-label">5</sup> +<p>Memory regions are page-aligned, which is a large power of two. Our alignment of 4 is much lower than this, so we're guaranteed to start off at an aligned address.</p> +</div> +<div class="footnote-definition" id="6"><sup class="footnote-definition-label">6</sup> +<p>If it turns out that the value was actually misaligned, we will miss it. You will notice this if, after going through the whole process, there are no results. It could mean that either the value type is wrong, or the value type is misaligned. In the worst case, the value is not stored directly but is rather computed with something like <code>maximum - stored</code>, or XORed with some magic value, or a myriad other things.</p> +</div> +<div class="footnote-definition" id="7"><sup class="footnote-definition-label">7</sup> +<p>You could do this without getting hit, and just keep on repeating the scan for the same value over and over again. This does work, but the results are suboptimal, because there are also many other values that didn't change. Scanning for a changed value is a better option.</p> +</div> +<div class="footnote-definition" id="8"><sup class="footnote-definition-label">8</sup> +<p>You could actually just go ahead and try to modify the memory at the hundred addresses you just found, although don't be surprised if the program starts to misbehave!</p> +</div> +<div class="footnote-definition" id="9"><sup class="footnote-definition-label">9</sup> +<p>Okay, we cannot fit infinity in an <code>i32</code>. However, we can fit sufficiently large numbers. Like <code>1000</code>, which is enough to complete the tutorial.</p> +</div> +</content> + </entry> + <entry xml:lang="en"> <title>Writing our own Cheat Engine: Introduction</title> <published>2021-02-07T00:00:00+00:00</published> - <updated>2021-02-07T00:00:00+00:00</updated> + <updated>2021-02-12T00:00:00+00:00</updated> <link href="https://lonami.dev/blog/woce-1/" type="text/html"/> <id>https://lonami.dev/blog/woce-1/</id> <content type="html"><p>This is part 1 on the <em>Writing our own Cheat Engine</em> series.</p>@@ -286,7 +691,7 @@ <p>Hooray 🎉! There's some processes we can't open, but that's because they're system processes. Security works!</p>
<h2 id="finale">Finale</h2> <p>That was a fairly long post when all we did was print a bunch of pids and their corresponding name. But in all fairness, we also laid out a good foundation for what's coming next.</p> <p>You can <a href="https://github.com/lonami/memo">obtain the code for this post</a> over at my GitHub. At the end of every post, the last commit will be tagged, so you can <code>git checkout step1</code> to see the final code for any blog post.</p> -<p>In the next post, we'll tackle the second step of the tutorial: Exact Value scanning.</p> +<p>In the <a href="/blog/woce-2">next post</a>, we'll tackle the second step of the tutorial: Exact Value scanning.</p> <h3 id="footnotes">Footnotes</h3> <div class="footnote-definition" id="1"><sup class="footnote-definition-label">1</sup> <p>You could say I simply love reinventing the wheel, which I do, but in this case, the codebase contains <em>far</em> more features than we're interested in. The (apparent) lack of structure and documentation regarding the code, along with the unfortunate <a href="https://github.com/cheat-engine/cheat-engine/issues/60">lack of license</a> for the source code, make it a no-go. There's a license, but I think that's for the distributed program itself.</p>
M
blog/index.html
→
blog/index.html
@@ -1,4 +1,4 @@
-<!DOCTYPE html><html lang=en><head><meta charset=utf-8><meta name=description content="Official Lonami's website"><meta name=viewport content="width=device-width, initial-scale=1.0, user-scalable=yes"><title> Lonami's Blog </title><link rel=stylesheet href=/style.css><body><article><nav class=sections><ul class=left><li><a href=/>lonami's site</a><li><a href=/blog class=selected>blog</a><li><a href=/golb>golb</a></ul><div class=right><a href=https://github.com/LonamiWebs><img src=/img/github.svg alt=github></a><a href=/blog/atom.xml><img src=/img/rss.svg alt=rss></a></div></nav><main><h1 class=title>My Blog</h1><p id=welcome onclick=pls_stop()>Welcome to my blog!<p>Here I occasionally post new entries, mostly tech related. Perhaps it's tips for a new game I'm playing, perhaps it has something to do with FFI, or perhaps I'm fighting the borrow checker (just kidding, I'm over that. Mostly).<hr><ul><li><a href=https://lonami.dev/blog/woce-1/>Writing our own Cheat Engine: Introduction</a><span class=dim> [mod sw; 'windows, 'rust, 'hacking] </span><li><a href=https://lonami.dev/blog/university/>Data Mining, Warehousing and Information Retrieval</a><span class=dim> [mod algos; 'series, 'bigdata, 'databases] </span><li><a href=https://lonami.dev/blog/new-computer/>My new computer</a><span class=dim> [mod hw; 'showoff] </span><li><a href=https://lonami.dev/blog/tips-outpost/>Tips for Outpost</a><span class=dim> [mod games; 'tips] </span><li><a href=https://lonami.dev/blog/ctypes-and-windows/>Python ctypes and Windows</a><span class=dim> [mod sw; 'python, 'ffi, 'windows] </span><li><a href=https://lonami.dev/blog/pixel-dungeon/>Shattered Pixel Dungeon</a><span class=dim> [mod games; 'tips] </span><li><a href=https://lonami.dev/blog/installing-nixos-2/>Installing NixOS, Take 2</a><span class=dim> [mod sw; 'os, 'nixos] </span><li><a href=https://lonami.dev/blog/breaking-ror/>Breaking Risk of Rain</a><span class=dim> [mod games; 'tips] </span><li><a href=https://lonami.dev/blog/world-edit/>WorldEdit Commands</a><span class=dim> [mod games; 'minecraft, 'worldedit, 'tips] </span><li><a href=https://lonami.dev/blog/asyncio/>An Introduction to Asyncio</a><span class=dim> [mod sw; 'python, 'asyncio] </span><li><a href=https://lonami.dev/blog/posts/>Atemporal Blog Posts</a><span class=dim> [mod algos; 'algorithms, 'culture, 'debate, 'foodforthought, 'graphics, 'optimization] </span><li><a href=https://lonami.dev/blog/graphs/>Graphs</a><span class=dim> [mod algos; 'graphs] </span><li><a href=https://lonami.dev/blog/installing-nixos/>Installing NixOS</a><span class=dim> [mod sw; 'os, 'nixos] </span></ul><script> +<!DOCTYPE html><html lang=en><head><meta charset=utf-8><meta name=description content="Official Lonami's website"><meta name=viewport content="width=device-width, initial-scale=1.0, user-scalable=yes"><title> Lonami's Blog </title><link rel=stylesheet href=/style.css><body><article><nav class=sections><ul class=left><li><a href=/>lonami's site</a><li><a href=/blog class=selected>blog</a><li><a href=/golb>golb</a></ul><div class=right><a href=https://github.com/LonamiWebs><img src=/img/github.svg alt=github></a><a href=/blog/atom.xml><img src=/img/rss.svg alt=rss></a></div></nav><main><h1 class=title>My Blog</h1><p id=welcome onclick=pls_stop()>Welcome to my blog!<p>Here I occasionally post new entries, mostly tech related. Perhaps it's tips for a new game I'm playing, perhaps it has something to do with FFI, or perhaps I'm fighting the borrow checker (just kidding, I'm over that. Mostly).<hr><ul><li><a href=https://lonami.dev/blog/woce-2/>Writing our own Cheat Engine: Exact Value scanning</a><span class=dim> [mod sw; 'windows, 'rust, 'hacking] </span><li><a href=https://lonami.dev/blog/woce-1/>Writing our own Cheat Engine: Introduction</a><span class=dim> [mod sw; 'windows, 'rust, 'hacking] </span><li><a href=https://lonami.dev/blog/university/>Data Mining, Warehousing and Information Retrieval</a><span class=dim> [mod algos; 'series, 'bigdata, 'databases] </span><li><a href=https://lonami.dev/blog/new-computer/>My new computer</a><span class=dim> [mod hw; 'showoff] </span><li><a href=https://lonami.dev/blog/tips-outpost/>Tips for Outpost</a><span class=dim> [mod games; 'tips] </span><li><a href=https://lonami.dev/blog/ctypes-and-windows/>Python ctypes and Windows</a><span class=dim> [mod sw; 'python, 'ffi, 'windows] </span><li><a href=https://lonami.dev/blog/pixel-dungeon/>Shattered Pixel Dungeon</a><span class=dim> [mod games; 'tips] </span><li><a href=https://lonami.dev/blog/installing-nixos-2/>Installing NixOS, Take 2</a><span class=dim> [mod sw; 'os, 'nixos] </span><li><a href=https://lonami.dev/blog/breaking-ror/>Breaking Risk of Rain</a><span class=dim> [mod games; 'tips] </span><li><a href=https://lonami.dev/blog/world-edit/>WorldEdit Commands</a><span class=dim> [mod games; 'minecraft, 'worldedit, 'tips] </span><li><a href=https://lonami.dev/blog/asyncio/>An Introduction to Asyncio</a><span class=dim> [mod sw; 'python, 'asyncio] </span><li><a href=https://lonami.dev/blog/posts/>Atemporal Blog Posts</a><span class=dim> [mod algos; 'algorithms, 'culture, 'debate, 'foodforthought, 'graphics, 'optimization] </span><li><a href=https://lonami.dev/blog/graphs/>Graphs</a><span class=dim> [mod algos; 'graphs] </span><li><a href=https://lonami.dev/blog/installing-nixos/>Installing NixOS</a><span class=dim> [mod sw; 'os, 'nixos] </span></ul><script> const WELCOME_EN = 'Welcome to my blog!' const WELCOME_ES = '¡Bienvenido a mi blog!' const APOLOGIES = "ok sorry i'll stop"
M
blog/woce-1/index.html
→
blog/woce-1/index.html
@@ -1,4 +1,4 @@
-<!DOCTYPE html><html lang=en><head><meta charset=utf-8><meta name=description content="Official Lonami's website"><meta name=viewport content="width=device-width, initial-scale=1.0, user-scalable=yes"><title> Writing our own Cheat Engine: Introduction | Lonami's Blog </title><link rel=stylesheet href=/style.css><body><article><nav class=sections><ul class=left><li><a href=/>lonami's site</a><li><a href=/blog class=selected>blog</a><li><a href=/golb>golb</a></ul><div class=right><a href=https://github.com/LonamiWebs><img src=/img/github.svg alt=github></a><a href=/blog/atom.xml><img src=/img/rss.svg alt=rss></a></div></nav><main><h1 class=title>Writing our own Cheat Engine: Introduction</h1><div class=time><p>2021-02-07</div><p>This is part 1 on the <em>Writing our own Cheat Engine</em> series.<p><a href=https://cheatengine.org/>Cheat Engine</a> is a tool designed to modify single player games and contains other useful tools within itself that enable its users to debug games or other applications. It comes with a memory scanner, (dis)assembler, inspection tools and a handful other things. In this series, we will be writing our own tiny Cheat Engine capable of solving all steps of the tutorial, and diving into how it all works underneath.<p>Needless to say, we're doing this for private and educational purposes only. One has to make sure to not violate the EULA or ToS of the specific application we're attaching to. This series, much like cheatengine.org, does not condone the illegal use of the code shared.<p>Cheat Engine is a tool for Windows, so we will be developing for Windows as well. However, you can also <a href=https://stackoverflow.com/q/12977179/4759433>read memory from Linux-like systems</a>. <a href=https://github.com/scanmem/scanmem>GameConqueror</a> is a popular alternative to Cheat Engine on Linux systems, so if you feel adventurous, you could definitely follow along too! The techniques shown in this series apply regardless of how we read memory from a process. You will learn a fair bit about doing FFI in Rust too.<p>We will be developing the application in Rust, because it enables us to interface with the Windows API easily, is memory safe (as long as we're careful with <code>unsafe</code>!), and is speedy (we will need this for later steps in the Cheat Engine tutorial). You could use any language of your choice though. For example, <a href=https://lonami.dev/blog/ctypes-and-windows/>Python also makes it relatively easy to use the Windows API</a>. You don't need to be a Rust expert to follow along, but this series assumes some familiarity with C-family languages. Slightly advanced concepts like the use of <code>unsafe</code> or the <code>MaybeUninit</code> type will be briefly explained. What a <code>fn</code> is or what <code>let</code> does will not be explained.<p><a href=https://github.com/cheat-engine/cheat-engine/>Cheat Engine's source code</a> is mostly written in Pascal and C. And it's <em>a lot</em> of code, with a very flat project structure, and files ranging in the thousand lines of code each. It's daunting<sup class=footnote-reference><a href=#1>1</a></sup>. It's a mature project, with a lot of knowledge encoded in the code base, and a lot of features like distributed scanning or an entire disassembler. Unfortunately, there's not a lot of comments. For these reasons, I'll do some guesswork when possible as to how it's working underneath, rather than actually digging into what Cheat Engine is actually doing.<p>With that out of the way, let's get started!<h2 id=welcome-to-the-cheat-engine-tutorial>Welcome to the Cheat Engine Tutorial</h2><details open><summary>Cheat Engine Tutorial: Step 1</summary> <blockquote><p>This tutorial will teach you the basics of cheating in video games. It will also show you foundational aspects of using Cheat Engine (or CE for short). Follow the steps below to get started.<ol><li>Open Cheat Engine if it currently isn't running.<li>Click on the "Open Process" icon (it's the top-left icon with the computer on it, below "File".).<li>With the Process List window now open, look for this tutorial's process in the list. It will look something like > "00001F98-Tutorial-x86_64.exe" or "0000047C-Tutorial-i386.exe". (The first 8 numbers/letters will probably be different.)<li>Once you've found the process, click on it to select it, then click the "Open" button. (Don't worry about all the > other buttons right now. You can learn about them later if you're interested.)</ol><p>Congratulations! If you did everything correctly, the process window should be gone with Cheat Engine now attached to the > tutorial (you will see the process name towards the top-center of CE).<p>Click the "Next" button below to continue, or fill in the password and click the "OK" button to proceed to that step.)<p>If you're having problems, simply head over to forum.cheatengine.org, then click on "Tutorials" to view beginner-friendly > guides!</blockquote></details><h2 id=enumerating-processes>Enumerating processes</h2><p>Our first step is attaching to the process we want to work with. But we need a way to find that process in the first place! Having to open the task manager, look for the process we care about, noting down the process ID (PID), and slapping it in the source code is not satisfying at all. Instead, let's enumerate all the processes from within the program, and let the user select one by typing its name.<p>From a quick <a href=https://ddg.gg/winapi%20enumerate%20all%20processes>DuckDuckGo search</a>, we find an official tutorial for <a href=https://docs.microsoft.com/en-us/windows/win32/psapi/enumerating-all-processes>Enumerating All Processes</a>, which leads to the <a href=https://docs.microsoft.com/en-us/windows/win32/api/psapi/nf-psapi-enumprocesses><code>EnumProcesses</code></a> call. Cool! Let's slap in the <a href=https://crates.io/crates/winapi><code>winapi</code></a> crate on <code>Cargo.toml</code>, because I don't want to write all the definitions by myself:<pre><code class=language-toml data-lang=toml>[dependencies] +<!DOCTYPE html><html lang=en><head><meta charset=utf-8><meta name=description content="Official Lonami's website"><meta name=viewport content="width=device-width, initial-scale=1.0, user-scalable=yes"><title> Writing our own Cheat Engine: Introduction | Lonami's Blog </title><link rel=stylesheet href=/style.css><body><article><nav class=sections><ul class=left><li><a href=/>lonami's site</a><li><a href=/blog class=selected>blog</a><li><a href=/golb>golb</a></ul><div class=right><a href=https://github.com/LonamiWebs><img src=/img/github.svg alt=github></a><a href=/blog/atom.xml><img src=/img/rss.svg alt=rss></a></div></nav><main><h1 class=title>Writing our own Cheat Engine: Introduction</h1><div class=time><p>2021-02-07<p>last updated 2021-02-12</div><p>This is part 1 on the <em>Writing our own Cheat Engine</em> series.<p><a href=https://cheatengine.org/>Cheat Engine</a> is a tool designed to modify single player games and contains other useful tools within itself that enable its users to debug games or other applications. It comes with a memory scanner, (dis)assembler, inspection tools and a handful other things. In this series, we will be writing our own tiny Cheat Engine capable of solving all steps of the tutorial, and diving into how it all works underneath.<p>Needless to say, we're doing this for private and educational purposes only. One has to make sure to not violate the EULA or ToS of the specific application we're attaching to. This series, much like cheatengine.org, does not condone the illegal use of the code shared.<p>Cheat Engine is a tool for Windows, so we will be developing for Windows as well. However, you can also <a href=https://stackoverflow.com/q/12977179/4759433>read memory from Linux-like systems</a>. <a href=https://github.com/scanmem/scanmem>GameConqueror</a> is a popular alternative to Cheat Engine on Linux systems, so if you feel adventurous, you could definitely follow along too! The techniques shown in this series apply regardless of how we read memory from a process. You will learn a fair bit about doing FFI in Rust too.<p>We will be developing the application in Rust, because it enables us to interface with the Windows API easily, is memory safe (as long as we're careful with <code>unsafe</code>!), and is speedy (we will need this for later steps in the Cheat Engine tutorial). You could use any language of your choice though. For example, <a href=https://lonami.dev/blog/ctypes-and-windows/>Python also makes it relatively easy to use the Windows API</a>. You don't need to be a Rust expert to follow along, but this series assumes some familiarity with C-family languages. Slightly advanced concepts like the use of <code>unsafe</code> or the <code>MaybeUninit</code> type will be briefly explained. What a <code>fn</code> is or what <code>let</code> does will not be explained.<p><a href=https://github.com/cheat-engine/cheat-engine/>Cheat Engine's source code</a> is mostly written in Pascal and C. And it's <em>a lot</em> of code, with a very flat project structure, and files ranging in the thousand lines of code each. It's daunting<sup class=footnote-reference><a href=#1>1</a></sup>. It's a mature project, with a lot of knowledge encoded in the code base, and a lot of features like distributed scanning or an entire disassembler. Unfortunately, there's not a lot of comments. For these reasons, I'll do some guesswork when possible as to how it's working underneath, rather than actually digging into what Cheat Engine is actually doing.<p>With that out of the way, let's get started!<h2 id=welcome-to-the-cheat-engine-tutorial>Welcome to the Cheat Engine Tutorial</h2><details open><summary>Cheat Engine Tutorial: Step 1</summary> <blockquote><p>This tutorial will teach you the basics of cheating in video games. It will also show you foundational aspects of using Cheat Engine (or CE for short). Follow the steps below to get started.<ol><li>Open Cheat Engine if it currently isn't running.<li>Click on the "Open Process" icon (it's the top-left icon with the computer on it, below "File".).<li>With the Process List window now open, look for this tutorial's process in the list. It will look something like > "00001F98-Tutorial-x86_64.exe" or "0000047C-Tutorial-i386.exe". (The first 8 numbers/letters will probably be different.)<li>Once you've found the process, click on it to select it, then click the "Open" button. (Don't worry about all the > other buttons right now. You can learn about them later if you're interested.)</ol><p>Congratulations! If you did everything correctly, the process window should be gone with Cheat Engine now attached to the > tutorial (you will see the process name towards the top-center of CE).<p>Click the "Next" button below to continue, or fill in the password and click the "OK" button to proceed to that step.)<p>If you're having problems, simply head over to forum.cheatengine.org, then click on "Tutorials" to view beginner-friendly > guides!</blockquote></details><h2 id=enumerating-processes>Enumerating processes</h2><p>Our first step is attaching to the process we want to work with. But we need a way to find that process in the first place! Having to open the task manager, look for the process we care about, noting down the process ID (PID), and slapping it in the source code is not satisfying at all. Instead, let's enumerate all the processes from within the program, and let the user select one by typing its name.<p>From a quick <a href=https://ddg.gg/winapi%20enumerate%20all%20processes>DuckDuckGo search</a>, we find an official tutorial for <a href=https://docs.microsoft.com/en-us/windows/win32/psapi/enumerating-all-processes>Enumerating All Processes</a>, which leads to the <a href=https://docs.microsoft.com/en-us/windows/win32/api/psapi/nf-psapi-enumprocesses><code>EnumProcesses</code></a> call. Cool! Let's slap in the <a href=https://crates.io/crates/winapi><code>winapi</code></a> crate on <code>Cargo.toml</code>, because I don't want to write all the definitions by myself:<pre><code class=language-toml data-lang=toml>[dependencies] winapi = { version = "0.3.9", features = ["psapi"] } </code></pre><p>Because <a href=https://docs.microsoft.com/en-us/windows/win32/api/psapi/nf-psapi-enumprocesses><code>EnumProcesses</code></a> is in <code>Psapi.h</code> (you can see this in the online page of its documentation), we know we'll need the <code>psapi</code> crate feature. Another option is to search for it in the <a href=https://docs.rs/winapi/><code>winapi</code> documentation</a> and noting down the parent module where its stored.<p>The documentation for the method has the following remark:<blockquote><p>It is a good idea to use a large array, because it is hard to predict how many processes there will be at the time you call <strong>EnumProcesses</strong>.</blockquote><p><em>Sidenote: reading the documentation for the methods we'll use from the Windows API is extremely important. There's a lot of gotchas involved, so we need to make sure we're extra careful.</em><p>1024 is a pretty big number, so let's go with that:<pre><code class=language-rust data-lang=rust>use std::io; use std::mem;@@ -164,4 +164,4 @@ 4620: firefox.exe
7964: cargo.exe 10052: cargo.exe 5756: memo.exe -</code></pre><p>Hooray 🎉! There's some processes we can't open, but that's because they're system processes. Security works!<h2 id=finale>Finale</h2><p>That was a fairly long post when all we did was print a bunch of pids and their corresponding name. But in all fairness, we also laid out a good foundation for what's coming next.<p>You can <a href=https://github.com/lonami/memo>obtain the code for this post</a> over at my GitHub. At the end of every post, the last commit will be tagged, so you can <code>git checkout step1</code> to see the final code for any blog post.<p>In the next post, we'll tackle the second step of the tutorial: Exact Value scanning.<h3 id=footnotes>Footnotes</h3><div class=footnote-definition id=1><sup class=footnote-definition-label>1</sup><p>You could say I simply love reinventing the wheel, which I do, but in this case, the codebase contains <em>far</em> more features than we're interested in. The (apparent) lack of structure and documentation regarding the code, along with the unfortunate <a href=https://github.com/cheat-engine/cheat-engine/issues/60>lack of license</a> for the source code, make it a no-go. There's a license, but I think that's for the distributed program itself.</div><div class=footnote-definition id=2><sup class=footnote-definition-label>2</sup><p>If it turns out that there are more than 1024 processes, our code will be unaware of those extra processes. The documentation suggests to perform the call again with a larger buffer if <code>count == provided capacity</code>, but given I have under 200 processes on my system, it seems unlikely we'll reach this limit. If you're worried about hitting this limit, simply use a larger limit or retry with a larger vector.</div><div class=footnote-definition id=3><sup class=footnote-definition-label>3</sup><p>C code would likely use <a href=https://docs.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-globalalloc><code>GlobalAlloc</code></a> here, but Rust's <code>Vec</code> handles the allocation for us, making the code both simpler and more idiomatic. In general, if you see calls to <code>GlobalAlloc</code> when porting some code to Rust, you can probably replace it with a <code>Vec</code>.</div><div class=footnote-definition id=4><sup class=footnote-definition-label>4</sup><p>This will be a recurring theme.</div><div class=footnote-definition id=5><sup class=footnote-definition-label>5</sup><p>…and similar to <code>EnumProcesses</code>, if the name doesn't fit in our buffer, the result will be truncated.</div></main><footer><div><p>Share your thoughts, or simply come hang with me <a href=https://t.me/LonamiWebs><img src=/img/telegram.svg alt=Telegram></a> <a href=mailto:totufals@hotmail.com><img src=/img/mail.svg alt=Mail></a></div></footer></article><p class=abyss>Glaze into the abyss… Oh hi there!+</code></pre><p>Hooray 🎉! There's some processes we can't open, but that's because they're system processes. Security works!<h2 id=finale>Finale</h2><p>That was a fairly long post when all we did was print a bunch of pids and their corresponding name. But in all fairness, we also laid out a good foundation for what's coming next.<p>You can <a href=https://github.com/lonami/memo>obtain the code for this post</a> over at my GitHub. At the end of every post, the last commit will be tagged, so you can <code>git checkout step1</code> to see the final code for any blog post.<p>In the <a href=/blog/woce-2>next post</a>, we'll tackle the second step of the tutorial: Exact Value scanning.<h3 id=footnotes>Footnotes</h3><div class=footnote-definition id=1><sup class=footnote-definition-label>1</sup><p>You could say I simply love reinventing the wheel, which I do, but in this case, the codebase contains <em>far</em> more features than we're interested in. The (apparent) lack of structure and documentation regarding the code, along with the unfortunate <a href=https://github.com/cheat-engine/cheat-engine/issues/60>lack of license</a> for the source code, make it a no-go. There's a license, but I think that's for the distributed program itself.</div><div class=footnote-definition id=2><sup class=footnote-definition-label>2</sup><p>If it turns out that there are more than 1024 processes, our code will be unaware of those extra processes. The documentation suggests to perform the call again with a larger buffer if <code>count == provided capacity</code>, but given I have under 200 processes on my system, it seems unlikely we'll reach this limit. If you're worried about hitting this limit, simply use a larger limit or retry with a larger vector.</div><div class=footnote-definition id=3><sup class=footnote-definition-label>3</sup><p>C code would likely use <a href=https://docs.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-globalalloc><code>GlobalAlloc</code></a> here, but Rust's <code>Vec</code> handles the allocation for us, making the code both simpler and more idiomatic. In general, if you see calls to <code>GlobalAlloc</code> when porting some code to Rust, you can probably replace it with a <code>Vec</code>.</div><div class=footnote-definition id=4><sup class=footnote-definition-label>4</sup><p>This will be a recurring theme.</div><div class=footnote-definition id=5><sup class=footnote-definition-label>5</sup><p>…and similar to <code>EnumProcesses</code>, if the name doesn't fit in our buffer, the result will be truncated.</div></main><footer><div><p>Share your thoughts, or simply come hang with me <a href=https://t.me/LonamiWebs><img src=/img/telegram.svg alt=Telegram></a> <a href=mailto:totufals@hotmail.com><img src=/img/mail.svg alt=Mail></a></div></footer></article><p class=abyss>Glaze into the abyss… Oh hi there!
A
blog/woce-2/index.html
@@ -0,0 +1,249 @@
+<!DOCTYPE html><html lang=en><head><meta charset=utf-8><meta name=description content="Official Lonami's website"><meta name=viewport content="width=device-width, initial-scale=1.0, user-scalable=yes"><title> Writing our own Cheat Engine: Exact Value scanning | Lonami's Blog </title><link rel=stylesheet href=/style.css><body><article><nav class=sections><ul class=left><li><a href=/>lonami's site</a><li><a href=/blog class=selected>blog</a><li><a href=/golb>golb</a></ul><div class=right><a href=https://github.com/LonamiWebs><img src=/img/github.svg alt=github></a><a href=/blog/atom.xml><img src=/img/rss.svg alt=rss></a></div></nav><main><h1 class=title>Writing our own Cheat Engine: Exact Value scanning</h1><div class=time><p>2021-02-12</div><p>This is part 2 on the <em>Writing our own Cheat Engine</em> series.<p>New around here? Skim over <a href=/blog/woce-1>part 1</a> to learn what this series is all about.<p>In the introduction, we spent a good deal of time enumerating all running processes just so we could find out the pid we cared about. With the pid now in our hands, we can do pretty much anything to its corresponding process.<p>It's now time to read the process' memory and write to it. If our process was a single-player game, this would enable us to do things like setting a very high value on the player's current health pool, making us invencible. This technique will often not work for multi-player games, because the server likely knows your true current health (the most you could probably do is make the client render an incorrect value). However, if the server is crappy and it trusts the client, then you're still free to mess around with your current health.<p>Even if we don't want to write to the process' memory, reading is still very useful. Maybe you could enhance your experience by making a custom overlay that displays useful information, or something that makes noise if it detects the life is too low, or even simulating a keyboard event to automatically recover some mana when you're running low.<p>Be warned about anti-cheat systems. Anything beyond a basic game is likely to have some protection measures in place, making the analysis more difficult (perhaps the values are scrambled in memory), or even pinging the server if it detects something fishy.<p><strong>I am not responsible for any bans!</strong> Use your brain before messing with online games, and don't ruin the fun for everyone else. If you get caught for cheating, I don't want to know about it.<p>Now that all <a href=https://www.urbandictionary.com/define.php?term=script%20kiddie>script kiddies</a> have left the room, let's proceed with the post.<h2 id=exact-value-scanning>Exact Value scanning</h2><details open><summary>Cheat Engine Tutorial: Step 2</summary> <blockquote><p>Now that you have opened the tutorial with Cheat Engine let's get on with the next step.<p>You can see at the bottom of this window is the text Health: xxx. Each time you click 'Hit me' your health gets decreased.<p>To get to the next step you have to find this value and change it to 1000<p>To find the value there are different ways, but I'll tell you about the easiest, 'Exact Value': First make sure value type is set to at least 2-bytes or 4-bytes. 1-byte will also work, but you'll run into an easy to fix problem when you've found the address and want to change it. The 8-byte may perhaps works if the bytes after the address are 0, but I wouldn't take the bet. Single, double, and the other scans just don't work, because they store the value in a different way.<p>When the value type is set correctly, make sure the scantype is set to 'Exact Value'. Then fill in the number your health is in the value box. And click 'First Scan'. After a while (if you have a extremely slow pc) the scan is done and the results are shown in the list on the left<p>If you find more than 1 address and you don't know for sure which address it is, click 'Hit me', fill in the new health value into the value box, and click 'Next Scan'. Repeat this until you're sure you've found it. (that includes that there's only 1 address in the list.....)<p>Now double click the address in the list on the left. This makes the address pop-up in the list at the bottom, showing you the current value. Double click the value, (or select it and press enter), and change the value to 1000.<p>If everything went ok the next button should become enabled, and you're ready for the next step.<p>Note: If you did anything wrong while scanning, click "New Scan" and repeat the scanning again. Also, try playing around with the value and click 'hit me'</blockquote></details><h2 id=our-first-scan>Our First Scan</h2><p>The Cheat Engine tutorial talks about "value types" and "scan types" like "exact value".<p>The <strong>value types</strong> will help us narrow down <em>what</em> we're looking for. For example, the integer type <code>i32</code> is represented in memory as 32 bits, or 4 bytes. However, <code>f32</code> is <em>also</em> represented by 4 bytes, and so is <code>u32</code>. Or perhaps the 4 bytes represent RGBA values of a color! So any 4 bytes in memory can be interpreted in many ways, and it's up to us to decide which way we interpret the bytes in.<p>When programming, numbers which are 32-bit wide are common, as they're a good (and fast) size to work with. Scanning for this type is often a good bet. For positive numbers, <code>i32</code> is represented the same as <code>u32</code> in memory, so even if the value turns out to not be signed, the scan is likely to work. Focusing on <code>i32</code> will save us from scanning for <code>f32</code> or even other types, like interpreting 8 bytes for <code>i64</code>, <code>f64</code>, or less bytes like <code>i16</code>.<p>The <strong>scan types</strong> will help us narrow down <em>how</em> we're looking for a value. Scanning for an exact value means what you think it does: interpret all 4 bytes in the process' memory as our value type, and check if they exactly match our value. This will often yield a lot of candidates, but it will be enough to get us started. Variations of the exact scan include checking for all values below a threshold, above, in between, or even just… unknown.<p>What's the point of scanning for unknown values if <em>everything</em> in memory is unknown? Sometimes you don't have a concrete value. Maybe your health pool is a bar and it nevers tell you how much health you actually have, just a visual indicator of your percentage left, even if the health is not stored as a percentage. As we will find later on, scanning for unknown values is more useful than it might appear at first.<p>We can access the memory of our own program by guessing random pointers and trying to read from them. But Windows isolates the memory of each program, so no pointer we could ever guess will let us read from the memory of another process. Luckily for us, searching for "read process memory winapi" leads us to the <a href=https://docs.microsoft.com/en-us/windows/win32/api/memoryapi/nf-memoryapi-readprocessmemory><code>ReadProcessMemory</code></a> function. Spot on.<pre><code class=language-rust data-lang=rust>pub fn read_memory(&self, addr: usize, n: usize) -> io::Result<Vec<u8>> { + todo!() +} +</code></pre><p>Much like trying to dereference a pointer pointing to released memory or even null, reading from an arbitrary address can fail for the same reasons (and more). We will want to signal this with <code>io::Result</code>. It's funny to note that, even though we're doing something that seems wildly unsafe (reading arbitrary memory, even if the other process is mutating it at the same time), the function is perfectly safe. If we cannot read something, it will return <code>Err</code>, but if it succeeds, it has taken a snapshot of the memory of the process, and the returned value will be correctly initialized.<p>The function will be defined inside our <code>impl Process</code>, since it conveniently holds an open handle to the process in question. It takes <code>&self</code>, because we do not need to mutate anything in the <code>Process</code> instance. After adding the <code>memoryapi</code> feature to <code>Cargo.toml</code>, we can perform the call:<pre><code class=language-rust data-lang=rust>let mut buffer = Vec::<u8>::with_capacity(n); +let mut read = 0; + +// SAFETY: the buffer points to valid memory, and the buffer size is correctly set. +if unsafe { + winapi::um::memoryapi::ReadProcessMemory( + self.handle.as_ptr(), + addr as *const _, + buffer.as_mut_ptr().cast(), + buffer.capacity(), + &mut read, + ) +} == FALSE +{ + Err(io::Error::last_os_error()) +} else { + // SAFETY: the call succeeded and `read` contains the amount of bytes written. + unsafe { buffer.set_len(read as usize) }; + Ok(buffer) +} +</code></pre><p>Great! But the address space is somewhat large. 64 bits large. Eighteen quintillion, four hundred forty-six quadrillion, seven hundred forty-four trillion, seventy-three billion, seven hundred nine million, five hundred fifty-one thousand, six hundred sixteen<sup class=footnote-reference><a href=#1>1</a></sup> large. You gave up reading that, didn't you? Anyway, 18'446'744'073'709'551'616 is a <em>big</em> number.<p>I am not willing to wait for the program to scan over so many values. I don't even have 16 <a href=https://en.wikipedia.org/wiki/Orders_of_magnitude_(data)>exbibytes</a> of RAM installed on my laptop yet<sup class=footnote-reference><a href=#2>2</a></sup>! What's up with that?<h2 id=memory-regions>Memory regions</h2><p>The program does not actually have all that memory allocated (surprise!). Random-guessing an address is extremely likely to point out to invalid memory. Reading from the start of the address space all the way to the end would not be any better. And we <strong>need</strong> to do better.<p>We need to query for the memory regions allocated to the program. For this purpose we can use <a href=https://docs.microsoft.com/en-us/windows/win32/api/memoryapi/nf-memoryapi-virtualqueryex><code>VirtualQueryEx</code></a>.<blockquote><p>Retrieves information about a range of pages within the virtual address space of a specified process.</blockquote><p>We have enumerated things before, and this function is not all that different.<pre><code class=language-rust data-lang=rust>fn memory_regions(&self) -> io::Result<winapi::um::winnt::MEMORY_BASIC_INFORMATION> { + let mut info = MaybeUninit::uninit(); + + // SAFETY: the info structure points to valid memory. + let written = unsafe { + winapi::um::memoryapi::VirtualQueryEx( + self.handle.as_ptr(), + std::ptr::null(), + info.as_mut_ptr(), + mem::size_of::<winapi::um::winnt::MEMORY_BASIC_INFORMATION>(), + ) + }; + if written == 0 { + Err(io::Error::last_os_error()) + } else { + // SAFETY: a non-zero amount was written to the structure + Ok(unsafe { info.assume_init() }) + } +} +</code></pre><p>We start with a base address of zero<sup class=footnote-reference><a href=#3>3</a></sup> (<code>std::ptr::null()</code>), and ask the function to tell us what's in there. Let's try it out, with the <code>impl-debug</code> crate feature in <code>Cargo.toml</code>:<pre><code class=language-rust data-lang=rust>dbg!(process.memory_regions()); +</code></pre><pre><code>>cargo run +Compiling memo v0.1.0 + +error[E0277]: `winapi::um::winnt::MEMORY_BASIC_INFORMATION` doesn't implement `std::fmt::Debug` + --> src\main.rs:185:5 + | +185 | dbg!(process.memory_regions()); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `winapi::um::winnt::MEMORY_BASIC_INFORMATION` cannot be formatted using `{:?}` because it doesn't implement `std::fmt::Debug` +</code></pre><p>That's annoying. It seems not everything has an <code>impl std::fmt::Debug</code>, and <a href=https://github.com/retep998/winapi-rs/issues/548#issuecomment-355278090>you're supposed to send a PR</a> if you want it to have debug, even if the <code>impl-debug</code> feature is set. I'm surprised they don't auto-generate all of this and have to rely on manually adding <code>Debug</code> as needed? Oh well, let's get rid of the feature and print it out ourselves:<pre><code>eprintln!( + "Region: + BaseAddress: {:?} + AllocationBase: {:?} + AllocationProtect: {:?} + RegionSize: {:?} + State: {:?} + Protect: {:?} + Type: {:?}", + region.BaseAddress, + region.AllocationBase, + region.AllocationProtect, + region.RegionSize, + region.State, + region.Protect, + region.Type, +); +</code></pre><p>Hopefully we don't need to do this often:<pre><code>>cargo run + Compiling memo v0.1.0 + Finished dev [unoptimized + debuginfo] target(s) in 0.60s + Running `target\debug\memo.exe` + +Region: + BaseAddress: 0x0 + AllocationBase: 0x0 + AllocationProtect: 0 + RegionSize: 65536 + State: 65536 + Protect: 1 + Type: 0 +</code></pre><p>Awesome! There is a region at <code>null</code>, and the <code>AllocationProtect</code> of zero indicates that "the caller does not have access" when the region was created. However, <code>Protect</code> is <code>1</code>, and that is the <em>current</em> protection level. A value of one indicates <a href=https://docs.microsoft.com/en-us/windows/win32/memory/memory-protection-constants><code>PAGE_NOACCESS</code></a>:<blockquote><p>Disables all access to the committed region of pages. An attempt to read from, write to, or execute the committed region results in an access violation.</blockquote><p>Now that we know that the first region starts at 0 and has a size of 64 KiB, we can simply query for the page at <code>(current base + current size)</code> to fetch the next region. Essentially, we want to loop until it fails, after which we'll know there are no more pages<sup class=footnote-reference><a href=#4>4</a></sup>:<pre><code class=language-rust data-lang=rust>pub fn memory_regions(&self) -> Vec<winapi::um::winnt::MEMORY_BASIC_INFORMATION> { + let mut base = 0; + let mut regions = Vec::new(); + let mut info = MaybeUninit::uninit(); + + loop { + // SAFETY: the info structure points to valid memory. + let written = unsafe { + winapi::um::memoryapi::VirtualQueryEx( + self.handle.as_ptr(), + base as *const _, + info.as_mut_ptr(), + mem::size_of::<winapi::um::winnt::MEMORY_BASIC_INFORMATION>(), + ) + }; + if written == 0 { + break regions; + } + // SAFETY: a non-zero amount was written to the structure + let info = unsafe { info.assume_init() }; + base = info.BaseAddress as usize + info.RegionSize; + regions.push(info); + } +} +</code></pre><p><code>RegionSize</code> is:<blockquote><p>The size of the region beginning at the base address in which all pages have identical attributes, in bytes.</blockquote><p>…which also hints that the value we want is "base address", not the "allocation base". With these two values, we can essentially iterate over all the page ranges:<pre><code class=language-rust data-lang=rust>dbg!(process.memory_regions().len()); +</code></pre><pre><code>>cargo run + Compiling memo v0.1.0 + Finished dev [unoptimized + debuginfo] target(s) in 0.63s + Running `target\debug\memo.exe` + +[src\main.rs:189] process.memory_regions().len() = 367 +</code></pre><p>That's a lot of pages!<h2 id=protection-levels>Protection levels</h2><p>Let's try to narrow the amount of pages down. How many pages aren't <code>PAGE_NOACCESS</code>?<pre><code class=language-rust data-lang=rust>dbg!(process + .memory_regions() + .into_iter() + .filter(|p| p.Protect != winapi::um::winnt::PAGE_NOACCESS) + .count()); +</code></pre><pre><code>295 +</code></pre><p>Still a fair bit! Most likely, there are just a few interleaved <code>NOACCESS</code> pages, and the rest are allocated each with different protection levels. How much memory do we need to scan through?<pre><code class=language-rust data-lang=rust>dbg!(process + .memory_regions() + .into_iter() + .filter(|p| p.Protect != winapi::um::winnt::PAGE_NOACCESS) + .map(|p| p.RegionSize) + .sum::<usize>()); +</code></pre><pre><code>4480434176 +</code></pre><p>Wait, what? What do you mean over 4 GiB? The Task Manager claims that the Cheat Engine Tutorial is only using 2.1 MB worth of RAM! Perhaps we can narrow down the <a href=https://docs.microsoft.com/en-us/windows/win32/memory/memory-protection-constants>protection levels</a> a bit more. If you look at the scan options in Cheat Engine, you will notice the "Memory Scan Options" groupbox. By default, it only scans for memory that is writable, and doesn't care if it's executable or not:<pre><code class=language-rust data-lang=rust>let mask = winnt::PAGE_EXECUTE_READWRITE + | winnt::PAGE_EXECUTE_WRITECOPY + | winnt::PAGE_READWRITE + | winnt::PAGE_WRITECOPY; + +dbg!(process + .memory_regions() + .into_iter() + .filter(|p| (p.Protect & mask) != 0) + .map(|p| p.RegionSize) + .sum::<usize>()); +</code></pre><p>Each memory protection level has its own bit, so we can OR them all together to have a single mask. When ANDing this mask with the protection level, if any bit is set, it will be non-zero, meaning we want to keep this region.<p>Don't ask me why there isn't a specific bit for "write", "read", "execute", and there are only bits for combinations. I guess this way Windows forbids certain combinations.<pre><code>2580480 +</code></pre><p>Hey, that's close to the value shown by the Task Manager! A handfull of megabytes is a lot more manageable than 4 entire gigabytes.<h2 id=actually-running-our-first-scan>Actually running our First Scan</h2><p>Okay, we have all the memory regions from which the program can read, write, and execute. Now we also can read the memory in these regions:<pre><code class=language-rust data-lang=rust>let regions = process + .memory_regions() + .into_iter() + .filter(|p| (p.Protect & mask) != 0) + .collect::<Vec<_>>(); + +println!("Scanning {} memory regions", regions.len()); + +regions.into_iter().for_each(|region| { + match process.read_memory(region.BaseAddress as _, region.RegionSize) { + Ok(memory) => todo!(), + Err(err) => eprintln!( + "Failed to read {} bytes at {:?}: {}", + region.RegionSize, region.BaseAddress, err, + ), + } +}) +</code></pre><p>All that's left is for us to scan for a target value. To do this, we want to iterate over all the <a href=https://doc.rust-lang.org/stable/std/primitive.slice.html#method.windows><code>slice::windows</code></a> of size equal to the size of our scan type.<pre><code class=language-rust data-lang=rust>let target: i32 = ...; +let target = target.to_ne_bytes(); + +... + +memory + .windows(target.len()) + .enumerate() + .for_each(|(offset, window)| { + if window == target { + println!( + "Found exact value at [{:?}+{:x}]", + region.BaseAddress, offset + ); + } + }) +</code></pre><p>We convert the 32-bit exact target value to its memory representation as a byte array in <a href=https://doc.rust-lang.org/stable/std/primitive.i32.html#method.to_ne_bytes>native byte order</a>. This way we can compare the target bytes with the window bytes. Another option is to interpret the window bytes as an <code>i32</code> with <code>from_be_bytes</code>, but <code>slice::windows</code> gives us slices of type <code>&[u8]</code>, and <code>from_be_bytes</code> wants an <code>[u8; 4]</code> array, so it's a bit more annoying to convert.<p>This is enough to find the value in the process' memory!<pre><code>Found exact value at [0x10000+aec] +Failed to read 12288 bytes at 0x13f8000: Only part of a ReadProcessMemory or WriteProcessMemory request was completed. (os error 299) +Found exact value at [0x14f0000+3188] +Found exact value at [0x14f0000+ac74] +... +Found exact value at [0x10030e000+1816] +Found exact value at [0x7ff8f7b93000+441a] +... +Found exact value at [0x7ff8fb381000+4023] +</code></pre><p>The tutorial starts out with health "100", which is what I scanned. Apparently, there are nearly a hundred of <code>100</code>-valued integers stored in the memory of the tutorial.<p>Attentive readers will notice that some values are located at an offset modulo 4. In Cheat Engine, this is known as "Fast Scan", which is enabled by default with an alignment of 4. Most of the time, values are aligned in memory, and this alignment often corresponds with the size of the type itself. For 4-byte integers, it's common that they're 4-byte aligned.<p>We can perform a fast scan ourselves with <a href=https://doc.rust-lang.org/stable/std/iter/trait.Iterator.html#method.step_by><code>step_by</code></a><sup class=footnote-reference><a href=#5>5</a></sup>:<pre><code class=language-rust data-lang=rust>memory + .windows(target.len()) + .enumerate() + .step_by(4) + .for_each(...) +</code></pre><p>As a bonus, over half the addresses are gone, so we have less results to worry about<sup class=footnote-reference><a href=#6>6</a></sup>.<h2 id=next-scan>Next Scan</h2><p>The first scan gave us way too many results. We have no way to tell which is the correct one, as they all have the same value. What we need to do is a <em>second</em> scan at the <em>locations we just found</em>. This way, we can get a second reading, and compare it against a new value. If it's the same, we're on good track, and if not, we can discard a location. Repeating this process lets us cut the hundreds of potential addresses to just a handful of them.<p>For example, let's say we're scanning our current health of <code>100</code> in a game. This gives us over a hundred addresses that point to the value of <code>100</code>. If we go in-game and get hit<sup class=footnote-reference><a href=#7>7</a></sup> by some enemy and get our health down to, say, <code>99</code> (we have a lot of defense), we can then read the memory at the hundred memory locations we found before. If this second reading is not <code>99</code>, we know the address does not actually point to our health pool and it just happened to also contain a <code>100</code> on the first scan. This address can be removed from the list of potential addresses pointing to our health.<p>Let's do that:<pre><code class=language-rust data-lang=rust>let mut locations = Vec::with_capacity(regions.len()); +// -snip- +locations.push(region.BaseAddress as usize + offset); +// -snip- +let target: i32 = ...; +let target = target.to_ne_bytes(); +locations.retain(|addr| match process.read_memory(*addr, target.len()) { + Ok(memory) => memory == target, + Err(_) => false, +}); + +println!("Now have {} locations", locations.len()); +</code></pre><p>We create a vector to store all the locations the first scan finds, and then retain those that match a second target value. You may have noticed that we perform a memory read, and thus a call to the Windows API, for every single address. With a hundred locations to read from, this is not a big deal, but oftentimes you will have tens of thousands of addresses. For the time being, we will not worry about this inefficiency, but we will get back to it once it matters:<pre><code>Scanning 98 memory regions +Which exact value to scan for?: 100 +Failed to read 12288 bytes at 0x13f8000: Only part of a ReadProcessMemory or WriteProcessMemory request was completed. (os error 299) +... +Found 49 locations +Which exact value to scan for next?: 99 +Now have 1 locations +</code></pre><p>Sweet! In a real-world scenario, you will likely need to perform these additional scans a couple of times, and even then, there may be more than one value left no matter what.<p>For good measure, we'll wrap our <code>retain</code> in a <code>while</code> loop<sup class=footnote-reference><a href=#8>8</a></sup>:<pre><code class=language-rust data-lang=rust>while locations.len() != 1 { + let target: i32 = ...; + let target = target.to_ne_bytes(); + locations.retain(...); +} +</code></pre><h2 id=modifying-memory>Modifying memory</h2><p>Now that we have very likely locations pointing to our current health in memory, all that's left is writing our new desired value to gain infinite health<sup class=footnote-reference><a href=#9>9</a></sup>. Much like there's a call to <code>ReadProcessMemory</code>, there's a different one to <a href=https://docs.microsoft.com/en-us/windows/win32/api/memoryapi/nf-memoryapi-writeprocessmemory><code>WriteProcessMemory</code></a>. Its usage is straightforward:<pre><code class=language-rust data-lang=rust>pub fn write_memory(&self, addr: usize, value: &[u8]) -> io::Result<usize> { + let mut written = 0; + + // SAFETY: the input value buffer points to valid memory. + if unsafe { + winapi::um::memoryapi::WriteProcessMemory( + self.handle.as_ptr(), + addr as *mut _, + value.as_ptr().cast(), + value.len(), + &mut written, + ) + } == FALSE + { + Err(io::Error::last_os_error()) + } else { + Ok(written) + } +} +</code></pre><p>Similar to how writing to a file can return short, writing to a memory location could also return short. Here we mimic the API for writing files and return the number of bytes written. The documentation indicates that we could actually ignore the amount written by passing <code>ptr::null_mut()</code> as the last parameter, but it does no harm to retrieve the written count as well.<pre><code class=language-rust data-lang=rust>let new_value: i32 = ...; +locations + .into_iter() + .for_each(|addr| match process.write_memory(addr, &new_value) { + Ok(n) => eprintln!("Written {} bytes to [{:x}]", n, addr), + Err(e) => eprintln!("Failed to write to [{:x}]: {}", addr, e), + }); +</code></pre><p>And just like that:<pre><code>Now have 1 location(s) +Enter new memory value: 1000 +Failed to write to [15d8b90]: Access is denied. (os error 5) +</code></pre><p>…oh noes. Oh yeah. The documentation, which I totally didn't forget to read, mentions:<blockquote><p>The handle must have <code>PROCESS_VM_WRITE</code> and <code>PROCESS_VM_OPERATION</code> access to the process.</blockquote><p>We currently open our process with <code>PROCESS_QUERY_INFORMATION</code> and <code>PROCESS_VM_READ</code>, which is enough for reading, but not for writing. Let's adjust <code>OpenProcess</code> to accomodate for our new requirements:<pre><code class=language-rust data-lang=rust>winapi::um::processthreadsapi::OpenProcess( + winnt::PROCESS_QUERY_INFORMATION + | winnt::PROCESS_VM_READ + | winnt::PROCESS_VM_WRITE + | winnt::PROCESS_VM_OPERATION, + FALSE, + pid, +) +</code></pre><p>Behold:<pre><code>Now have 1 location(s) +Enter new memory value: 1000 +Written 4 bytes to [15d8b90] +</code></pre><p><img src=https://user-images.githubusercontent.com/6297805/107829541-3f4f2d00-6d8a-11eb-87c4-e2f2d505afbc.png alt="Tutorial complete with memo"><p>Isn't that active <em>Next</em> button just beautiful?<h2 id=finale>Finale</h2><p>This post somehow ended up being longer than part one, but look at what we've achieved! We completed a step of the Cheat Engine Tutorial <em>without using Cheat Engine</em>. Just pure Rust. Figuring out how a program works and reimplementing it yourself is a great way to learn what it's doing behind the scenes. And now that this code is yours, you can extend it as much as you like, without being constrained by Cheat Engine's UI. You can automate it as much as you want.<p>And we're not even done. The current tutorial has nine steps, and three additional graphical levels.<p>In the next post, we'll tackle the third step of the tutorial: Unknown initial value. This will pose a challenge, because with just 2 MiB of memory, storing all the 4-byte aligned locations would require 524288 addresses (<code>usize</code>, 8 bytes). This adds up to twice as much memory as the original program (4 MiB), but that's not our main concern, having to perform over five hundred thousand API calls is!<h3 id=footnotes>Footnotes</h3><div class=footnote-definition id=1><sup class=footnote-definition-label>1</sup><p>I did in fact use an online tool to spell it out for me.</div><div class=footnote-definition id=2><sup class=footnote-definition-label>2</sup><p>16 GiB is good enough for my needs. I don't think I'll ever upgrade to 16 EiB.</div><div class=footnote-definition id=3><sup class=footnote-definition-label>3</sup><p>Every address we query should have a corresponding region, even if it's not allocated or we do not have access. This is why we can query for the memory address zero to get its corresponding region.</div><div class=footnote-definition id=4><sup class=footnote-definition-label>4</sup><p>Another option is to <a href=https://docs.microsoft.com/en-us/windows/win32/api/sysinfoapi/nf-sysinfoapi-getsysteminfo><code>GetSystemInfo</code></a> to determine the <code>lpMinimumApplicationAddress</code> and <code>lpMaximumApplicationAddress</code> and only work within bounds.</div><div class=footnote-definition id=5><sup class=footnote-definition-label>5</sup><p>Memory regions are page-aligned, which is a large power of two. Our alignment of 4 is much lower than this, so we're guaranteed to start off at an aligned address.</div><div class=footnote-definition id=6><sup class=footnote-definition-label>6</sup><p>If it turns out that the value was actually misaligned, we will miss it. You will notice this if, after going through the whole process, there are no results. It could mean that either the value type is wrong, or the value type is misaligned. In the worst case, the value is not stored directly but is rather computed with something like <code>maximum - stored</code>, or XORed with some magic value, or a myriad other things.</div><div class=footnote-definition id=7><sup class=footnote-definition-label>7</sup><p>You could do this without getting hit, and just keep on repeating the scan for the same value over and over again. This does work, but the results are suboptimal, because there are also many other values that didn't change. Scanning for a changed value is a better option.</div><div class=footnote-definition id=8><sup class=footnote-definition-label>8</sup><p>You could actually just go ahead and try to modify the memory at the hundred addresses you just found, although don't be surprised if the program starts to misbehave!</div><div class=footnote-definition id=9><sup class=footnote-definition-label>9</sup><p>Okay, we cannot fit infinity in an <code>i32</code>. However, we can fit sufficiently large numbers. Like <code>1000</code>, which is enough to complete the tutorial.</div></main><footer><div><p>Share your thoughts, or simply come hang with me <a href=https://t.me/LonamiWebs><img src=/img/telegram.svg alt=Telegram></a> <a href=mailto:totufals@hotmail.com><img src=/img/mail.svg alt=Mail></a></div></footer></article><p class=abyss>Glaze into the abyss… Oh hi there!
M
sitemap.xml
→
sitemap.xml
@@ -194,7 +194,11 @@ <lastmod>2020-07-03</lastmod>
</url> <url> <loc>https://lonami.dev/blog/woce-1/</loc> - <lastmod>2021-02-07</lastmod> + <lastmod>2021-02-12</lastmod> + </url> + <url> + <loc>https://lonami.dev/blog/woce-2/</loc> + <lastmod>2021-02-12</lastmod> </url> <url> <loc>https://lonami.dev/blog/world-edit/</loc>