diff --git a/bindings/cs/README.md b/bindings/cs/README.md new file mode 100644 index 0000000..59f1b0a --- /dev/null +++ b/bindings/cs/README.md @@ -0,0 +1,3 @@ +# libhat-sharp + +These bindings are not up-to-date, but should be compatible with v0.9.0 diff --git a/bindings/java/src/main/java/me/zero/libhat/Hat.java b/bindings/java/src/main/java/me/zero/libhat/Hat.java index bfedafb..ebf8ce2 100644 --- a/bindings/java/src/main/java/me/zero/libhat/Hat.java +++ b/bindings/java/src/main/java/me/zero/libhat/Hat.java @@ -2,6 +2,7 @@ import com.sun.jna.Native; import com.sun.jna.Pointer; +import com.sun.jna.ptr.PointerByReference; import me.zero.libhat.jna.Libhat; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -18,19 +19,12 @@ public final class Hat { private Hat() {} - public enum Status { - SUCCESS, - ERR_UNKNOWN, - SIG_INVALID, - SIG_EMPTY, - SIG_NO_BYTE - } - /** * Creates a new {@link Signature} from the specified string representation of a byte pattern. For example: * * The returned {@link Signature} is backed by a native heap allocation, and {@link Signature#close()} must be * called when the object is done being used, either explicitly or through a try-with-resources block. @@ -42,17 +36,17 @@ public enum Status { */ public static @NotNull Signature parseSignature(@NotNull final String signature) { Objects.requireNonNull(signature); - final Pointer[] handle = new Pointer[1]; - final int status = Libhat.INSTANCE.libhat_parse_signature(signature, handle); + final PointerByReference out = new PointerByReference(); + final int status = Libhat.INSTANCE.libhat_parse_signature(signature, out); if (status != 0) { - throw new RuntimeException("libhat internal error " + Status.values()[status]); + throw new LibhatException(status); } - return new Signature(handle[0]); + return new Signature(out.getValue()); } /** - * Creates a new {@link Signature} that describes a pattern matching the specified {@code bytes} where the - * corresponding {@code mask} value is non-zero. In other words, a mask value of {@code 0} indicates a wildcard. + * Creates a new {@link Signature} that describes a pattern matching the bits of {@code bytes} where the + * corresponding {@code mask} bit is 1. In other words, where {@code (buffer & mask) == (bytes & mask)}. * The returned {@link Signature} is backed by a native heap allocation, and {@link Signature#close()} must be * called when the object is done being used, either explicitly or through a try-with-resources block. * @@ -69,12 +63,12 @@ public enum Status { if (bytes.length != mask.length) { throw new IllegalArgumentException("Mismatch between bytes.length and mask.length"); } - final Pointer[] handle = new Pointer[1]; - final int status = Libhat.INSTANCE.libhat_create_signature(bytes, mask, bytes.length, handle); + final PointerByReference out = new PointerByReference(); + final int status = Libhat.INSTANCE.libhat_create_signature(bytes, mask, new Libhat.size_t(bytes.length), out); if (status != 0) { - throw new RuntimeException("libhat internal error " + Status.values()[status]); + throw new LibhatException(status); } - return new Signature(handle[0]); + return new Signature(out.getValue()); } /** @@ -82,17 +76,20 @@ public enum Status { * range including {@link ByteBuffer#position()} and up to but excluding {@link ByteBuffer#limit()}. If a match is * found, an {@link OptionalInt} containing the absolute position into {@code buffer} is returned. The underlying * memory address of the returned result will not be aligned on any particular boundary. The specified - * {@link ByteBuffer} must be a direct buffer. + * {@link ByteBuffer} must be a direct buffer. Additional hints may be specified to optimize the scan based on + * known properties of the buffer contents. * * @param signature The pattern to match * @param buffer The buffer to search + * @param hints The hints to use * @return The absolute position into {@code buffer} where a match was found, * or {@link OptionalInt#empty()} if there was no match * @throws IllegalArgumentException if the buffer is not direct or the signature has already been closed * @throws NullPointerException if any arguments are {@code null} */ - public static OptionalInt findPattern(@NotNull final Signature signature, @NotNull final ByteBuffer buffer) { - return findPattern(signature, buffer, ScanAlignment.X1); + public static OptionalInt findPattern(@NotNull final Signature signature, @NotNull final ByteBuffer buffer, + @NotNull final ScanHint... hints) { + return findPattern(signature, buffer, ScanAlignment.X1, hints); } /** @@ -100,18 +97,20 @@ public static OptionalInt findPattern(@NotNull final Signature signature, @NotNu * range including {@link ByteBuffer#position()} and up to but excluding {@link ByteBuffer#limit()}. If a match is * found, an {@link OptionalInt} containing the absolute position into {@code buffer} is returned. The underlying * memory address of the returned result will be byte aligned per the specified {@link ScanAlignment}. The specified - * {@link ByteBuffer} must be a direct buffer. + * {@link ByteBuffer} must be a direct buffer. Additional hints may be specified to optimize the scan based on known + * properties of the buffer contents. * * @param signature The pattern to match * @param buffer The buffer to search * @param alignment The result address alignment + * @param hints The hints to use * @return The absolute position into {@code buffer} where a match was found, * or {@link OptionalInt#empty()} if there was no match * @throws IllegalArgumentException if the buffer is not direct * @throws NullPointerException if any arguments are {@code null} */ public static OptionalInt findPattern(@NotNull final Signature signature, @NotNull final ByteBuffer buffer, - @NotNull final ScanAlignment alignment) { + @NotNull final ScanAlignment alignment, @NotNull final ScanHint... hints) { Objects.requireNonNull(signature); Objects.requireNonNull(buffer); Objects.requireNonNull(alignment); @@ -126,8 +125,9 @@ public static OptionalInt findPattern(@NotNull final Signature signature, @NotNu final Pointer result = Libhat.INSTANCE.libhat_find_pattern( Objects.requireNonNull(signature.handle), new Pointer(start), - count, - alignment.alignment() + new Libhat.size_t(count), + alignment.alignment(), + ScanHint.toFlags(hints) ); if (result == Pointer.NULL) { @@ -139,33 +139,38 @@ public static OptionalInt findPattern(@NotNull final Signature signature, @NotNu /** * Finds the byte pattern described by the given {@link Signature} in the specified {@code section} of the * specified {@code module}. If a match is found, an {@link Optional} containing a Pointer to the match is returned. - * The underlying memory address of the returned result will not be aligned on any particular boundary. + * The underlying memory address of the returned result will not be aligned on any particular boundary. Additional + * hints may be specified to optimize the scan based on known properties of the buffer contents. * * @param signature The pattern to match * @param module The target module * @param section The section to search in the module + * @param hints The hints to use * @return A pointer to the memory where a match was identified, or {@link Optional#empty()} if none was found. * @throws NullPointerException if any arguments are {@code null} */ public static Optional findPattern(@NotNull final Signature signature, @NotNull final ProcessModule module, - @NotNull final String section) { - return findPattern(signature, module, section, ScanAlignment.X1); + @NotNull final String section, @NotNull final ScanHint... hints) { + return findPattern(signature, module, section, ScanAlignment.X1, hints); } /** * Finds the byte pattern described by the given {@link Signature} in the specified {@code section} of the * specified {@code module}. If a match is found, an {@link Optional} containing a Pointer to the match is returned. * The underlying memory address of the returned result will be byte aligned per the specified {@link ScanAlignment}. + * Additional hints may be specified to optimize the scan based on known properties of the buffer contents. * * @param signature The pattern to match * @param module The target module * @param section The section to search in the module * @param alignment The result address alignment + * @param hints The hints to use * @return A pointer to the memory where a match was identified, or {@link Optional#empty()} if none was found. * @throws NullPointerException if any arguments are {@code null} */ public static Optional findPattern(@NotNull final Signature signature, @NotNull final ProcessModule module, - @NotNull final String section, @NotNull final ScanAlignment alignment) { + @NotNull final String section, @NotNull final ScanAlignment alignment, + @NotNull final ScanHint... hints) { Objects.requireNonNull(signature); Objects.requireNonNull(module); Objects.requireNonNull(section); @@ -173,46 +178,117 @@ public static Optional findPattern(@NotNull final Signature signature, final Pointer result = Libhat.INSTANCE.libhat_find_pattern_mod( Objects.requireNonNull(signature.handle), - module.handle, + Objects.requireNonNull(module.handle), section, - alignment.alignment() + alignment.alignment(), + ScanHint.toFlags(hints) ); return Optional.ofNullable(result); } /** - * Wrapper around {@link #parseSignature(String)} and {@link #findPattern(Signature, ByteBuffer)} + * Wrapper around {@link #parseSignature(String)} and {@link #findPattern(Signature, ByteBuffer, ScanHint...)}. + * Additional hints may be specified to optimize the scan based on known properties of the buffer contents. * * @param signature A byte pattern string - * @param buffer The buffer to search + * @param buffer The buffer to search + * @param hints The hints to use * @return The search result * @throws NullPointerException if any arguments are {@code null} */ - public static OptionalInt findPattern(@NotNull final String signature, @NotNull final ByteBuffer buffer) { + public static OptionalInt findPattern(@NotNull final String signature, @NotNull final ByteBuffer buffer, + @NotNull final ScanHint... hints) { try (final Signature sig = parseSignature(signature)) { - return findPattern(sig, buffer); + return findPattern(sig, buffer, hints); } } /** - * Wrapper around {@link #parseSignature(String)} and {@link #findPattern(Signature, ByteBuffer, ScanAlignment)} + * Wrapper around {@link #parseSignature(String)} and {@link #findPattern(Signature, ByteBuffer, ScanAlignment, ScanHint...)}. + * Additional hints may be specified to optimize the scan based on known properties of the buffer contents. * * @param signature A byte pattern string - * @param buffer The buffer to search + * @param buffer The buffer to search * @param alignment The memory address alignment of the result + * @param hints The hints to use * @return The search result * @throws NullPointerException if any arguments are {@code null} */ public static OptionalInt findPattern(@NotNull final String signature, @NotNull final ByteBuffer buffer, - @NotNull final ScanAlignment alignment) { + @NotNull final ScanAlignment alignment, @NotNull final ScanHint... hints) { try (final Signature sig = parseSignature(signature)) { - return findPattern(sig, buffer, alignment); + return findPattern(sig, buffer, alignment, hints); + } + } + + /** + * Returns whether the entire memory region pointed to by {@code buffer}, including {@link ByteBuffer#position()} + * and up to but excluding {@link ByteBuffer#limit()}, is readable. The specified {@link ByteBuffer} must be a direct + * buffer. + * + * @param buffer A direct buffer to an arbitrary memory region + * @return Whether the memory is readable + * @throws IllegalArgumentException if the buffer is not direct + * @throws NullPointerException if any arguments are {@code null} + */ + public static boolean isReadable(@NotNull final ByteBuffer buffer) { + Objects.requireNonNull(buffer); + if (!buffer.isDirect()) { + throw new IllegalArgumentException("Provided buffer must be direct"); + } + + final long start = Pointer.nativeValue(Native.getDirectBufferPointer(buffer)) + buffer.position(); + final int count = buffer.remaining(); + return Libhat.INSTANCE.libhat_is_readable(new Pointer(start), new Libhat.size_t(count)); + } + + /** + * Returns whether the entire memory region pointed to by {@code buffer}, including {@link ByteBuffer#position()} + * and up to but excluding {@link ByteBuffer#limit()}, is writable. The specified {@link ByteBuffer} must be a direct + * buffer. + * + * @param buffer A direct buffer to an arbitrary memory region + * @return Whether the memory is writable + * @throws IllegalArgumentException if the buffer is not direct + * @throws NullPointerException if any arguments are {@code null} + */ + public static boolean isWritable(@NotNull final ByteBuffer buffer) { + Objects.requireNonNull(buffer); + if (!buffer.isDirect()) { + throw new IllegalArgumentException("Provided buffer must be direct"); + } + + final long start = Pointer.nativeValue(Native.getDirectBufferPointer(buffer)) + buffer.position(); + final int count = buffer.remaining(); + return Libhat.INSTANCE.libhat_is_writable(new Pointer(start), new Libhat.size_t(count)); + } + + /** + * Returns whether the entire memory region pointed to by {@code buffer}, including {@link ByteBuffer#position()} + * and up to but excluding {@link ByteBuffer#limit()}, is executable. The specified {@link ByteBuffer} must be a direct + * buffer. + * + * @param buffer A direct buffer to an arbitrary memory region + * @return Whether the memory is executable + * @throws IllegalArgumentException if the buffer is not direct + * @throws NullPointerException if any arguments are {@code null} + */ + public static boolean isExecutable(@NotNull final ByteBuffer buffer) { + Objects.requireNonNull(buffer); + if (!buffer.isDirect()) { + throw new IllegalArgumentException("Provided buffer must be direct"); } + + final long start = Pointer.nativeValue(Native.getDirectBufferPointer(buffer)) + buffer.position(); + final int count = buffer.remaining(); + return Libhat.INSTANCE.libhat_is_executable(new Pointer(start), new Libhat.size_t(count)); } /** - * Returns the module for the executable used to create this process + * Returns the module for the executable used to create this process. The returned {@link ProcessModule} is backed + * by a native heap allocation, and {@link ProcessModule#close()} must be called when the object is done being used, + * either explicitly or through a try-with-resources block. * * @return The module * @throws IllegalStateException If the process module could not be retrieved @@ -225,7 +301,9 @@ public static OptionalInt findPattern(@NotNull final String signature, @NotNull /** * Returns an {@link Optional} containing a handle to the module with the specified name, if such a module exists, * otherwise the returned optional is empty. If the provided name is {@code null}, the module for the executable - * used to create the current process is returned. + * used to create the current process is returned. The returned {@link ProcessModule} is backed by a native heap + * allocation, and {@link ProcessModule#close()} must be called when the object is done being used, either + * explicitly or through a try-with-resources block. * * @param module The module name, may be {@code null} * @return The module diff --git a/bindings/java/src/main/java/me/zero/libhat/LibhatException.java b/bindings/java/src/main/java/me/zero/libhat/LibhatException.java new file mode 100644 index 0000000..9f28283 --- /dev/null +++ b/bindings/java/src/main/java/me/zero/libhat/LibhatException.java @@ -0,0 +1,23 @@ +package me.zero.libhat; + +import me.zero.libhat.jna.Libhat; + +/** + * @author Brady + */ +public class LibhatException extends RuntimeException { + + private final int status; + + LibhatException(final int status) { + super(Libhat.INSTANCE.libhat_status_to_string(status)); + this.status = status; + } + + /** + * @return The actual status code representing the error that occurred + */ + public final int status() { + return this.status; + } +} diff --git a/bindings/java/src/main/java/me/zero/libhat/ProcessModule.java b/bindings/java/src/main/java/me/zero/libhat/ProcessModule.java index ae83071..aec34d0 100644 --- a/bindings/java/src/main/java/me/zero/libhat/ProcessModule.java +++ b/bindings/java/src/main/java/me/zero/libhat/ProcessModule.java @@ -1,17 +1,116 @@ package me.zero.libhat; import com.sun.jna.Pointer; +import me.zero.libhat.jna.Libhat; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.nio.ByteBuffer; +import java.util.Objects; +import java.util.Optional; +import java.util.function.Predicate; /** * @author Brady */ -public final class ProcessModule { +public final class ProcessModule implements AutoCloseable { - @NotNull + @Nullable Pointer handle; ProcessModule(@NotNull final Pointer handle) { - this.handle = handle; + this.handle = Objects.requireNonNull(handle); + } + + @Override + public void close() { + if (this.handle != Pointer.NULL) { + Libhat.INSTANCE.libhat_free(this.handle); + this.handle = Pointer.NULL; + } + } + + /** + * @return The base address of this module, as a {@code long}. + */ + public long getBaseAddress() { + return Libhat.INSTANCE.libhat_module_address(this.checkHandle()).longValue(); + } + + /** + * Returns the complete memory region for this module. This may include portions which are uncommitted. + * To verify whether the region is safe to read, use {@link Hat#isReadable(ByteBuffer)}. + * + * @return The module data + * @throws IllegalStateException If the module data was empty + */ + public @NotNull ByteBuffer getModuleData() { + final ByteBuffer data = Libhat.INSTANCE.libhat_module_get_data(this.checkHandle()).toBuffer(); + if (data == null) { + throw new IllegalStateException("Module data was unexpectedly empty"); + } + return data; + } + + /** + * Returns the executable memory region containing machine code for the module. The standard section which + * contains executable code for the current platform will be returned first. If it cannot be identified + * by name, the first executable region defined by the module will be returned instead. + * + * @return The module's executable data, or {@link Optional#empty()} if it cannot be found. + */ + public @NotNull Optional getExecutableData() { + return Optional.ofNullable(Libhat.INSTANCE.libhat_module_get_executable_data(this.checkHandle()).toBuffer()); + } + + /** + * Returns the memory region for a named section. On Linux-based platforms, section names are lazily loaded + * from the file that initialized the module, and internally cached for subsequent calls. On systems using the + * Mach-O format, "SEGNAME,SECNAME" is supported for disambiguation. i.e. "__TEXT,__const" vs "__DATA,__const" + * + * @param name The section name + * @return The data for the section, or {@link Optional#empty()} if it cannot be found. + * @throws NullPointerException if any arguments are {@code null} + */ + public @NotNull Optional getSectionData(@NotNull final String name) { + Objects.requireNonNull(name); + return Optional.ofNullable(Libhat.INSTANCE.libhat_module_get_section_data(this.checkHandle(), name).toBuffer()); + } + + /** + * Invokes the callback for each named linker section defined by this module as long as it returns true. The + * returned byte range is not guaranteed to have page aligned begin and end addresses. The returned protections + * are yielded from the section headers, and may not reflect the current virtual protections for the relevant + * memory pages. On Linux-based platforms, section names are lazily loaded from the file that initialized the + * module, and internally cached for subsequent calls. + * + * @param callback The callback to accept each section + * @throws NullPointerException if any arguments are {@code null} + */ + public void forEachSection(@NotNull final Predicate<@NotNull Section> callback) { + Objects.requireNonNull(callback); + Libhat.INSTANCE.libhat_module_for_each_section(this.checkHandle(), (name, data, prot, ud) + -> callback.test(new Section(name, data.toBuffer(), Protection.fromFlags(prot))), null); + } + + /** + * Invokes the callback for each memory segment defined by this module as long as it returns true. Depending on + * the platform, a segment may be represented by multiple linker sections. The returned byte range is not + * guaranteed to have page aligned begin and end addresses. The returned protections are yielded from the + * segment headers, and may not reflect the current virtual protections for the relevant memory pages. + * + * @param callback The callback to accept each segment + * @throws NullPointerException if any arguments are {@code null} + */ + public void forEachSegment(@NotNull final Predicate<@NotNull Segment> callback) { + Objects.requireNonNull(callback); + Libhat.INSTANCE.libhat_module_for_each_segment(this.checkHandle(), (data, prot, ud) + -> callback.test(new Segment(data.toBuffer(), Protection.fromFlags(prot))), null); + } + + @NotNull + private Pointer checkHandle() { + return Objects.requireNonNull(this.handle, + "Attempted operation on a ProcessModule that has already been freed"); } } diff --git a/bindings/java/src/main/java/me/zero/libhat/Protection.java b/bindings/java/src/main/java/me/zero/libhat/Protection.java new file mode 100644 index 0000000..d1148a7 --- /dev/null +++ b/bindings/java/src/main/java/me/zero/libhat/Protection.java @@ -0,0 +1,30 @@ +package me.zero.libhat; + +import org.jetbrains.annotations.NotNull; + +import java.util.EnumSet; + +/** + * @author Brady + */ +public enum Protection { + READ, + WRITE, + EXECUTE; + + private final int bit; + + Protection() { + this.bit = (1 << this.ordinal()); + } + + static @NotNull EnumSet fromFlags(final int flags) { + final EnumSet set = EnumSet.noneOf(Protection.class); + for (final Protection flag : Protection.values()) { + if ((flags & flag.bit) != 0) { + set.add(flag); + } + } + return set; + } +} diff --git a/bindings/java/src/main/java/me/zero/libhat/ScanHint.java b/bindings/java/src/main/java/me/zero/libhat/ScanHint.java new file mode 100644 index 0000000..bff6b6b --- /dev/null +++ b/bindings/java/src/main/java/me/zero/libhat/ScanHint.java @@ -0,0 +1,37 @@ +package me.zero.libhat; + +import org.jetbrains.annotations.NotNull; + +/** + * @author Brady + */ +public enum ScanHint { + /** + * The data being scanned is x86_64 machine code + */ + X86_64, + + /** + * Only utilize byte pair based scanning if the signature starts with a byte pair + */ + PAIR0, + + /** + * The data being scanned is AArch64 machine code + */ + AARCH64; + + private final int bit; + + ScanHint() { + this.bit = (1 << this.ordinal()); + } + + static int toFlags(@NotNull ScanHint... hints) { + int mask = 0; + for (final ScanHint hint : hints) { + mask |= hint.bit; + } + return mask; + } +} diff --git a/bindings/java/src/main/java/me/zero/libhat/Section.java b/bindings/java/src/main/java/me/zero/libhat/Section.java new file mode 100644 index 0000000..33febcb --- /dev/null +++ b/bindings/java/src/main/java/me/zero/libhat/Section.java @@ -0,0 +1,42 @@ +package me.zero.libhat; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.nio.ByteBuffer; +import java.util.EnumSet; +import java.util.Objects; +import java.util.Optional; + +/** + * @author Brady + */ +public final class Section { + + @NotNull + private final String name; + + @Nullable + private final ByteBuffer data; + + @NotNull + private final EnumSet protection; + + Section(@NotNull final String name, @Nullable final ByteBuffer data, @NotNull final EnumSet protection) { + this.name = Objects.requireNonNull(name); + this.data = data; + this.protection = Objects.requireNonNull(protection); + } + + public @NotNull String getName() { + return this.name; + } + + public @NotNull Optional getData() { + return Optional.ofNullable(this.data); + } + + public @NotNull EnumSet getProtection() { + return this.protection; + } +} diff --git a/bindings/java/src/main/java/me/zero/libhat/Segment.java b/bindings/java/src/main/java/me/zero/libhat/Segment.java new file mode 100644 index 0000000..0e7c7e6 --- /dev/null +++ b/bindings/java/src/main/java/me/zero/libhat/Segment.java @@ -0,0 +1,34 @@ +package me.zero.libhat; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.nio.ByteBuffer; +import java.util.EnumSet; +import java.util.Objects; +import java.util.Optional; + +/** + * @author Brady + */ +public final class Segment { + + @Nullable + private final ByteBuffer data; + + @NotNull + private final EnumSet protection; + + Segment(@Nullable final ByteBuffer data, @NotNull final EnumSet protection) { + this.data = data; + this.protection = Objects.requireNonNull(protection); + } + + public @NotNull Optional getData() { + return Optional.ofNullable(data); + } + + public @NotNull EnumSet getProtection() { + return protection; + } +} diff --git a/bindings/java/src/main/java/me/zero/libhat/jna/Libhat.java b/bindings/java/src/main/java/me/zero/libhat/jna/Libhat.java index 3040398..a4c68af 100644 --- a/bindings/java/src/main/java/me/zero/libhat/jna/Libhat.java +++ b/bindings/java/src/main/java/me/zero/libhat/jna/Libhat.java @@ -1,6 +1,10 @@ package me.zero.libhat.jna; import com.sun.jna.*; +import com.sun.jna.ptr.PointerByReference; +import org.jetbrains.annotations.Nullable; + +import java.nio.ByteBuffer; /** * @author Brady @@ -9,51 +13,192 @@ public interface Libhat extends Library { Libhat INSTANCE = Native.load("libhat_c", Libhat.class); + // ------------------------------------------------------------------------ + // C types + // ------------------------------------------------------------------------ + + class size_t extends IntegerType { + private static final long serialVersionUID = 1L; + + public size_t() { + this(0); + } + + public size_t(long value) { + super(Native.SIZE_T_SIZE, value, true); + } + } + + class uintptr_t extends IntegerType { + private static final long serialVersionUID = 1L; + + public uintptr_t() { + this(0); + } + + public uintptr_t(long value) { + super(Native.POINTER_SIZE, value, true); + } + } + + // ------------------------------------------------------------------------ + // Structures + // ------------------------------------------------------------------------ + + @Structure.FieldOrder({ "data", "size" }) + class Span extends Structure { + public Pointer data; + public size_t size; + + public Span() {} + + public Span(final Pointer p) { + super(p); + read(); + } + + @Nullable + public final ByteBuffer toBuffer() { + if (this.data == null || this.size.longValue() == 0) { + return null; + } + return this.data.getByteBuffer(0, this.size.longValue()); + } + + public static class ByValue extends Span implements Structure.ByValue {} + } + + // ------------------------------------------------------------------------ + // Callback types + // ------------------------------------------------------------------------ + + interface ForEachSectionCallback extends Callback { + boolean invoke(String name, Span.ByValue data, int protection, Pointer userData); + } + + interface ForEachSegmentCallback extends Callback { + boolean invoke(Span.ByValue data, int protection, Pointer userData); + } + + // ------------------------------------------------------------------------ + // API + // ------------------------------------------------------------------------ + + /* + * const char* libhat_status_to_string(libhat_status status); + */ + String libhat_status_to_string(int status); + /* - * libhat_status_t libhat_parse_signature( - * const char* signatureStr, - * signature_t** signatureOut + * libhat_status libhat_parse_signature( + * const char* signatureStr, + * const libhat_signature** signatureOut * ); */ - int libhat_parse_signature(String signatureStr, Pointer[] signatureOut); + int libhat_parse_signature(String signatureStr, PointerByReference signatureOut); /* - * libhat_status_t libhat_create_signature( - * const char* bytes, - * const char* mask, - * size_t size, - * signature_t** signatureOut + * libhat_status libhat_create_signature( + * const char* bytes, + * const char* mask, + * size_t size, + * const libhat_signature** signatureOut * ); */ - int libhat_create_signature(byte[] bytes, byte[] mask, int size, Pointer[] signatureOut); + int libhat_create_signature(byte[] bytes, byte[] mask, size_t size, PointerByReference signatureOut); /* * const void* libhat_find_pattern( - * const signature_t* signature, - * const void* buffer, - * size_t size, - * scan_alignment align + * const libhat_signature* signature, + * const void* buffer, + * size_t size, + * libhat_alignment align, + * libhat_hint hints * ); */ - Pointer libhat_find_pattern(Pointer signature, Pointer buffer, long size, int align); + Pointer libhat_find_pattern(Pointer signature, Pointer buffer, size_t size, int align, int hints); /* * const void* libhat_find_pattern_mod( - * const signature_t* signature, - * const void* module, - * const char* section, - * scan_alignment align + * const libhat_signature* signature, + * const libhat_module* module, + * const char* section, + * libhat_alignment align, + * libhat_hint hints + * ); + */ + Pointer libhat_find_pattern_mod(Pointer signature, Pointer module, String section, int align, int hints); + + /* + * uintptr_t libhat_module_address(const libhat_module* module); + */ + uintptr_t libhat_module_address(Pointer module); + + /* + * libhat_span libhat_module_get_data(const libhat_module* module); + */ + Span.ByValue libhat_module_get_data(Pointer module); + + /* + * libhat_span libhat_module_get_executable_data(const libhat_module* module); + */ + Span.ByValue libhat_module_get_executable_data(Pointer module); + + /* + * libhat_span libhat_module_get_section_data(const libhat_module* module, const char* name); + */ + Span.ByValue libhat_module_get_section_data(Pointer module, String name); + + /* + * void libhat_module_for_each_section( + * const libhat_module* module, + * libhat_for_each_section_cb callback, + * void* user_data * ); */ - Pointer libhat_find_pattern_mod(Pointer signature, Pointer module, String section, int align); + void libhat_module_for_each_section(Pointer module, ForEachSectionCallback callback, Pointer userData); + + /* + * void libhat_module_for_each_segment( + * const libhat_module* module, + * libhat_for_each_segment_cb callback, + * void* user_data + * ); + */ + void libhat_module_for_each_segment(Pointer module, ForEachSegmentCallback callback, Pointer userData); + + /* + * bool libhat_is_readable(const void* data, size_t size); + */ + boolean libhat_is_readable(Pointer data, size_t size); + + /* + * bool libhat_is_writable(const void* data, size_t size); + */ + boolean libhat_is_writable(Pointer data, size_t size); + + /* + * bool libhat_is_executable(const void* data, size_t size); + */ + boolean libhat_is_executable(Pointer data, size_t size); + + /* + * const libhat_module* libhat_get_process_module(); + */ + Pointer libhat_get_process_module(); /* - * const void* libhat_get_module(const char* name); + * const libhat_module* libhat_get_module(const char* name); */ Pointer libhat_get_module(String name); /* - * void libhat_free(void* mem); + * const libhat_module* libhat_module_at(const void* address); + */ + Pointer libhat_module_at(Pointer address); + + /* + * void libhat_free(const void* object); */ - void libhat_free(Pointer mem); + void libhat_free(Pointer object); } diff --git a/include/libhat/c/libhat.h b/include/libhat/c/libhat.h index bcaa44c..4210ae4 100644 --- a/include/libhat/c/libhat.h +++ b/include/libhat/c/libhat.h @@ -20,13 +20,15 @@ #define LIBHAT_API #endif +#include #include +#include #ifdef __cplusplus extern "C" { #endif -typedef enum libhat_status_t { +typedef enum libhat_status { libhat_success, // The operation was successful libhat_err_unknown, libhat_err_sig_missing_masked_byte, @@ -34,48 +36,102 @@ typedef enum libhat_status_t { libhat_err_sig_empty_signature, libhat_err_sig_expected_wildcard, libhat_err_sig_invalid_token_length, -} libhat_status_t; - -typedef enum scan_alignment { - scan_alignment_x1 = 1, - scan_alignment_x4 = 4, - scan_alignment_x16 = 16, -} scan_alignment_t; - -typedef struct signature { - void* data; - size_t count; -} signature_t; - -LIBHAT_API libhat_status_t libhat_parse_signature( - const char* signatureStr, - signature_t** signatureOut +} libhat_status; + +typedef enum libhat_alignment { + libhat_alignment_x1 = 1, + libhat_alignment_x4 = 4, + libhat_alignment_x16 = 16, +} libhat_alignment; + +typedef enum libhat_hint { + libhat_hint_none = 0, + libhat_hint_x86_64 = 1 << 0, + libhat_hint_pair0 = 1 << 1, + libhat_hint_aarch64 = 1 << 2, +} libhat_hint; + +typedef enum libhat_protection { + libhat_protection_none = 0b000, + libhat_protection_read = 0b001, + libhat_protection_write = 0b010, + libhat_protection_execute = 0b100, +} libhat_protection; + +typedef struct libhat_signature libhat_signature; +typedef struct libhat_module libhat_module; + +typedef struct libhat_span { + const void* data; + size_t size; +} libhat_span; + +typedef bool(*libhat_for_each_section_cb)(const char* name, libhat_span data, libhat_protection prot, void* user_data); +typedef bool(*libhat_for_each_segment_cb)(libhat_span data, libhat_protection prot, void* user_data); + +LIBHAT_API const char* libhat_status_to_string(libhat_status status); + +LIBHAT_API libhat_status libhat_parse_signature( + const char* signatureStr, + const libhat_signature** signatureOut ); -LIBHAT_API libhat_status_t libhat_create_signature( - const char* bytes, - const char* mask, - size_t size, - signature_t** signatureOut +LIBHAT_API libhat_status libhat_create_signature( + const char* bytes, + const char* mask, + size_t size, + const libhat_signature** signatureOut ); LIBHAT_API const void* libhat_find_pattern( - const signature_t* signature, - const void* buffer, - size_t size, - scan_alignment_t align + const libhat_signature* signature, + const void* buffer, + size_t size, + libhat_alignment align = libhat_alignment_x1, + libhat_hint hints = libhat_hint_none ); LIBHAT_API const void* libhat_find_pattern_mod( - const signature_t* signature, - const void* module, - const char* section, - scan_alignment_t align + const libhat_signature* signature, + const libhat_module* module, + const char* section, + libhat_alignment align = libhat_alignment_x1, + libhat_hint hints = libhat_hint_none ); -LIBHAT_API const void* libhat_get_module(const char* name); +LIBHAT_API uintptr_t libhat_module_address(const libhat_module* module); + +LIBHAT_API libhat_span libhat_module_get_data(const libhat_module* module); + +LIBHAT_API libhat_span libhat_module_get_executable_data(const libhat_module* module); + +LIBHAT_API libhat_span libhat_module_get_section_data(const libhat_module* module, const char* name); + +LIBHAT_API void libhat_module_for_each_section( + const libhat_module* module, + libhat_for_each_section_cb callback, + void* user_data +); + +LIBHAT_API void libhat_module_for_each_segment( + const libhat_module* module, + libhat_for_each_segment_cb callback, + void* user_data +); + +LIBHAT_API bool libhat_is_readable(const void* data, size_t size); + +LIBHAT_API bool libhat_is_writable(const void* data, size_t size); + +LIBHAT_API bool libhat_is_executable(const void* data, size_t size); + +LIBHAT_API const libhat_module* libhat_get_process_module(); + +LIBHAT_API const libhat_module* libhat_get_module(const char* name); + +LIBHAT_API const libhat_module* libhat_module_at(const void* address); -LIBHAT_API void libhat_free(void* mem); +LIBHAT_API void libhat_free(const void* object); #ifdef __cplusplus } diff --git a/include/libhat/process.hpp b/include/libhat/process.hpp index 476c088..03ffddc 100644 --- a/include/libhat/process.hpp +++ b/include/libhat/process.hpp @@ -63,7 +63,7 @@ LIBHAT_EXPORT namespace hat::process { friend hat::process::module get_process_module(); friend std::optional get_module(std::string_view); - friend std::optional module_at(void* address); + friend std::optional module_at(const void* address); std::shared_ptr impl{}; }; @@ -86,5 +86,5 @@ LIBHAT_EXPORT namespace hat::process { /// Returns the module containing the specified address. If the given address is not located within a /// loaded module, std::nullopt is returned instead. - [[nodiscard]] std::optional module_at(void* address); + [[nodiscard]] std::optional module_at(const void* address); } diff --git a/include/libhat/result.hpp b/include/libhat/result.hpp index cc0812d..33d60f1 100644 --- a/include/libhat/result.hpp +++ b/include/libhat/result.hpp @@ -98,7 +98,7 @@ LIBHAT_EXPORT namespace hat { #endif } - LIBHAT_CONSTEXPR_RESULT T& value() { + LIBHAT_CONSTEXPR_RESULT T& value() & { #ifdef LIBHAT_RESULT_EXPECTED return impl.value(); #else @@ -106,7 +106,15 @@ LIBHAT_EXPORT namespace hat { #endif } - LIBHAT_CONSTEXPR_RESULT const T& value() const { + LIBHAT_CONSTEXPR_RESULT T&& value() && { +#ifdef LIBHAT_RESULT_EXPECTED + return std::move(impl).value(); +#else + return std::get<0>(std::move(impl)); +#endif + } + + LIBHAT_CONSTEXPR_RESULT const T& value() const& { #ifdef LIBHAT_RESULT_EXPECTED return impl.value(); #else @@ -114,7 +122,15 @@ LIBHAT_EXPORT namespace hat { #endif } - LIBHAT_CONSTEXPR_RESULT E& error() { + LIBHAT_CONSTEXPR_RESULT const T&& value() const&& { +#ifdef LIBHAT_RESULT_EXPECTED + return std::move(impl).value(); +#else + return std::get<0>(std::move(impl)); +#endif + } + + LIBHAT_CONSTEXPR_RESULT E& error() & { #ifdef LIBHAT_RESULT_EXPECTED return impl.error(); #else @@ -122,11 +138,27 @@ LIBHAT_EXPORT namespace hat { #endif } - LIBHAT_CONSTEXPR_RESULT const E& error() const { + LIBHAT_CONSTEXPR_RESULT E&& error() && { +#ifdef LIBHAT_RESULT_EXPECTED + return std::move(impl).error(); +#else + return std::get<1>(std::move(impl)); +#endif + } + + LIBHAT_CONSTEXPR_RESULT const E& error() const& { #ifdef LIBHAT_RESULT_EXPECTED return impl.error(); #else return std::get<1>(impl); +#endif + } + + LIBHAT_CONSTEXPR_RESULT const E&& error() const&& { +#ifdef LIBHAT_RESULT_EXPECTED + return std::move(impl).error(); +#else + return std::get<1>(std::move(impl)); #endif } }; diff --git a/src/c/libhat.cpp b/src/c/libhat.cpp index 3e20022..cc418c3 100644 --- a/src/c/libhat.cpp +++ b/src/c/libhat.cpp @@ -3,31 +3,62 @@ #include #include -static signature_t* allocate_signature(const hat::signature_view signature) { - const auto bytes = std::as_bytes(signature); - auto* mem = malloc(sizeof(signature_t) + bytes.size()); - auto* sig = static_cast(mem); - sig->data = static_cast(mem) + sizeof(signature_t); - sig->count = signature.size(); - std::memcpy(sig->data, bytes.data(), bytes.size()); - return sig; +namespace { + + struct libhat_ffi_object { + virtual ~libhat_ffi_object() = default; + }; + + template + struct libhat_ffi_wrapper : libhat_ffi_object, T { + + template + explicit libhat_ffi_wrapper(Args&&... args) : T(std::forward(args)...) {} + }; } -static hat::scan_alignment to_cpp_align(const scan_alignment align) { +struct libhat_signature final : libhat_ffi_wrapper { + using libhat_ffi_wrapper::libhat_ffi_wrapper; +}; + +struct libhat_module final : libhat_ffi_wrapper { + using libhat_ffi_wrapper::libhat_ffi_wrapper; +}; + +static hat::scan_alignment to_cpp_align(const libhat_alignment align) { switch (align) { - case scan_alignment_x1: + case libhat_alignment_x1: return hat::scan_alignment::X1; - case scan_alignment_x4: + case libhat_alignment_x4: return hat::scan_alignment::X4; - case scan_alignment_x16: + case libhat_alignment_x16: return hat::scan_alignment::X16; } exit(EXIT_FAILURE); } +static hat::scan_hint to_cpp_hints(const libhat_hint hints) { + return static_cast(hints); +} + extern "C" { -LIBHAT_API libhat_status_t libhat_parse_signature(const char* signatureStr, signature_t** signatureOut) { +LIBHAT_API const char* libhat_status_to_string(const libhat_status status) { +#define STATUS_CASE(x) case x: return #x + switch (status) { + STATUS_CASE(libhat_success); + STATUS_CASE(libhat_err_unknown); + STATUS_CASE(libhat_err_sig_missing_masked_byte); + STATUS_CASE(libhat_err_sig_element_parse_error); + STATUS_CASE(libhat_err_sig_empty_signature); + STATUS_CASE(libhat_err_sig_expected_wildcard); + STATUS_CASE(libhat_err_sig_invalid_token_length); + } +#undef STATUS_CASE + return "invalid value for libhat_status"; +} + +LIBHAT_API libhat_status libhat_parse_signature(const char* signatureStr, const libhat_signature** signatureOut) { auto result = hat::parse_signature(signatureStr); if (!result.has_value()) { *signatureOut = nullptr; @@ -41,78 +72,124 @@ LIBHAT_API libhat_status_t libhat_parse_signature(const char* signatureStr, sign } return libhat_err_unknown; } - *signatureOut = allocate_signature(result.value()); + *signatureOut = new libhat_signature{std::move(result).value()}; return libhat_success; } -LIBHAT_API libhat_status_t libhat_create_signature( - const char* bytes, - const char* mask, - const size_t size, - signature_t** signatureOut +LIBHAT_API libhat_status libhat_create_signature( + const char* bytes, + const char* mask, + const size_t size, + const libhat_signature** signatureOut ) { hat::signature signature{}; + bool containsByte = false; signature.reserve(size); for (size_t i{}; i < size; i++) { - if (static_cast(mask[i])) { - signature.emplace_back(static_cast(bytes[i])); - } else { - signature.emplace_back(std::nullopt); - } + containsByte |= signature.emplace_back( + static_cast(bytes[i]), + static_cast(mask[i]) + ).all(); + } + if (!containsByte) { + return libhat_err_sig_missing_masked_byte; } - *signatureOut = allocate_signature(signature); + *signatureOut = new libhat_signature{std::move(signature)}; return libhat_success; } LIBHAT_API const void* libhat_find_pattern( - const signature_t* signature, - const void* buffer, - const size_t size, - const scan_alignment align + const libhat_signature* signature, + const void* buffer, + const size_t size, + const libhat_alignment align, + const libhat_hint hints ) { - const hat::signature_view view{ - static_cast(signature->data), - signature->count - }; - const auto begin = static_cast(buffer); const auto end = static_cast(buffer) + size; - const auto result = hat::find_pattern(begin, end, view, to_cpp_align(align)); + const auto result = hat::find_pattern(begin, end, *signature, to_cpp_align(align), to_cpp_hints(hints)); return result.has_result() ? result.get() : nullptr; } LIBHAT_API const void* libhat_find_pattern_mod( - const signature_t* signature, - const void* module, - const char* section, - const scan_alignment align + const libhat_signature* signature, + const libhat_module* module, + const char* section, + const libhat_alignment align, + const libhat_hint hints ) { - const hat::signature_view view{ - static_cast(signature->data), - signature->count - }; + const auto result = hat::find_pattern(*signature, section, *module, to_cpp_align(align), to_cpp_hints(hints)); + return result.has_result() ? result.get() : nullptr; +} + +LIBHAT_API uintptr_t libhat_module_address(const libhat_module* module) { + return module->address(); +} - const auto mod = hat::process::module_at(const_cast(module)); - if (!mod.has_value()) { - return nullptr; +LIBHAT_API libhat_span libhat_module_get_data(const libhat_module* module) { + const auto data = module->get_module_data(); + return {data.data(), data.size()}; +} + +LIBHAT_API libhat_span libhat_module_get_executable_data(const libhat_module* module) { + const auto data = module->get_executable_data(); + return {data.data(), data.size()}; +} + +LIBHAT_API libhat_span libhat_module_get_section_data(const libhat_module* module, const char* name) { + const auto data = module->get_section_data(name); + return {data.data(), data.size()}; +} + +LIBHAT_API void libhat_module_for_each_section(const libhat_module* module, const libhat_for_each_section_cb callback, void* user_data) { + std::string buffer; + module->for_each_section([=, &buffer](auto name, auto data, auto prot) { + buffer.assign(name); + return callback(buffer.c_str(), {data.data(), data.size()}, static_cast(prot), user_data); + }); +} + +LIBHAT_API void libhat_module_for_each_segment(const libhat_module* module, const libhat_for_each_segment_cb callback, void* user_data) { + module->for_each_segment([=](auto data, auto prot) { + return callback({data.data(), data.size()}, static_cast(prot), user_data); + }); +} + +LIBHAT_API bool libhat_is_readable(const void* data, size_t size) { + return hat::process::is_readable({static_cast(data), size}); +} + +LIBHAT_API bool libhat_is_writable(const void* data, size_t size) { + return hat::process::is_writable({static_cast(data), size}); +} + +LIBHAT_API bool libhat_is_executable(const void* data, size_t size) { + return hat::process::is_executable({static_cast(data), size}); +} + +LIBHAT_API const libhat_module* libhat_get_process_module() { + return new libhat_module{hat::process::get_process_module()}; +} + +LIBHAT_API const libhat_module* libhat_get_module(const char* name) { + if (!name) { + return libhat_get_process_module(); } - const auto result = hat::find_pattern(view, section, mod.value(), to_cpp_align(align)); - return result.has_result() ? result.get() : nullptr; + if (auto mod = hat::process::get_module(name); mod.has_value()) { + return new libhat_module{std::move(mod).value()}; + } + return nullptr; } -LIBHAT_API const void* libhat_get_module(const char* name) { - if (name) { - if (const auto mod = hat::process::get_module(name); mod.has_value()) { - return reinterpret_cast(mod.value().address()); - } else { - return nullptr; - } +LIBHAT_API const libhat_module* libhat_module_at(const void* address) { + if (auto mod = hat::process::module_at(address); mod.has_value()) { + return new libhat_module{std::move(mod).value()}; } - return reinterpret_cast(hat::process::get_process_module().address()); + return nullptr; } -LIBHAT_API void libhat_free(void* mem) { - free(mem); +LIBHAT_API void libhat_free(const void* object) { + delete static_cast(object); } } // extern "C" diff --git a/src/os/linux/Process.cpp b/src/os/linux/Process.cpp index 59fcf8e..75a23a0 100644 --- a/src/os/linux/Process.cpp +++ b/src/os/linux/Process.cpp @@ -262,7 +262,7 @@ namespace hat::process { return module; } - std::optional module_at(void* address) { + std::optional module_at(const void* address) { Dl_info dlinfo{}; if (!dladdr(address, &dlinfo)) { return {}; diff --git a/src/os/mac/Process.cpp b/src/os/mac/Process.cpp index 9bbb526..7c3b785 100644 --- a/src/os/mac/Process.cpp +++ b/src/os/mac/Process.cpp @@ -310,7 +310,7 @@ namespace hat::process { return {}; } - std::optional module_at(void* address) { + std::optional module_at(const void* address) { Dl_info dlinfo{}; if (!dladdr(address, &dlinfo)) { return {}; diff --git a/src/os/win32/Process.cpp b/src/os/win32/Process.cpp index c287a11..74a6230 100644 --- a/src/os/win32/Process.cpp +++ b/src/os/win32/Process.cpp @@ -108,7 +108,7 @@ namespace hat::process { return {}; } - std::optional module_at(void* address) { + std::optional module_at(const void* address) { HMODULE out{}; const auto status = GetModuleHandleExW( GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS,