Abstract
- History indicates that the security community commonly takes a divide-and-conquer approach to battling malware threats: identify the essential and inalienable components of an attack, then develop detection and prevention techniques that directly target one or more of the essential components.
์ญ์ฌ์ ๋ฐ๋ฅด๋ฉด ๋ณด์ ์ปค๋ฎค๋ํฐ๋ ์ผ๋ฐ์ ์ผ๋ก ๋งฌ์จ์ด ์ํ๊ณผ ์ธ์ฐ๊ธฐ ์ํด ๋ถํ ์ ๋ณต ๋ฐฉ์์ ์ทจํฉ๋๋ค. ๊ณต๊ฒฉ์ ํ์ ๊ตฌ์ฑ ์์์ ์๋ํ ์ ์๋ ๊ตฌ์ฑ ์์๋ฅผ ์๋ณํ ๋ค์, ํ์ ๊ตฌ์ฑ ์์ ์ค ํ๋ ์ด์์ ์ง์ ๋์์ผ๋ก ํ๋ ํ์ง ๋ฐ ๋ฐฉ์ง ๊ธฐ์ ์ ๊ฐ๋ฐํฉ๋๋ค.
- This abstraction is evident in much of the literature for buffer overflow attacks including, for instance, stack protection and NOP sled detection. It comes as no surprise then that we approach shellcode detection and prevention in a similar fashion.
์ด ์ถ์ํ๋ ์๋ฅผ ๋ค์ด ์คํ ๋ณดํธ ๋ฐ NOP ์ฌ๋ ๋ ๊ฐ์ง๋ฅผ ํฌํจํ ๋ฒํผ ์ค๋ฒํ๋ก ๊ณต๊ฒฉ์ ๋ํ ๋ง์ ๋ฌธํ์์ ๋ถ๋ช ํฉ๋๋ค. ์ฐ๋ฆฌ๊ฐ ๋น์ทํ ๋ฐฉ์์ผ๋ก ์์ฝ๋ ํ์ง ๋ฐ ๋ฐฉ์ง์ ์ ๊ทผํ๋ค๋ ๊ฒ์ ๋๋ผ์ด ์ผ์ด ์๋๋๋ค.
- However, the common belief that components of polymorphic shellcode (e.g., the decoder) cannot reliably be hidden suggests a more implicit and broader assumption that continues to drive contemporary research: namely, that valid and complete representations of shellcode are fundamentally different in structure than benign payloads.
๊ทธ๋ฌ๋, ๋คํ ์ ธ ์ฝ๋์ ๊ตฌ์ฑ ์์(์: ๋์ฝ๋)๋ฅผ ์์ ์ ์ผ๋ก ์จ๊ธธ ์ ์๋ค๋ ์ผ๋ฐ์ ์ธ ๋ฏฟ์์ ํ๋ ์ฐ๊ตฌ๋ฅผ ๊ณ์ ์ถ์งํ๋ ๋ ์๋ฌต์ ์ด๊ณ ๊ด๋ฒ์ํ ๊ฐ์ ์ ์ ์ํ๋ค. ์ฆ, ์ ธ ์ฝ๋์ ์ ํจํ๊ณ ์์ ํ ํํ์ ์์ฑ ํ์ด๋ก๋์ ๊ตฌ์กฐ๊ฐ ๊ทผ๋ณธ์ ์ผ๋ก ๋ค๋ฅด๋ค๋ ๊ฒ์ด๋ค.
- While the first tenet of this assumption is philosophically undeniable (i.e., a string of bytes is either shellcode or it is not), truth of the latter claim is less obvious if there exist encoding techniques capable of producing shellcode with features nearly indistinguishable from non-executable content.
์ด ๊ฐ์ ์ ์ฒซ ๋ฒ์งธ ์์น์ ์ฒ ํ์ ์ผ๋ก ๋ถ์ธํ ์ ์์ง๋ง(์ฆ, ๋ฐ์ดํธ์ ๋ฌธ์์ด์ ์ ธ ์ฝ๋์ด๊ฑฐ๋ ๊ทธ๋ ์ง ์์), ์คํ ๋ถ๊ฐ๋ฅํ ๋ด์ฉ๊ณผ ๊ฑฐ์ ๊ตฌ๋ณํ ์ ์๋ ํน์ง์ ๊ฐ์ง ์ ธ ์ฝ๋๋ฅผ ์์ฑํ ์ ์๋ ์ธ์ฝ๋ฉ ๊ธฐ์ ์ด ์กด์ฌํ๋ค๋ฉด ํ์์ ์ฃผ์ฅ์ ์ง์ค์ ๋ ๋ช ๋ฐฑํ๋ค.
- In this paper, we challenge the assumption that shellcode must conform to superficial and discernible representations.
๋ณธ ๋ ผ๋ฌธ์์, ์ฐ๋ฆฌ๋ ์ ธ ์ฝ๋๊ฐ ํผ์์ ์ด๊ณ ์๋ณ ๊ฐ๋ฅํ ํํ์ ์ค์ํด์ผ ํ๋ค๋ ๊ฐ์ ์ ๋์ ํ๋ค.
- Specifically, we demonstrate a technique for automatically producing English Shellcode, transforming arbitrary shellcode into a representation that is superficially similar to English prose.
ํนํ, ์ฐ๋ฆฌ๋ ์์์ ์ ธ ์ฝ๋๋ฅผ ํ๋ฉด์ ์ผ๋ก ์์ด ์ฐ๋ฌธ๊ณผ ์ ์ฌํ ํํ์ผ๋ก ๋ณํํ๋ ์์ด ์ ธ ์ฝ๋๋ฅผ ์๋์ผ๋ก ์์ฑํ๋ ๊ธฐ์ ์ ์์ฐํ๋ค.
- The shellcode is completely self-contained i.e., it does not require an external loader and executes as valid IA32 code) and can typically be generated in under an hour on commodity hardware.
์์ฝ๋๋ ์๋ฒฝํ sellf-contained ๋์ด์ ธ์๊ณ ( ์ฆ, IA32์ ํ๋นํ ์คํ์ด๋ ์ธ๋ถ ๋ก๋๋ฅผ ์๊ตฌํ์ง ์๋๋ค) ๊ทธ๋ฆฌ๊ณ ์ ํ์ ์ผ๋ก ํ์๊ฐ์์ ๋ฐ์๋์ด์ง๋ค.
- Our primary objective in this paper is to promote discussion and stimulate new ideas for thinking ahead about preventive measures for tackling evolutions in code-injection attacks.
์ด ๋ ผ๋ฌธ์์ ์ฐ๋ฆฌ์ ์ฃผ๋ ๋ชฉ์ ์ ํ ๋ก ์ ์ด์งํ๊ณ ์ฝ๋ ์ฃผ์ ๊ณต๊ฒฉ์์ ์งํ๋ฅผ ๋ค๋ฃจ๊ธฐ ์ํ ์๋ฐฉ ์กฐ์น์ ๋ํด ๋ฏธ๋ฆฌ ์๊ฐํ ์ ์๋ ์๋ก์ด ์์ด๋์ด๋ฅผ ์๊ทนํ๋ ๊ฒ์ด๋ค.
Conclusion
- In this paper we revisit the assumption that shellcode need be fundamentally different in structure than non-executable data.
์ด ๋ ผ๋ฌธ์์ ์ฐ๋ฆฌ๋ ์ ธ ์ฝ๋๊ฐ ์คํ ๋ถ๊ฐ๋ฅํ ๋ฐ์ดํฐ์ ๊ทผ๋ณธ์ ์ผ๋ก ๋ค๋ฅธ ๊ตฌ์กฐ๊ฐ ํ์ํ๋ค๋ ๊ฐ์ ์ ๋ค์ ์ดํด๋ณธ๋ค.
- Specifically, we elucidate how one can use natural language generation techniques to produce shellcode that is superficially similar to English prose.
๊ตฌ์ฒด์ ์ผ๋ก, ์ฐ๋ฆฌ๋ ์ด๋ป๊ฒ ์์ฐ์ด ์์ฑ ๊ธฐ์ ์ ์ฌ์ฉํ์ฌ ํ๋ฉด์ ์ผ๋ก ์์ด ์ฐ๋ฌธ๊ณผ ์ ์ฌํ ์์ฝ๋๋ฅผ ์์ฑํ ์ ์๋์ง๋ฅผ ์ค๋ช ํ๋ค.
- We argue that this new development poses significant challenges for inline payloadbased inspection (and emulation) as a defensive measure, and also highlights the need for designing more efficient techniques for preventing shellcode injection attacks altogether.
์ฐ๋ฆฌ๋ ์ด ์๋ก์ด ๊ฐ๋ฐ์ด ๋ฐฉ์ด ์๋จ์ผ๋ก์ ์ธ๋ผ์ธ ํ์ด๋ก๋ ๊ธฐ๋ฐ ๊ฒ์ฌ(๋ฐ ์๋ฎฌ๋ ์ด์ )์ ์ค์ํ ๊ณผ์ ๋ฅผ ์ ๊ธฐํ๊ณ , ๋ํ ์ ธ ์ฝ๋ ์ฃผ์ ๊ณต๊ฒฉ์ ์์ ํ ๋ฐฉ์งํ๊ธฐ ์ํ ๋ณด๋ค ํจ์จ์ ์ธ ๊ธฐ์ ์ ์ค๊ณํด์ผ ํ ํ์์ฑ์ ๊ฐ์กฐํ๋ค๊ณ ์ฃผ์ฅํ๋ค.
Introduction
- Definition of code-injection attack: Code-injection attacks are perhaps one of the most common attacks on modern computer systems. These attacks are used to deliver and run arbitrary code on victimsโ machines, often enabling unauthorized access and control of system resources, applications, and data. Typically, the vulnerabilities being exploited arise due to some level of neglect on the part of system and application developers to properly define and reject invalid program input. Indeed, the canonical consequences of such neglect, which include buffer and heap overflow attacks, format string attacks, and (more recently) heap spray attacks, categorically demonstrate some of the most popular code-injection techniques.
- Generally speaking, an attackerโs first objective in a codeinjection attack is to gain control of a machineโs program counter.
- The program counter is a special purpose machine register that identifies the next instruction scheduled for execution. By gaining control of the program counter, an attacker is able to redirect program execution and disruptthe intended behavior of the program.
- With the ability to manipulate the program counter, attackers sometimes redirect a victimโs machine to execute (already present) application or system code in a manner beneficial to an attackerโs intent.
- For instance, return-to-libc attacks provide a well-documented example of this kind of manipulation.
- In a code-injection attack, however, attackers redirect the program counter to execute code delivered by the attackers themselves.
- Depending on the details of the particular vulnerability that an attacker is targeting,injected code can take several forms including source code for an interpreted scripting-language engine, intermediate byte-code, or natively-executable machine code.
- Despite differences in the style and implementation of different exploits, e.g., buffer overflow versus format string attacks, all code-injection attacks share a common component: the injected code.
- This payload, once executed after successful manipulation of the program counter, often provides attackers with arbitrary control over a vulnerable machine.
- Frequently (though not always), attackers deliver a payload that simply launches a command shell. It is for this reason that many in the hacking community generically refer to the payload portion of a code-injection attack as shellcode.
- Among those less familiar with code-injection attacks, there is sometimes a subtle misconception that shellcode is necessarily delivered in tandem with whichever message ultimately exploits a vulnerability and grants an attacker control of the program counter.
- While this assumption typically holds in more traditional buffer overflow vulnerabilities, modern attacks demonstrate that attackers have developed numerous techniques to covertly deliver (and ultimately store into memory) shellcode separately from and prior to triggering the exploit.
- For instance, if an attacker can manipulate memory at a known heap address, they may store their shellcode there, using its address later when overwriting a return address on the stack.
- We draw attention to this distinction because our use of the term shellcode in this paper specifically denotes the injected code irrespective of individual attacks or vulnerabilities.
- Typically, shellcode takes the form of directly executable machine code, and consequently, several defensive measures that attempt to detect its presence, or prevent its execution altogether, have been proposed.
- Indeed, automated inspection of user input, system memory, or network traffic for content that appears statistically or superficially executable are now common.
- However, as expected, a number of techniques have been developed that circumvent these protective measures, or make their job far more difficult (e.g., polymorphism).
- Recently, it has been suggested that even polymorphic shellcode is constrained by an essential component: the decoder.
- The argument is that the decoder is a necessary and executable companion to encoded shellcode, enabling the encoded portion of the payload to undergo an inverse transformation to its original and executable form.
- Since the decoder must be natively executable, the prevailing thought is that we can detect its presence assuming that this portion of the payload will bear some identifiable features not common to valid or non-executable data.
- It is this assumption that shellcode is fundamentally different in structure than non-executable payload dataโthat continues to drive some avenues of contemporary research (e.g., [27, 16, 15, 26]).
- By challenging the assumption that shellcode must conform to superficial and discernible representations, we question whether protective measures designed to assume otherwise are likely to succeed.
- Specifically, we demonstrate a technique for automatically producing English Shellcode that is, transforming arbitrary shellcode into a representation that is statistically similar to English prose.
- By augmenting corpora-based natural-language generation with additional constraints uniquely dictated by each instance of shellcode, we generate encodings complete with decoder that remain statistically faithful to the corpus and yield identical execution as the original shellcode.
- While we in no way claim that instantiations of this encoding are irrefutably indistinguishable from authentic English proseโindeed, as shown later, it is clear they are notโthe expected burden associated with reliably detecting English-encoded shellcode variants in juxtaposition to genuine payloads at line speed raises concerns about current preventative approaches.
- Similar to the goal of Song et al. [20], our objective in this paper is to promote discussion and stimulate new ideas for thinking about how to tackle evolutions in code-injection attacks.
- Although most of the attacks observed today have used relatively naยจฤฑve shellcode engines [17, 26], exploit code will likely continue to evade intrusion detection and prevention systems because malcode developers do not follow the โrulesโ.
- As this cat and mouse game plays on, it is clear that the attackers will adapt.
- So should we, especially as it pertains to exploring new directions for preventative measures against code-injection attacks.
On the ARMS RACE
- In this paper, we focus on natively-executable shellcode for x86 processors.
- In this case, machine code and shellcode are fundamentally identical; they both adhere to the same binary representation directly executable by the processor.
- Shellcode developers are often faced with constraints that limit the range of byte-values accepted by a vulnerable application.
- For instance, many applications restrict input to certain character-sets (e.g., printable, alphanumeric, MIME), or filter input with common library routines like isalnum and strspn.
- The difficulty in overcoming these restrictions and bypassing input filters depends on the range of acceptable input.
- Of course, these restrictions can be bypassed by writing shellcode that does not contain restricted bytevalues (e.g., null-bytes).
- Although such restrictions often limit the set of operations available for use in an attack, attackers have derived encodings to convert unconstrained shellcode honoring these restrictions by building equivalency operations from reduced instruction sets (e.g., [25, 11]).
- Of special note are the alphanumeric encoding engines [18] present in Metasploit (see www.metasploit.com).
- These engines convert arbitrary payloads to representations composed only of letters and numerical digits.(์ด ์์ง์ ์์์ ํ์ด๋ก๋๋ฅผ ๋ฌธ์์ ์ซ์๋ก๋ง ๊ตฌ์ฑ๋ ํํ์ผ๋ก ๋ณํํฉ๋๋ค.)
- These encodings are significant for two reasons.
- First, alphanumeric shellcode can be stored in atypical and otherwise unsuspected contexts such as syntactically valid file and directory names or user passwords (์์ซ์ ์์ฝ๋๋ ๊ตฌ๋ฌธ์ ์ผ๋ก ์ ํจํ ํ์ผ ๋ฐ ๋๋ ํ ๋ฆฌ ์ด๋ฆ ๋๋ ์ฌ์ฉ์ ์ํธ์ ๊ฐ์ ๋น์ ํ์ ์ด๊ฑฐ๋ ์์ฌ๋์ง ์๋ ์ปจํ ์คํธ์ ์ ์ฅํ ์ ์์ต๋๋ค.).
- Second, the alphanumeric character set is significantly smaller than the set of characters available in Unicode and UTF-8 encodings( ์์ซ์ ๋ฌธ์ ์งํฉ์ ์ ๋์ฝ๋ ๋ฐ UTF-8 ์ธ์ฝ๋ฉ์์ ์ฌ์ฉํ ์ ์๋ ๋ฌธ์ ์งํฉ๋ณด๋ค ์๋นํ ์์ต๋๋ค.).
- This means that **the set of instructions available for composing alphanumeric shellcode is relatively small. **
- To cope with these restrictions, patching or self-modification is often used.
- Since alphanumeric engines produce encodings automatically, a decoder is required.
- The challenge then is to develop an encoding scheme and decoder that use only alphanumeric characters (and hence, a restricted instruction set), yet are together capable of encoding arbitrary payloads.
- The top three rows in Figure 1 show examples using the Metasploit framework.
Example encodings of a Linux IA32 Bind Shell. The PexAlphaNum and Alpha2 encodings were generated using the Metasploit Framework. A hash symbol in the last column represents a character that is either unprintable or from the extended ASCII character set.
- We note that much of the literature describing code injection attacks (and prevention) assumes a standard attack template consisting of the basic components found traditionally in buffer-overflow attacks: a NOP sled, shellcode, and one or more pointers to the shellcode [1, 12, 23, 27].
- Not surprisingly, the natural reaction has been to develop techniques that detect such structure or behavior [20, 23, 16, 15, 27, 14]. While emulation and static analysis have been successful in identifying some of the failings of advanced shellcode, in the limit, the overhead will likely make doing so improbable.
- Moreover, attacks are not constrained to this layout and so attempts at merely detecting this structure can be problematic; infact, identifying each component has its own unique set of challenges [1, 13], and it has been suggested that malicious polymorphic behavior cannot be modeled effectively [20].
- In support of that argument, we provide a concrete instantiation that shows that the decoder can share the same properties as benign data.
Related Work
(๋ถ๋ฅ๋ฅผ ์๊ฐํ๊ณ , ์ธ๋ถ์ ์ธ ๊ด๋ จ ์ฐ๊ตฌ๋ค์ ์๊ฐํ๋คโ>๊ด์ฐฎ์๋ฏ)
- Three types of defensive approaches about code-injection attacks
- Tools and techniques to both limit the spoils of exploitations and to prevent developers from writing vulnerable code.
- Examples of such approaches include automatic bounds protection for buffers [4] and static checking of format strings at compile-time, utilizing โsafeโ versions of system libraries, and address-space layout randomization [19], etc
- While these techniques reduce the attack surface for code-injection attacks, no combination of such techniques seems to systematically eliminate the threat of code-injection [6, 21].
- In light of persistent vulnerabilities, the second category of countermeasures focuses on preventing the execution of injected code.
- In this realm, researchers have demonstrated some success using methods that randomize the instructionset [22] or render portions of the stack non-executable.
- Although these approaches can be effective, instruction-set randomization is considered too inefficient for some workloads.
- Additionally, recent work by Buchanan et al. demonstrates that without better support for constraining program behavior, execution-redirection attacks are still possible [3].
- The third category for code-injection defense consists of content-based input-validation techniques.
- These approaches are either host or network-based and are typically used as components in intrusion detection systems.
- User-input or network traffic is considered suspicious when it appears executable or anomalous as determined by heuristic, signature, or simulation.
- In this area, Toth and Kruegel detect some buffer overflow exploits by interpreting network payloads as executable code and analyzing their execution structure [23]. They divide machine instructions into two categories separated by those that modify the program counter, i.e., jump instructions, and others that do not. Their experiments show that, under some circumstances, it is possible to identify payloads with executable code by evaluating the maximum length of instruction sequences that fall between jump instructions, and find that payloads with lower maximum execution lengths are typically benign. However, their evaluation does not include an analysis of polymorphic code, and Kolesnikov et al. show that polymorphic blending attacks evade this detection approach [9].
- Tools and techniques to both limit the spoils of exploitations and to prevent developers from writing vulnerable code.
- Lastly, Song et al. examine popular polymorphic shellcode engines to assess their strengths and weaknesses [20].
- Our work supports their observations in that while todayโs polymorphic engines do generate observable artifacts, these artifacts are not intrinsically symptomatic of polymorphic code.
- However, while they advise that modeling acceptable content or behavior may lead to a better long-term solution for preventing shellcode delivery, we argue that even modeling acceptable content will be rife with its own set of challenges, as exemplified by English shellcode.
- Specifically, by generating malicious code that draws from a language model built using only benign content, statistical measures of intent become less accurate and the signal-to-noise ratio between malicious code and valid network data declines.
Towards English shellcode
- Shellcode, like other compiled code, is simply an ordered list of machine instructions.
- At the lowest level of representation, each instruction is stored as a series of bytes signifying a pattern of signals that instruct the CPU to manipulate data as desired.
- Like machine instructions, non-executable data is represented in byte form.
- Coincidentally, some character strings from the ASCII character and native machine instructions have identical byte representations.
- Moreover, it is even possible to find examples of this phenomenon that parse as grammatically correct English sentences.
- For instance, ASCII representation of the phrase โShake Shake Shake!โ is byte-equivalent to the following sequence of Intel instructions: push %ebx; push โake โ; push %ebx; push โake โ; push %ebx; push โake!โ.
- However, it is unlikely that one could construct meaningful code by simply concatenating English phrases that exhibit this property.
- Abiding by the rules of English grammar simply excludes the presence of many instructions and significantly limits the availability and placement of others.
- For example, add, mov, and call instructions cannot be constructed using this method.
- Therefore, while it may be possible to construct some instances of shellcode with coherent objectives in this manner, the versatility of this technique is severely restricted by its limitations.
- Rather than find these instances, our goal is instead to develop an automated approach for transforming arbitrary shellcode into an English representation.
High-level Overview
- What follows is a brief description of the method we have developed for encoding arbitrary shellcode as English text.
- This English shellcode is completely self-contained, i.e., it does not require an external loader, and executes as valid IA32 code.
- The steps depicted in Figure 3 complement the brief overview of our approach presented below.
- English-Compatible Decoder (์์ดํธํ ๋์ฝ๋)
- Write a decoder that is capable of encoding generic payloads using only English-compatible instructions.
์์ด ํธํ ๋ช ๋ น์ด๋ง ์ฌ์ฉํ์ฌ ์ผ๋ฐ ํ์ด๋ก๋๋ฅผ ์ธ์ฝ๋ฉํ ์ ์๋ ๋์ฝ๋๋ฅผ ์์ฑํ์ญ์์ค.
- Write a decoder that is capable of encoding generic payloads using only English-compatible instructions.
- Language Model Generation (์ธ์ด ๋ชจ๋ธ ์์ฑ)
- Generate and train a natural language model with a large and diverse corpus of English text.
ํฌ๊ณ ๋ค์ํ ์์ด ํ ์คํธ ๋ง๋ญ์น๋ฅผ ์ฌ์ฉํ์ฌ ์์ฐ์ด ๋ชจ๋ธ์ ์์ฑํ๊ณ ๊ต์กํฉ๋๋ค.
- Generate and train a natural language model with a large and diverse corpus of English text.
- Viterbi Search and Execution (๋น๋ฒํฐ ๊ฒ์๊ณผ ์คํ)
- Using Viterbi search, traverse the language model, executing and scoring each candidate decoder.
Viterbi ๊ฒ์์ ์ฌ์ฉํ์ฌ ์ธ์ด ๋ชจ๋ธ์ ํ์ํ๊ณ ๊ฐ ํ๋ณด ๋์ฝ๋๋ฅผ ์คํํ๊ณ ์ ์๋ฅผ ๋งค๊น๋๋ค
- Using Viterbi search, traverse the language model, executing and scoring each candidate decoder.
- Encode Target Shellcode (ํ๊ฒ ์์ฝ๋๋ฅผ ์ธ์ฝ๋ฉํฉ๋๋ค.)
- Continue to traverse the language model, encoding the target shellcode as English. Upon delivery, this code will be decoded and executed.
๋์ ์ ธ ์ฝ๋๋ฅผ ์์ด๋ก ์ธ์ฝ๋ฉํ์ฌ ์ธ์ด ๋ชจ๋ธ์ ๊ณ์ ํ์ํฉ๋๋ค. ์ ๋ฌ ์๋ฃ์ ์ด ์ฝ๋๋ ๋์ฝ๋ฉ๋์ด ์คํ๋ฉ๋๋ค.
- Continue to traverse the language model, encoding the target shellcode as English. Upon delivery, this code will be decoded and executed.
- One can envision a typical usage scenario (see Figure 4) where the English shellcode (composed of a natively executable decoder and an encoded payload containing arbitrary shellcode) is first generated offline.
-
Once the English shellcode is delivered to a vulnerable machine and its vulnerability is triggered, execution is redirected to the English shellcode, initiating the decoding process and launching the target shellcode contained in the payload. (์์ด ์์ฝ๋๊ฐ ์ทจ์ฝํ ์์คํ ์ ์ ๋ฌ๋๊ณ ์ทจ์ฝ์ฑ์ด ํธ๋ฆฌ๊ฑฐ๋๋ฉด ์คํ์ด ์์ด ์์ฝ๋๋ก ๋ฆฌ๋๋ ์ ๋์ด ๋์ฝ๋ฉ ํ๋ก์ธ์ค๋ฅผ ์์ํ๊ณ ํ์ด๋ก๋์ ํฌํจ๋ ๋์ ์์ฝ๋๋ฅผ ์์ํฉ๋๋ค)
- First, a list of English-compatible instructions were compiled and categorized loosely by behavior, i.e., whether an instruction performs a jump, executes a boolean operation, or manipulates the stack.
- Some excerpts from the list are shown in Figure 2. Using this list and its categorization to guide development, a decoder was written that is capable of encoding generic payloads using only instructions from our list.
-
This intermediate result is similar in spirit to the alphanumeric decoders, however,our decoder is further constrained by a guiding principle to avoid certain character patterns that might later make finding an English equivalent more difficult, e.g., the string of mostly capital letters that compose the PexAlphaNum and Alpha2 decoders depicted in Figure 1 would likely result in poor English shellcode.
- The basic idea then is to find a strings of English words that mimic the execution behavior of our custom decoder.
- To achieve this goal, we use a smoothed n-gram language model.
//n-gram model์ ํ์ฉํด payload๋ฅผ encodeํ์ฌ ๋์ผํ ๋์์ ํ๋ english shell code๋ฅผ ๋ง๋๋ ๊ณผ์
- That model is trained using a large set of English text, and is used to deduce the probability of word sequences.
- As language generation proceeds, each instruction in the decoder is assigned a numerical value.
- Intuitively, as we select candidate strings from the language model, each is executed under supervision.
- We use the numerical values to indicate the strength of each candidate.
- If a candidate string produces the same net effect of the first instruction of our decoder when executed, we say that its score is one.
- If a candidate string produces the net effect of the first two instructions, its score is two (and so on).
- At each stage, highscoring candidates are kept (and used in driving the language model forward) and low-scoring candidates (or those that crash the simulator) are discarded.
- Ultimately, we traverse the language model using a beam search to find strings of words that score the highest possible value and operate in an identical manner as the decoder developed by hand.
- Finally, to encode the original payload, we continue to sample strings from our language model all the while generating prose that is functionally equivalent to the target shellcode when executed.
Our Approach (๋ฌธ์ ์ ๋ค์ ๋์ถ)
- Recall that unlike other attack components, the decoder must reside in memory as executable code.
- This exposure can make identifying the decoder a useful step in facilitating detection and prevention (e.g., by determining if a portion of the payload โlooksโ executable).
- Thus, from an attackerโs perspective, masking the decoder may reduce the likelihood of detection and help to facilitate clandestine attacks.
- Designing a decoder under unnatural constraints can be very challenging, and this difficulty is not unique to English shellcode.
- Self-modification is often used to address this problem whereby permissible code modifies portions of the payload such that non-compliant instructions are patched in at runtime, thereby passing any input filters.
- These additional instructions provide an attacker with more versatility and may make an otherwise impotent attack quite powerful.
- (problem) Self-modification is particularly useful for overcoming some of the challenges unique to English shellcode.
- (problem) Among the English-compatible instructions, for example, there is no native support for loops or addition.
- (problem) Issues like these are relevant because decoding a payload without certain instructions, while possible, can quickly become impractical.
- (problem) For instance, a decoder without a looping mechanism must be proportional in length to the length of its encoded payload, possibly exposing its existence by nature of its size and form on the wire.
The decoder
- We are able to avoid these problems by building a selfmodifying decoder that has the form: initialization, decoder, encoded payload.
- Intuitively, the first component builds an initial decoder in memory (through self-modification) which when executed, expands the working instruction set, providing the decoder with IA32 operations beyond those provide by English prose.
- The decoder then decodes the next segment (the encoded payload), again via self-modification.
- We build our decoder using a number of principles that help guide its design.
- First and foremost, the decoder must use only English-compatible instructions or the goal of creating English shellcode cannot be realized.
- Furthermore, we are particularly interested in English-compatible instructions that can be used, alone or in conjunction, to produce useful instructions (via self modification) that are not English-compatible.
- For example, our decoder uses multiple and instructions (which are English-compatible) to generate add instructions (which are not English-compatible).
- Taken together, it could be said that these first two goals also provide a foundation for the design of alphanumeric decoders.
- However, our third design principle, which is not shared by alphanumeric shellcode engines, is to favor instructions that have less-constrained ASCII equivalents.
- For instance, we will likely favor the instruction push %eax (โPโ) over push %ecx (โQโ) when designing our decoder since the former is more common in English text.
- The same guiding principle is applied when choosing literal values.
- It is important to note that even though we followed these principles in designing our decoder, they are not hard requirements and there are other capable approaches.
- What we provide here is a proof of concept to demonstrate our point.
Initilization
// {1์ฉ ์ฆ๊ฐ๋ฅผ ํ๋ฉด register์ ์ํฅ์ ์ฃผ๋๊น ๊ทธ๊ฑธ ์ํ์ํค๋ ๊ธฐ์ ์ ์ธ ๋ฐฉ๋ฒ์ ๋งํ๋ค}
- In its initialization phase, the decoder overwrites key machine registers and patches in machine instructions that are not English compatible.
- After successful exploitation of a software vulnerability, we assume that a pointer to the shellcode resides in one of the general purpose registers or other accessible memory.
- As pointed out by Polychronakis et al., this is common in non-self-contained shellcode [16].
- In order to execute the target shellcode, a pointer to the encoded shellcode is needed.
- This pointer must address memory far beyond the first byte of the shellcode since one must first reserve space for the decoder.
- Since the register containing the address of the shellcode is known, we can copy its pointer and add an offset to reach the encoded payload.
- Using only English-compatible ASCII characters, the increment instruction inc is the most obvious candidate for increasing a registerโs value.
- However, this one-byte instruction will only increase the value of a register in increments of one, yielding no space for the decoder. Used this way, the inc instruction is insufficient.
- Since the register containing the address of the shellcode is known, we can copy its pointer and add an offset to reach the encoded payload.
- Using only English-compatible ASCII characters, the increment instruction inc is the most obvious candidate for increasing a registerโs value.
- However, this one-byte instruction will only increase the value of a register in increments of one, yielding no space for the decoder.
- Used this way, the inc instruction is insufficient.
- However, a single inc instruction can be used to increase a register value in increments of 256 after manipulating the alignment of the stack.
- This process is depicted in Figure 5.
- For instance, we can first push the shellcode pointer onto the stack and shift the stack pointer %esp by one byte.
- Once shifted, the pop instruction places the three least-significant bytes of the shellcode pointer into a register where its value is increased using inc multiple times.
- Afterwards, the value of this register is pushed back onto the stack and the stack is realigned.
- The top of the stack, which at first contained the shellcode pointer, now contains the same value increased by increments of 256.
- By popping this value into a register, we can use it to address the encoded payload.
Unpacking the decoder
- To facilitate looping, instructions that are not English compatible (e.g., the lods and add instructions) are needed.
- However, to generate these, the shellcode can manipulate its contents to patch in the required instructions.
- For example, an and instruction can be used to create an add instruction.
- The opcode for and is equivalent to the ASCII space character (0x20), which is convenient because the space character is the most common character in English.
- The variant used in our proof of concept is three bytes in length and takes the form โAND r/m8, r8โ.
- Its first parameter, r/m8, addresses the bytes to be modified, while the second specifies one of the partial registers.
- The opcode for the add operations we create is 0x00. This means that the partial register and the byte addressed by the r/m8 operand must yield a value of zero when the and operation is executed.
- Thus, the partial registers used are chosen such that a zero byte is created at r/m8.