- .global main
- main:
- ljmpl *target
- 1:
- .code32
- movl $1, %eax # __NR_exit == 1 from asm/unistd_32.h
- movl $2, %ebx # status == 0
- sysenter
- ret
- .data
- target:
- .long 1b # address (32 bits)
- .word 0x23 # segment selector (16 bits)
这里,ljmpl指令使用target标签处的内存,该标签是一个32位指令指针,后跟一个16位段选择器(这里指向用户空间的32位代码段0x23)。这里的目标地址1b不是十六进制值,它实际上是对标签1的引用;b代表“向后”。这个标签处的代码是32位的,这就是为什么我们使用sysenter,而不是以前使用的syscall。调用约定也不同,实际上,我们需要使用32位ABI中的系统调用号(SYS_exit在64位系统上是60,但这里是1)。另一个有趣的事情是,如果你尝试在strace下运行这段代码,将会看到如下所示的结果:
- [...]
- write(1, "\366\242[\204\374\177\0\0\0\0\0\0\0\0\0\0\376\242[\204\374\177\0\0\t\243[\204\374\177\0\0"..., 140722529079224
- +++ exited with 0 +++
- struct ljmp_target {
- uint32_t rip;
- uint16_t cs;
- } __attribute__((packed));
- struct data {
- struct ljmp_target ljmp;
- };
- static struct data *data;
- int main(...)
- {
- ...
- void *addr = mmap(NULL, PAGE_SIZE,
- PROT_READ | PROT_WRITE,
- MAP_PRIVATE | MAP_ANONYMOUS | MAP_32BIT,
- -1, 0);
- if (addr == MAP_FAILED)
- error(EXIT_FAILURE, errno, "mmap()");
- data = (struct data *) addr;
- ...
- }
- void emit_code()
- {
- ...
- // ljmp *target
- *out++ = 0xff;
- *out++ = 0x2c;
- *out++ = 0x25;
- for (unsigned int i = 0; i < 4; ++i)
- *out++ = ((uint64_t) &data->ljmp) >> (8 * i);
- // cs:rip (jump target; in our case, the next instruction)
- data->ljmp.cs = 0x23;
- data->ljmp.rip = (uint64_t) out;
- ...
- }
- #include
- #include
- #include
- #include
- int main(...)
- {
- pid_t child = fork();
- if (child == -1)
- error(EXIT_FAILURE, errno, "fork()");
- if (child == 0) {
- // make us a tracee of the parent
- if (ptrace(PTRACE_TRACEME, 0, 0, 0) == -1)
- error(EXIT_FAILURE, errno, "ptrace(PTRACE_TRACEME)");
- // give the parent control
- raise(SIGTRAP);
- ...
- exit(EXIT_SUCCESS);
- }
- // parent; wait for child to stop
- while (1) {
- int status;
- if (waitpid(child, &status, 0) == -1) {
- if (errno == EINTR)
- continue;
- error(EXIT_FAILURE, errno, "waitpid()");
- }
- if (WIFEXITED(status))
- exit(WEXITSTATUS(status));
- if (WIFSIGNALED(status))
- exit(EXIT_FAILURE);
- if (WIFSTOPPED(status) && WSTOPSIG(status) == SIGTRAP)
- break;
- continue;
- }
- // set debug registers and stop tracing
- if (ptrace(PTRACE_POKEUSER, child, offsetof(struct user, u_debugreg[0]), ...) == -1)
- error(EXIT_FAILURE, errno, "ptrace(PTRACE_POKEUSER)");
- if (ptrace(PTRACE_POKEUSER, child, offsetof(struct user, u_debugreg[7]), ...) == -1)
- error(EXIT_FAILURE, errno, "ptrace(PTRACE_POKEUSER)");
- if (ptrace(PTRACE_DETACH, child, 0, 0) == -1)
- error(EXIT_FAILURE, errno, "ptrace(PTRACE_DETACH)");
- ...
- }
- // stddef.h offsetof() doesn't always allow non-const array indices,
- // so precompute them here.
- const unsigned int debugreg_offsets[] = {
- offsetof(struct user, u_debugreg[0]),
- offsetof(struct user, u_debugreg[1]),
- offsetof(struct user, u_debugreg[2]),
- offsetof(struct user, u_debugreg[3]),
- };
- for (unsigned int i = 0; i < 4; ++i) {
- // try random addresses until we succeed
- while (true) {
- unsigned long addr = get_random_address();
- if (ptrace(PTRACE_POKEUSER, child, debugreg_offsets[i], addr) != -1)
- break;
- }
- // Condition:
- // 0 - execution
- // 1 - write
- // 2 - (unused)
- // 3 - read or write
- unsigned int condition = std::uniform_int_distribution
- if (condition == 2)
- condition = 3;
- // Size
- // 0 - 1 byte
- // 1 - 2 bytes
- // 2 - 8 bytes
- // 3 - 4 bytes
- unsigned int size = std::uniform_int_distribution
- unsigned long dr7 = ptrace(PTRACE_PEEKUSER, child, offsetof(struct user, u_debugreg[7]), 0);
- dr7 &= ~((1 | (3 << 16) | (3 << 18)) << i);
- dr7 |= (1 | (condition << 16) | (size << 18)) << i;
- ptrace(PTRACE_POKEUSER, child, offsetof(struct user, u_debugreg[7]), dr7);
- }
- int $0x80
- sysenter
- syscall
- enum entry_type {
- // system calls + software interrupts
- ENTRY_SYSCALL,
- ENTRY_SYSENTER,
- ENTRY_INT,
- ENTRY_INT_80,
- ENTRY_INT3,
- // exceptions
- ENTRY_DE, // Divide error
- ENTRY_OF, // Overflow
- ENTRY_BR, // Bound range exceeded
- ENTRY_UD, // Undefined opcode
- ENTRY_SS, // Stack segment fault
- ENTRY_GP, // General protection fault
- ENTRY_PF, // Page fault
- ENTRY_MF, // x87 floating-point exception
- ENTRY_AC, // Alignment check
- NR_ENTRY_TYPES,
- };
- enum entry_type type = (enum entry_type) std::uniform_int_distribution
- // Some entry types require a setup/preamble; do that here
- switch (type) {
- case ENTRY_DE:
- // xor %eax, %eax
- *out++ = 0x31;
- *out++ = 0xc0;
- break;
- case ENTRY_MF:
- // pxor %xmm0, %xmm0
- *out++ = 0x66;
- *out++ = 0x0f;
- *out++ = 0xef;
- *out++ = 0xc0;
- break;
- case ENTRY_BR:
- // xor %eax, %eax
- *out++ = 0x31;
- *out++ = 0xc0;
- break;
- case ENTRY_SS:
- {
- uint16_t sel = get_random_segment_selector();
- // movw $imm, %bx
- *out++ = 0x66;
- *out++ = 0xbb;
- *out++ = sel;
- *out++ = sel >> 8;
- }
- break;
- default:
- // do nothing
- break;
- }
- ...
- switch (type) {
- // system calls + software interrupts
- case ENTRY_SYSCALL:
- // syscall
- *out++ = 0x0f;
- *out++ = 0x05;
- break;
- case ENTRY_SYSENTER:
- // sysenter
- *out++ = 0x0f;
- *out++ = 0x34;
- break;
- case ENTRY_INT:
- // int $x
- *out++ = 0xcd;
- *out++ = std::uniform_int_distribution
- break;
- case ENTRY_INT_80:
- // int $0x80
- *out++ = 0xcd;
- *out++ = 0x80;
- break;
- case ENTRY_INT3:
- // int3
- *out++ = 0xcc;
- break;
- // exceptions
- case ENTRY_DE:
- // div %eax
- *out++ = 0xf7;
- *out++ = 0xf0;
- break;
- case ENTRY_OF:
- // into (32-bit only!)
- *out++ = 0xce;
- break;
- case ENTRY_BR:
- // bound %eax, data
- *out++ = 0x62;
- *out++ = 0x05;
- *out++ = 0x09;
- for (unsigned int i = 0; i < 4; ++i)
- *out++ = ((uint64_t) &data->bound) >> (8 * i);
- break;
- case ENTRY_UD:
- // ud2
- *out++ = 0x0f;
- *out++ = 0x0b;
- break;
- case ENTRY_SS:
- // Load %ss again, with a random segment selector (this is not
- // guaranteed to raise #SS, but most likely it will). The reason
- // we don't just rely on the load above to do it is that it could
- // be interesting to trigger #SS with a "weird" %ss too.
- // movw %bx, %ss
- *out++ = 0x8e;
- *out++ = 0xd3;
- break;
- case ENTRY_GP:
- // wrmsr
- *out++ = 0x0f;
- *out++ = 0x30;
- break;
- case ENTRY_PF:
- // testl %eax, (xxxxxxxx)
- *out++ = 0x85;
- *out++ = 0x04;
- *out++ = 0x25;
- for (int i = 0; i < 4; ++i)
- *out++ = ((uint64_t) page_not_present) >> (8 * i);
- break;
- case ENTRY_MF:
- // divss %xmm0, %xmm0
- *out++ = 0xf3;
- *out++ = 0x0f;
- *out++ = 0x5e;
- *out++ = 0xc0;
- break;
- case ENTRY_AC:
- // testl %eax, (page_not_writable + 1)
- *out++ = 0x85;
- *out++ = 0x04;
- *out++ = 0x25;
- for (int i = 0; i < 4; ++i)
- *out++ = ((uint64_t) page_not_writable + 1) >> (8 * i);
- break;
- }