[HN Gopher] LLM4Decompile: Decompiling Binary Code with LLM
       ___________________________________________________________________
        
       LLM4Decompile: Decompiling Binary Code with LLM
        
       Author : Davidbrcz
       Score  : 381 points
       Date   : 2024-03-17 10:15 UTC (1 days ago)
        
 (HTM) web link (github.com)
 (TXT) w3m dump (github.com)
        
       | potatoman22 wrote:
       | It's interesting the 6b model outperforms the 33b model. I wonder
       | if it means the 33b model needs more training data? It was
       | pretrained on ~1 million C programs, compared to DeepSeek-Coder,
       | which was trained on 2 trillion tokens, which is a few orders of
       | magnitude more data.
       | 
       | I'm also curious about how this compares to non-LLM solutions.
        
         | mattashii wrote:
         | > on ~1 million C programs, compared to [...] 2 trillion
         | tokens, which is a few orders of magnitude more data.
         | 
         | Is that comparable like that? This would assume that the
         | average C program of the set is orders (plural) of magnitude
         | less than 2m tokens in size, which could indeed be true but
         | sounds like an optimistic assumption.
        
         | Der_Einzige wrote:
         | This has been the dynamics with LLMs for awhile. The majority
         | of LLMs are massively _undertrained_. 7b models are the least
         | "undertrained" mainstream models we have, hence why they have
         | proliferated so much among the LLM fine-tuning community.
        
         | albertan017 wrote:
         | Yes, it's not easy to train a 33B model. An interesting point
         | is, naive fine-tuning, which means if one followed the standard
         | way to fine-tune the model. Training a larger model is tricky,
         | not only the data amount matters, everything like data
         | cleaning, learning rate, and decays will affect the final
         | performance.
        
       | maCDzP wrote:
       | Can this be used for deobfuscation of code? I really hadn't
       | thought about LLM being a tool during reverse engineering.
        
         | Tiberium wrote:
         | Big LLMs like GPT-4 (and even GPT 3.5 Turbo) can be directly
         | used to beautify obfuscated/minified JS, see e.g.
         | https://thejunkland.com/blog/using-llms-to-reverse-javascrip...
         | and https://news.ycombinator.com/item?id=34503233
        
         | Eager wrote:
         | I have tried feeding some of the foundation models obfuscated
         | code from some of the competitions.
         | 
         | People might think that the answers would be in the training
         | data already, but I didn't find that to be the case. At least
         | in my small experiments.
         | 
         | The model's did try to guess what the code does. They would say
         | things like, "It seems to be trying to print some message to
         | the console". I wasn't able to get full solutions.
         | 
         | It's definitely worth more research, not just as a curiosity,
         | but these kinds of problems are good proxies for other tasks
         | and also excellent benchmarks for LLMs particularly.
        
         | evmar wrote:
         | I did a little experiment with this here:
         | 
         | https://neugierig.org/software/blog/2023/01/compiling-advent...
        
         | albertan017 wrote:
         | Thanks! The model is trained only for O0-3, not support for
         | obfuscation. There's still a long way for llm to go.
        
       | kken wrote:
       | Pretty wild how well GPT4 is still doing in comparison. It's
       | significantly better than their model at creating compilable
       | code, but is less accurate at recreating functional code. Still
       | quite impressive.
        
         | Nuzzerino wrote:
         | I'd be impressed if it could do C++ as well as C, which this
         | doesn't.
        
         | albertan017 wrote:
         | Yes, GPT4 is very impressive, as it's not directly trained on
         | the decompilation. We're working on improving our model, please
         | keep watching updates!
        
       | nebula8804 wrote:
       | Will be interesting to see is there is some way to train a
       | decompilation module based on who we know developed the
       | application and use their previous code used as training. For
       | example: Super Mario 64 and Zelda 64 were fully decompiled and a
       | handful of other N64 games are in the process. I wonder if we
       | could map which developers worked on these two games (maybe even
       | guess who did what module) and then use that to more easily
       | decompile any other game that had those developers working on it.
       | 
       | If this gets really good, maybe we can dream of having a fully
       | de-obfuscated and open source life. All the layers of binary
       | blobs in a PC can finally be decoded. All the drivers can be
       | open. Why not do the OS as well! We don't have to settle for
       | Linux, we can bring back Windows XP and back port modern security
       | and app compatibility into the OS and Microsoft can keep their
       | Windows 11 junk...at least one can dream! :D
        
         | ZitchDog wrote:
         | I doubt the code would be identifiable. It wouldn't be the
         | actual code written, but it would be very similar. But I assume
         | many elements of code style would be lost, and any semblance of
         | code style would be more or less hallucinated.
        
           | K0IN wrote:
           | if it can make test from the decompiled code, we could
           | reimplement it with our code style. might be cool to have
           | some bunch of llms working together with feedback loops.
        
         | coddle-hark wrote:
         | I wrote my bachelor thesis on something tangential --
         | basically, some researchers found that it was possible _in some
         | very specific circumstances_ to train a classifier to do author
         | attribution (i.e. figure out who wrote the program) based just
         | on the compiled binaries they produced. I don't think the
         | technique has been used for anything actually useful, but it's
         | cool to see that individual coding style survives the
         | compilation process, so much so that you can tell one person's
         | compiled programs apart from another's.
        
           | astrange wrote:
           | Do you mean the whole binary or just the text
           | segment/instructions?
           | 
           | Because I think this gets a lot easier if you can look at the
           | symbol table, strings, and codesigning certificate.
        
         | userbinator wrote:
         | _If this gets really good, maybe we can dream of having a fully
         | de-obfuscated and open source life. All the layers of binary
         | blobs in a PC can finally be decoded. All the drivers can be
         | open. Why not do the OS as well!_
         | 
         | Decompilers already exist and are really good. If an LLM can do
         | the same as these existing compilers, you can bet the lawyers
         | will consider it an equivalent process. The main problem is
         | legal/political, not technical.
        
           | acureau wrote:
           | I don't know if I'd call the output of modern de-compilers
           | "very good", not for native code anyway. They're just a
           | little better than raw disassembly. Even state of the art de-
           | compilers struggle to reconstruct control flow, distinguish
           | data from code, identify the presence of a variable, let
           | alone its type, and they fundamentally lack context. If a LLM
           | could be used even just to reliably reconstruct symbol
           | information it would be game-changing.
        
       | kukas wrote:
       | Hey, I am working on my own LLM-based decompiler for Python
       | bytecode (https://github.com/kukas/deepcompyle). I feel there are
       | not many people working on this research direction but I think it
       | could be quite interesting, especially now that longer attention
       | contexts are becoming feasible. If anyone knows a team that is
       | working on this, I would be quite interested in cooperation.
        
         | ok123456 wrote:
         | Is there a benefit from using an LLM for Python byte code?
         | Python byte code is high enough level that it's possible to
         | translate it directly to source code from my experience.
        
           | kukas wrote:
           | My motivation is that the existing decompilers work only for
           | Python versions till ~3.8. Having a model that could be
           | finetuned with every new Python version release might
           | overcome the need for highly specialized programmer that is
           | able to update the decompiler to be compatible with the new
           | version.
           | 
           | It is also a toy example for me to set up a working pipeline
           | and then try to decompile more interesting targets.
        
         | a2code wrote:
         | Why Python? First, python is a language with a large open-
         | source library. Second, I do not think it is used for software
         | that is distributed as binaries?
        
           | Retr0id wrote:
           | Closed-source python exists, and it is frequently distributed
           | in compiled binaries (especially in mediocre malware).
           | 
           | As a (supposedly) non-malicious example, the "Nightshade"
           | watermarking tool is distributed as closed-source pre-
           | compiled Python
           | https://nightshade.cs.uchicago.edu/downloads.html
        
         | maple3142 wrote:
         | There is [PyLingual](https://pylingual.io/), but it is not open
         | source unfortunately. I am not sure if it is also LLM based.
        
         | albertan017 wrote:
         | I found lots of decompilation work are conducted on C. It seems
         | not much python projects are compiled into binaries.
        
       | jagrsw wrote:
       | Decompilation is somewhat a default choice for ML in the world of
       | comp-sec.
       | 
       | Searching for vulns and producing patches in source code is a bit
       | problematic, as the databases of vulnerable source code examples
       | and their corresponding patches are neither well-structured nor
       | comprehensive, and sometimes very, very specific to the analyzed
       | code (for higher abstraction type of problems). So, it's not easy
       | to train something usable beyond standard mem safety problems and
       | use of unsafe APIs.
       | 
       | The area of fuzzing is somewhat messy, with sporadic efforts
       | undertaken here and there, but it also requires a lot of
       | preparatory work, and the results might not be groundbreaking
       | unless we reach a point where we can feed an ML model the entire
       | source code of a project, allowing it to analyze and identify all
       | bugs, producing fixes and providing offending inputs. i.e. not
       | yet.
       | 
       | While decompilation is a fairly standard problem, it is possible
       | to produce input-output pairs somewhat at will based on existing
       | source code, using various compiler switches, CPU architectures,
       | ABIs, obfuscations, syscall calling conventions. And train models
       | on those input-output pairs (i.e. in reversed order).
        
         | albertan017 wrote:
         | Thanks! But people want an all-in-one solution for
         | decompilation. Given the vast array of architectures and
         | compilation settings, and the fact that these information are
         | usually not predetermined, finding a way to effectively
         | navigate this complexity is quite difficult.
        
       | a2code wrote:
       | The problem is interesting in at least two aspects. First, an
       | ideal decompiler would eliminate proprietary source code. Second,
       | the abundant publicly available C code allows you to simply make
       | a dataset of paired ASM and source code. There is also a lot of
       | variety with optimization level, compiler choice, and platform.
       | 
       | What is unclear to me is: why did the authors fine-tune the
       | DeepSeek-Coder model? Can you train an LLM from zero with a
       | similar dataset? How big does the LLM need to be? Can it run
       | locally?
        
         | 3abiton wrote:
         | I assume it's related to the cost of training vs fine-tuning.
         | It could be also a starting point to validate an idea.
        
         | mike_hearn wrote:
         | Most proprietary code runs behind firewalls and won't be
         | affected by this one way or another.
         | 
         | It's basically always better to start training with a pre-
         | trained model rather than random, even if what you want isn't
         | that close to what you start with.
        
         | saagarjha wrote:
         | Ideal decompilers do not exist. In some sense they can never
         | exist as compilers are lossy, but even taking a liberal view of
         | "high level understanding of the resulting code" this is
         | essentially the AGI for computer security. Nobody has come
         | close to it!
        
         | albertan017 wrote:
         | Thanks! Training a language model from scratch is data-
         | intensive; Llama2 was developed using 2 trillion tokens, while
         | our dataset is around 4 billion.
         | 
         | The appropriate size of the model is not straightforward to
         | determine. In our experiments, a 7 billion parameter model
         | achieved 21% executability compared to just 10% for a 1 billion
         | parameter model. However, their re-compilability rates are
         | quite similar.
         | 
         | To run a 1 billion parameter model, a minimum of 2GB GPU memory
         | is necessary, which is feasible on most GPUs. A 7 billion
         | parameter model needs 14GB, suitable for GPUs like the
         | 3090/4090 series. For running a 33 billion parameter model, an
         | A100 GPU (80G) would be the single card option, although
         | technically a MacBook could work, but you won't really want to
         | use it.
        
       | madisonmay wrote:
       | This is an excellent use case for LLM fine-tuning, purely because
       | of the ease of generating a massive dataset of input / output
       | pairs from public C code
        
         | bt1a wrote:
         | I would also think that generating a very large amount of C
         | code using coding LLMs (using deepseek, for example, +
         | verifying that the output compiles) as synthetic training data
         | would be quite beneficial in this situation. Generally the
         | quality of synthetic training data is one of the main concerns,
         | but in this case, the ability for the code to compile is the
         | crux.
        
           | Zambyte wrote:
           | I would think that the primary benefit of this over existing
           | decompiler tools would be the ability to use sensible names
           | for identifiers, break up a project to be a sensible set of
           | modules, and maybe even add realistic / helpful comments. If
           | you're synthesizing code to do that, you'll probably gain on
           | the front of generating code that compiles, at the cost of
           | these advantages.
        
       | klik99 wrote:
       | This is a fascinating idea, but (honest question, not a
       | judgement) would the output be reliable? It would be hard to
       | identify hallucinations since recompiling could produce different
       | machine code. Particularly if there is some novel construct that
       | could be a key part of the code. Are there ways of also reporting
       | the LLMs confidence in sections like this when running
       | generatively? It's an amazing idea but I worry it would stumble
       | invisibly on the parts that are most critical. I suppose it would
       | just need human confirmation on the output
        
         | Eager wrote:
         | This is why round-tripping the code is important.
         | 
         | If you decompile the binary to source, then compile the source
         | back to binary you should get the original binary.
         | 
         | You just need to do this enough times until the loss drops to
         | some acceptable amount.
         | 
         | It's a great task for reinforcement learning, which is known to
         | be unreasonably effective for these types of problems.
        
           | thfuran wrote:
           | >If you decompile the binary to source, then compile the
           | source back to binary you should get the original binary.
           | 
           | You really can't expect that if you're not using exactly the
           | same version of exactly the same compiler with exactly the
           | same flags, and often not even then.
        
             | Eager wrote:
             | You try your best, and if you provide enough examples, it
             | will undoubtedly get figured out.
        
               | thfuran wrote:
               | What exactly are you suggesting will get figured out?
        
               | spqrr wrote:
               | The mapping from binary to source code.
        
               | layer8 wrote:
               | The question was about the reverse mapping.
        
               | thfuran wrote:
               | Even ignoring all sources of irreproducibility, there
               | does not exist a bijection between source and binary
               | artifact irrespective of tool chain. Two different
               | toolchains could compile the same source to different
               | binaries or different sources to the same binary. And you
               | absolutely shouldn't be ignoring sources of
               | irreproducibility in this context, since they'll cause
               | even the same toolchain to keep producing different
               | binaries given the same source.
        
               | achrono wrote:
               | Exactly, but neither the source nor the binary is what's
               | truly important here. The real question is: can the LLM
               | generate the _functionally valid_ source equivalent of
               | the binary at hand? If I disassemble Microsoft Paint, can
               | I get code that will result in a mostly functional
               | version of Microsoft Paint, or will I just get 515
               | compile errors instead?
        
               | Brian_K_White wrote:
               | This is what I thought the question was really about.
               | 
               | I assume that an llm will simply see patterns that look
               | similar to other patterns and make assosciations and
               | assume ewuivalences on that level, meanwhile real code is
               | full of things where the programmer, especially assembly
               | programmers, modify something by a single instruction or
               | offset value etc to get a very specific and functionally
               | important result.
               | 
               | Often the result is code that not only isn't obvious,
               | it's nominaly flatly wrong, violating standards, specs,
               | intended function, datasheet docs, etc. If all you knew
               | were the rules written in the docs, the code is broken
               | and invalid.
               | 
               | Is the llm really going to see or understand the intent
               | of that?
               | 
               | They find matching patterns in other existing stuff, and
               | to the user who can not see the infinite body of that
               | other stuff the llm pulled from, it looks like the llm
               | understood the intent of a question, but I say it just
               | found the prior work of some human who understood a
               | similar intent somewhere else.
               | 
               | Maybe an llm or some other flavor of ai can operate some
               | other way like actually playing out the binary like
               | executing in a debugger and map out the results not just
               | look at the code as fuzzy matching patterns. Can that
               | take the place of understanding the intents the way a
               | human would reading the decompiled assembly?
               | 
               | Guess we'll be finding out sooner of later since of
               | course it will all be tried.
        
               | fao_ wrote:
               | Except LLMs cannot reason.
        
               | LoganDark wrote:
               | LLMs can mimic past examples of reasoning from the
               | dataset. So, it can re-use reasoning that it has already
               | been trained on. If the network manages to generalize
               | well enough across its training data, then it can get
               | close to reproducing general reasoning. But it can't yet
               | fully get there, of course.
        
               | mrtesthah wrote:
               | Do you have evidence LLMs can indeed generalize outside
               | their training data distribution?
               | 
               | https://twitter.com/abacaj/status/1721223737729581437/pho
               | to/...
        
               | LoganDark wrote:
               | No. I know only that they can generalize within it, and
               | only to a limited degree, but don't have solid evidence
               | of even that.
        
               | lolinder wrote:
               | I think you're misunderstanding OP's objection. It's not
               | simply a matter of going back and forth with the LLM
               | until eventually (infinite monkeys on typewriters style)
               | it gets the same binary as before: Even if you got the
               | _exact same source code_ as the original there 's still
               | no automated way to tell that you're done because the
               | bits you get back out of the recompile step will almost
               | certainly not be the same, even if your decompiled source
               | were identical in every way. They might even vary quite
               | substantially depending on a lot of different
               | environmental factors.
               | 
               | Reproducible builds are hard to pull off cooperatively,
               | when you control the pipeline that built the original
               | binary and can work to eliminate all sources of
               | variation. It's simply not going to happen in a
               | decompiler like this.
        
               | blagie wrote:
               | Well, no, but yes.
               | 
               | The critical piece is that this can be done in training.
               | If I collect a large number of C programs from github,
               | compile them (in a deterministic fashion), I can use that
               | as a training, test, and validation set. The output of
               | the ML ought to compile to the same way given the same
               | environment.
               | 
               | Indeed, I can train over multiple deterministic build
               | environments (e.g. different compilers, different
               | compiler flags) to be even more robust.
               | 
               | The second critical piece is that for something like a
               | GAN, it doesn't need to be identical. You have two ML
               | algorithms competing:
               | 
               | - One is trying to identify generated versus ground-truth
               | source code
               | 
               | - One is trying to generate source code
               | 
               | Virtually all ML tasks are trained this way, and it
               | doesn't matter. I have images and descriptions, and all
               | the ML needs to do is generate an indistinguishable
               | description.
               | 
               | So if I give the poster a lot more benefit of the doubt
               | on what they wanted to say, it can make sense.
        
               | lolinder wrote:
               | Oh, I was assuming that Eager was responding to klik99's
               | question about how we could identify hallucinations in
               | the output--round tripping doesn't help with that.
               | 
               | If what they're actually saying is that it's possible to
               | train a model to low loss and then you just have to trust
               | the results, yes, what you say makes sense.
        
               | blagie wrote:
               | I haven't found many places where I trust the results of
               | an ML algorithm. I've found many places where they work
               | astonishingly well 30-95% of the time, which is to say,
               | save me or others a bunch of time.
               | 
               | It's been years, but I'm thinking back through things
               | I've reverse-engineered before, and having something
               | which kinda works most of the time would be super-useful
               | still as a starting point.
        
               | incrudible wrote:
               | Have you ever trained a GAN?
        
               | blagie wrote:
               | Technically, yes!
               | 
               | A more reasonable answer, though, is "no."
               | 
               | I've technically gone through random tutorials and
               | trained various toy networks, including a GAN at some
               | point, but I don't think that should really count. I also
               | have a ton of experience with neural networks that's
               | decades out-of-date (HUNDREDS of nodes, doing things like
               | OCR). And I've read a bunch of modern papers and used a
               | bunch of Hugging Face models.
               | 
               | Which is to say, I'm not completely ignorant, but I do
               | not have credible experience training GANs.
        
               | weinzierl wrote:
               | That's true but a solvable problem. I once tried to
               | reproduce the build of an uncooperative party and it was
               | mainly tedious and boring.
               | 
               | The space of possible compiler arguments is huge, but
               | ultimately what is actually used is mostly on a small
               | surface.
               | 
               | Apart from that, I wrote a small tool to normalize the
               | version string, timestamps and file path' in the binaries
               | before I compared them. I know there are other sources of
               | non-determinism, but these three things were enough in my
               | case.
               | 
               | The hardest part were the numerous file path' from the
               | build machine. I had not expected that. In hindsight,
               | stripping both binaries before comparison might have
               | helped, but I don't remember why I didn't do that.
        
               | junon wrote:
               | Err, no, sorry, it won't. Compilers don't work that way.
               | There's a lot of ways to compile down source to machine
               | code and the output changes from compiler version to
               | compiler version. The LLM would have to know exactly how
               | the compiler worked at which version to do this. So the
               | idea is technically possible but not technically
               | feasible.
        
             | dheera wrote:
             | Maybe we then need an LLM to tell us if two pieces of
             | compiled code are equivalent in an input-output mapping
             | sense (ignoring execution time).
             | 
             | I'm actually serious; it would be exceedingly easy to get
             | training data for this just by running the same source code
             | through a bunch of different compiler versions and
             | optimization flags.
        
               | thfuran wrote:
               | Why would an llm be the tool for that job?
        
               | dheera wrote:
               | Without analytical thinking how else would you come to
               | conviction that two functions are identical, for a
               | computationally unfeasible number of possible inputs?
        
               | kolinko wrote:
               | Formal logic / formal proofs. We have good systems for
               | verifying that.
               | 
               | The proper flow is that you use LLM to generate
               | decompilation steps, along with potential proofs, and
               | then use old algorithms from 1970s that verify that the
               | steps are correct.
               | 
               | Source: I built a decompiler for EVM, arguably the best
               | one on the market, and to some extent it was how it
               | worked (and others comparable in class).
               | 
               | The issue was always the exploration of possible
               | transformations of code, once you manage to find the
               | right ones (which LLMs can propose way better than old
               | hard coded rules and SMT solvers), it's simple to verify
               | that the transformations are correct.
        
               | saagarjha wrote:
               | An LLM cannot do this. I don't even mean this in a formal
               | sense, because your problem is addressed by Rice's
               | Theorem, which places bounds on what any system (LLM or
               | not) can do here; I mean it in the sense that an LLM
               | isn't even appropriate to use here because the best it
               | can possibly do is provide you with its best guess at the
               | answer. And while this might be a useful property for
               | decompilation in general that's not what was being
               | discussed here.
        
               | dheera wrote:
               | Rice's theorem does NOT prevent a program from giving
               | correct answers to non-trivial properties of programs
               | (including the halting problem or other undecidable
               | problems) for 99.99% of inputs and "I don't know" for
               | 0.01% of inputs. It only states that you cannot write a
               | program that provides a correct and definitive yes-or-no
               | for 100% of inputs.
               | 
               | For a decompiler, being able to decompile even 90% of
               | programs would be awesome. We're not looking for
               | theoretical perfectness.
        
             | vasvir wrote:
             | Right.
             | 
             | A less formidable problem with higher chances of succeeding
             | is from a given binary to figure out first compiler,
             | compiler-version, compiler-flags.
             | 
             | From there you could have a model for every combination or
             | at least a model for the compiler variant and use the other
             | info (version, flags) as input to the model.
        
             | moyix wrote:
             | Yes, that's a limitation of trying to ensure exact binary
             | reconstruction. Luckily there is also a separate line of
             | work on detecting the compiler version and optimization
             | flags based on a binary - it turns out this is not _that_
             | hard and it 's easy to get a bunch of labeled data for a
             | classifier.
             | 
             | If folks are interested in reading more there's a nice
             | paper by Grammatech on the idea:
             | https://eschulte.github.io/data/bed.pdf (though it's pre-
             | LLM and uses evolutionary algorithms on the initial
             | decompilation to search for a version that recompiles
             | exactly).
        
           | codethief wrote:
           | > you should get the original binary
           | 
           | According to the project's README, they only seem to be
           | checking mere "re-compilability" and "re-executability" of
           | the decompiled code, though.
        
           | 1024core wrote:
           | > If you decompile the binary to source, then compile the
           | source back to binary you should get the original binary.
           | 
           | Doesn't that depend on the compiler's version though? Or, for
           | that matter, even the sub-version. Every compiler does things
           | differently.
        
             | fulafel wrote:
             | From the README:
             | 
             | > By re-compiling the decompiled output and running the
             | test cases, we assess if the decompilation preserved the
             | program logic and behavior.
             | 
             | As this is in the metrics section, I guess fully automating
             | this is not part of the research.
        
         | sebastianconcpt wrote:
         | Generators' nature is to hallucinate.
        
           | DougBTX wrote:
           | One man's hallucination is another's creativity.
        
             | sebastianconcpt wrote:
             | Well we need to remember that "hallucination" here is not a
             | concept but a language figure for the output of a
             | stochastic parroting machine. So what you mentinoed would
             | be a digitally induced halluciation out of some dancing
             | matrix multiplications / electrons on silicon.
        
         | riedel wrote:
         | One could as well use differential fuzzing.
        
           | klik99 wrote:
           | I'm amazed that there are so many good responses above only
           | this mentions fuzzing. In the context of security, inputs
           | might be non-linear things like adjacent memory, so I don't
           | see anyway to be confident about equilivancy without
           | substantial fuzzing.
           | 
           | Honestly I just don't see a way to formally verify this at
           | all, it's sounds like it could be a very useful tool but I
           | don't see a way for it to be fully confident. But, heck, just
           | getting you 90% of the way towards understanding it with LLMs
           | is still amazing and useful in real life.
        
         | layer8 wrote:
         | The way to do this is to have a formal verification tool that
         | takes the input, the output, and a formal proof that the input
         | matches the semantics of the output, and have the LLM create
         | the formal proof alongside the output. Then you can run the
         | verification tool to check if the LLM's output is correct
         | according to the proof that it also provided.
         | 
         | Of course, building and training an LLM that can provide such
         | proofs will be the bigger challenge, but it would be a safe a
         | way to detect hallucinations.
        
           | thfuran wrote:
           | Good luck formally proving Linux.
        
             | layer8 wrote:
             | The goal is to prove that the source code matches the
             | machine code, not to prove that the code implements some
             | intended higher-level semantics. This has nothing to do
             | with formally proving the correctness of the Linux kernel.
        
           | djinnandtonic wrote:
           | What if there are hallucinations in the verification tool?
        
             | thfuran wrote:
             | Then it's not a formal verification tool. Generative models
             | are profoundly unfit for that purpose.
        
             | layer8 wrote:
             | There may be bugs, but not hallucinations. Bugs are at
             | least reproducible, and the source code of the verification
             | tool is much, much smaller than an LLM, so has a much
             | higher chance of its finite number of bugs to be found,
             | whereas with an LLM it is probably impossible to remove all
             | hallucinations.
             | 
             | To turn your question around: What if the compiler that
             | compiles your LLM implementation "hallucinates"? That would
             | be the closer parallel.
        
             | smellf wrote:
             | I think the idea is that you'd have two independently-
             | develooed systems, one LLM decompiling the binary and the
             | other LLM formally verifying. If the verifier disagrees
             | with the decompiler you won't know which tool is right and
             | which is wrong, but if they agree then you'll know the
             | decompiled result is correct, since both tools are unlikely
             | to hallucinate the same thing.
        
               | layer8 wrote:
               | No, the idea is that the verifier is a human-written
               | program, like the many formal-verification tools that
               | already exist, not an LLM. There is zero reason to make
               | this an LLM.
               | 
               | It makes sense to use LLMs for the decompilation and the
               | proof generation, because both arguably require
               | creativity, but a mere proof verifier requires zero
               | creativity, only correctness.
        
           | natsch wrote:
           | That would require the tool to prove the equivalence of the
           | two programs, which is generally undecidable. Maybe this
           | could be weakened to preserving some properties of the
           | program.
        
             | ngruhn wrote:
             | That doesn't mean that it's impossible, right? Just that no
             | tool is guaranteed to give an answer in any case. And those
             | cases might be 90%, 10% or it-doesn't-matter-in-practice %
        
             | layer8 wrote:
             | No, it would not. It would require the LLM to provide a
             | proof for the program that it outputs, which seems
             | reasonable in the same way that a human decompiling a
             | program would be able to provide a record of his/her
             | reasoning.
             | 
             | The formal verifier would then merely check the provided
             | proof, which is a simple mechanical process.
             | 
             | This is analogous to a mathematician providing a detailed
             | proof and a computer checking it.
             | 
             | What is impossible due to undecidability is for two
             | _arbitrary_ programs, to either prove or disprove their
             | equivalence. However, the two programs we are talking about
             | are highly correlated, and thus not arbitrary at all with
             | respect to each other. If an LLM is able to provide a
             | correct decompilation, then in principle it should also be
             | able to provide a proof of the correctness of that
             | decompilation.
        
               | saagarjha wrote:
               | Yes, and then someone needs to check that proof. It's not
               | particularly clear if that decompilation proof would be
               | any more helpful than just doing the lifting by hand.
        
               | logicchains wrote:
               | No, nobody needs to check the proof; that's the whole
               | point of formal theorem proving, the machine checks it
               | for you.
        
         | afro88 wrote:
         | The detail how they measure this in the readme. This is
         | directed at all the sibling comments as well!
         | 
         | TLDR they recompile and then re-execute (including test
         | suites). From the results table it looks like GPT4 still
         | "outperforms" their model in recompilation, but their
         | recompiled code has a much better re-execution success rate
         | (less hallucinations). But, that re-execution rate is still
         | pretty lacking (around 14%), even if better than GPT4.
        
         | londons_explore wrote:
         | Even if it isn't fully reliable, often it's only necessary to
         | modify a few functions for most changes one wants to make to a
         | binary.
         | 
         | You'd therefore only need to recompile those few functions.
        
         | userbinator wrote:
         | LLMs are by nature probabilistic, which is why they work
         | reasonably well for "imprecise" domains like natural language
         | processing. Expecting one to do decompilation, or disassembly
         | for that matter, is IMHO very much a "wrong tool for the job"
         | --- but perhaps it's just an exploratory exercise for the "just
         | use an LLM" meme that seems to be a common trend these days.
         | 
         | The bigger argument against the effectiveness of this approach
         | is that existing decompilers can already do a much better job
         | with far less processing power.
        
           | czl wrote:
           | In the future efficient rule based compilers and decompiler
           | may be generated by AI systems trained on inputs and outputs
           | of what we use today.
           | 
           | This effort is an exploration to find a radically different
           | AI way that may give superior results.
           | 
           | Yes. For all the reasons you give above, AI for this job is
           | not practical today.
        
           | kolinko wrote:
           | LLMs are also good at connecting the dots and figuring out
           | patterns in a program. For this case it's ok if they miss
           | even 99% of the times, because you can formally verify which
           | solution is correct, and checking a 100 solutions is orders
           | of magnitude faster than checking millions/billions/trillions
           | of possibilities that a traditional solver needs to check.
        
         | GoblinSlayer wrote:
         | Sounds like it should be able to split the code into functions
         | with inferred API, then you should be able to fuzz these
         | functions in binary and source versions.
        
       | ReptileMan wrote:
       | Let's hope it kills Denuvo ...
        
         | Retr0id wrote:
         | Decompilation and deobfuscation are related but distinct tasks
        
       | AndrewKemendo wrote:
       | If successful wouldn't you be replicating the compilers machine
       | code 1:1?
       | 
       | In which case that means fully complete code can live in the
       | "latent space" but is distributed as probabilities
       | 
       | Or perhaps more likely would it be replicating the logic only,
       | which can then be translated into the target language
       | 
       | I would guess that any binary that requires a non-deterministic
       | input (key, hash etc...) to compile would break this
       | 
       | Fascinating
        
       | m3kw9 wrote:
       | Basically predicting code token by token except now you don't
       | even have a large enough context size and worse, you are using
       | RAG
        
       | xorvoid wrote:
       | As someone who is actively developing a decompiler to reverse
       | engineer old DOS 8086 video games, I'd have a hard time trusting
       | an LLM to do this correctly. My standard is accurate semantics
       | lifting from Machine Code to C. Reversing assembly to C is very
       | delicate. There are many patterns that tend to _usually_ map to
       | obvious C constructs... except when they don 't. And that assumes
       | the original source was C. Once you bump into routines that were
       | hand-coded assembly and break every established rule in the
       | calling conventions, all bets are off. I'm somewhat convinced
       | that decompilation cannot be made fully-automatic. Instead a good
       | decompiler is just a lever-arm on the manual work a reverser
       | would otherwise be doing. Corollary: I'm also somewhat convinced
       | that only the decompiler's developers can really use it most
       | effectively because they know where the "bodies are buried" and
       | where different heuristics and assumptions were made. Decompilers
       | are compilers with all the usual engineering challenges, plus a
       | hard inference problem tacked on top.
       | 
       | All that said, I'm not a pessimist on this idea. I think it has
       | pretty great promise as a technique for general reversing
       | security analysis where the reversing is done mostly for
       | "discovery" and "understanding" rather than for perfect semantic
       | lifting to a high-level language. In that world, you can afford
       | to develop "hypotheses" and then drill down to validate if you
       | think you've discovered something big.
       | 
       | Compiling and testing the resulting decompilation is a great
       | idea. I do that as well. The limitation here is TEST SUITE. Some
       | random binary doesn't typically come with a high-coverage test
       | suite, so you have to develop your own acceptance criterion as
       | you go along. In other words: write tests for a function whose
       | computation you don't understand (ha). I suppose a form of
       | static-analysis / symbolic-computation might be handy here (I
       | haven't explored that). Here you're also beset with challenges of
       | specifying which machine state changes are important and which
       | are superfluous (e.g. is it okay if the x86 FLAGS register isn't
       | modified in the decompiled version, probably yes, but sometimes
       | no).
       | 
       | In my case I don't have access to the original compiler and even
       | if I did, I'm not sure I could convince it to reproduce the same
       | code. Maybe this is more feasible for more modern binaries where
       | you can assume GCC, Clang, MSVC, or ICC.
       | 
       | At any rate: crazy hard, crazy fun problem. I'm sure LLMs have a
       | role somewhere, but I'm not sure exactly where: the future will
       | tell. My guess is some kind of "copilot" / "assistant" type role
       | rather than directly making the decisions.
       | 
       | (If this is your kind of thing... I'll be writing more about it
       | on my blog soonish...)
        
         | a2code wrote:
         | I would devise a somewhat loose metric. Consider you assign a
         | percentage as to how much a binary is disassembled. As in, 0%
         | means the binary is in assembly and 100% means the whole binary
         | is now C code. The ideal decompiler would result in 100% for
         | any binary.
         | 
         | My prediction is that this percentage will increase with time.
         | It would be interesting to construct data for this metric.
         | 
         | It is important to define the limitations of using LLMs for
         | this endeavor. I would like to emphasize your subtle point. The
         | compiler used for the original binary may not be the same as
         | the one you use. The probability of this increases with time,
         | as compilers improve or the platform on which the binary runs
         | becomes obsolete. This is a problem for validation, as in you
         | cannot directly compare original assembly code with assembly
         | after compiling C code (that came from decompiling).
         | 
         | Perhaps assembly routines could be given a likelihood, as in
         | how sure the LLM is that some C code maps to assembly. Then,
         | routines with hand-coded assembly would have a lower
         | likelihood.
        
           | xorvoid wrote:
           | Could you expand on how this metric would be practically
           | defined?
           | 
           | The problem isn't lifting to C code, but rather "good C
           | code". For example you can do a 1-to-1 translation on each
           | assembly instruction to C code that will do the same Machine
           | state changes. This is not usually why you want, as it comes
           | with a lot of extra cruft. When people think "decompiler"
           | they think of n output that looks like what they would
           | personal write. But that's very Ill-defined. And, personally
           | idk how one would define such a thing.
        
             | a2code wrote:
             | I am brainstorming here.
             | 
             | In practice, perhaps a C program that acts as a validation
             | test. The source code of this C program is not publicly
             | available. Only the binary is distributed. Let us name the
             | binary ctestbox.
             | 
             | When ctestbox is run, it creates a multiplicity of new text
             | or binary files. Each of these is like a unit test.
             | 
             | Consider a tool that decompiles a binary. Given ctestbox,
             | this tool should make a.out which when run, ideally creates
             | identical text or binary files. Now you simply count the
             | number of identical files as a metric.
        
         | ouraf wrote:
         | I'm curious about the decompilation process.
         | 
         | I know compiling is a lossy process and optimization can make
         | things even harder to remap, but if an LLM can recognize
         | patterns correctly, chunk or classify each routine or even give
         | a more palatable overview of what a part of the code is meant
         | to do step by step, it becomes closer to what HexRays offer
         | with their assembly to pseudo code translator. And from that
         | point, it can make serviceable translations to real world
         | languages.
         | 
         | LLMs won't replace an engineer, but maybe they can help
         | romhackers in identifying bugs or how some values are
         | calculated by a game.
        
         | albertan017 wrote:
         | Thanks. We acknowledge that an LLM cannot completely replace
         | human expertise in decompilation, much like GPT-4 has not
         | achieved true human-like intelligence. However, the aim of our
         | llm4decompile project is to do something like GPT-4, and offer
         | assistance and enhance productivity in the decompilation
         | process.
         | 
         | As for test suites, it's one of our project's main challenges--
         | figuring out which functions satisfy the expectations of
         | reverse engineers, how to autonomously produce high-coverage
         | test suites, and how to objectively qualify decompilation
         | outcomes without relying solely on human judgment. Looking
         | forward to your advices!
        
       | mdaniel wrote:
       | relevant: https://news.ycombinator.com/item?id=34250872 ( _G-3PO:
       | A protocol droid for Ghidra, or GPT-3 for reverse-engineering_ <h
       | ttps://github.com/tenable/ghidra_tools/blob/main/g3po/g3po....>;
       | Jan, 2023; 44 comments)
       | 
       |  _ed_ : seems they have this, too, which may value your
       | submission: https://github.com/tenable/awesome-llm-cybersecurity-
       | tools#a...
        
         | albertan017 wrote:
         | We find several work on refining Ghidra decompilation results
         | with GPTs, that could be another interesting directions!
        
       | sinuhe69 wrote:
       | For me the huge difference between re-compilability and re-
       | excuteability scores is very interesting. GTP4 achieved 8x% on
       | re-compilability (syntactically correct) but abysmal 1x% in re-
       | excutability (schematically correct) demonstrated once again its
       | overgrown mimicry capacity.
        
         | sitkack wrote:
         | > overgrown mimicry
         | 
         | I don't think it shows that. GPT4 was not trained on
         | decompiling binaries back into C. Amazing result for an
         | untrained task.
         | 
         | We are soon going to have robust toolchain detection from
         | binaries, and source recovery with variable and function names.
        
           | albertan017 wrote:
           | We're interested in the toolchain, could you share the link
           | or reference to it? GPT4 does an amazing work, we're also
           | very surprised that it can work.
        
       | speedylight wrote:
       | I have thought about doing something similar for heavily
       | obfuscated JavaScript. Very useful for security research I
       | imagine!
        
         | albertan017 wrote:
         | Ideally, with a substantial dataset of obfuscated JavaScript
         | and corresponding raw code, a language model could potentially
         | make good predictions. The first key difficulty, however, is
         | collecting a large-scale dataset and setting up a system for
         | automatic compilation and segment out the binary-source pairs.
        
       | quantum_state wrote:
       | It seems the next logical step would be LLMAssistedHacking to
       | turn things up side down...
        
       | mahaloz wrote:
       | It's always cool to see different approaches in this area, but I
       | worry its benchmarks are meaningless without a comparison of non-
       | AI based approaches (like IDA Pro). It would be interesting to
       | see how this model holds up on metrics from previous papers in
       | security.
        
         | albertan017 wrote:
         | Thanks! We're working on Ghidra/IDA pro. The problem we face is
         | the right kind of data to test with and how to evaluate it.
         | It's like there's no "standard" benchmark/metrics that everyone
         | uses for decompilation.
        
           | mahaloz wrote:
           | As others have said, the standardization of metrics is still
           | something debated, but at the same time, this space has been
           | explored by various top-tier papers that your paper did not
           | cite. For example, DREAM [1], evaluated using the classic
           | metric of goto-emittence. Rev.ng [2], evaluated using
           | Cyclomatic Complexity and gotos. SAILR [3], evaluated using
           | the previous metrics and a Graph Edit Distance score for the
           | structure of the code.
           | 
           | I feel that without a justification for dropping previously
           | established metrics by the peer review process, you weaken
           | your new metrics. However, I still think this is an
           | interesting paper. It just could be made more legit by
           | thoroughly reading/citing previous work in the area and
           | building an argument for why you may go against it.
           | 
           | [1]: https://net.cs.uni-
           | bonn.de/fileadmin/ag/martini/Staff/yakdan... [2]:
           | https://rev.ng/downloads/asiaccs-2020-paper.pdf [3]:
           | https://www.usenix.org/system/files/sec23winter-
           | prepub-301-b...
        
       | YeGoblynQueenne wrote:
       | If I read the "re-executability" results in the Results figure
       | right then that's a great idea but it doesn't really work:
       | 
       | https://raw.githubusercontent.com/albertan017/LLM4Decompile/...
       | 
       | To clarify:
       | 
       | >> Re-executability provides this critical measure of semantic
       | correctness. By re-compiling the decompiled output and running
       | the test cases, we assess if the decompilation preserved the
       | program logic and behavior. Together, re-compilability and re-
       | executability indicate syntax recovery and semantic preservation
       | - both essential for usable and robust decompilation.
        
       | Nuzzerino wrote:
       | How does it actually compare to non-LLM decompilers IDA, Binja,
       | etc? I only see comparisons with other LLMs.
        
         | albertan017 wrote:
         | Thanks! We're working on Ghidra/IDA pro. The problem we face is
         | the right kind of data to test with and how to evaluate it.
         | It's like there's no "standard" benchmark/metrics that everyone
         | uses for decompilation.
        
       | saagarjha wrote:
       | The approach here is interesting in that it answers a question a
       | lot of people have been asking: "what happens if we pipe a binary
       | into a trained LLM and ask it to decompile it?" The answer is
       | that it doesn't really work at all right now! This is a
       | surprising result because the design of the paper kind of doesn't
       | allow for any other conclusion to be drawn. Notably, if the LLM
       | did a really good job in the evaluation they designed it would
       | still be unclear whether it was actually useful, because the test
       | "does it compile and pass a few test cases" is not actually a
       | very good way to test a decompiler.
       | 
       | A couple people here have suggested that the generated
       | decompilation should match the source code exactly, which is a
       | challenging thing to achieve and still hotly debated on whether
       | it is a good metric or not. But the results here show that we're
       | starting to barely get past the "does it produce code" stage and
       | move towards "does it produce code that looks vaguely correct"
       | status but we're definitely not there yet. Future steps of "is
       | this a useful tool to drive decompilation" and "does this do
       | better than state of the art" and "is this perfect at decompiling
       | things" are still a long ways away. So it's good to look at as a
       | negative result as this area continues to attract new interest.
        
         | albertan017 wrote:
         | Thanks! Our initial experiments indicate that for simple cases,
         | such as short snippets (tens of lines) of code without external
         | dependencies, the LLM can decompile very well. However, for
         | more complicated examples, it tends to offer speculative
         | solutions, and the utility of these results is challenging to
         | assess. The determination of whether the decompiled output is
         | correct or useful is subjective and lacks a universal standard.
         | One approach we're considering is utilizing GPT-4 as a
         | benchmark to evaluate other models' performance. We're open to
         | further suggestions to refine our evaluation methods.
        
       | xvilka wrote:
       | I think using higher-level input, e.g. the intermediate language
       | like RzIL[1] could produce better results and is more scalable
       | for making such decompliation multiplatform. As RzIL text form
       | resemples SMT, it should make LLM easier to "understand" the
       | meaning. Moreover, information from binary such as symbols,
       | signatures, debug information (DWARF, PDB, etc) could enrich the
       | result further. You can download Rizin[2] and try for yourself by
       | calling `aaa` then `plf` for any chosen functions for
       | architectures supported by RzIL. See the example excerpt for a
       | function with this disassembly:                 |       |
       | 0x140007e51      movsd qword [rdi + 0x50], xmm2       |       |
       | 0x140007e56      mov   qword [rdi + 0x48], 0       |       |
       | 0x140007e5e      call  sym.rz_test.exe_ht_pp_free          ;
       | sym.rz_test.exe_ht_pp_free       |       |   0x140007e63
       | movaps xmm7, xmmword [var_38h]       |       |   0x140007e68
       | movaps xmm6, xmmword [var_28h]       |       |   0x140007e6d
       | mov   rbp, qword [var_10h]       |       +-> 0x140007e72      add
       | rsp, 0x48       |           0x140007e76      pop   r15       |
       | 0x140007e78      pop   rdi       +           0x140007e79      ret
       | 0x140007e6d (set rbp (loadw 0 64 (+ (var rsp) (bv 64 0x68))))
       | 0x140007e72 (seq (set op1 (var rsp)) (set op2 (bv 64 0x48)) (set
       | sum (+ (var op1) (var op2))) (set rsp (var sum)) (set _result
       | (var sum)) (set _popcnt (bv 8 0x0)) (set _val (cast 8 false (var
       | _result))) (repeat (! (is_zero (var _val))) (seq (set _popcnt (+
       | (var _popcnt) (ite (lsb (var _val)) (bv 8 0x1) (bv 8 0x0)))) (set
       | _val (>> (var _val) (bv 8 0x1) false)))) (set pf (is_zero (mod
       | (var _popcnt) (bv 8 0x2)))) (set zf (is_zero (var _result))) (set
       | sf (msb (var _result))) (set _result (var sum)) (set _x (var
       | op1)) (set _y (var op2)) (set cf (|| (|| (&& (msb (var _x)) (msb
       | (var _y))) (&& (! (msb (var _result))) (msb (var _y)))) (&& (msb
       | (var _x)) (! (msb (var _result)))))) (set of (|| (&& (&& (! (msb
       | (var _result))) (msb (var _x))) (msb (var _y))) (&& (&& (msb (var
       | _result)) (! (msb (var _x)))) (! (msb (var _y)))))) (set af (||
       | (|| (&& (msb (cast 4 false (var _x))) (msb (cast 4 false (var
       | _y)))) (&& (! (msb (cast 4 false (var _result)))) (msb (cast 4
       | false (var _y))))) (&& (msb (cast 4 false (var _x))) (! (msb
       | (cast 4 false (var _result))))))))       0x140007e76 (seq (set
       | r15 (cast 64 false (loadw 0 64 (+ (var rsp) (bv 64 0x0))))) (set
       | rsp (+ (var rsp) (bv 64 0x8))))       0x140007e78 (seq (set rdi
       | (loadw 0 64 (+ (var rsp) (bv 64 0x0)))) (set rsp (+ (var rsp) (bv
       | 64 0x8))))       0x140007e79 (seq (set tgt (loadw 0 64 (+ (var
       | rsp) (bv 64 0x0)))) (set rsp (+ (var rsp) (bv 64 0x8))) (jmp (var
       | tgt)))
       | 
       | [1] https://github.com/rizinorg/rizin/blob/dev/doc/rzil.md
       | 
       | [2] https://rizin.re
        
         | albertan017 wrote:
         | Thanks! The concern is how to uniformly uplift binary code from
         | various architectures and configurations to the same IR like
         | RzIL? Is there a method to automate the disassembly process
         | reliably across these different systems?
        
           | xvilka wrote:
           | What do you mean? The Rizin code does all the hard part (we
           | add uplifting code for every architecture manually; you can
           | see a list of supported architectures using `rz-asm -L` and
           | check for the `I` letter, which means "IL." You need to call
           | the necessary APIs. See, for example, how it's done in one of
           | the integration tests[1]. As for the use of Rizin from
           | Python, we have a rz-bindgen[2][3].
           | 
           | [1] https://github.com/rizinorg/rizin/blob/dev/test/integrati
           | on/...
           | 
           | [2] https://rizin.re/posts/gsoc-2022-rz-bindgen/
           | 
           | [3] https://github.com/rizinorg/rz-bindgen
        
       | dolmen wrote:
       | It seems to me that the objdump step (to transform binary to
       | human readable assembly) seems an unnecessary waste of runtime
       | resources.
       | 
       | It should be possible to tokenize directly from the binary.
        
         | albertan017 wrote:
         | Thanks! Processing raw binary data directly would be
         | inefficient for the language model, as it's not designed to
         | interpret strings of zeros and ones but for understanding
         | higher-level instructions (like code and natural language).
        
       | dwrodri wrote:
       | I have been planning to work on something like this. I think that
       | eventually, someone will crack the "binary in -> good source code
       | out of LLM" pipeline but we are probably a few years away from
       | that still. I say a few years because I don't think there's a
       | huge pile of money sitting at the end of this problem, but maybe
       | I'm wrong.
       | 
       | A really good "stop-gap" approach would be to build a
       | decompilation pipeline using Ghidra in headless mode and then
       | combine the strict syntax correctness of a decompiler with the
       | "intuition/system 1 skills" of an LLM. My inspiration for this
       | setup comes from two recent advancements, both shared here on HN:
       | 
       | 1. AlphaGeometry: The Decompiler and the LLM should complement
       | each other, covering each other's weaknesses.
       | https://deepmind.google/discover/blog/alphageometry-an-olymp...
       | 
       | 2. AICI: We need a better way of "hacking" on top of these
       | models, and being able to use something like AICI as the "glue"
       | to coordinate the generation of C source. I don't really want the
       | weights of my LLM to be used to generate syntactically correct C
       | source, I want the LLM to think in terms of variable names,
       | "snippet patterns" and architectural choices while other tools
       | (Ghidra, LLVM) worry about the rest.
       | https://github.com/microsoft/aici
       | 
       | Obviously this is all hand-wavey armchair commentary from a
       | former grad student who just thinks this stuff is cool. Huge
       | props to these researchers for diving into this. I know the
       | authors already mentioned incorporating Ghidra into their future
       | work, so I know they're on the right track.
        
       ___________________________________________________________________
       (page generated 2024-03-18 23:02 UTC)