Saltos

Desviando o fluxo de execução do código

Provavelmente você já sabe o que é um desvio de fluxo de código em uma linguagem de alto nível. Algo como uma instrução if que condicionalmente executa um determinado bloco de código, ou um for que executa várias vezes o mesmo bloco de código. Tudo isso é possível devido ao desvio do fluxo de código. Vamos a um pseudo-exemplo de um if:

1. Compare o valor de X com Y
2. Se o valor de X for maior, pule para 4.
3. Adicione 2 ao valor de X
4.

Repare que se a comparação no passo 1 der que o valor de X é maior, a instrução no passo 2 faz um desvio para o passo 4. Desse jeito o passo 3 nunca será executado. Porém caso a condição no passo 2 for falsa, isto é, o valor de X não é maior do que o valor de Y então o desvio não irá acontecer e o passo 3 será executado.

Ou seja o passo 3 só será executado sob uma determinada condição. Isso é um código condicional, isso é um if. Repare que o resultado da comparação no passo 1 precisa ficar armazenado em algum lugar, e este "lugar" é o registrador FLAGS.

Salto não condicional

Antes de vermos um desvio de fluxo condicional vamos entender como é o próprio desvio de fluxo em si. Na verdade existem muito mais registradores do que os que eu já citei. E um deles é o registrador IP, sigla para Instruction Pointer (ponteiro de instrução). Esse registrador também acompanha o tamanho do barramento interno, assim como os registradores gerais:

IP

EIP

RIP

16 bits

32 bits

64 bits

Assim como o nome sugere o Instruction Pointer serve como um ponteiro para a próxima instrução a ser executada pelo processador. Desse jeito é possível mudar o fluxo do código simplesmente alterando o valor de IP, porém não é possível fazer isso diretamente com uma instrução como a mov.

Na arquitetura x86 existem as instruções de jump, salto em inglês, que alteram o valor de IP permitindo assim que o fluxo seja alterado. A instrução de jump não condicional, intuitivamente, se chama JMP. Esse desvio de fluxo é algo muito semelhante com a instrução goto da linguagem C, inclusive em boa parte das vezes o compilador converte o goto para meramente um JMP.

O uso da instrução JMP é feito da seguinte forma:

jmp endereço

Onde o operando você pode passar um rótulo que o assembler irá converter para o endereço corretamente. Veja o exemplo na nossa PoC:

bits 64

global assembly
assembly:
  mov eax, 555
  jmp .end

  mov eax, 333

.end:
  ret

A instrução na linha 8 nunca será executada devido ao JMP na linha 6.

Repare que na linha 10 estamos usando um rótulo local que foi explicado no tópico sobre a sintaxe do nasm.

Registrador FLAGS

O registrador FLAGS também é estendido junto ao tamanho do barramento interno. Então temos:

FLAGS

EFLAGS

RFLAGS

16 bits

32 bits

64 bits

Esse registrador, diferente dos registradores gerais, não pode ser acessado diretamente por uma instrução. O valor de cada bit do registrador é testado por determinadas instruções e são ligados e desligados por outras instruções. É testando o valor dos bits do registrador FLAGS que as instruções condicionais funcionam.

Salto condicional

Os jumps condicionais, normalmente referidos como Jcc, são instruções que condicionalmente fazem o desvio de fluxo do código. Elas verificam os valores dos bits do registrador FLAGS e, com base nos valores, será decidido se o salto será tomado ou não. Assim como no caso do JMP as instruções Jcc também recebem como operando o endereço para onde devem tomar o salto caso a condição seja atendida. Se ela não for atendida então o fluxo de código continuará normalmente.

Eis a lista dos saltos condicionais mais comuns:

Instrução

Nome estendido

Condição

JE

Jump if Equal

Pula se for igual

JNE

Jump if Not Equal

Pula se não for igual

JL

Jump if Less than

Pula se for menor que

JG

Jump if Greater than

Pula se for maior que

JLE

Jump if Less or Equal

Pula se for menor ou igual

JGE

Jump if Greater or Equal

Pula se for maior ou igual

O nome Jcc para se referir aos saltos condicionais vem do prefixo 'J' seguido de 'cc' para indicar uma condição, que é o formato da nomenclatura das instruções.

Exemplo: JLE -- 'J' prefixo, 'LE' condição (Less or Equal)

Essa mesma nomenclatura também é usada para as outras instruções condicionais, como por exemplo CMOVcc.

A maneira mais comum usada para setar as flags para um salto condicional é a instrução CMP. Ela recebe dois operandos e compara o valor dos dois, com base no resultado da comparação ela seta as flags corretamente. Agora um exemplo na nossa PoC:

bits 64

global assembly
assembly:
  mov eax, 0

  mov rbx, 7
  mov rcx, 5  
  cmp rbx, rcx
  jle .end

  mov eax, 1
.end:
  ret

Na linha 10 temos um Jump if Less or Equal para o rótulo local .end, e logo na linha anterior uma comparação entre RBX e RCX. Se o valor de RBX for menor ou igual a RCX, então o salto será tomado e a instrução na linha 12 não será executada. Desta forma temos algo muito parecido com o if no pseudocódigo abaixo:

eax = 0;
rbx = 7;
rcx = 5;
if(rbx > rcx){
  eax = 1;
}
return;

Repare que a condição para o código ser executado é exatamente o oposto da condição para o salto ser tomado. Afinal de contas a lógica é que caso o salto seja tomado o código não será executado.

Experimente modificar os valores de RBX e RCX, e também teste usando outros Jcc.

Last updated