AETERNAE AI RESEARCH LLC INDEPENDENT RESEARCH
ÆAETERNAERESEARCH
Sign inRequest access
← Malware catalog

Attributed metadata history

REPORTPUBLIC METADATA

Reversing embedded WebAssembly: part 2. Recovering function names from a WAMR .aot in pure C

﷽ Introduction to Malware Binary Triage (IMBT) Course Looking to level up your skills? Get 10% off using coupon code: MWNEWS10 for any flavor. Enroll Now and Save 10%: Coupon Code MWNEWS10 Note: Affiliate link – your enrollment helps support this platform at no extra cost to you. Hello, cybersecurity enthusiasts and white hackers! In part 1 we reversed the WAMR .aot container from real bytes and wrote unaot - a tiny, dependency-free C parser that verifies the \0aot magic, walks the section table (handling the 4-byte alignment rule), and decodes TARGET_INFO so we know exactly which CPU the embedded TEXT blob is compiled for. That told us where the native code is and how to disassemble it. But a disassembly of a few hundred anonymous functions is nearly useless. What a reverse engineer actually wants is names: which blob of machine code is add, which is fib, which is the exported entry point. This part builds exactly that - a symbolized function table - by decoding two more sections, EXPORT and FUNCTION, and joining them. We will also give the tool some color so the output is readable. Still one small C file, still no libraries. the plan The information we need is split across two sections, and neither one is enough on its own: FUNCTION gives us, for every defined function, the offset into TEXT where its native code starts (and its type index). It gives us the geography - but no names. EXPORT gives us names mapped to function indices - but no offsets. The two share a key: the function index. So the whole trick of part 2 is: Decode both, join on the index, and we have a symbol table that maps every native function to a name and a precise file offset. Let’s take the sections one at a time. the EXPORT section EXPORT is the name source. Its layout (from WAMR’s aot_loader.c, load_export_section) is a count followed by that many export entries: u32 count entry[count]: u32 index (4-byte aligned) u8 kind 0=func 1=table 2=memory 3=global 4=tag str name The one part that will trip you up is how WAMR stores a string. It is not a plain C string and not a bare length-prefixed buffer. It is: u16 length (2-byte aligned) u8 bytes[length] <- the trailing '\0' IS counted in length So a name like add is stored with length = 4: the three characters a, d, d, plus a trailing NUL that is included in the count. If you print length bytes you get a stray NUL at the end; if you assume the length excludes the terminator you read one byte short on the next field and desync. We copy length bytes and then drop the last one. Here is decode_export. Note the two alignment steps - index is 4-aligned, the string length is 2-aligned - mirroring the loader’s alignment-aware reads from part 1: /* EXPORT: u32 count, then { u32 index(align4), u8 kind, string name }. * string = u16 len(align2) + len bytes (trailing '\0' counted in len). */ static void decode_export(reader_t *r, size_t body, uint32_t size) { size_t end = body + size, pos = body; uint32_t count = rd_u32_at(r, pos); pos += 4; if (r->err) { printf(" <export truncated>\n"); return; } printf(" %u export(s):\n", count); for (uint32_t i = 0; i < count; i++) { pos = align_up(pos, 4); uint32_t index = rd_u32_at(r, pos); pos += 4; uint8_t kind = rd_u8_at(r, pos); pos += 1; pos = align_up(pos, 2); uint16_t slen = rd_u16_at(r, pos); pos += 2; if (r->err || pos + slen > end) { printf(" <export %u truncated>\n", i); break; } char name[96]; size_t n = slen < sizeof(name) ? slen : sizeof(name) - 1; for (size_t j = 0; j < n; j++) name[j] = (char) rd_u8_at(r, pos + j); name[n ? n - 1 : 0] = 0; /* drop the counted trailing '\0' */ pos += slen; printf(" [%u] %-6s index=%-3u \"%s\"\n", i, export_kind_name(kind), index, name); if (kind == 0 && g_expn < MAXF) { /* remember func names for symbolization */

Reference details

Source category
research-report
First reported
Oct 12, 2026 05:26 UTC
Last reported
Oct 12, 2026 05:26 UTC
Catalog updated
Oct 13, 2026 05:17 UTC
Record ID
452d77af24c5b6379acccf3ba0b0e1d2f10dc2afd6efd9a2c842f9c76c48a82a

Published indicators

Network indicators are visibly defanged. This page does not make them clickable or send them to another service.

No validated file hashes or network indicators are stored for this record.

Source observations

intelligence_src_f9879f264b7a8ec1d7

Source-provided label
Publisher RSS metadata
Evidence class
mentioned in report
Confidence
Not reported
Observed
Oct 12, 2026 05:26 UTC
Retrieved
Oct 13, 2026 05:17 UTC

This record contains metadata and attribution only. It does not host, download, unpack, render, or execute malware samples.

Feed collection status

Sources represented on this page. Publication and observation dates describe the content; successful batch dates describe local collection. A recent batch does not certify complete coverage or current provider availability.

1 sources: 0 stale, 0 needing attention, 0 paused, 0 unverified.

Review source update dates and status
  • intelligence_src_f9879f264b7a8ec1d7Recent successful batchLast successful batch: Last attempt:

Stale means older than twice the recorded collection interval, with a minimum of one hour; without an interval, the threshold is 24 hours. Cached content remains available while a feed is stale, paused, or unverified.