DEF CON SG 1 - CSIT Village - Kan Onn Kit - Struct Reconstruction In Aether
Watch on YouTubeVideo summary
The presentation introduces Aether, an AI-powered reverse engineering co-pilot designed to assist cyber security specialists in analyzing complex malware. While existing tools like A-Cube effectively handle common threats, they often struggle with advanced or unknown malware that requires manual reverse engineering. Aether addresses this gap by acting as a force multiplier for analysts, utilizing AI to rename functions and variables, add meaningful comments, and summarize code logic. This significantly reduces the time-consuming nature of deciphering obfuscated code, allowing human experts to focus on high-level threat understanding rather than tedious syntax interpretation.
A core innovation discussed is the system's ability to perform struct reconstruction, which groups related variables into user-defined structures to clarify code intent. The speaker highlights significant limitations in current open-source solutions and standard LLM workflows when using tools like IDA Pro MCP, specifically regarding accuracy in field alignment, usability requiring excessive prompting, and slow processing speeds. To overcome these challenges, the team developed a bespoke agentic workflow that decomposes struct creation into distinct phases: gathering relevant context, applying definitions with proper padding, and assessing coherence across the entire binary. This approach ensures that generated structures accurately reflect the actual data layouts used by the malware.
The development process involved refining the system's architecture to handle the inherent randomness of Large Language Models and their tendency toward hallucinations or irrelevant reasoning. By implementing chain-of-thought prompting and splitting complex tasks into specialized sub-nodes, the team created a more efficient and accurate tool that outperforms standard models like GPT-5 in speed and reliability. Testing on real-world malware samples, such as White Fly APT, demonstrated that while the system can automatically identify most struct fields correctly, some manual intervention is still required for specific details like memory pointers or encryption keys that the decompiler cannot fully resolve.
In conclusion, the speaker presented an evolved version of the struct creation system that simplifies its architecture to a single loop, fixing previous issues with offset calculation and nested structures. The final solution not only maintains high accuracy but also integrates seamlessly with Aether's other features for one-click struct generation without extensive manual prompting. The team has made this advanced reverse engineering tool open source, inviting the community to explore their GitHub repository and provide feedback, ultimately aiming to democratize access to powerful AI-assisted analysis capabilities for the broader security industry.
Read the full video transcript
Hi, hi. Can you hear me? Loud and clear?
Okay. Hi, I'm Ankit. I'm a former cyber
specialist who worked with CSIT on
Aether and call that development. I'll
be sharing on how we designed and
architected the struct creation system
that can be currently found in Aether.
So, for those who missed our talk
yesterday, Aether stands for the AI
engine to help engineers reverse. It is
an AI-powered reverse engineering
co-pilot that assists reverse engineers
with tedious reverse engineering tasks
in their day-to-day at jobs.
So, here's some background as to why we
made Aether. Cyber incidents often
involve malware, and this malware
research and analysis team will conduct
an in-depth investigation to understand
the complexities of advanced malware.
These technical insights will then be
used for incident response and threat
hunting.
Now, we do have our in-house automated
analysis platform called A-Cube to help
triage known malware. A-Cube is based on
the Cape V2 sandbox with and has worked
well for us in the past.
However, it is built and treat to triage
and analyze common malware, and not
specialized malware that we often see in
cyber incidents. Manual reverse
engineering is still required to handle
these unknown malware. So, that's where
Aether comes in. It acts as a force
multiplier to assist our reverse
engineers to find out what these threats
do.
To give an illustration of how Aether
works, consider this code snippet. We
can see that there are some magic
string, and an experienced malware
analyst will intelligently guess that
this is some sort of magic bytes in the
network communication.
However, we can't really infer what this
magic string means from this code
snippet. A human analyst will need to go
deeper into other functions to perform
further analysis, and this is very
time-consuming.
So, after running Aether, this is how
the code snippet will look like. We can
see that the function and variables have
been meaningfully renamed, and we can
see that Aether has also added
meaningful comments that that can easily
read.
Now, if we read the same code snippet
again, we can infer much more things,
such as the decryption algorithm,
decryption key, packet signature, etc.
The nested function logic is brought up
for the analysis, and this reduces the
effort on analyzing the malware.
So, Ether now has three main groups of
features that work in tandem with each
other to help a human analyst generate
accurate insights.
So, first we have the AI annotations,
which we have talked about in the
previous talk yesterday.
This helps rename functions and
variables and set meaningful comments.
It is also capable of performing
function summarization, which overall
helps improve readability of the pseudo
code. But, this still has limitations.
If you look at the V12 variable there,
we can see it looks like some sort of
struct-like variable that is hard for a
human to read without creating proper
structs. So, we went one step further to
use AI to perform struct reconstruction.
Now, you can see that there are many
different fields there, and it has been
properly renamed, and you can better
understand what this code snippet is
doing.
So, we also recently started development
on the Ether chatbot, which helps
perform analysis across different
functions and provide answers to user
queries.
So, this chatbot will tap on our context
engine and benefit from our previous
annotations made by Ether to give a more
accurate and comprehensive analysis.
So, for the rest of this presentation,
let us go into a deep dive into how this
struct reconstruction feature works.
So, what's a struct? Structs, which are
short for structures, are user-defined
types that group variables of different
types under one name. Instead of
defining multiple variables, a struct
allows one to package different data
types into one single structure.
For example, this first pseudo code has
code that has multiple references and
variables with unknown pointers that we
is hard for analysts to understand and
comprehend.
So, if we can define an appropriate
struct, such as this student struct
defined here, the purpose of the pseudo
code becomes much clearer. For example,
this seems to be some sort of printing
of what the student has and the date of
birth, etc. So, structs are very useful
in understanding what a piece of pseudo
code does.
And this is especially important and
useful when we're analyzing malware. The
moment that we can accurately define a
struct for the current pseudo code, we
should be able to understand the rest of
the pseudo code better and how its
output is used in other functions.
So, before we decided to create our own
bespoke struct creation system, we want
to have a sensing of where the current
open-source solutions are with their
struct creation capabilities. We created
a toy C++ program, uh student_info.c,
that implements a student struct, like
you see here, to test the abilities of
the state-of-the-art models, as shown on
the side here.
We note that the courses field is not
used in the original code, so we do not
expect the LLMs to infer what its use
will be in the pseudo code.
So, currently, when a malware analyst
wants to use AI to assist them in
creating a struct, they would use a then
state-of-the-art LLM, like GPT-5, along
with a model context protocol, MCP, like
IDA Pro MCP, which is a very popular uh
model context protocol to interact with
IDA.
This allows the LLM to interact with the
decompiler and to create the struct.
However, LLMs are quite bad at making
these structs.
We can classify the so-called badness of
these structs in three different ways.
First, the accuracy. Even though IDA
will graciously handle the addition of
padding between fields when we declare
them via the interface or using IDA
Python, it does not add padding when we
set it using IDA Pro MCP.
This is a fundamental problem when LLMs
use the tools that IDA Pro MCP provides.
The misalignment of these fields, as you
can see here, will cascade into the
pseudo code, causing the generated
struct to be inaccurate and the analysis
to be wrong.
Second, usability. A lot of hand-holding
is required to direct these
state-of-the-art models to do what we
want it to do. GPT-5 seems to be able to
formulate a decent strategy to gather
the required components of the binary
before reconstructing the struct, but we
still needed to put in a lot of effort
to develop a decent prompt for it to do
so.
Finally, speed. GPT-5 takes forever to
run. It took at least 8 to 19 minutes to
generate these results and would often
take longer if the structs are more
complicated or if major corrections are
needed.
In reality, struct reconstruction is
very taxing. When malware analysts have
tried to recreate structs, they would
first do have to have a feeling for
where the variables are and what
variables are indeed structs, something
that an LM is currently unable to do. To
use an example, if V12 here was not
highlighted, would you on initial guess
that it is actually a struct?
Furthermore, analysts would then have to
identify all the areas where this same
struct variable is used across all the
functions and then make an educated
guess of what the fields of the struct
are.
The biggest challenge that we face when
creating structs is to maintain
coherence across all the functions that
use this struct. Even after making a
guess of the struct, they would still
need to check if the fields names agree
with what the code is doing. This
involves the analyst going into many,
many functions to ensure that the names
make sense, which takes a very long
time.
We believe that we can use an agentic
workflow to automate these two steps.
So, we took inspiration from our
in-house reverse engineers to come up
with a way to guide the agent to
accurately recreate the struct.
We decompose the problem of struct
reconstruction into three main phases.
The gathering phase would first find and
gather relevant struct structs
functions, sorry. They would first find
relevant functions to the code that is
relevant to the struct creator's
context. This context is different from
Aether's annotator's context. Here,
we're just focusing on the pseudocode
that's relevant to reconstructing the
current pseudo struct variable.
Next, the applying phase will then use
appropriate tools exposed by the IDA Pro
MCP to define and set the struct within
IDA's local types.
Finally, the assessing phase will check
if the set struct matches the existing
context by making the agent reflect on
its actions.
Alas, although such a high-level
architecture should work in theory,
problems arose when we tried to
implement it as is.
When we tested Aether, we realized the
ability for LLMs to reason is critical
to obtain accurate annotations and
struct creations.
We knew that hallucinations or
inaccuracies in the annotated output
would reduce analyst trust in Aether,
and so we needed a way for the LLM to
assess its own correctness on the fly.
So, reasoning LLMs seems to be a great
fit for our needs.
However, these LLMs are not without
issue. Our experiments indicated that
many open-source LLMs often produce
bubbles and irrelevant reasoning that
often repeats what was derived or ends
up looping endlessly for thousands of
tokens. As you can see here, this could
also result in the LLM crashing out.
So, another desire was for the LLMs to
be imbued with the reverse engineering
mindset and knowledge. This was sorely
lacking in current SOTA, sorry,
state-of-the-art reverse engineering
LLMs, and we needed a lot of additional
prompting to direct the model to perform
its analysis like a reverse engineer.
So, to solve both of these issues, we
turned to a tried and true method we
introduced way before the boom in LLMs,
chain-of-thought prompting.
First described by Wei et al. in 2022,
chain-of-thought prompting is the method
of imbuing the model with a series of
reasoning steps that we wanted it to
consider when solving a problem. Of
course, reasoning LLMs typically have
reasoning built in, but chain-of-thought
prompting allows us to have finer
control over the quantity and quality of
the model's reasoning. Our desire for a
more directed, controlled, and relevant
thought process can be achieved by
replacing the reasoning phase of
reasoning LMs with chain-of-thought
prompting.
So, as an example of how we did this,
let us examine part of the
chain-of-thought prompt of the setter.
Remember, the setter's job is to take a
human-readable guess of the C struct and
add appropriate padding for setting
within IDA. So, the focus of the setter
LM should be only on adding padding, not
checking for the correctness of the
offsets or the accuracy of the names.
This is reflected in this preamble of
the chain-of-thought prompt, which asks
to only identify the fields and the
offsets in the struct. It does not ask
whether the names make sense, just that
the offsets are correct. Of course, we
also provided LMs with an example of a
reasoning attempt that we felt was
applicable for what it should be doing.
With chain-of-thought prompting, we can
see an obvious change in the thinking of
the LMs. Before chain-of-thought
prompting, the default thinking mode of
the LMs is quite dense. It generates a
lot of reasoning and explanations, but a
significant portion of the thinking is
too verbose and unhelpful for the
current task. Sometimes, the LM would
even repeat itself and go into circles.
With chain-of-thought prompting, you can
see that the thinking portion here is
sparser, and we can even see a little
bit of its initial analysis beginning at
the bottom of this image.
This allows us to reduce token usage of
by the LM and direct its reasoning and
processing to what we want it to do,
which ultimately also reduces analysis
time.
So, even with the use of
chain-of-thought prompting, there were
some issues with the idealized
architecture that we dreamed of having.
Our architecture had to evolve to
incorporate other desires that were not
present in the initial design phase.
These considerations meant that our
idealized form of the struct creator had
to evolve with our needs.
After several months of R&D and testing,
here's what the struct creator became.
From our idealized architecture of five
nodes, our struct creation system blew
up and became this behemoth that you can
see on the right. This increased
complexity is also needed to deal with
the increase in control that a human
analyst has on the system and to deal
with the intricacies with IDA's API.
Our struct creation system now also uses
a non-reasoning LLM, country coder for
ATB 825B, which is a result of our
switching to chain of thought prompting.
So, let us zoom into this applying phase
for a second. Why is it so complicated?
Well, our initial idea for just a single
guess node that creates a human-readable
C struct and then getting the setter to
add appropriate padding before setting
in IDA has some issues. Due to the
inherent randomness of LLMs, the struct
that was set in IDA could have some
issues such as incorrect padding or
syntax errors. Now, it is quite wasteful
to go through the entire loop just to
fix these issues that can be corrected
immediately.
So, that's why we have an inner loop
that you can see here, returning back to
the setter if the struct has issues that
can be fixed immediately.
So, zooming out again, the difference in
these two loops, the inner loop within
the applying phase and the outer loop
between the assessing and gathering
phase, is their purpose. The inner
applying loop focuses on struct level
issues such as the padding and type
issues mentioned previously.
The outer assessing loop focuses more on
the context level issues. This agent is
responsible for checking if the
coherence is maintained across all the
functions when setting the struct. So,
does the struct fit the given code
context? Does the code access any
reserved fields? These issues and more
are what's checked by the assessing
phase and this outer loop.
So, to check whether IDA struct creator
beats IDA Pro MCP and other SOTA LLMs,
we use the same student info.c sample as
before, beginning the analysis from the
same function and directing the LLM on
which variable to create the struct for.
So, here's Aether's output on the
tower.c sample when compared to original
sample.
Based on the generated structs, we can
see that the fields offsets in Aether's
struct are correct with appropriate
padding inserted to maintain alignment.
The accuracy of the names and types is
also maintained despite using a
non-reasoning LLM.
This quick test shows that our solution
has addressed many pain points with IDA
Pro MCP, even though we use country
coder, which is a much less more much
less powerful model as compared to
GPT-5.
So, let us compare Aether's performance
with IDA Pro MCP when using GPT-5.
Aether is generally more accurate with
IDA Pro MCP with issues regarding
padding and offsets occurring less
frequently than with GPT-5 and IDA Pro
MCP.
The struct creation system is also
integrated with Aether's other features,
allowing for one-click struct creation
without the need of hand-holding or
prompting.
Aether is also way faster than IDA Pro
MCP, taking only a fraction of the time
as compared to GPT-5.
It should be emphasized, though, that
the performance of this struct creator
heavily depends on the quality of
Aether's output of Aether's annotations
before activating the struct creator.
So, how well does this struct creation
system perform when dealing with real
malware? We tested our system on the
White Fly APT malware sample found in
2018.
In particular, we looked at the second
stage of the malware, which was
responsible for interacting and
communicating with the White Fly C2
server.
Our internal malware analyst already
generated a report of the sample, so we
could reference it and make sure that
Aether's output agreed with what's known
internally.
The White Fly sample first needed some
manual patching of the IAT, the in-
import address table, within the IDA
project. Once that's done, we asked IDA
to generate annotations for the
functions in the binary to allow our
struct creator to have sufficient
context for determining the structs used
within the binary. Without these
annotations, the struct creator would
not be able to function well. So, that
is important to note here.
Within the binary, we noticed several
functions that seem to use some structs.
For example, within the AIRS.C2
communication function found at 401 to
E1, we see that V4 seems to be some sort
of hook struct.
We ran the struct creator on V4 and saw
that the generated struct was decent.
When we cross-reference the struct with
our internal report, we found that it
matched our own analyst's findings.
So, looking further into that function,
we see that V14 also appears to be some
other struct. This time handling some
sort of network communication.
Even though our generated struct, as you
can see here, looks quite barren, it
surprisingly matched our own internal
report on what this struct is.
For the struct to be more complete,
however, some manual intervention is
needed. For example, this identified V
table field ideally would have been
populated with pointers to the
functions. However, as of now, IDA is
unable to populate this V table because
it cannot dive into the memory of the
binary. So, analysts still have to go
into the referenced memory locations to
identify and fill in what's in the V
table manually.
Regardless, we can see that the struct
creator got the fields right.
So, what about structs with more fields?
So, let's look at the pseudo code that
has been teased at the start of the
presentation. This is the AIR perform
injection function at 4029C7.
In here, we see that V12 seems to be
some sort of payload with decryption and
fields that depend on the architecture
of the system running the malware.
So, the struct creator seemed to have
some humor because this is its first
attempt.
I would do I think you will agree that
you want more detail than just generic
fields. But it's not technically wrong.
The subsequent attempts were better with
it inferring fields that mainly match
what was found in the binary.
One issue though was that this command
base address or the command base address
above the reserved three field should
actually be some sort of encryption key
such as encryption key 32-bit.
The larger issue with the struct creator
is that the type of the variable V12
that the struct creator decided on was a
struct pointer instead of a struct type.
This affects the quality of the
generated pseudo code and the assessor's
ability to check the correctness of the
generic of the guessed struct. So once
we set the correct type on the right,
the pseudo code became much cleaner.
Albeit with some minor errors like the
command base address issue mentioned
above and a reserved six access that you
can see over there.
So looking back at our architecture, we
could see that there's some issues with
our approach. One big problem is with
correctly and accurately identifying the
offsets that each field should be at
since LLMs are inherently bad with
numbers.
Our current approach also doesn't work
well with nested structs.
We really wanted to fix the issue with
incorrect offsets so we set out to
redesign the struct creation tool by
changing the focus of what the LLMs
does.
While our old approach tried creating
the struct as a C-like manner and asking
the LLM to add appropriate padding, our
our revised approach would ask the LLM
to identify appropriate parts of the
pseudo code that indicate offsets of the
fields. Then either Python can
automatically resolve padding within
IDA.
This shift would involve splitting the
existing guesser node into two sub
nodes. The identifier sub node would
identify possible nested structs within
the main struct and the field guesser
would identify the offsets and types of
the fields within each struct.
So, as a comparison, here's the
different versions of the struct creator
system. The architecture of the first
version is much more complex with many
sub nodes and two loops to achieve the
struct creation task successfully.
The new and improved version on the
right is much simpler and only requires
one loop to determine the fields that
are present in the struct and to set it
within IDA.
When we ran the new struct creation
system on the V12 variable again, we see
that the variables Sorry, the issues
that we found in with the variables
previously have been fixed in the new
version. The previously named V6 Sorry,
reserved 6 access has been changed
correctly to check if it's a 64-bit
system and the command base address has
been correctly renamed to be a
decryption key for 32-bit architectures.
So, with that, we have come to the end
of my talk. Uh if you're interested in
seeing uh further developments on the
new struct creation system or to try out
what IDA can do, we are excited to
announce that it's open source. You can
find our GitHub page or scan the QR code
to access it and we would love to hear
your feedback on IDA. You can also try
out IDA on the booths at the back, which
also is works in tandem with AQR. And
with that, I wish you have a great day
ahead and enjoy the rest of the
conference. Thank you.