Difference between revisions of "Shellcode/Environment"
Chantal21I (Talk | contribs) |
|||
(59 intermediate revisions by 3 users not shown) | |||
Line 1: | Line 1: | ||
− | + | It is possible use [[shellcode]] to [[#x86/x64_GetCPU_(any_OS)|determine instruction set architecture]], [[#GetPc|the process counter]], [[#Last_call|the location last returned to]], or [[#int3_breakpoints|bypass and detect int3 breakpoints]] within the current execution environment. | |
− | + | {{info|<center>The code and ideas discussed here are part of an [[shellcode|all-encompassing shellcode portal]]. Everything described here and the full source of any given code is available in [[Shellcode/Appendix#Environment|the appendix]], as well as in the downloadable [[shellcodecs]] package.</center>}} | |
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
== GetPc == | == GetPc == | ||
− | The ''' | + | The '''GetPc''' technique is implementation of code which obtains the current instruction pointer. This can be useful when writing [[Shellcode/Self-modifying|self-modifying shellcode]], or other code that must become aware of its environment, as environment information cannot be supplied prior to execution of the code. |
=== x86 (32 bit) === | === x86 (32 bit) === | ||
Line 167: | Line 27: | ||
call getpc ; the %rax register now contains %rip on the next line | call getpc ; the %rax register now contains %rip on the next line | ||
</source>}} | </source>}} | ||
+ | |||
+ | * Alternatively: | ||
+ | {{code|text=<source lang="asm"> | ||
+ | jmp startup | ||
+ | pc: | ||
+ | nop | ||
+ | startup: | ||
+ | lea -1(%rip), %rax ; the %rax register now contains the address of `pc'. | ||
+ | </source>}} | ||
+ | |||
+ | == Last call == | ||
+ | Typically, when [[shellcode]] is being executed at the time of a [[buffer overflow]], assuming that the nop sled does not modify the stack, the [[memory addresses|pointer]] to the beginning of the executing code is at -0x8(%rsp), or -0x4(%esp), because it was just ''[[return oriented programming|returned to]]'' as a result of the [[call stack]] being overwritten during the overflow process. In many cases, this can be used in place of a '''[[#GetPc|GetPc]]''' for [[Shellcode/Self-modifying|polymorphic shellcode]]. The [[Shellcode/Alphanumeric#Last_call|alphanumeric last call]] for x64 systems comes out to 13 bytes. | ||
+ | |||
+ | === 32-bit === | ||
+ | ==== Null-free ==== | ||
+ | {{code|text=<source lang="asm"> | ||
+ | mov -0x4(%esp), %eax | ||
+ | </source>}} | ||
+ | |||
+ | === 64-bit === | ||
+ | ==== Null-free ==== | ||
+ | {{code|text=<source lang="asm"> | ||
+ | mov -0x8(%rsp), %rax | ||
+ | </source>}} | ||
+ | |||
+ | == int3 breakpoints == | ||
+ | Int3 breakpoints can be detected during out-of-line code execution when the code in question is being debugged by an in-line debugger. | ||
+ | |||
+ | {{code|text=<source lang="asm"> | ||
+ | .text | ||
+ | .global _start | ||
+ | _start: | ||
+ | |||
+ | jmp startup | ||
+ | |||
+ | go_retro: | ||
+ | pop %rcx | ||
+ | inc %rcx | ||
+ | jmp *%rcx | ||
+ | |||
+ | startup: | ||
+ | call go_retro | ||
+ | |||
+ | volatile_segment: | ||
+ | push $0x3458686a | ||
+ | push $0x0975c084 | ||
+ | nop | ||
+ | </source>}} | ||
+ | |||
+ | The relevant code in this snippet is: | ||
+ | |||
+ | {{code|text=<source lang="asm"> | ||
+ | push $0x3458686a | ||
+ | push $0x0975c084 | ||
+ | </source>}} | ||
+ | |||
+ | When the code jumps to the code directly after the first ''push'' (0x68), it gets read by the CPU as: | ||
+ | |||
+ | 0: 6a 68 pushq $0x68 | ||
+ | 2: 58 pop %rax | ||
+ | 3: 34 68 xor $0x68,%al | ||
+ | 5: 85 c0 test %eax,%eax | ||
+ | 7: 75 09 jne 0x09 | ||
+ | |||
+ | However, it is read by an inline disassembler as: | ||
+ | |||
+ | d: 68 6a 68 58 34 pushq $0x3458686a | ||
+ | 12: 68 84 c0 75 09 pushq $0x975c084 | ||
+ | 17: 90 nop | ||
+ | |||
+ | This is because an inline disassembler does not recognize code based on how it is executed but on how it looks in memory; however, because the first ''0x68'' is skipped completely, the code is executed differently than what appears in memory. What this code actually does is detect breakpoints. First, it moves ''0x68'' into ''%rax''. Then, if a breakpoint has been set on the second push instruction, the ''xor $0x68,%al'' instruction will become ''xor $0xcc,%al'' (0xcc is the breakpoint instruction), and instead of ''%rax'' being nulled (0x68 xor 0x68 becomes 0), it will become 0xa4. The test instruction checks if ''%rax'' is zero: if it is not zero the code then ''jmp''s 0x09 bytes forward (this behaviour can be adjusted to act however the programmer desires). This code allows arbitrary shellcode to detect breakpoints and act differently depending on whether or not they exist. | ||
+ | |||
+ | The following is a demonstration of this specific code in use. In the first demonstration, a breakpoint is set on the ''nop'' instruction and the breakpoint is hit. In the second, the breakpoint is set on the second ''push'' instruction, and the breakpoint is skipped. | ||
+ | |||
+ | {} shellcode gdb loaders/loader-64 | ||
+ | Reading symbols from /home/user/loaders/loader-64...(no debugging symbols found)...done. | ||
+ | (gdb) break ret_to_shellcode | ||
+ | Breakpoint 1 at 0x4000b1 | ||
+ | |||
+ | (gdb) run "$(generators/shellcode-generator.py --file=int3 --raw)" | ||
+ | Starting program: /home/user/loaders/loader-64 "$(generators/shellcode-generator.py --file=int3 --raw)" | ||
+ | |||
+ | Breakpoint 1, 0x00000000004000b1 in ret_to_shellcode () | ||
+ | (gdb) x/24i $rax | ||
+ | 0x7ffff7fbe000: jmp 0x7ffff7fbe008 | ||
+ | 0x7ffff7fbe002: pop %rcx | ||
+ | 0x7ffff7fbe003: inc %rcx | ||
+ | 0x7ffff7fbe006: jmpq *%rcx | ||
+ | 0x7ffff7fbe008: callq 0x7ffff7fbe002 | ||
+ | 0x7ffff7fbe00d: pushq $0x3458686a | ||
+ | 0x7ffff7fbe012: pushq $0x975c084 | ||
+ | '''0x7ffff7fbe017''': '''nop''' | ||
+ | '''...''' | ||
+ | (gdb) break '''*0x7ffff7fbe017''' | ||
+ | Breakpoint 2 at '''0x7ffff7fbe017''' | ||
+ | (gdb) c | ||
+ | Continuing. | ||
+ | |||
+ | '''Breakpoint 2, 0x00007ffff7fbe017 in ?? ()''' | ||
+ | '''(gdb) quit''' | ||
+ | A debugging session is active. | ||
+ | |||
+ | Inferior 1 [process 9760] will be killed. | ||
+ | |||
+ | Quit anyway? (y or n) y | ||
+ | |||
+ | |||
+ | |||
+ | {} shellcode gdb loaders/loader-64 | ||
+ | Reading symbols from /home/user/loaders/loader-64...(no debugging symbols found)...done. | ||
+ | (gdb) break ret_to_shellcode | ||
+ | Breakpoint 1 at 0x4000b1 | ||
+ | (gdb) run "$(generators/shellcode-generator.py --file=int3 --raw)" | ||
+ | Starting program: /home/user/loaders/loader-64 "$(generators/shellcode-generator.py --file=int3 --raw)" | ||
+ | Breakpoint 1, 0x00000000004000b1 in ret_to_shellcode () | ||
+ | (gdb) x/24i $rax | ||
+ | 0x7ffff7fbe000: jmp 0x7ffff7fbe008 | ||
+ | 0x7ffff7fbe002: pop %rcx | ||
+ | 0x7ffff7fbe003: inc %rcx | ||
+ | 0x7ffff7fbe006: jmpq *%rcx | ||
+ | 0x7ffff7fbe008: callq 0x7ffff7fbe002 | ||
+ | 0x7ffff7fbe00d: pushq $0x3458686a | ||
+ | '''0x7ffff7fbe012''': '''pushq $0x975c084''' | ||
+ | 0x7ffff7fbe017: nop | ||
+ | '''...''' | ||
+ | (gdb) break '''*0x7ffff7fbe012''' | ||
+ | Breakpoint 2 at '''0x7ffff7fbe012''' | ||
+ | (gdb) c | ||
+ | '''Continuing.''' | ||
+ | '''[Inferior 1 (process 9778) exited normally]''' | ||
+ | '''(gdb)''' | ||
+ | |||
+ | {{social}} |
Latest revision as of 02:32, 25 April 2013
It is possible use shellcode to determine instruction set architecture, the process counter, the location last returned to, or bypass and detect int3 breakpoints within the current execution environment.
Contents
GetPc
The GetPc technique is implementation of code which obtains the current instruction pointer. This can be useful when writing self-modifying shellcode, or other code that must become aware of its environment, as environment information cannot be supplied prior to execution of the code.
x86 (32 bit)
jmp startup getpc: mov (%esp), %eax ret startup: call getpc ; the %eax register now contains %eip on the next line |
x64
jmp startup getpc: mov (%rsp), %rax ret startup: call getpc ; the %rax register now contains %rip on the next line |
- Alternatively:
jmp startup pc: nop startup: lea -1(%rip), %rax ; the %rax register now contains the address of `pc'. |
Last call
Typically, when shellcode is being executed at the time of a buffer overflow, assuming that the nop sled does not modify the stack, the pointer to the beginning of the executing code is at -0x8(%rsp), or -0x4(%esp), because it was just returned to as a result of the call stack being overwritten during the overflow process. In many cases, this can be used in place of a GetPc for polymorphic shellcode. The alphanumeric last call for x64 systems comes out to 13 bytes.
32-bit
Null-free
mov -0x4(%esp), %eax |
64-bit
Null-free
mov -0x8(%rsp), %rax |
int3 breakpoints
Int3 breakpoints can be detected during out-of-line code execution when the code in question is being debugged by an in-line debugger.
.text .global _start _start: jmp startup go_retro: pop %rcx inc %rcx jmp *%rcx startup: call go_retro volatile_segment: push $0x3458686a push $0x0975c084 nop |
The relevant code in this snippet is:
push $0x3458686a push $0x0975c084 |
When the code jumps to the code directly after the first push (0x68), it gets read by the CPU as:
0: 6a 68 pushq $0x68 2: 58 pop %rax 3: 34 68 xor $0x68,%al 5: 85 c0 test %eax,%eax 7: 75 09 jne 0x09
However, it is read by an inline disassembler as:
d: 68 6a 68 58 34 pushq $0x3458686a 12: 68 84 c0 75 09 pushq $0x975c084 17: 90 nop
This is because an inline disassembler does not recognize code based on how it is executed but on how it looks in memory; however, because the first 0x68 is skipped completely, the code is executed differently than what appears in memory. What this code actually does is detect breakpoints. First, it moves 0x68 into %rax. Then, if a breakpoint has been set on the second push instruction, the xor $0x68,%al instruction will become xor $0xcc,%al (0xcc is the breakpoint instruction), and instead of %rax being nulled (0x68 xor 0x68 becomes 0), it will become 0xa4. The test instruction checks if %rax is zero: if it is not zero the code then jmps 0x09 bytes forward (this behaviour can be adjusted to act however the programmer desires). This code allows arbitrary shellcode to detect breakpoints and act differently depending on whether or not they exist.
The following is a demonstration of this specific code in use. In the first demonstration, a breakpoint is set on the nop instruction and the breakpoint is hit. In the second, the breakpoint is set on the second push instruction, and the breakpoint is skipped.
{} shellcode gdb loaders/loader-64 Reading symbols from /home/user/loaders/loader-64...(no debugging symbols found)...done. (gdb) break ret_to_shellcode Breakpoint 1 at 0x4000b1 (gdb) run "$(generators/shellcode-generator.py --file=int3 --raw)" Starting program: /home/user/loaders/loader-64 "$(generators/shellcode-generator.py --file=int3 --raw)" Breakpoint 1, 0x00000000004000b1 in ret_to_shellcode () (gdb) x/24i $rax 0x7ffff7fbe000: jmp 0x7ffff7fbe008 0x7ffff7fbe002: pop %rcx 0x7ffff7fbe003: inc %rcx 0x7ffff7fbe006: jmpq *%rcx 0x7ffff7fbe008: callq 0x7ffff7fbe002 0x7ffff7fbe00d: pushq $0x3458686a 0x7ffff7fbe012: pushq $0x975c084 0x7ffff7fbe017: nop ... (gdb) break *0x7ffff7fbe017 Breakpoint 2 at 0x7ffff7fbe017 (gdb) c Continuing. Breakpoint 2, 0x00007ffff7fbe017 in ?? () (gdb) quit A debugging session is active. Inferior 1 [process 9760] will be killed. Quit anyway? (y or n) y
{} shellcode gdb loaders/loader-64 Reading symbols from /home/user/loaders/loader-64...(no debugging symbols found)...done. (gdb) break ret_to_shellcode Breakpoint 1 at 0x4000b1 (gdb) run "$(generators/shellcode-generator.py --file=int3 --raw)" Starting program: /home/user/loaders/loader-64 "$(generators/shellcode-generator.py --file=int3 --raw)" Breakpoint 1, 0x00000000004000b1 in ret_to_shellcode () (gdb) x/24i $rax 0x7ffff7fbe000: jmp 0x7ffff7fbe008 0x7ffff7fbe002: pop %rcx 0x7ffff7fbe003: inc %rcx 0x7ffff7fbe006: jmpq *%rcx 0x7ffff7fbe008: callq 0x7ffff7fbe002 0x7ffff7fbe00d: pushq $0x3458686a 0x7ffff7fbe012: pushq $0x975c084 0x7ffff7fbe017: nop ... (gdb) break *0x7ffff7fbe012 Breakpoint 2 at 0x7ffff7fbe012 (gdb) c Continuing. [Inferior 1 (process 9778) exited normally] (gdb)