Análise do princípio de ROP (Programação Orientada ao Retorno)

Primeiro, olhe para um código:

#include <stdio.h>
#include <stdlib.h>

// 下面的dummy_libc_part1和dummy_libc_part2假设是GLIBC库里的任意两段函数
void dummy_libc_part1()
{
    
    
	// ... 这里可能会有别的指令
	__asm("mov 0(%rsp), %rdi");
	__asm("popq %r13");
	__asm("call *%r14");
	__asm("ret");
	// ... 这里可能会有别的指令
}

void dummy_libc_part2()
{
    
    
	// ... 这里可能会有别的指令
	__asm("popq %r14");
	__asm("ret");
	// ... 这里可能会有别的指令
}

int main(int argc, char **argv)
{
    
    
	__asm("pushq $0x400545");
	__asm("pushq $0x62");
	__asm("pushq $0x400521");
	__asm("pushq $0x400400");
	__asm("pushq $0x40052f");
	__asm("ret");
	printf(".");
}

Adivinha qual será o resultado? Não tente compilar e executar, pode não ser igual ao nosso em sua máquina.

Assumimos que dummy_libc_part1 / dummy_libc_part2 são todas sequências existentes, semelhantes às do GLIBC. Então, usando essas sequências de instrução, você não precisa escrever nenhuma instrução. Você só precisa empilhar dados na pilha para obter saltos arbitrários na lógica do programa. Isso é ROP! Use a instrução ret pronta para organizar cuidadosamente o conteúdo na pilha para obter a injeção de código.

No espaço de endereço de um programa, o lugar mais fácil para tocarmos é a pilha. Dessa forma, um dos principais problemas do ROP é como organizar os dados de maneira inteligente na pilha.

GLIBC é um tesouro de sequências de instruções, nas quais você pode encontrar quase qualquer sequência disponível. Mas o problema está em como você pode unir algumas sequências dispersas em lógica baseada em push / call e return / pop. Esta não é uma tarefa fácil, assim como cada palavra do poema de Li Bai está no dicionário, mas por Você não pode escrever os poemas de Li Bai em um dicionário.

Vamos dar uma olhada na lógica de empilhamento da pilha no código original deste artigo:
Insira a descrição da imagem aqui

Na prática ROP, definitivamente não usará push para construir pilha como o código acima, mas usará buffer overflow. Os mais notórios com vulnerabilidades de estouro de buffer são funções de string.

No entanto, na arquitetura x86_64, a função string foi finalmente eliminada, porque os 16 bits altos do espaço de endereço do processo são forçados a 0, o que significa que ao usar a função string truncada por 0 para estourar, apenas um endereço pode ser substituído. Semelhante ao seguinte quadro de pilha, usar funções de string é absolutamente impossível de construir:

0x00007fffffffe498
0x00007fffffffe3b0
0x00007fffffffdcb8
0x00007ffff7aa39aa

Portanto, a escrita de ROP é um ofício muito habilidoso, sua tarefa é encontrar a sequência de instruções disponível no GLIBC ou no LIB vinculado ao processo injetado. O princípio é apenas permitir que você entenda as palavras, parágrafos e capítulos, e o real fora de contexto é uma arte.

Os sistemas atuais vêm com proteção ASLR (Address space layout randomization). Portanto, mesmo se você hackear um programa com sucesso, o código hack não será reutilizável. Você deve fazer o mesmo trabalho artístico todas as vezes. Quando este trabalho continuar Após a repetição, não é mais uma arte, e o implementador imediatamente degenera em um trabalhador da linha de produção.

Não o gerente.


Os sapatos de couro em Wenzhou, Zhejiang, estão molhados, por isso não engordam na chuva.

Acho que você gosta

Origin blog.csdn.net/dog250/article/details/108633034
Recomendado
Clasificación