Syscall hijacking usando Assembly x64

Dando sequência ao post anterior onde vimos uma introdução à LKM usando Assembly x64, agora vamos partir para alterar uma entrada na tabela de syscalls no Linux (kernel 2.6.*).
A técnica usada neste post é baseada no post do memset's blog [1].

A técnica é bem simples, para nós podermos alterar a sys_call_table (que possui os endereços das funções que implementam as funcionalidades providas pelo Kernel) nós precisamos de permissão de escrita em sua página de memória, que é marcada como read-only. Então vem a pergunta: Como fazemos isso? Muito simples! Basta controlarmos o registrador de controle do processador para habilitar e desabilitar a flag de escrita, mais especificamente o registrador CR0 [2], iremos mudar o bit 16 dele, que se refere a WP (write protect) para 0, logo poderemos alterar a sys_call_table, e quando terminado, voltamos o bit ao normal.

Para essa tarefa preparei duas macros (lembrando que estou usando o GAS (GNU Assembler):

# Macro para desabilitar proteção de escrita (bit WP do cr0) em páginas de memória
.macro __disable_write_protection
	movq %cr0, %rdx
	movq $0x10000, %rcx
	notq %rcx
	andq %rcx, %rdx
	movq %rdx, %cr0
.endm
 
# Macro para habilitar a proteção de escrita (bit WP do cr0) em páginas de memória
.macro __enable_write_protection
	movq %cr0, %rdx
	orq $0x10000, %rdx
	movq %rdx, %cr0
.endm

E quanto a parte de ter acesso a sys_call_table, partiremos do princípio que obtemos o endereço dela manualmente. Obtendo o endereço da seguinte forma:

$ cat /boot/System.map-2.6.32-5-amd64 | grep sys_call_table
ffffffff81307240 R sys_call_table
ffffffff81310010 r ia32_sys_call_table

O primeiro endereço é o que nos interessa. Usaremos ele como endereço base para podermos acessar a syscall que deserjamos alterar. No caso, iremos alterar a syscall setreuid (responsável por alterar o real e efetivo user id no processo corrente [3]) vamos precisar do número dela...

$ cat /usr/include/asm/unistd_64.h | grep setreuid
#define __NR_setreuid				113
__SYSCALL(__NR_setreuid, sys_setreuid)

Aí está ele, este será nosso índice na sys_call_table. (i.e. sys_call_table[113])

Tendo estas informações montei a seguinte macro para auxiliar:

# Macro para carregar o endereço da syscall setreuid em %rsi
.macro __get_setreuid_ptr
	movq $0xffffffff81307240, %rsi  # endereço da sys_call_table
	movq $113, %rcx                 # 113 = sys_setreuid
	leaq (%rsi, %rcx, 8), %rsi      # endereço da sys_setreuid
.endm

Veja que em %rsi estará o endereço da syscall referente a setreuid na sys_call_Table. Com isso, poderemos alterá-lo para uma função que deserjamos, da seguinte forma:

# Nossa implementação da sys_setreuid
my_setreuid:
	xorq %rax, %rax
	ret
 
...
init_module:
	__disable_write_protection
	__get_setreuid_ptr	
	leaq my_setreuid(%rip), %rcx 
	# Altera o endereço para apontar para nossa setreuid
	movq %rcx, (%rsi)
	__enable_write_protection
...

Veja abaixo o código completo:

# Macro para carregar o endereço da syscall setreuid em %rsi
.macro __get_setreuid_ptr
	movq $0xffffffff81307240, %rsi  # endereço da sys_call_table
	movq $113, %rcx                 # 113 = sys_setreuid
	leaq (%rsi, %rcx, 8), %rsi      # endereço da sys_setreuid
.endm
 
# Macro para desabilitar proteção de escrita (bit WP do cr0) em páginas de memória
.macro __disable_write_protection
	movq %cr0, %rdx
	movq $0x10000, %rcx
	notq %rcx
	andq %rcx, %rdx
	movq %rdx, %cr0
.endm
 
# Macro para habilitar a proteção de escrita (bit WP do cr0) em páginas de memória
.macro __enable_write_protection
	movq %cr0, %rdx
	orq $0x10000, %rdx
	movq %rdx, %cr0
.endm
 
	.section .rodata
init_msg:     .string "<1>Modulo inicializado!\n"
exit_msg:     .string "<1>Modulo finalizado!\n"
hijack:       .string "<1>setreuid hijacked!\n"
setreuid_msg: .string "<1>sys_setreuid addr = %lx\n"
 
	.section .bss
# Onde guardaremos o endereço original da sys_setreuid
.lcomm old_setreuid, 8
 
	.section .text
.global init_module
.global cleanup_module
.global my_setreuid
 
# Implementação da sys_setreuid que só mostra uma mensagem
my_setreuid:
	callq *old_setreuid
	movq %rax, %r12
 
	leaq hijack, %rdi
	callq printk
 
	xorq %rax, %rax
	ret	
 
init_module:
	leaq init_msg, %rdi
	callq printk
 
	__disable_write_protection
 
	__get_setreuid_ptr	
 
	leaq setreuid_msg, %rdi
	movq (%rsi), %rsi	
	# Guarda o antigo endereço
	movq %rsi, old_setreuid(%rip)	
	# Printa o endereço atual
	callq printk
 
	__get_setreuid_ptr	
	leaq my_setreuid(%rip), %rcx 
	# Altera o endereço para apontar para nossa setreuid
	movq %rcx, (%rsi)
 
	leaq setreuid_msg, %rdi
	movq (%rsi), %rsi
	# Printa o novo endereço
	callq printk
 
	__enable_write_protection
 
	xorq %rax, %rax
	ret
 
cleanup_module:
	leaq exit_msg, %rdi
	callq printk
 
	__get_setreuid_ptr
 
	leaq setreuid_msg, %rdi
	movq (%rsi), %rsi	
	# Printa o endereço atual na sys_call_table para setreuid
	callq printk
 
	__disable_write_protection
 
	__get_setreuid_ptr
 
	leaq setreuid_msg, %rdi
	# Coloca o antigo endereço (o original) devolta para a sys_call_table
	movq old_setreuid(%rip), %rax
	movq %rax, (%rsi)
	movq (%rsi), %rsi
 
	__enable_write_protection
 
	# Mostra o endereço que escrevemos devolta na sys_call_table
	callq printk
 
	xorq %rax, %rax
	ret
 
	.section .modinfo, "aS", @progbits
__kernel_version:     .string "kernel_version=2.6.32"
__module_license:     .string "license=GPL"
__module_author:      .string "author=Felipe Pena"
__module_depends:     .string "depends="
__module_description: .string "description=Modulo de teste"

Obs.: nomeei ele como hijack.S, se usar outro nome, precisa alterar o test-objs no Makefile.

O Makefile:

obj-m  := test.o
test-objs := hijack.o
 
 
KDIR   := /lib/modules/$(shell uname -r)/build
PWD    := $(shell pwd)
 
default:
	$(MAKE) -C $(KDIR) SUBDIRS=$(PWD) modules
 
clean:
	rm -rf *.ko *.o *.order *.symvers *.mod.c .*.cmd .tmp_versions

Vamos ao teste!

$ sudo insmod test.ko 
$ dmesg | tail -3
[ 9410.331081] Modulo inicializado!
[ 9410.331083] sys_setreuid addr = ffffffff8105f77d
[ 9410.331084] sys_setreuid addr = ffffffffa00a5000
$ sudo echo 1
1
$ dmesg | tail -1
[ 9480.619493] setreuid hijacked!
$ sudo rmmod test 
$ dmesg | tail -3
[ 9513.300367] Modulo finalizado!
[ 9513.300371] sys_setreuid addr = ffffffffa00a5000
[ 9513.300374] sys_setreuid addr = ffffffff8105f77d

Repare que tanto no init como no cleanup eu coloquei pra imprimir o endereço original da sys_setreuid, e o da nossa implementação. Além disso, guardo o endereço da implementação original para poder chamá-lo na nossa implementação, bem como para colocar devolta na sys_call_table quando estiver removendo o módulo (rmmod).

Referências

[1] http://memset.wordpress.com/2010/12/03/syscall-hijacking-kernel-2-6-syst...
[2] http://en.wikipedia.org/wiki/Control_register
[3] http://pubs.opengroup.org/onlinepubs/009604499/functions/setreuid.html

Post new comment

The content of this field is kept private and will not be shown publicly.
  • Web page addresses and e-mail addresses turn into links automatically.
  • Allowed HTML tags: <a> <em> <strong> <cite> <code> <ul> <ol> <li> <dl> <dt> <dd>
  • Lines and paragraphs break automatically.

More information about formatting options