Professional Documents
Culture Documents
org/Simple_Instructions
Simple Instructions
From SkullSecurity
Recall that a pointer is a data type that stores an address as its value. Since registers are simply 32-bit values
with no actual types, any register may or may not be a pointer, depending on what is stored. It is the
1 of 9 8/10/2019, 6:08 PM
Simple Instructions - SkullSecurity https://wiki.skullsecurity.org/Simple_Instructions
responsibility of the program to treat pointers as pointers and to treat non-pointers as non-pointers.
If a value is a pointer, it can be dereferenced. Recall that dereferencing a pointer retrieves the value stored at the
address being pointed to. In assembly, this is generally done by putting square brackets ("[" and "]") around the
register. For example:
Doing Nothing
The nop instruction is probably the simplest instruction in assembly. nop is short for "no operation" and it does
nothing. This instruction is used for padding.
mov is the instruction used for assignment, analogous to the "=" sign in most languages. mov can move data
between a register and memory, two registers, or a constant to a register. Here are some examples:
movsx and movzx are special versions of mov which are designed to be used between signed (movsx) and
unsigned (movzx) registers of different sizes.
movsx means move with sign extension. The data is moved from a smaller register into a bigger register, and the
sign is preserved by either padding with 0's (for positive values) or F's (for negative values). Here are some
examples:
movzx means move with zero extension. The data is moved from a smaller register into a bigger register, and the
sign is ignored. Here are some examples:
2 of 9 8/10/2019, 6:08 PM
Simple Instructions - SkullSecurity https://wiki.skullsecurity.org/Simple_Instructions
lea
lea is very similar to mov, except that math can be done on the original value before it is used. The "[" and "]"
characters always surround the second parameter, but in this case they do not indicate dereferencing, it is easiest
to think of them as just being part of the formula.
lea is generally used for calculating array offsets, since the address of an element of the array can be found with
[arraystart + offset*datasize]. lea can also be used for quickly doing math, often with an addition and a
multiplication. Examples of both uses are below.
add, sub
A register can have either another register, a constant value, or a pointer added to or subtracted from it. The
syntax of addition and subtraction is fairly simple:
inc, dec
3 of 9 8/10/2019, 6:08 PM
Simple Instructions - SkullSecurity https://wiki.skullsecurity.org/Simple_Instructions
All logical instructions are bitwise. If you don't know what "bitwise arithmetic" means, you should probably
look it up. The simplest way of thinking of this is that each bit in the two operands has the operation done
between them, and the result is stored in the first one.
The instructions are pretty self-explanatory: and does a bitwise 'and', or does a bitwise 'or', xor does a bitwise
'xor', and neg does a bitwise negation.
and eax, 7 ; eax = eax & 7 -- because 7 is 000..000111, this clears all bits
except for the last three.
or eax, 16 ; eax = eax | 16 -- because 16 is 000..00010000, this sets the 5th
bit from the right to "1".
xor eax, 1 ; eax = eax ^ 1 -- this toggles the right-most bit in eax, 0=>1 or
1=>0.
xor eax, FFFFFFFFh ; eax = eax ^ 0xFFFFFFFF -- this toggles every bit in eax, which is
identical to a bitwise negation.
neg eax ; eax = ~eax -- inverts every bit in eax, same as the previous.
xor eax, eax ; eax = 0 -- this clears eax quickly, and is extremely
common.
Multiplication and division are the trickiest operations commonly used, because of how they deal with overflow
issues. Both multiplication and division make use of the 64-bit register edx:eax.
mul multiplies the unsigned value in eax with the operand, and stores the result in the 64-bit pointer edx:eax.
imul does the same thing, except the value is signed. Here are some examples of mul:
When used with two parameters, mul instead multiplies the first by the second as expected:
div divides the 64-bit value in edx:eax by the operand, and stores the quotient in eax. The remainder (modulus)
is stored in edx. In other words, div does both division and modular division, at the same time. Typically, a
program will only use one or the other, so you will have to check which instructions follow to see whether eax
or edx is saved. Here are some examples:
cdq is generally used immediately before idiv. It stands for "convert double to quad." In other words, convert
the 32-bit value in eax to the 64-bit value in edx:eax, overwriting anything in edx with either 0's (if eax is
positive) or F's (if eax is negative). This is very similar to movsx, above.
xor edx, edx is generally used immediately before div. It clears edx to ensure that no leftover data is divided.
4 of 9 8/10/2019, 6:08 PM
Simple Instructions - SkullSecurity https://wiki.skullsecurity.org/Simple_Instructions
Here is a common use of xor and div (the results are the same as the previous example):
These are used to do a binary shift, equivalent to the C operations << and >>. They each take two operations:
the register to use, and the number of places to shift the value in the register. As computers operate in base 2,
these commands can be used as a faster replacement for multiplication/division operations involving powers of
2.
Divide by 2 (unsigned):
Multiply by 4 (signed):
Jumping Around
Instructions in this section are used to compare values and to make jumps. These jumps are used for calls, if
statements, and every type of loop. The operand for most jump instructions is the address to jump to.
jmp
jmp, or jump, sends the program execution to the specified address no matter what. Here is an example:
5 of 9 8/10/2019, 6:08 PM
Simple Instructions - SkullSecurity https://wiki.skullsecurity.org/Simple_Instructions
call, ret
call is similar to jump, except that in addition to sending the program to the specified address, it also saves
("pushes") the address of the executable instruction onto the stack. This will be explained more in a later
section.
ret removes ("pops") the first value off of the stack, and jumps to it. In almost all cases, this value was placed
onto the stack by the call instruction. If the stack pointer is at the wrong location, or the saved address was
overwritten, ret attempts to jump to an invalid address which usually crashes the program. In some cases, it may
jump to the wrong place where the program will almost inevitably crash.
ret can also have a parameter. This parameter is added to the stack immediately after ret executes its jump. This
addition allows the function to remove values that were pushed onto the stack. This will be discussed in a later
section.
The combination of call and ret are used to implement functions. Here is an example of a simple function:
call 4000h
...... ; any amount of code
4000h:
mov eax, 1
ret ; Because eax represents the return value, this function would return 1, and
nothing else would happen
cmp, test
cmp, or compare, compares two operands and sets or unsets flags in the flags register based on the result.
Specialized jump commands can check these flags to jump on certain conditions. One way of remembering how
cmp works is to think of it as subtracting the second parameter from the first, comparing the result to 0, and
throwing away the result.
test is very similar to cmp, except that it performs a bitwise 'and' operation between the two operands. test is
most commonly used to compare a variable to itself to check if it's zero.
jz and je (which are synonyms) will jump to the address specified if and only if the 'zero' flag is set, which
indicates that the two values were equal. In other words, "jump if equal".
jnz and jne (which are also synonyms) will jump to the address specified if and only if the 'zero' flag is
not set, which indicates that the two values were not equal. In other words, "jump if different".
jl and jb (which are synonyms) jumps if the first parameter is less than the second.
jg jumps if the first parameter is greater than the second.
jle jumps if the 'less than' or the 'zero' flag is set, so "less than or equal to".
jge jumps if the first is "greater than or equal to" the second.
These jumps are all used to implement various loops and conditions. For example, here is some C code:
if(a == 3)
6 of 9 8/10/2019, 6:08 PM
Simple Instructions - SkullSecurity https://wiki.skullsecurity.org/Simple_Instructions
b;
else
c;
And here is how it might look in assembly (not exactly assembly, but this is an example):
10 cmp a, 3
20 jne 50
30 b
40 jmp 60
50 c
60
10 mov ecx, 0
20 a
30 b
40 inc ecx
50 cmp ecx, 5
60 jl 20
push, pop
push decrements the stack pointer by the size of the operand, then saves the operand to the new address. This
line:
push ecx
sub esp, 4
mov [esp], ecx
pop sets the operand to the value on the stack, then increments the stack pointer by the size of the operand. This
assembly:
pop ecx
7 of 9 8/10/2019, 6:08 PM
Simple Instructions - SkullSecurity https://wiki.skullsecurity.org/Simple_Instructions
pushaw and pushad save all 16-bit or 32-bit registers (respectively) onto the stack.
popaw and popad restore all 16-bit or 32-bit registers from the stack.
Questions
Feel free to edit this section and post questions, and I will do my best to answer them; however, you may need
to contact me to let me know that a question exists.
10 mov ecx, 0
20 a
30 b
40 cmp ecx, 5
50 jl 20
Wont this just loop forever because ecx is never incremented? Total noob here so i may have missed something
obvious.
Yes, my mistake.
10 mov ecx, 0
20 a
30 b
40 inc ecx
50 comp ecx, 5
60 jl 20
Changed.
Also, The stack is decremented when pushed, but increased when poped? Isn't this counterintuitive?
Yes, the stack starts high and grows downwards. Welcome to x86 assembler!
8 of 9 8/10/2019, 6:08 PM
Simple Instructions - SkullSecurity https://wiki.skullsecurity.org/Simple_Instructions
normal
9 of 9 8/10/2019, 6:08 PM