Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Fail to change CS register value from kernel mode. invalid opcode: 0000

From module code with this snippet I'm trying to change the value of CS register:

asm("pushq %rax");
asm("mov $0x10,%rax");
asm("mov %rax,%cs");
asm("popq %rax");

Actually the CS register contains segment selector - $0x10, also this value returned by kernel macro __KERNEL_CS. And what I want to do that is rewrite CS register with exactly the same value. Unfortunately I get error, without the snippet the error is absent.

Aug  1 20:26:37 myhost kernel: [ 2905.693297] invalid opcode: 0000 [#1] SMP 
Aug  1 20:26:37 myhost kernel: [ 2905.694223] CPU: 0 PID: 7140 Comm: insmod Tainted: P           OE   4.4.0-148-generic #174~14.04.1-Ubuntu
Aug  1 20:26:37 myhost kernel: [ 2905.694362] task: ffff88007a0edb00 ti: ffff880068c54000 task.ti: ffff880068c54000
Aug  1 20:26:37 myhost kernel: [ 2905.694420] RIP: 0010:[<ffffffffc114e114>]  [<ffffffffc114e114>] hello_init+0x44/0xe0 [hello_module]
Aug  1 20:26:37 myhost kernel: [ 2905.694497] RSP: 0018:ffff880068c57ca0  EFLAGS: 00010282
Aug  1 20:26:37 myhost kernel: [ 2905.694540] RAX: 0000000000000010 RBX: ffffffff81e15080 RCX: 0000000000005768
Aug  1 20:26:37 myhost kernel: [ 2905.694595] RDX: 000000000000b1e4 RSI: 0000000000000246 RDI: 0000000000000246
Aug  1 20:26:37 myhost kernel: [ 2905.694649] RBP: ffff880068c57cc0 R08: 3231203a657a6973 R09: 6461202c66373a37
Aug  1 20:26:37 myhost kernel: [ 2905.694703] R10: 203a737365726464 R11: 0000000000000363 R12: ffff8800b7e7e980
Aug  1 20:26:37 myhost kernel: [ 2905.694757] R13: 0000000000000000 R14: ffffffffc114e0d0 R15: ffff880068c57eb0
Aug  1 20:26:37 myhost kernel: [ 2905.694813] FS:  00007f8c1c36b740(0000) GS:ffff88013fa00000(0000) knlGS:0000000000000000
Aug  1 20:26:37 myhost kernel: [ 2905.694875] CS:  0010 DS: 0000 ES: 0000 CR0: 0000000080050033
Aug  1 20:26:37 myhost kernel: [ 2905.694920] CR2: 00005575a6b20248 CR3: 0000000075efc000 CR4: 0000000000060670
Aug  1 20:26:37 myhost kernel: [ 2905.694975] Stack:
Aug  1 20:26:37 myhost kernel: [ 2905.694995]  0000000000000000 007f000081e15080 ffff88013fa0c000 ffffffff81e15080
Aug  1 20:26:37 myhost kernel: [ 2905.695065]  ffff880068c57d38 ffffffff8100216f ffff880068c57eb0 ffff880068c57d10
Aug  1 20:26:37 myhost kernel: [ 2905.695133]  0000000000000246 0000000000000002 ffffffff811eab7f ffff88013b003c00
Aug  1 20:26:37 myhost kernel: [ 2905.695201] Call Trace:
Aug  1 20:26:37 myhost kernel: [ 2905.695230]  [<ffffffff8100216f>] do_one_initcall+0xcf/0x200
Aug  1 20:26:37 myhost kernel: [ 2905.695281]  [<ffffffff811eab7f>] ? kmem_cache_alloc_trace+0x1af/0x220
Aug  1 20:26:37 myhost kernel: [ 2905.695338]  [<ffffffff8118d293>] ? do_init_module+0x27/0x1d2
Aug  1 20:26:37 myhost kernel: [ 2905.695387]  [<ffffffff8118d2cc>] do_init_module+0x60/0x1d2
Aug  1 20:26:37 myhost kernel: [ 2905.695432]  [<ffffffff8110b42d>] load_module+0x145d/0x1b50
Aug  1 20:26:37 myhost kernel: [ 2905.695480]  [<ffffffff81107b60>] ? __symbol_put+0x40/0x40
Aug  1 20:26:37 myhost kernel: [ 2905.695528]  [<ffffffff812137a1>] ? kernel_read+0x41/0x60
Aug  1 20:26:37 myhost kernel: [ 2905.695574]  [<ffffffff8110bcee>] SYSC_finit_module+0x7e/0xa0
Aug  1 20:26:37 myhost kernel: [ 2905.695620]  [<ffffffff8110bd2e>] SyS_finit_module+0xe/0x10
Aug  1 20:26:37 myhost kernel: [ 2905.697884]  [<ffffffff8182d61b>] entry_SYSCALL_64_fastpath+0x22/0xcb
Aug  1 20:26:37 myhost kernel: [ 2905.700094] Code: eb 03 c0 0f 01 45 ee 0f b7 75 ee 48 8b 4d f0 48 c7 c7 9d f0 14 c1 31 c0 89 f2 e8 73 eb 03 c0 8c 5d ec 41 55 49 c7 c5 10 00 00 00 <49> 8e cd 41 5d 8c 4d ea 0f b7 55 ec 0f b7 75 ea 48 c7 c7 b8 f0 
Aug  1 20:26:37 myhost kernel: [ 2905.704976] RIP  [<ffffffffc114e114>] hello_init+0x44/0xe0 [hello_module]
Aug  1 20:26:37 myhost kernel: [ 2905.707358]  RSP <ffff880068c57ca0>
Aug  1 20:26:37 myhost kernel: [ 2905.719667] ---[ end trace 48f04fe6e7ff0ed6 ]---

Updated with R13 register

Aug  1 21:05:25 myhost kernel: [  146.818158] invalid opcode: 0000 [#1] SMP 
Aug  1 21:05:25 myhost kernel: [  146.818699] CPU: 1 PID: 5108 Comm: insmod Tainted: P           OE   4.4.0-148-generic #174~14.04.1-Ubuntu
Aug  1 21:05:25 myhost kernel: [  146.818778] task: ffff880097a45b00 ti: ffff880085d58000 task.ti: ffff880085d58000
Aug  1 21:05:25 myhost kernel: [  146.818810] RIP: 0010:[<ffffffffc109e114>]  [<ffffffffc109e114>] hello_init+0x44/0xe0 [hello_module]
Aug  1 21:05:25 myhost kernel: [  146.818854] RSP: 0018:ffff880085d5bca0  EFLAGS: 00010282
Aug  1 21:05:25 myhost kernel: [  146.818880] RAX: 000000000000001f RBX: ffffffff81e15080 RCX: 0000000000002298
Aug  1 21:05:25 myhost kernel: [  146.818911] RDX: 00000000000051a2 RSI: 0000000000000246 RDI: 0000000000000246
Aug  1 21:05:25 myhost kernel: [  146.818942] RBP: ffff880085d5bcc0 R08: 3231203a657a6973 R09: 6461202c66373a37
Aug  1 21:05:25 myhost kernel: [  146.818973] R10: 203a737365726464 R11: 0000000000000353 R12: ffff880085cc0b60
Aug  1 21:05:25 myhost kernel: [  146.819004] R13: 0000000000000010 R14: ffffffffc109e0d0 R15: ffff880085d5beb0
Aug  1 21:05:25 myhost kernel: [  146.819037] FS:  00007fd564d44740(0000) GS:ffff88013fa40000(0000) knlGS:0000000000000000
Aug  1 21:05:25 myhost kernel: [  146.819072] CS:  0010 DS: 0000 ES: 0000 CR0: 0000000080050033
Aug  1 21:05:25 myhost kernel: [  146.819097] CR2: 0000558a2aa16248 CR3: 0000000085d5c000 CR4: 0000000000060670
Aug  1 21:05:25 myhost kernel: [  146.819128] Stack:
Aug  1 21:05:25 myhost kernel: [  146.819139]  0000000000000000 007f000081e15080 ffff88013fa4c000 ffffffff81e15080
Aug  1 21:05:25 myhost kernel: [  146.819178]  ffff880085d5bd38 ffffffff8100216f ffff880085d5beb0 ffff880085d5bd10
Aug  1 21:05:25 myhost kernel: [  146.819217]  0000000000000246 0000000000000002 ffffffff811eab7f ffff88013b003c00
Aug  1 21:05:25 myhost kernel: [  146.819255] Call Trace:
Aug  1 21:05:25 myhost kernel: [  146.819273]  [<ffffffff8100216f>] do_one_initcall+0xcf/0x200
Aug  1 21:05:25 myhost kernel: [  146.819301]  [<ffffffff811eab7f>] ? kmem_cache_alloc_trace+0x1af/0x220
Aug  1 21:05:25 myhost kernel: [  146.819332]  [<ffffffff8118d293>] ? do_init_module+0x27/0x1d2
Aug  1 21:05:25 myhost kernel: [  146.819359]  [<ffffffff8118d2cc>] do_init_module+0x60/0x1d2
Aug  1 21:05:25 myhost kernel: [  146.819385]  [<ffffffff8110b42d>] load_module+0x145d/0x1b50
Aug  1 21:05:25 myhost kernel: [  146.819412]  [<ffffffff81107b60>] ? __symbol_put+0x40/0x40
Aug  1 21:05:25 myhost kernel: [  146.819440]  [<ffffffff812137a1>] ? kernel_read+0x41/0x60
Aug  1 21:05:25 myhost kernel: [  146.819466]  [<ffffffff8110bcee>] SYSC_finit_module+0x7e/0xa0
Aug  1 21:05:25 myhost kernel: [  146.819492]  [<ffffffff8110bd2e>] SyS_finit_module+0xe/0x10
Aug  1 21:05:25 myhost kernel: [  146.820820]  [<ffffffff8182d61b>] entry_SYSCALL_64_fastpath+0x22/0xcb
Aug  1 21:05:25 myhost kernel: [  146.822117] Code: eb 0e c0 0f 01 45 ee 0f b7 75 ee 48 8b 4d f0 48 c7 c7 9d f0 09 c1 31 c0 89 f2 e8 73 eb 0e c0 8c 5d ec 41 55 49 c7 c5 10 00 00 00 <49> 8e cd 41 5d 8c 4d ea 0f b7 55 ec 0f b7 75 ea 48 c7 c7 b8 f0 
Aug  1 21:05:25 myhost kernel: [  146.824981] RIP  [<ffffffffc109e114>] hello_init+0x44/0xe0 [hello_module]
Aug  1 21:05:25 myhost kernel: [  146.826376]  RSP <ffff880085d5bca0>
Aug  1 21:05:25 myhost kernel: [  146.833469] ---[ end trace 6525f2f63d2f58dd ]---
like image 913
Pirate Avatar asked Dec 11 '22 02:12

Pirate


2 Answers

There is no mov instruction to write to cs. From Intel® 64 and IA-32 architectures software developer’s manual, MOV spec:

The MOV instruction cannot be used to load the CS register. Attempting to do so results in an invalid opcode exception (#UD).

You need to do a far jump to change cs, check restrictions in ch.5.8 for changing cs.

like image 144
Renat Avatar answered Apr 28 '23 19:04

Renat


You can't load CS directly with a selector with MOV. You will need to use a RETFQ or IRETQ instruction to change CS and go to a 64-bit offset. From the instruction set reference:

The MOV instruction cannot be used to load the CS register. Attempting to do so results in an invalid opcode exception (#UD). To load the CS register, use the far JMP, CALL, or RET instruction.

You are getting #UD (invalid opcode) and that is why your module crashes.

In 64-bit code there is no FAR CALL or FAR JMP that takes a selector and a 64-bit offset as operands (immediate values) as there is no ptr16:64 variant.

enter image description here

In 32-bit mode you could have done an ljmpl $0x10, $offset but in 64-bit code you have to use an alternative1 like RETFQ (or an IRETQ if you need a privilege level change). You could use code like this:

asm ("push %[sel]\n\t"
     "push $1f\n\t"
     "retfq\n\t"
     "1:"
     :
     : [sel]"i"(__KERNEL_CS));

If you want to use basic inline assembly as in your question you need to place consecutive ASM statements into one statement. The compiler is allowed to generate code between your individual ASM statements which isn't what you'd want. A basic ASM version would be:

asm ("push $0x10\n\t"
     "push $1f\n\t"
     "retfq\n\t"
     "1:");

It is important to note that you need to override RETF to be of Quadword size as the default RETF in 64-bit code assumes a 32-bit return address and we want to specify that we want to return to a 64-bit address. From the Intel documentation for RET:

In 64-bit mode, the default operation size of this instruction is the stack-address size, i.e. 64 bits. This applies to near returns, not far returns; the default operation size of far returns is 32 bits.

This code simulates a FAR JMP to the next instruction by pushing the selector on the stack2 followed by the offset (label 1:) of the instruction after the retfq. retfq will pop off the offset and selector and transfer control to that address which happens to be the next instruction.


Footnotes:

  • 1It is possible to create a variable that is 10 bytes in length which includes a QWORD for the offset to jump to and a WORD for the selector. You can then use the JMP m16:64 jump variant. To encode a JMP m16:m64 in GCC's inline assembly using AT&T syntax you could do this:

    asm ("push %[sel]\n\t"
         "push $1f\n\t"
         "rex.W ljmp *(%%rsp)\n\t"
         "1:add $16, %%rsp\n\t"
         :
         : [sel]"i"(__KERNEL_CS));
    

    The code builds a 16:64 (selector:offset) pointer on the stack and does an indirect FAR JMP through RSP to it. The 16:64 address points to the instruction after the FAR JMP. The rex.W prefix promotes the FAR JMP so that the pointer is decoded with a 64-bit offset rather than a 32-bit offset. The stack is then cleaned up removing the pointer from the stack.

    I don't know the context or the reason you are changing CS, but if it is a frequent occurrence that the code is called then you may wish to emit the 16:64 pointer in the code itself. In the following example the 16:64 pointer is stored after the FAR JMP instruction, and the instruction to jump to is at label 2: (after the 16:64 pointer):

    asm ("rex.W ljmp *1f\n\t"
         /* The pointer is being stored with the code.
            The jump will be to the label after the pointer itself at label '2:'. */
         "1:  .quad 2f\n\t"
         "    .word %c[sel]\n\t"
         "2:\n\t"
         :
         : [sel]"i"(__KERNEL_CS));
    
  • 2The Linux kernel itself doesn't have a Red Zone so it is safe to push information on the stack directly with inline assembly. In user mode code you would have to adjust RSP to avoid clobbering the 128 bytes directly below RSP.

like image 21
Michael Petch Avatar answered Apr 28 '23 20:04

Michael Petch