-1

In Cheat Engine there is function for analyse memory. The result of this analysis is types of memory bytes. I can't understand the algorithm of this analysis. How can it define that these bytes are float and these bytes are pointer?

3
  • The Cheat Engine is just guessing basing on some heuristics, the results aren't 100% accurate, for example if the structure field contains value that is a valid memory address it marks it as pointer etc.
    – morsisko
    Commented Feb 27, 2021 at 18:40
  • @morsisko What kind of heuristics can be used for identify float numbers? Commented Feb 28, 2021 at 10:25
  • Just a cursory glance, but it looks like Cheat Engine interprets data as potential floats, and relies on a human to look for either known-good values encoded as floats, or distributions which are in a compact range. Commented Feb 28, 2021 at 15:41

2 Answers 2

1

Floats can be compared against known good values or ranges.

Pointers can be identified as having values which are addressable memory in the context of program execution.

-1

Here's a way, it's not tremendously reliable, but it has the advantage of being purely bit math.

In real life, I would recommend combining this with common sense checks like these: (pseudocode)

Not a float:

  1. the rounded value of the floating point number is bigger than the integer version; or
  2. the floating point number always effectively 0; but is always changing; or

Maybe a float:

  1. the observed range of floating point values extend from 0.0 to 1.0 (1000.0, 360.0, 90.0...) or -180.0 to 180.0... or anything else that would make sense for the context
  2. the observed floating point values are always rounded (e.g. 0.0, 1.0, -1.0, 2.0) -- though this can be confused with integer values for much larger values (see rule #1 of section #1).
namespace sfinktah::math {

    constexpr uint32_t maskExponent = 0b01111111100000000000000000000000; // 0x7f800000, INFINITY
    constexpr uint32_t maskMantissa = 0b00000000011111111111111111111111; // 0x007fffff
    constexpr uint32_t maskSign     = 0b10000000000000000000000000000000; // 0x80000000
    constexpr uint32_t maskBigExp   = 0b01100000000000000000000000000000; // numbers > 3.6893488E19

    constexpr bool constexpr_is_infinite(uint32_t f)
        { return (f & (maskExponent | maskSign)) == maskExponent; }
    constexpr bool constexpr_is_nan(uint32_t f)
        { return (f & maskExponent) == maskExponent && f & maskMantissa; }
    constexpr bool constexpr_is_finite(uint32_t f)
        { return (f & maskExponent) != maskExponent; }
    constexpr bool constexpr_is_miniscule(uint32_t f)
        { return (f & maskExponent) == 0; }
    constexpr bool constexpr_is_massive(uint32_t f)
        { return (f & maskBigExp) == maskBigExp; }
    constexpr bool constexpr_is_float(uint32_t f)
        { return (f & maskExponent) && constexpr_is_finite(f)
            && !constexpr_is_massive(f) && !constexpr_is_miniscule(f); }
}

Here is an IDAPython script (though except for one line, it could work without IDA) that demonstrates the difficulties (and in some cases ease) by which one can guess whether a number is a float. Note that careful attention has to be paid to 0 which is 0 as an integer, and as a float.

import idc
from underscore import _

maskExponent = 0b01111111100000000000000000000000; # 0x7f800000, INFINITY
maskMantissa = 0b00000000011111111111111111111111; # 0x007fffff
maskSign = 0b10000000000000000000000000000000;     # 0x80000000
maskBigExp = 0b01100000000000000000000000000000;   # numbers > 3.6893488E19

def dword_as_float(f):
    """ stolen from IDAPython's idc.py """
    tmp = struct.pack("I", f)
    return struct.unpack("f", tmp)[0]

def is_infinite(f):  return (f & (maskExponent | maskSign)) == maskExponent
def is_nan(f):       return (f & maskExponent) == maskExponent and f & maskMantissa
def is_miniscule(f): return (f & maskExponent) == 0
def is_massive(f):   return (f & maskBigExp) == maskBigExp
def hex8(f):         return "0x{:08x}".format(f)

checkfns   = [ hex8, dword_as_float, is_infinite, is_nan, is_miniscule, is_massive ]
checknames = [ 'hex', 'float', 'is_infinite', 'is_nan', 'is_miniscule', 'is_massive' ]
checks     = _.zipObject(checknames, checkfns)

def get_floats(start, count, stride = 4):
    for i in range(count):
        dword = idc.get_wide_dword(start + i * stride)
        results = [(f[0], f[1](dword)) for f in checks.items()]
        r = []
        for t in results:
            if not t[0].startswith('is_'): r.append(t[1])
            elif not t[1]: continue
            else: r.append(t[0])
        print(r)

Partial output (source: a series of xmmword data definitions).

['0x3eb33333', 0.3499999940395355]
['0x41000000', 8.0]
['0x3eb33333', 0.3499999940395355]
['0x3dcccccd', 0.10000000149011612]
['0x00000000', 0.0, 'is_miniscule']
['0x00000000', 0.0, 'is_miniscule']
['0x00000000', 0.0, 'is_miniscule']
['0x00000000', 0.0, 'is_miniscule']
['0x3f4ccccd', 0.800000011920929]
['0x3d4ccccd', 0.05000000074505806]
['0x3d4ccccd', 0.05000000074505806]
['0x43800000', 256.0]
['0x3ea3d70a', 0.3199999928474426]
['0x3dcccccd', 0.10000000149011612]
['0xf7000000', -2.596148429267414e+33, 'is_massive']
['0x705e61f2', 2.752963231901203e+29, 'is_massive']

Note: lines with flags like 'is_massive' or 'is_miniscule' would fail the basic bitwise float test, sometimes incorrectly.

6
  • Please add some rationale behind these. Only Maybe #1 seems to have some basis. Commented Mar 2, 2021 at 1:10
  • @pythonpython Maybe #2 is related to the use of matrices for translation and transform of object in computer graphics, especially in games (which we can assume is a reasonable context given the use of cheat engine). The others are observations made during many years reverse engineering games and observing memory in unknown structures, usually in ReClass (a project to which I contribute). The C++ constexpr code is self-explanatory if one understands the basic principles, otherwise one is better consulting a friendly guide on how floating point numbers are structured. Commented Mar 9, 2021 at 19:04
  • Oh I'm well versed in IEEE 754 and understand the code. It's just that your approach will trigger on all sorts of field boundaries that normally occur . For example the LSB of a BE Integer, Followed by the top 3 MSB's of another BE Integer. I suppose if you know for certain the location of the value in memory and the extent. Commented Mar 10, 2021 at 2:03
  • @pythonpython Oh, that constexpr method could not possible be worse than it is. I took the 754 spec and turned it into code, and played with h-schmidt.net/FloatConverter/IEEE754.html (anyone looking to solve this problem should bookmark that site). Though the final is_float was a failure, the individual building blocks should be useful. Regarding BE ints or non-aligned storage, I think you have to limit the problem space in order to be even 50% accurate. If you can't tell whether 3F800000 is 1.0 or a BE 32831 (and that's assuming perfect alignment), it's hopeless. Commented Mar 10, 2021 at 9:47
  • I think a good avenue would be to look at an analysis of significand bits to infer whether a rounding mode is being applied. That would rely on the percentage of values which were being approximated by the floating point representation, vs being exactly represented. Commented Mar 10, 2021 at 16:24

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.