If I know the CPU architecture of a target, can I send instructions embedded in an image?

  • Can I send instructions embedded in an image to a target, if I know his CPU architecture?

    Sure you can. But it's not going to execute them. Actually most bytes are valid CPU instructions.

    sure, just write then on a piece of paper and take a photo... :)

    Note that there are image formats that explicitly rely on "arbitrary code execution" (mostly from the old era when compatibility and saving memory and CPU was more important than securing images). The most notable is probably WMF, where the feature was very reasonable and safe in the original implementation, but the safety was lost when porting the code from Windows 3.0 (16-bit) to Windows NT (32-bit, and only on x86). So this was one of the rare cases where e.g. Windows 98 (desktop) was safer than Windows NT (server/professional) :)

    Did vulnerabilities lead to asking this question?

    Of course you can, and for any other form of media too. So you better trust your media viewer to not have bugs or unexpected "features".

  • yshavit

    yshavit Correct answer

    3 years ago

    CPU instructions are given in what are called opcodes, and you're right that those live in memory just like your image. They live in conceptually different "areas" of memory, though.

    For instance, you could imagine an opcode "read" (0x01) that reads a byte of input from stdin and puts it somewhere, and another operand "add" (0x02) that adds two bytes. Some opcodes can take arguments, so we'll give our example opcodes some: the "read" opcode takes an operand for where to store the byte it reads, and the "add" one takes three operands: two for where to read its inputs, and one for where to write the result to.

    0x01 a1 // read a byte to memory location 0xa1
    0x01 a2 // read a byte to memory location 0xa2
    0x02 a1 a2 a3 // read the bytes at locations 0xa1 and 0xa2, add them,
                  // and write the result to location 0xa3

    This is typical of how a lot of instructions work: most of them just operate on data that's in memory, and some of them put new data into memory from the outside world (from reading stdin, in this example, or from reading a file or network interface).

    While it's true that the the instructions and the data they operate on are both in memory, the program will only run the instructions. It's the job of the CPU, the OS, and the program to all make sure that happens. When they fail, you can in fact load data into the execution space, and it's a serious security bug. Buffer overflows are probably the most famous example of such a bug. But other than those kinds of bugs, you can essentially think of the data space and the execution space as being separate chunks of CPU.

    In a toy computer, using the example above, the memory might look something like:

    (loc)  | Initial      | After op 1   | After op 2   | After op 3   |
    0x00   | *01 a1       |  01 a1       |  01 a1       |  01 a1       |
    0x02   |  01 a2       | *01 a2       |  01 a2       |  01 a2       |
    0x04   |  02 a1 a2 a3 |  02 a1 a2 a3 | *02 a1 a2 a3 |  02 a1 a2 a3 |
    0x08   |  99          |  99          |  99          | *99          |
    0xa1   |  00          |  03          |  03          |  03          |
    0xa2   |  00          |  00          |  04          |  04          |
    0xa3   |  00          |  00          |  00          |  07          |

    In that example, the asterisk (*) points to the next opcode that will be executed. The leftmost column specifies the starting memory location for that line. So for instance, the second line shows us two bytes of memory (with values 01 and a2) at locations 0x02 (explicitly in the left-hand column) and 0x03.

    (Please understand that this is all a big simplification. For instance, in a real computer the memory can be interleaved -- you won't just have one chunk of instructions and one chunk of everything else. The above should be good enough for this answer, though. )

    Note that as we run the program, the memory in areas 0xa1 - 0xa3 changes, but the memory in 0x00 - 0x08 does not. The data in 0x00 - 0x08 is our program's executable, while the memory in areas 0xa1 - 0xa3 is memory the program uses to do the number crunching.

    So to get back to your example: the data in your jpeg will get loaded into the memory by opcodes, and will be manipulated by opcodes, but will not be loaded into their same area in memory. In the example above, the two values 0x03 and 0x04 were never in opcode area, which is what the CPU executes; they were only in the area that the opcodes read and write from. Unless you found a bug (like a buffer overflow) which let you write data into that opcode space, your instructions won't be executed; they'll just be the data that gets manipulated by the program's execution.

    Your answer is at best partially correct. Apart from the simplification that all data must reside on the "stack", it is a wrong assumption that manipulating data on the stack cannot be used for arbitrary code execution.

    @JimmyB He literally says it's a big simplification, and the question isn't talking about exploits, rather just inserting code into an image. The answer does a good job of explaining in simple terms why code inserted into an image won't just execute.

    @Blackhawk: Almost everything about the stack is wrong. It's sort of vaguely similar to how stack machine architectures operate, but most modern computers don't use a stack machine architecture, and even stack machines use the stack as a temporary holding ground for instruction inputs and outputs, not as where all data is kept. (The call stack used in modern computer architectures is a different thing, and that stack doesn't work like this answer describes either.)

    Also, buffer overflows generally overwrite things like return addresses, not executable memory.

    While the "not unless you exploit a bug" part is correct, this answer goes into lots and lots of unnecessary and *wrong* detail that makes it seem more informative to an inexperienced reader, but actually makes the answer worse than the short but correct answers that didn't get accepted.

    @user2357112 I was striving to come up with a little "toy VM," but I think you're right that it sacrificed too much, and didn't simplify enough. I got rid of mentions of the stack. Let me know what you think.

    (I did omit discussions of registers and such, though -- I don't think they'd add much to what the OP is asking about.)

    What would von Neumann say?

    It would probably be much simpler to say "Data is only executed if the IP register is changed to the address of the data in memory" than to go through all of this.

    @forest I think that would mainly work if someone already had a lot of this knowledge. I was trying to target someone who does understand that the program lives in memory, and that so does the image data, but doesn't understand how they're kept distinct. I personally find it helpful to give a concrete if simplified example, though I definitely understand that other people think it doesn't work. :)

    @Blackhawk That is "proof from authority", the above is "lies told to children". Lies told to children have value.

    There should be a button to move it to chat, if you want to do that.

    @yshavit I think you should remove the statement "While it's true that the the instructions and the data they operate on are both in memory, they're in different parts of memory." because it is at best misleading and at worst just plain wrong.

    @JimmyJames They're in different conceptual places. It's true that the two spaces are interspersed physically, but the point I was trying to get across is that they're treated as different, and in fact when they're accidentally not treated as different, that's a bug. What do you think about tweaking it to "they're in conceptually separate parts of memory"?

    @yshavit Better? It's a hard concept to explain without understanding the way machine code executes. The succinct way I would put it is something like: "data executes as instructions only when a running process interprets them that way."

    But modern systems are Von Neumann architecture, not Harvard architecture, so it's nothing but an execute bit that determines if a page is executable, not where it lives in memory (ignoring the fact that x86 CPUs are Harvard internally, what with separate L1d and L1i caches).

    @forest: x86 has coherent i-cache (unlike many other architectures which require explicit flush/sync insns before you can reliably execute newly-stored machine-code bytes), so from a software PoV modern x86 is pure von Neumann. Observing stale instruction fetching on x86 with self-modifying code. And BTW, split L1 caches for a unified address-space is usually described as a Modified Harvard architecture, of the almost-von-Neumann variety. https://en.wikipedia.org/wiki/Modified_Harvard_architecture#Split-cache_(or_almost-von-Neumann)_architecture

    @PeterCordes Yep, I know that it's a Modified Harvard Architecture, but that's invisible to the ISA.

    @forest: yup, invisible in the ISA is exactly what I was saying, too. :) (Well technically, on paper x86 is allowed to run stale instructions until a jmp/branch, but actual hardware implementations choose to be stronger than the paper ISA because that's actually easier than being *exactly* as strong as the spec but never weaker. That's what Andy Glew's answer on the linked Q&A is about. (Andy was one of the architects of Intel's P6 uarch). Also in your earlier comment you left out the "Modified", and I was feeling pedantic. :P Non-modified Harvard implies separate address-spaces.

License under CC-BY-SA with attribution

Content dated before 7/24/2021 11:53 AM

Tags used