I'm happy to announce that my student Luke Dramko's paper "Idioms: A Simple and Effective Framework for Turbo-Charging Local Neural Decompilation with Well-Defined Types" has been accepted to NDSS 2026! Put simply, the paper shows that neural decompilers benefit greatly from explicitly predicting and recovering user-defined types (structs, unions, etc.) referenced in decompiled code.
Paper & Code
The paper has a great motivating example that I'll borrow here. The example starts with a C function that uses a struct type:
struct hash {
int hash_size;
int item_cnt;
struct gap_array *data;
int (*hash_make_key)(void *item);
int (*cmp_item)(void *item1, void *item2);
};
struct gap_array {
int len;
void **array;
};
int hash_find_index(struct hash *h, void *item) {
void *cnx;
int index = hash_make_key(h, item);
int cnt = 0;
cnx = gap_get(h->data, index);
while (cnx != NULL) {
if (cnt++ > h->hash_size) return -1;
if (!h->cmp_item(cnx, item)) break;
index = hash_next_index(h, index);
cnx = gap_get(h->data, index);
}
if (cnx == NULL) return -1;
return index;
}If you've read this blog, it will probably not surprise you that even state-of-the-art decompilers like Hex-Rays struggle to recover this code in a human-readable form. Here is the output from Hex-Rays:
__int64 __fastcall func4(__int64 a1, __int64 a2) {
int v2; // eax
__int64 result; // rax
int v4; // [rsp+10h] [rbp-10h]
unsigned int v5; // [rsp+14h] [rbp-Ch]
__int64 i; // [rsp+18h] [rbp-8h]
v5 = func2(a1, a2);
v4 = 0;
for (i = func1(*(_QWORD *)(a1 + 8), v5); i;
i = func1(*(_QWORD *)(a1 + 8), v5)) {
v2 = v4++;
if (v2 > *(_DWORD *)a1) return 0xFFFFFFFFLL;
if (!(*(unsigned int(__fastcall **)(__int64, __int64))(a1 + 24))(i, a2))
break;
v5 = func3((_DWORD *)a1, v5);
}
if (i)
result = v5;
else
result = 0xFFFFFFFFLL;
return result;
}There are a lot of problems with this output, but the most serious is that the
information about the hash struct has been completely lost.
A very exciting line of decompilation research is neural decompilation, which leverages neural models to either (1) directly decompile code, or (2) improve the decompiled code from traditional (non-neural) decompilers. I am personally extremely excited about the latter approach, which uses neural models to post-process the output of traditional decompilers such as Hex-Rays. Traditional decompilers have been studied for decades, so why not leverage their strengths while using neural models to fix their weaknesses? One popular example is the LLM4Decompile models. Here is an example of LLM4Decompile's output for this function:
int FUN_00100155(struct FUN_0009ff84 *VAR_0,void *VAR_1){
int VAR_2;
int VAR_3;
void *VAR_4;
VAR_2 = FUN_0009ff86(VAR_0, VAR_1);
VAR_3 = 0;
VAR_4 = FUN_0009ff88(VAR_0->VAR_5, VAR_2);
while (VAR_4) {
if (VAR_0->VAR_6 < VAR_3) { return -1; }
if (!VAR_0->VAR_7(VAR_4, VAR_1)) { break; }
VAR_2 = FUN_0009ff89(VAR_0, VAR_2);
VAR_4 = FUN_0009ff88(VAR_0->VAR_5, VAR_2);
VAR_3++;
}
if (VAR_4) { return VAR_2; }
return -1;
}Unlike Hex-Rays, LLM4Decompile correctly identifies that the function's
arguments are pointers and that the first argument is a pointer to a struct. But
what is struct FUN_0009ff84 and what are its fields, VAR_5, VAR_6, and
VAR_7? And perhaps most importantly for reverse engineers, what offsets are
those fields at? This information is crucial for understanding the code, but
it has been omitted by the model.
One of Idioms' main contributions is to modify the training process of neural models so that they produce well-defined types such as structs with named fields. Unsurprisingly, this makes decompiled code much easier to understand. Here is the output of Idioms on this function:
struct hash_t {
int size;
int count;
struct hash_table_t *table;
int (*hash)(void *key);
int (*cmp)(void *key1, void *key2);
};
struct hash_table_t {
int size;
void **items;
};
int hash_find(struct hash_t *hash, void *key) {
int index = hash_index(hash, key);
int i = 0;
void *item = hash_get(hash->table, index);
while (item != ((void *)0)) {
if (i++ > hash->size) { return -1; }
if (hash->cmp(item, key) == 0) { break; }
index = hash_next(hash, index);
item = hash_get(hash->table, index);
}
return (item == ((void *)0)) ? -1 : index;
}Perhaps surprisingly, in addition to improving the readability of the code, jointly predicting both code and types also significantly improves the accuracy of the decompiled code!
Across multiple models and evaluation metrics, Idioms consistently outperforms prior neural decompilers:
Surprisingly (to me), Idioms also outperforms standalone type recovery tools such as Retypd, BinSub, TRex, and TypeForge, by at least 73%. This suggests that generative, context-aware approaches may be well suited to resolving the inherent ambiguity of type recovery than prior approaches, even though this was not the original motivation of Idioms.
Powered with by Gatsby 5.0