Unexplored Areas
So far our goal has been to construct a PDF that is able to read and exfiltrate data from the hosting domain through HTTP requests. In this section, we will enumerate a few other interesting scenarios that we didn’t explore in depth, but that may enable bypassing some other web security features with PDFs.
If the goal is to exfiltrate just the document in which the injection occurs, then PDF forms might come handy. If there are two injection points, one could construct a PDF where the data between the injection points becomes the content of a form field. This form can then be submitted, and the content of the field can be read. When there is one injection point, it’s possible to set a flag on PDF forms that instructs the reader to submit the whole PDF file as is, which, in this case, includes the content to be exfiltrated. We weren’t able to get this to work reliably, but with some additional work, this could be a viable technique.
This technique might be usable in other PDF readers, like modern browsers’ built-in PDF plugins. It would also be interesting to have a look at the API surface these PDF readers expose, but we didn’t have the resources to have a deeper look into these yet.
Content Security Policy is a protection mechanism that can be used to prevent turning an HTML injection into XSS, by limiting the set of scripts the page is allowed to run. In other words, when an effective CSP is in place, it is impossible to run attacker-provided JavaScript code in the HTML page, even if the attacker has partial control over the HTML code of the page through an injection. Adobe Reader ignores the CSP HTTP header and can be forced to interpret the page as PDF with embedded Flash or FormCalc. Note that in this scenario we assume that the injection is unconstrained when it comes to the character set, so there’s no need to avoid newlines or other characters. This only works in HTML pages that don’t have a
Modern browsers block popups by default. This protection can be bypassed basically in all browsers running the Adobe Reader plugin by using the app.launchURL("URL", true) JavaScript API.
Last, but not least, we’ve run into many Adobe Reader memory corruption errors during our research. This indicates that the features we’ve tested are not widely used and fuzzed, so they might be a good target for future fuzzing projects.
Acknowledgments and Related Work
No research is done in a vacuum; Comma Chameleon was only possible because of prior research, inspiration, and collaboration with others in the community.
Using the PDF format for extracting same origin resources was first researched by Vladimir Vorontsov.26 Alex Inführ later presented various vulnerabilities in Adobe Reader.27
Vladimir and Alex demonstrated that PDF files could embed the scripts in the simple calculation language, FormCalc, to issue HTTP requests to same-origin URLs and read the responses. This requires no confirmation from the user and can be instrumented externally, so it was a natural fit for Rosetta Flash-style exploitation.
Following Alex’s proof of concept in 2015, @irsdl demonstrated a way of instrumenting the FormCalc script from the embedding, attacker-controlled page. The abovementioned served as a starting point for the Comma Chameleon research.
Comma Chameleon is part of a larger research initiative focused on modern MIME sniffing and as such was done with help of Claudio Criscione, Sebastian Lekies, Michele Spagnuolo, and Stephan Pfistner.
Throughout the research, we’ve used multiple PDF parser quirks demonstrated by Ange Albertini in his Corkami project.28
We’d like to thank all of the above!
12:5 A Crisis of Existential Import; or, Putting the VM in M/o/Vfuscator
by Chris Domas
A programmer writes code. That is his purpose: to define the sequence of instructions that must be carried out to perform a desired action. Without code, he serves no purpose, fulfills no need. What then would be the effect on our existential selves if we found that all code was the same, that every program could be written and executed exactly as every other? What if the net result of our century of work was precisely . . . nothing?
Here, we demonstrate that all programs, on all architectures,29 can be reduced to the same instruction stream; that is, the sequence of instructions executed by the processor can be made identical for every program. On careful analysis, it is necessary to observe that this is subtly distinct from prior classes of research. In an interpreter, we might say that the same instructions (those that compose the VM) can execute multiple programs, and this is correct; however, in an interpreter the sequence of the instructions executed by the processor changes depending on the program being executed—that is, the instruction streams differ. Alternatively, we note that it has been shown that the x86 MMU is itself Turing-complete, allowing a program to run with no instructions at all.30
In this sense, on x86, we could argue that any program, compiled appropriately, could be reduced to no instructions—thereby inducing an equivalence in their instruction streams. However, this peculiarity is unique to x86, and it could be argued that the MMU is then performing the calculations, even if the processor core is not—different calculations are being performed for different programs, they are just being performed “elsewhere.”
Instead, we demonstrate that all programs, on any architecture, could be simplified to a single, universal instruction stream, in which the computations performed are precisely equivalent for every program—if we look only at the instructions, rather than their data.
In our proof of concept, we will illustrate reducing any C program to the same instruction stream on the x86 architecture. It should be straightforward to understand the adaptation to other languages and architectures.
We begin the reduction with a rather ridiculous tool called the M/o/Vfuscator. The M/o/Vfuscator allows us to compile any C program into only x86 mov instructions. That is not to say the instructions are all the same—the registers, operands, addressing modes, and access sizes vary depending on the program—but the instructions are all of the mov variety. What would be the point of such a thing? Nothing at all, but it does provide a useful beginning for us—by compiling programs into only mov instructions, we greatly simplify the instruction stream, making further reduction feasible. The mov instructions are executed in a continuous loop, and compiling a program31 produces an instruction stream as follows:
But our mov instructions are of all varieties—from simple mov eax, edx to complex mov dl, [esi+4*ecx+0x19afc09], and everything in between. Many architectures will not support such complex addressing modes (in any instruction), so we further simplify the instruction stream to produce a uniform variety of movs. Our immediate goal is to convert the diverse x86 movs to a simple, 4-byte, indexed addressing varieties, using as few registers as possible. This will simplify the instruction stream for further processing and mimic the simple load and store operations found on RISC type architectures. As an example, let us assume 0x10000 is a 4-byte scratch location, and esi is kept at zero. Then mov eax, edx can be converted to
We have replaced the register-to-register mov variety with a standard 4-byte indexed memory read and write. Similarly, if we pad our data so that an oversized memory read will not fault, and pad our scratch space to allow writes to spill, then mov al, [0x20000] can be rewritten as
For more complex addressing forms, such as mov dx, [eax + 4*ebx + 0xdeadbeef], we break out the extra bit shift and addition using the same technique the M/o/Vfuscator uses—a series of movs to perform the shift and sum, allowing us to accumulate (in the example) eax+4*ebx into a single register, so that the mov can be reduced back to an indexed addressing eax+0xdeadbeef.
With such transforms, we are able to rewrite our diverse-mov program so that all reads are of the form mov esi/edi, [base + esi/edi] and all writes of the form mov [base + esi/edi], esi/edi, where base i
s some fixed address. By inserting dummy reads and writes, we further homogenize the instruction stream so that it consists only of alternating reads and writes. Our program now appears as (for example):
The only variation is in the choice of register and the base address in each instruction. This simplification in the instruction stream now allows us to more easily apply additional transforms to the code. In this case, it enables writing a non-branching mov interpreter. We first envision each mov as accessing “virtual,” memory-based registers, rather than CPU registers. This allows us to treat registers as simple addresses, rather than writing logic to select between different registers. In this sense, the program is now
where _esi and _edi are labels on 4-byte memory locations, and MOVE is a pseudo-instruction, capable of accessing multiple memory addresses. With the freedom of the pseudo-instruction MOVE, we can simplify all instructions to the exact same form:
We can now define each MOVE by its tuple of memory addresses:
and write this as a list of operands:
We now write an interpreter for our pseudo-mov. Let us assume the physical esi register now holds the address of a tuple to execute:
Finally, we execute this single MOVE interpreter in an infinite loop. To each tuple in the operand list, we append the address of the next tuple to execute, so that esi (the tuple pointer) can be loaded with the address of the next tuple at the end of each transfer iteration. This creates the final system:
The operand list is generated by the compiler, and the single universal program appended to it. With this, we can compile all C programs down to this exact instruction stream. The instructions are simple, permitting easy adaptation to other architectures. There are no branches in the code, so the precise sequence of instructions executed by the processor is the same for all programs. The logic of the program is effectively distilled to a list of memory addresses, unceremoniously processed by a mundane, endless data transfer loop.
So, what does this mean for us? Of course, not so much. It is true, all “code” can be made equivalent, and if our job is to code, then our job is not so interesting. But the essence of our program remains—it had just been removed from the processor, diffused instead into a list of memory addresses. So rather, I suppose, that when all logic is distilled to nothing, and execution has lost all meaning—well, then, a programmer’s job is no longer to “code,” but rather to “data!”
This project, and the proof of concept reducing compiler, can be found at Github32 and as an attachment.33 The full code elaborates on the process shown here, to allow linking reduced and non-reduced code. Examples of AES and Minesweeper running with identical instructions are included.
12:6 A JCL Adventure with Network Job Entries
by Soldier of Fortran
Mainframes. Long the cyberpunk mainstay of expert hackers, they have spent the last thirty years in relative obscurity within the hallowed halls of hackers/crackers. But no longer! There are many ways to break into mainframes, and this article will outline one of the most secret components hushed up within the dark corners of mainframe mailing lists: Network Job Entry (NJE).
Operating System and Interaction
With the advent of the mainframe, IBM really had a winner on their hands: one of the first multipurpose computers that could serve multiple different activities on the same hardware. Prior to OS/360, you only had single-purpose computers. For example, you’d get a machine that helps you track inventory at all your stores. It worked so well that you figured you wanted to use it to process your payroll. No can do, you needed a separate bespoke system for that. Enter IBMs OS/360, and, from large to small, you had a system that was multipurpose but could also scale as your needs did. It made IBM billions, which was good because it almost cost the company its very existence. OS/360 was released in 1964 and (though re-written entirely today) still exists around the world as z/OS.
z/OS is composed of many different components that this article doesn’t have the time to get in to, but trust me when I say there are thousands of pages to be read out there about using and operating z/OS. A brief overview, however, is needed to understand how NJE (Network Job Entry) works, and what you can do with it.
Time Sharing and UNIX
You need a way to interact with z/OS. There are many different ways, but I’m going to outline two here: OMVS and TSO.
OMVS is the easiest, because it’s really just UNIX. In fact, you’ll often hear USS, or Unix System Services, mentioned instead of OMVS. For the curious, OMVS stands for Open MVS; (MVS stands for Multiple Virtual Storage, but I’ll save virtual storage for its own article.) Shown in Figure 12.4, OMVS is easy—because it’s UNIX, and thus uses familiar UNIX commands.
TSO is just as easy as OMVS—when you understand that it is essentially a command prompt with commands you’ve never seen or used before. TSO stands for Time Sharing Option. Prior to the common era, mainframes were single-use—you’d have a stack of cards and have a set time to input them and wait for the output. Two people couldn’t run their programs at the same time. Eventually, though, it became possible to share the time on a mainframe with multiple people. This option to share time was developed in the early seventies and remained optional until 1974. Figure 12.5 shows the same commands as in Figure 12.4, but this time in TSO.
Datasets and Members; Files and Data
In the examples above you had a little taste of the file system on z/OS. OMVS looks and feels like UNIX, and it’s a core component of the operating system; however, its file system resides within what we call a dataset. Datasets are what z/OS people would refer to as files or folders. They are composed of either fixed-length or variable-length data.34 You can also create what is called a PDS or Partitioned Data Set, what you or I would call a folder. Let’s take a look at the TSO command listds again, but this time we’ll pass it the parameter members.
Figure 12.4: OMVS
Figure 12.5: TSO
Here we can see that the file EXAMPLE was in fact a folder that contained the files MANIFEST and PHRACK. Of course this would be too easy if they just called it “files” and “folders;” no, these are called datasets and members.
Another thing you may be noticing is that there seem to be dots instead of slashes to denote folders/files hierarchy. It’s natural to assume—if you don’t use mainframes—that the nice comforting notion of a hierarchy carries over with some minimal changes—but you’d be wrong. z/OS doesn’t really have the concept of a folder hierarchy.
The files dade.file1.g2 and dade.file2.g2 are simply named this way for convenience. The locations, on disk, of various datasets, etc. are controlled by the system catalogue—which is another topic to save away for a future article. Regardless, those dots do serve a purpose and have specific names. The text before the first dot is called a High Level Qualifier, or HLQ. This convention allows security products the ability to provide access to clusters of datasets based on the HLQ. The other ‘levels’ also have names, but we can just call them qualifiers and move on. For example, in the listds example above we wanted to see the members of the file DADE.EXAMPLE where the HLQ is DADE.
Figure 12.6: Simple JCL File
Jobs and Languages
Now that you understand a little about the file system and the command interfaces, it is time to introduce JES2 and JCL. JES2, or Job Entry Subsystem v2, is used to control batch operations. What are batch operations? Simply put, these are automated commands/actions that are taken programmatically. Let’s say you’re McDonalds and need to process invoices for all the stores and print the results. The invoice data is stored in a dataset, you do some work on that data, and print out the results. You’d use multiple different programs to do that, so you write up a script that does this work for you. In z/OS we’d refer to the work being performed as a job, and the script would be referred to as JCL, or Job Control Language.
There are many options and intricacies of JCL and of using JCL, and I won’t be going over those. Instead, I’m going to show you a few
examples and explain the components.
Figure 12.6 shows a very simple JCL file. In JCL each line starts with a //. This is required for every line that’s not parameters or data being passed to a program. The first line is known as the job card. Every JCL file starts with it. In our example, the NAME of the job is USSINFO, then comes the TYPE (JOB) followed by the job name (JOBNAME) and programs exec cat and netstat. The remaining items can be understood by reading documentation and tutorials.35
Next we have the STEP. We give each job step a name. In our example, we gave the first step the name UNIXCMD. This step executes the program BPXBATCH.
What the hell is BPXBATCH? Essentially, all UNIX programs, commands, etc., start with BPX. In our JCL, BPXBATCH means “UNIX BATCH,” which is exactly what this program is doing. It’s executing commands in UNIX through JES as a batch process. So, using JCL we EXECute the ProGraM BPXBATCH: EXEC PGM=BPXBATCH
Skipping STDIN and STDOUT, which just mean to use the defaults, we get to STDPARM. These are the options we wish to pass to BPXBATCH (PARM stands for parameters). It takes UNIX commands as its options and executes them in UNIX. In our example, it’s catting the file example/manifest and displaying the current IP configuration with netstat home. If you ran this JCL, it would cat the file /dade/example/manifest, execute netstat home, and print any output to STDOUT, which really means it will print it to the log of your job activities.
PoC or GTFO, Volume 2 Page 27