Obsess over every detail. Ask why it works. Ask why it isn’t built another way.

TRUE OR FALSE

first wut is boolean? boolean is just all yap about true or false tbh, We will use 0 as shorthand for “false” and 1 as shorthand for “true”.

AND

AND is true if both of the inputs are true.

OR

OR is true if either input is true.

XOR

Exclusive-OR is true if only one input is true.

NOT

NOT flips the value to the opposite value

Boolean logic in C

Logical operators

In C “logical” boolean operations, which operate over a statement and are interpreted as true if the result is non-zero or false if the result is zero.

1
2
3
The logical AND operator is &&
The logical OR operator is ||
The logical NOT operator is !

Bitwise operators

In C “bitwise” boolean operations, the boolean operation is computed individually on each bit in the same index of the two operands. So if you’re doing a bitwise AND of two variables, it is computed as:

1
2
3
4
output_bit[0] = input1_bit[0] AND input2_bit[0]
output_bit[1] = input1_bit[1] AND input2_bit[1]
...
output_bit[N] = input1_bit[N] AND input2_bit[N]
1
2
3
4
The bitwise AND operator is &
The bitwise OR operator is |
The bitwise XOR operator is ^
The bitwise NOT operator is ~
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
#include <stdio.h>
#define uint64 unsigned long long

unsigned long long main ()
{
    unsigned int i = 0x50da;
    unsigned int j = 0xc0ffee;
    uint64 k = 0x7ea707a11ed;
    k ^= ~( i & j ) | 0x7ab00;
    return k;
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
    push    rbp {__saved_rbp}
    mov     rbp, rsp {__saved_rbp}
    sub     rsp, 0x30
    call    __main
    mov     dword [rbp-0x4 {var_c}], 0x50da
    mov     dword [rbp-0x8 {var_10}], 0xc0ffee
    mov     rax, 0x7ea707a11ed
    mov     qword [rbp-0x10 {var_18}], rax  {0x7ea707a11ed}
    mov     eax, dword [rbp-0x4]
    and     eax, dword [rbp-0x8]
    not     eax
    or      eax, 0x7ab00
    mov     eax, eax  {0xffffaf35}
    xor     qword [rbp-0x10], rax  {0x7ea8f85bed8}
    mov     rax, qword [rbp-0x10]  {0x7ea8f85bed8}
    add     rsp, 0x30
    pop     rbp {__saved_rbp}
    retn    {__return_addr}

We can see the new instructions AND, NOT, OR, and XOR!

AND - Bitwise AND

C binary operator & not && that’s logical AND. Destination operand can be r/mX or register, source operand can be r/mX or register or immediate. No source and destination as r/mXs.

1
2
3
4
5
6
and al, bl

       00110011b ( al - 0x33 )
AND    01010101b ( bl - 0x55 )

RESULT 00010001b ( al - 0x11 )

Btw we calculated this by vertically comparing them, left to right, 1 to 1 is 1, 1 to 0 is 0, etc. because AND is only if both inputs are true.

AND al, 0x42

1
2
3
4
       00110011b ( al - 0x33  )
AND    01000010b ( imm - 0x42 )

RESULT 00000010b ( al - 0x02  )

OR - Bitwise OR

C binary operator | not || that’s logical OR. Destination operand can be r/mX or register, source operand can be r/mX or register or immediate, no source and destination as r/mXs. Calculation of this is if either is true.

OR al, bl

1
2
3
4
       00110011b ( al - 0x33 )
OR     01010101b ( bl - 0x55 )

RESULT 01110111b ( al - 0x77 )

OR al, 0x42

1
2
3
4
       00110011b ( al - 0x33  )
OR     01000010b ( imm - 0x42 ) 

RESULT 01110011b ( al - 0x73  )

XOR - Bitwise Exclusive OR

C binary operator ^. Destination operand can be r/mX or register, source operand can be r/mX or register or immediate, no source and destination as r/mXs. XOR is commonly used to zero a register, by XORing it with itself because it’s faster than a MOV. So frequently compiler will generate a XOR of a register with itself in order to zero that register.

1
2
3
4
       00110011b ( al - 0x33 )
XOR    00110011b ( al - 0x33 )

RESULT 00000000b ( al - 0x00 )

All 0s because it’s only true if one input is true.

NOT - One’s Complement Negation

C unary operator ~ not ! that’s logical NOT. Single source/destination operand can be r/mX. NOT only flips the bits. That’s all!

For Loops

C code:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
#include <stdio.h>

int main ()
{
    int i;
    for ( i = 0; i < 10; i++ )
    {
        printf ( "i = %d\n", i );
    }
    i--;
}

Disassembled:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
main:
    push    rbp {__saved_rbp}
    mov     rbp, rsp {__saved_rbp}
    sub     rsp, 0x30
    call    __main
    mov     dword [rbp-0x4 {i}], 0x0 
    jmp     0x14000156b

    mov     eax, dword [rbp-0x4 {i}]
    lea     rcx, [rel _.rdata]  {"i = %d\n"}
    mov     edx, eax
    call    printf
    add     dword [rbp-0x4 {i}], 0x1  ; normally in Visual Studio you're going to see inc (for increment)

    cmp     dword [rbp-0x4 {i}], 0x9
    jle     0x140001556

    sub     dword [rbp-0x4 {var_c} {i}], 0x1  ; and dec (for decrement) but we're in binary ninja so
    mov     eax, 0x0  ; normally we'll also see xor eax, eax in Visual Studio because the programmer forgot to put return 0
    add     rsp, 0x30
    pop     rbp {__saved_rbp}
    retn    {__return_addr}

INC/DEC (increment/decrement) (add/sub)

Single source/destination operand can be r/mX. Increase or decrease the value by 1. When optimized, compiler will tend to favor not using inc/dec that’s why we’re seeing add and sub in Binary Ninja because it’s directed by the Intel optimization guide. So their presence may be indicative of hand-written or un-optimized code. This modifies OF, SF, ZF, AF, PF, and CF flags.

1
rax 0xbe5077ed

xor rax, rax

1
rax 0x0

inc rax

1
rax 0x1

Another example:

1
rax 0x70ad57001

mov rax, 0

1
rax 0x0

dec rax

1
rax 0xFFFFFFFF'FFFFFFFF