Learning assembly from scratch including:
- Assembly programming
- Reverse and analyse binary files
- Environments & Tools
- Set Up Environments
- Memory Allocation
- Registers
- System Calls
- Function Calls
- Instructions
- Debug
Reversing Engineering with Radare 2
-
x86_64 architecture
-
Linux CentOS 7 with nasm
NOTICE, the following docker image supports x86_64 architecture.
- Build the docker image
$docker build . -t assembly
- Run the image in the background and share the workspace with the container
$docker run -d -it -v $(pwd):/app/src --name assembly assembly
- Run the bash in the container
$docker exec -it assembly /bin/bash
- build and execute
$./exec 2_hello.asm
- Define constants or functions
section .data
somePtr:
dd constant0
someFunc:
...
ret
- Access constants
mov ... DWORD[somePtr] ...
-
Table for generating constants
Instruction C++ Access Register Bits Bytes dq 0x3 long QWORD[somePtr] r _ _ 64 8 dd 0x3 int DWORD[somePtr] e _ _ 32 4 dw 0x3 short WORD[somePtr] _ _ 16 2 db 0x3 char BYTE[somePtr] _ l 8 1 -
Example 1 - Load a statically allocated integer from memory
mov eax, DWORD [myInt] ;copy myInt into eax
ret
section .data
myInt:
dd 0xa3a2a1a0 ;"data DWORD" containing this value
- Example 2 - Copy a pointer value into a register
mov rdx, myIntPtr ;copy the address myIntPtr into rdx (like C++: p=someIntPtr;)
mov eax, DWORD [rdx] ;read memory rdx points to (like C++: return *p;)
ret
section .data
myIntPtr: ; A place in memory, where we're storing an integer
dd 123 ; "data DWORD", our integer
- Example 3 - A 4-digit array
mov eax, DWORD [arr+4*2] ;read arr[2]
ret
section .data
arr: ;An integer array
dd 100 ;"data DWORD", arr[0]
dd 101 ;arr[1]
dd 102 ;arr[2]
dd 103 ;arr[3]
- Example 4 - String
movzx eax, BYTE[myStr + 2] ;read this byte into eax
ret
section .data
myStr:
db "woa" ;= db 'w','o','a'
;= db 'woa'
;= db 'w' db 'o' db 'a'
- Example 5 - update data
mov DWORD[func+1],7 ;overwrite constant loaded by first, 0xb8 instruction
call func
ret
section .data
func:
mov eax,2 ;<- modified at runtime!
ret
64-bit | 32-bit | 16-bit | 8-bit |
---|---|---|---|
rax | eax | ax | ah and al |
rbx | ebx | bx | bh and bl |
rcx | ecx | cx | ch and cl |
rdx | edx | dx | dh and dl |
rsp | esp | sp | spl |
rbp | ebp | bp | bpl |
rsi | esi | si | sil |
rdi | edi | di | dil |
r8 - r15 | r8d - r15d | r8w - r15w | r8b - r15b |
For example:
0x1122334455667788
================ rax (64 bits)
======== eax (32 bits)
==== ax (16 bits)
== ah (8 bits)
== al (8 bits)
- rip: what executes next
- eax: Primary accumulator, it's usually reserved for the return value
- ebx: Base register, used in indexed addressing.
- ecx: Counter register, stores the loop count in iterative operations.
- edx: Data register, it is also used in input/output operations. It is also used with eax register along with edx for multiply and divide operations involving large values.
- rsp: stack pointer (top)
- rbp: base pointer (bottom)
- rsi: source index
- rdi: destination index
- r8 - r15
- rax: syscall number
- rdi: arg0
- rsi: arg1
- rdx: arg2
- r10-r8-r9: arg3-arg5
EAX | Name | EBX | ECX | EDX |
---|---|---|---|---|
1 | sys_exit | int | ||
2 | sys_fork | struct pt_regs | ||
3 | sys_read | unsigned int | char * | size_t |
4 | sys_write | unsigned int | const char * | size_t |
5 | sys_open | const char * | int | int |
6 | sys_close | unsigned int |
- rax: return value
- rdi: arg0
- rsi: arg1
- rdx: arg2
- rcx-r8-r9: arg3-arg5
- CPU flags: ZF(Zero Flag): cmp, jump, test
- Stack: push, pop, call, leave, rat
- Control flow: call, jump
Run the executive file with gdb
$gdb 2_hello
- list: show the code
- list x,y: show the code from line x to y
- x86_64 architecture
- Linux Debian 10 with radare2
- macOS with cutter (Optional, cutter is basically a GUI of radare2)
- Build the docker image
$docker build . -t radare2
- Run the image in the background and share the workspace with the container
$docker run -d -it -v $(pwd):/app/src --name radare2 radare2
- Run the bash in the container
$docker exec -it radare2 /bin/bash
- Get started by crackmes
- Stack
push rbp ;stash old value of rbp on the stack
mov rbp, rsp ;rbp == stack pointer at the start of function
sub rsp, 1000 ;make some room on the stack
- Function and variables
mov eax, 3 ;int x = 3
jmp f ;goto f
mov eax, 0 ;<- never executed
f: ;f
ret ;return x
call
vsjmp
mov edi,1000
call f
add eax, 7 ;will be executed when f is done
ret
f:
mov eax, edi ;copy our first parameter into eax (to be returned)
ret ;go back to where the function is called
mov edi,1000
jmp f
add eax, 7 ;<- never executed
ret
f:
mov eax, edi ;copy our first parameter into eax (to be returned)
ret ;go back to main
- More control flow
mov eax, 3 ;int x = 3
cmp eax, 4 ;how does eax compare to 4?
je f ;jump to f if it's equal
mov eax, 3 ;int x = 3
cmp eax, 4 ;how does eax compare to 4?
jl f ;jump to f if eax is less than 4
cmp eax, 3 ;subtracts 3 from eax value, but it won't change eax
sub eax, 3 ;subtracts 3 from eax value, but it will change eax
-
Signed & unsigned value
- Use
jg
orjl
to compare 2 signed values - Use
ja
orjb
to compare 2 unsigned values - To compare a signed value to a unsigned value
English Less Than Less or Equal Equal Greater or Equal Greater Than Not Equal C/C++ < <= == >= > != Assembly (signed) jl jle je or jz jge jg jne or jnz Assembly (unsigned) jb jbe je or jz jae ja jne or jnz - Use
- Get started
$ r2 -d ./crackme0x00 # analyse a binary file with r2 in debug mode
> aa # or aaa
> pdf@PROCEDURE_NAME # any procedure you want to focus on, e.g. main.
- Type commands
> : # type : to enter command mode
> enter # quit the command mode
- Add a breakpoint and continue running
> db ADDRESS # E.g. db 0x004006e5
> dc # continue running
> s # step
> S # step over
> ood # restart execution
- Switch to visual mode
> v # switch to visual mode
> p # switch to different panel
> q # leave the visual mode.
- Print a value at an address or register
> ? REG_NAME # e.g. ? rax
> dr?REG_NAME # e.g. dr?rax
> px @REG_NAME # e.g. px @rax
> px BYTES @REG_NAME # e.g. px 4 @rax
- Help
> d? # print the manual of debug commands
- Quit
> q
A simple debug flow could be
- Execute and analyse the binary file, and check what main procedure does.
$ r2 -d ./crackme0x00
> aa
> pdf@main
- Check the details of key procedures.
> pdf@PROC_NAME
- Add breakpoints and continue debugging
> db ADDRESS # E.g. db 0x004006e5
> db ADDRESS # E.g. db 0x004007e5
- Check all the breakpoints and remove unwanted ones.
> db
> db -ADDRESS # E.g. db 0x004007e5
- Execute the programme step by step, or jump to next breakpoint.
> dc # jump to next breakpoint
> V # switch to the visual mode to better check the values of registers
> s # move on to next line
> S # step over next line
> TAB # click on tab to show the details of registers
- Restart the programme if the process is finished.
> ood
> q # exit the visual mode