Tipo de programa de auto-modificação em C [duplicado]
-
03-07-2019 - |
Pergunta
É possível escrever uma função C que faz o seguinte?
- Alocar um monte de memória no heap
- Grava código de máquina nele
- Executa essas instruções máquinas
É claro que eu teria de restaurar o estado da pilha para o que era antes da execução dessas instruções de máquina manualmente, mas eu quero saber se isso é possível em primeiro lugar.
Solução
É certamente possível . Por várias razões, nós passamos um grande esforço dos últimos 30-40 anos tentando fazê-lo o mais difícil possível, mas é possível. Na maioria dos sistemas de agora, existem mecanismos de hardware e software que tentam espaço de dados proteger de ser executada.
O básico, porém, são bastante simples: você constrói um pedaço de código, e montá-lo, quer por OR4 mão através de um compilador. Você precisa então um fragmento de espaço de código, para que você inserir o código em seu programa
unsigned int prgm[] = { 0x0F, 0xAB, 0x9A ... }; // Random numbers, just as an example
desde que você queria usar o pilha você precisa malloc o espaço
void * myspace ;
if((myspace= malloc(sizeof(prgm))) != NULL) {
memcpy(myspace, pgrm, sizeof(pgrm));
} else { // allocation error
}
Agora, o que você precisa é uma maneira de obter o contador de programa para apontar para esse pedaço de dados que também é o seu pedaço de código. Aqui é onde você precisa de um pouco de astúcia. Definir o contador de programa não é grande coisa; isso é apenas uma instrução de salto para a sua máquina subjacente. Mas como fazer isso?
Uma das maneiras mais fáceis é por mexer propositalmente com a pilha. A pilha, mais uma vez, conceitualmente, é algo como isto (os detalhes dependem tanto do seu sistema operacional e compilador pares, e no seu hardware):
| subroutine return addr | | parameters ... | | automatic variables |
O truque básico aqui é conseguir sneakily o endereço do seu código para o endereço de retorno; quando uma rotina retorna, ele basicamente salta para que addrfess retorno. Se você pode fingir-lo, o PC será definido para onde quiser.
Então, o que você precisa é uma rotina, vamos chamá-lo "goThere ()"
void goThere(void * addr){
int a ; // observe above; this is the first space
// on the stack following the parameters
int * pa; // so we use it's address
pa = (&a - (sizeof(int)+(2*sizeof(void*))) ; // so use the address
// but back up by the size of an int, the pointer on the
// stack, and the return address
// Now 'pa' points to the routine's return add on the stack.
*pa = addr; // sneak the address of the new code into return addr
return ; // and return, tricking it into "returning"
// to the address of your special code block
}
Será que vai funcionar? Bem, talvez, dependendo do hardware e sistema operacional. A maioria dos modernos de OS irá proteger a pilha (via mapeamento de memória ou similar) a partir do PC movendo para ele. Isso é uma coisa útil para fins de segurança, porque tínhamos acabado bem não deixá-lo tomar esse tipo de controle completo.
Outras dicas
Isto é muito semelhante ao esta questão :)
Leia código de chamada armazenado na pilha do vc ++ . Em POSIX, mprotect
parece ser apropriada (olhar para man mprotect
):
char *mem = malloc(sizeof(code));
mprotect(mem, sizeof(code), PROT_READ|PROT_WRITE|PROT_EXEC);
memcpy(mem, code, sizeof(code));
// now arrange some code to jump to mem. But read the notes here on casting
// from void* to a function pointer:
// http://www.opengroup.org/onlinepubs/009695399/functions/dlsym.html
No entanto, ele diz:
Se PROT_EXEC tem qualquer efeito diferente de PROT_READ é Architecture e do kernel dependente versão. Em algumas arquiteturas de hardware (por exemplo, i386), PROT_WRITE implica PROT_READ.
Então é melhor, verifique primeiro se do seu sistema operacional, que funciona.
RE: restaurar manualmente a pilha
Se você seguir as convenções de chamada usadas por sua plataforma / compilador dentro do código de máquina que você gera, então você não deve ter que fazer qualquer pilha manual de restauração. O compilador irá fazer isso por você, quando você faz
* pFunc (args)
deve adicionar qualquer etapas de manipulação da pilha de chamadas pós que são necessários pré apropriado ou.
Apenas certifique-se que você siga as convenções corretas dentro do código gerado, no entanto.