# 2020 程安 Reverse筆記1 (逆向的各種trick)
###### tags: `程式安全`
## Compiler Walk Through
1. hello.c
```c=
#include <stdio.h>
#define A 'hello'
int main(){
puts(A);
return 0;
}
```
2. Preprocecssor把`define`展開
`gcc -E -o hello.i hello.c`
preprocessor會
```c=
//cat hello.i
...
extern void funlockfile (FILE *__stream) __attribute__ ((__nothrow__ , __leaf__));
# 858 "/usr/include/stdio.h" 3 4
extern int __uflow (FILE *);
extern int __overflow (FILE *, int);
# 873 "/usr/include/stdio.h" 3 4
# 2 "hello.c" 2
# 4 "hello.c"
int main(){
puts("hello");
return 0;
}
```
3. Compiler將code編譯成assembly
`gcc -S hello.s hello.i`
```asm=
.file "hello.c"
.text
.section .rodata
.LC0:
.string "hello"
.text
.globl main
.type main, @function
main:
.LFB0:
.cfi_startproc
endbr64
pushq %rbp
.cfi_def_cfa_offset 16
.cfi_offset 6, -16
movq %rsp, %rbp
.cfi_def_cfa_register 6
leaq .LC0(%rip), %rdi
movl $0, %eax
call put@PLT
movl $0, %eax
popq %rbp
.cfi_def_cfa 7, 8
ret
.cfi_endproc
.LFE0:
.size main, .-main
.ident "GCC: (Ubuntu 9.3.0-17ubuntu1~20.04) 9.3.0"
.section .note.GNU-stack,"",@progbits
.section .note.gnu.property,"a"
.align 8
.long 1f - 0f
.long 4f - 1f
.long 5
0:
.string "GNU"
1:
.align 8
.long 0xc0000002
.long 3f - 2f
2: .file "hello.c"
.text
.section .rodata
.LC0:
.string "hello"
.text
.globl main
.type main, @function
main:
.LFB0:
.cfi_startproc
endbr64
pushq %rbp
.cfi_def_cfa_offset 16
.cfi_offset 6, -16
movq %rsp, %rbp
.cfi_def_cfa_register 6
leaq .LC0(%rip), %rdi
call puts@PLT
movl $0, %eax
popq %rbp
.cfi_def_cfa 7, 8
ret
.cfi_endproc
.LFE0:
.size main, .-main
.ident "GCC: (Ubuntu 9.3.0-17ubuntu1~20.04) 9.3.0"
.section .note.GNU-stack,"",@progbits
.section .note.gnu.property,"a"
.align 8
.long 1f - 0f
.long 4f - 1f
.long 5
0:
.string "GNU"
1:
.align 8
.long 0xc0000002
.long 3f - 2f
2:
.long 0x3
3:
.align 8
4:
```
4. Assembler組譯器將assembly轉成machine code
`gcc -c -o hello.o hello.s`
此時可以用`objdump`dump出來,但還無法執行,因為reloc的部分還沒有處理
ex.第11行應該是`call puts@plt`,但因為library還沒link進來,所以會直接跳到下一行
```asm=
hello.o: file format elf64-x86-64
Disassembly of section .text:
0000000000000000 <main>:
0: f3 0f 1e fa endbr64
4: 55 push rbp
5: 48 89 e5 mov rbp,rsp
8: 48 8d 3d 00 00 00 00 lea rdi,[rip+0x0] # f <main+0xf>
f: e8 00 00 00 00 call 14 <main+0x14>
14: b8 00 00 00 00 mov eax,0x0
19: 5d pop rbp
1a: c3
```
5. Linker負責link library function
`gcc -o hello hello.o`
這時才會變成真正可執行的binary
## Calling Convention
### cdecl (C預設的呼叫慣例,Linux全部都是這種) : caller清空stack
- 由<font color='red'>caller</font>清空stack
- ex. `main`執行`myadd(1,2)`
```asm
main:
...
push 2
push 1
call myadd
add esp, 0x8 ; 由main(caller)清空stack
... ; 0x8 = 2個參數
```
- 優: 實作不定長度參數的function很方便(ex `printf`等format系列的)
- 缺: 浪費code空間,因為不用每次呼叫function後都要加上`add rsp,...`,這個指令佔3byte
### stdcall (Win32 API) : callee清空stack
- 聲明語法`int __stdcall function(arg,...)`
- 如何用gcc編譯stdcall:
`int __attribute__((stdcall))function(arg,...)`
- 由<font color='red'>callee</font>清空stack
- ex. `main`執行`myadd(1,2)`
```asm
main:
...
push 2
push 1
call myadd
...
myadd:
mov ..., [ebp+0x8]
mov ..., [ebp+0xc]
...
leave
ret 0x8 ; 由myadd(callee)清空stack
; 0x8 = 2個參數
```
- 缺: 不定長度參數的function無法處理
- 優: 節省code空間
### fastcall
- 聲明語法`int __fastcall function(arg,...)`
- 前兩個<font color='red'>DWORD以下size</font>的參數分別用`rcx`及`rdx`傳遞,後面的參數一樣用stack傳,由<font color='red'>callee</font>清空stack
- 優: 較快
### thiscall
- `thiscall`無法被明確聲明,因為`thiscall`是c++中class中function的預設呼叫慣例
- 如果參數個數確定,`this`指標通過<font color='red'>`ecx`</font>傳遞
如果參數不定長度,`this`指標由<font color='red'>stack</font>傳遞(在參數都push上去之後)
- ex myadd1
```clike=
#include <iostream>
#include <stdarg.h>
using namespace std; //va_list
class test{
public:
int myadd1(int a, int b){ //參數固定長度
return a+b;
}
int myadd2(int num,...){ //參數不定長度
va_list arg;
va_start(arg, num);
int res=0;
for (int i = 0; i < num; i++)
{
res += i;
}
return res;
}
};
int main(){
test A;
int res1 = A.myadd1(1,2);
int res2 = A.myadd2(3, 1, 2, 3);
cout << res1 << endl;
cout << res2 << endl;
return 0;
}
```
- 轉成assembly後
```asm
00401410 <_main>:
401410: 8d 4c 24 04 lea ecx,[esp+0x4]
401414: 83 e4 f0 and esp,0xfffffff0
401417: ff 71 fc push DWORD PTR [ecx-0x4]
40141a: 55 push ebp
40141b: 89 e5 mov ebp,esp
40141d: 51 push ecx
40141e: 83 ec 34 sub esp,0x34
401421: e8 3a 06 00 00 call 401a60 <___main>
401426: 8d 45 ef lea eax,[ebp-0x11]
401429: c7 44 24 04 02 00 00 mov DWORD PTR [esp+0x4],0x2
401430: 00
401431: c7 04 24 01 00 00 00 mov DWORD PTR [esp],0x1
401438: 89 c1 mov ecx,eax ; 參數固定,用ecx傳遞this指標
40143a: e8 89 28 00 00 call 403cc8 <__ZN4test6myadd1Eii>
40143f: 83 ec 08 sub esp,0x8
401442: 89 45 f4 mov DWORD PTR [ebp-0xc],eax
401445: c7 44 24 10 03 00 00 mov DWORD PTR [esp+0x10],0x3
40144c: 00
40144d: c7 44 24 0c 02 00 00 mov DWORD PTR [esp+0xc],0x2
401454: 00
401455: c7 44 24 08 01 00 00 mov DWORD PTR [esp+0x8],0x1
40145c: 00
40145d: c7 44 24 04 03 00 00 mov DWORD PTR [esp+0x4],0x3
401464: 00
401465: 8d 45 ef lea eax,[ebp-0x11]
401468: 89 04 24 mov DWORD PTR [esp],eax ; 參數不固定,用stack傳遞指標
40146b: e8 70 28 00 00 call 403ce0 <__ZN4test6myadd2Eiz>
401470: 89 45 f0 mov DWORD PTR [ebp-0x10],eax
401473: 8b 45 f4 mov eax,DWORD PTR [ebp-0xc]
401476: 89 04 24 mov DWORD PTR [esp],eax
401479: b9 .byte 0xb9
```
## Optimization最佳化
- `gcc --help=optimizers`看有哪些最佳化選項
`gcc -Q -O1 --help=optimizers`可以看在`O1`哪些optimizer是開啟或關閉,有`O0-O3`(`O0`表沒有最佳化)
- 常用
- 常數摺疊(constant folding): 把結果直接算出來
- ex. `a = 1*2*3*4;` $\Rightarrow$ `a = 24;`
- 常數傳播(constant propogate)
- ex.
```c
int x = 14;
int y = x - 5;
return x+y;
```
$\Rightarrow$ propogate
```c
int x = 14;
int y = 14 - 5;
return 14 + y ;
```
$\Rightarrow$ folding
```c
int x = 14;
int y = 9;
return 14 + y ;
```
$\Rightarrow$ propogate
```c
int x = 14;
int y = 9;
return 14 + 9 ;
```
$\Rightarrow$ folding
```c
int x = 14; //被拔掉
int y = 9; //被拔掉
return 23 ; //不會用到x跟y,所以optimization會拔掉x跟y的宣告
```
- Tail call: 返回值是call某function後的結果
Tail Recursion: 返回值為遞迴呼叫的結果
- ex.
```c
int search(int data){
return advance_search(data);
}
```
- 為了減少stack的使用
- ex.
```c=
#include <stdio.h>
int sum(int n) {
if (n == 0)
return 0;
else
return sum(n - 1) + n;
}
int main(){
printf('%d',sum(10));
return 0;
}
//gcc -o add add.c
//gcc -o add2 add.c -O2 //optimize
```
- add
```asm=
sum:
push rbp
mov rbp,rsp
sub rsp,0x10
mov DWORD PTR [rbp-0x4],edi
cmp DWORD PTR [rbp-0x4],0x0
jne 1165 <sum+0x1c>
mov eax,0x0
jmp 1177 <sum+0x2e>
mov eax,DWORD PTR [rbp-0x4]
sub eax,0x1
mov edi,eax
call 1149 <sum>
mov edx,DWORD PTR [rbp-0x4]
add eax,edx
leave
ret
```
- add2
```asm=
sum:
xor eax,eax
test edi,edi ;檢查edi是否=0
je 1197 <sum+0x17> ; =0跳到第9行
nop WORD PTR [rax+rax*1+0x0]
add eax,edi
sub edi,0x1 ; 若edi - 1 = 0會設定ZF
jne 1190 <sum+0x10> ; if ZF=0,跳第2行
ret
```
- magic trick : 減少除法(div)的使用
- 想法
1. 用乘法處理
2. 如果是2的冪次方更好,用shift的即可
- ex. 計算`x / 9`
$\frac{x}{9} = \frac{x}{2^n} \cdot \frac{2^n}{9}$
`n`可由編譯器決定,因此compile時就可以事先將$\frac{2^n}{9}$算好
(ex. `n`取`33`, `hex(ceil(2^33/9)) = 0x38e38e39`)
$\Rightarrow \frac{x}{9} = \frac{x \cdot 0x38e38e39}{2^{33}}$
$\rightarrow$ 即將$x\cdot0x38e38e39$向右移33bit = 右移1bit
- 如果是`idiv`還要多考慮sign的問題
- 因為c的除法是<font color='red'>向下取整</font>, i.e $-3/2 = -2$, $3/2 = 1$
- 除數為負
$\lfloor\frac{-3}{2}\rfloor + 1 = \lceil\frac{-3}{2}\rceil$
- 除數為正
$\lfloor\frac{3}{2}\rfloor + 0 = \lceil\frac{3}{2}\rceil$
- `0x38e38e39`必為整數,因此判斷乘數的正負即可
- 第31bit即sign bit
- sign bit = 0: 算數右移31bit得`0`
- `結果 = (x*0x38e38e39 >> 1) - 0`
- sign bit = 1: 算術右移31bit得`11...1 = -1`
- `結果 = (x*0x38e38e39 >> 1) - (-1)`
## Class and Struct
### struct內對齊: `min(datatype_size, pack)`
- ex 計算struct佔的記憶體空間
```c=
#include <stdio.h>
typedef struct {
unsigned char v1; //1byte
unsigned int v2; //4byte
unsigned short v3; //2byte
unsigned char v4[3]; //3byte
unsigned long long v5; //8byte
} myStrA;
void main() {
// 輸出struct大小
printf("Size of myStrA = %d\n", sizeof(myStrA));
// 建立方便辨識的測試資料
myStrA a;
a.v1 = 0x11;
a.v2 = 0x22334455;
a.v3 = 0x6677;
a.v4[0] = 0x12;
a.v4[1] = 0x34;
a.v4[2] = 0x56;
a.v5 = 0x8899AABBCCDDEEFF;
// 輸出 myStrA 內部實際的資料
unsigned char *ptr = (unsigned char *) &a;
int i, s = sizeof(myStrA);
printf("a = ");
for (i = 0; i < s; ++i) {
printf("%02X ", ptr[i]);
}
printf("\n");
}
/*
結果:
Size of myStrA = 24
a = 11 00 00 00 55 44 33 22 77 66 12 34 56 00 00 00 FF EE DD CC BB AA 99 88
|v1| | v2 | v3 | v4 | | v5 |
```
:::info
可以發現雖然v1只有1byte,但在記憶體上還是保留了4byte,
而short佔2byte卻保留了8byte
原因是因為preprocessor在處理時預設會以<font color='red'>該datatype長度的倍數</font>對其進行對齊
- v1為char,char以1byte的倍數對齊,此時offset = 0x0,放上v1後offset = 0x1
- v2為int,int以4byte的倍數對齊,因此compiler跳過3byte,讓offset = 0x4再放上v2,offset = 0x8
- v3為short,short以2byte得倍數對齊,8為2的倍數,因此直接放上v3,offset = 0xa
- v4為一個char array,char以1byte的倍數對齊,因此直接把三個element放上去,offset 0xd
- v5為long int,以8byte的倍數對齊,因此跳過3byte,讓offset為0x10再放上v5
:::
- 此外,可以用`#pragma pack()`指定對齊的長度(1,2,4,16,沒指定預設為8),compiler會<font color='blue'>拿指定的長度跟datatype預設的長度</font>做比較,並<font color='red'>取小的那個</font>
- ex. code前面加上`#pragma pack(2)`之後結果變為
```
Size of myStrA = 20
a = 11 00 55 44 33 22 77 66 12 34 56 00 FF EE DD CC BB AA 99 88
|v1| | v2 | v3 | v4 | | v5 |
```
:::danger
- v1放完後offset = 0x1,
v2為int,因為指定2byte對齊
故取`min(2,4) = 2`對齊,所以v1放完只跳過1byte
- v4放完後offset = 0xb
v5取`min(2,8) = 2`對齊,因此只跳過1byte
:::
### struct本身也要對齊: `min(max_size_member, pack)`
- ex
```c=
#include <stdio.h>
typedef struct {
unsigned char v1; //1byte
unsigned int v2; //4byte
unsigned short v3; //2byte
} myStrA
void main() {
// 輸出struct大小
printf("Size of myStrA = %d\n", sizeof(myStrA));
// 建立方便辨識的測試資料
myStrA a;
a.v1 = 0x11;
a.v2 = 0x22334455;
a.v3 = 0x6677;
// 輸出 myStrA 內部實際的資料
unsigned char *ptr = (unsigned char *) &a;
int i, s = sizeof(myStrA);
printf("a = ");
for (i = 0; i < s; ++i) {
printf("%02X ", ptr[i]);
}
printf("\n");
}
/*
結果
Size of myStrA = 12
a = 11 00 00 00 55 44 33 22 77 66 00 00
|v1| | v2 | v3 |
```
:::info
- struct內應該佔 1 + 3 + 4 + 2 = 10byte,卻佔了12byte
- 因為struct本身也要對齊,
取`min(max_size_member, pack) = min(sizeof(int), pack=8) = min(4,8) = 4`對齊,
因此把struct補到12byte
:::
### C++ name mangle
- 為了解決不能使用跟c library一樣函數名稱的問題,C++加入了namespace來解決
- C++的繼承後Override或Overloading(多載)可能造成多個函數共用一個函數名稱(差別在<font color='blue'>參數個數或型態不同</font>)
`Note: override要父class在該函數前面宣告為virtual(虛函式),繼承的class才可以改寫`
- 為了讓compiler可以區分多個不同的同名函式,必須要對產生的符號動一點手腳,這就是<font color='red'>Name Mangling</font>
```c=
#include <iostream>
using namespace std;
int func(int a){
return a;
}
float func(float b){
return b;
}
class C {
public:
int func(int c){
return c;
};
class C2 {
public:
int func(int d){
return d;
}
};
C2* do_something(){
C2 *c2 = new C2;
return c2;
}
};
namespace N {
int func(int e){
return e;
};
class C {
public:
int func(int f){
return f;
};
};
}
class C1:public C{ //C1繼承C
public:
float func(float g){ //override
return g;
}
};
int main(){
int a = func(1); //func(int)
float b = func(2.5f); //func(float)
//class C
C *c = new C;
int c_ = c->func(3); //C::func(int)
//class C::C2
C::C2 *d = c->do_something();
int d_ = d->func(4); //C::C2::func(int)
//namespace N
int e = N::func(5); //N::func(int)
N::C *f = new N::C;
//class N::C
int f_ = f->func(6); //N::C::func(int)
C1 *g = new C1;
float g_ = g->func(7.5f); //C1::func(float)
cout << a << endl;
cout << b << endl;
cout << c_ << endl;
cout << d_ << endl;
cout << e << endl;
cout << f_ << endl;
cout << g_ << endl;
return 0;
}
```
:::info
objdump出來之後,每個function的name變成
a. `func(int)` $\Rightarrow$ `_Z4funci`
b. `func(float)` $\Rightarrow$ `_Z4funcf`
c. `C::func(int)` $\Rightarrow$ `_ZN1C4funcEi`
d. `C::C2::func(int)` $\Rightarrow$ `_ZN1C2C24funcEi`
e. `N::func(int)` $\Rightarrow$ `_ZN1N4funcEi`
f. `N::C::func(int)` $\Rightarrow$ `_ZN1N1C4funcEi`
g. `C1::func(float)` $\Rightarrow$ `_ZN2C14funcEf`
<br>
- 可以用`c++filt <mangling_name>`來還原

:::
### C++ virtual table
- 當new一個class,會去找該class的constructor(或其父class的constructor)
- 會把this(struct的第一個member)設成一個function(該類別的第一個virtual function)
- ex
```clike=
#include <string>
#include <algorithm> //for std::reverse
#include <iostream>
using namespace std;
class human{
protected:
string nation;
public:
virtual string getNation(){
return this->nation;
}
virtual string ReverseNation(){
reverse(nation.begin(), nation.end());
}
};
class Taiwanese:public human{
public:
Taiwanese(){
nation = "Taiwan";
}
string getNation(){
return "You should be a Chinese!!";
}
};
int main(){
Taiwanese* MinYeon = new Taiwanese();
cout << *(string *)(((char*)MinYeon)+8) << endl; // origin nation
//MinYeon->nation is not work, since nation is protected
cout << MinYeon->getNation() << endl;
}
/*
output:
Taiwan
You should be a Chinese!!
```
開ida看之後
- `human`的constructor

```=
03CB0 ; `vtable for'Taiwanese
03CB0 _ZTV9Taiwanese dq 0 ; offset to this
03CB8 dq offset _ZTI9Taiwanese ; `typeinfo for'Taiwanese
03CC0 off_3CC0 dq offset _ZN9Taiwanese9getNationB5cxx11Ev
03CC0 ; DATA XREF: Taiwanese::Taiwanese(void)+1D↑o
03CC8 dq offset _ZN5human13ReverseNationB5cxx11Ev
03CD0 public _ZTV5human ; weak
03CD0 ; `vtable for'human
03CD0 _ZTV5human dq 0 ; offset to this
03CD8 dq offset _ZTI5human ; `typeinfo for'human
03CE0 off_3CE0 dq offset _ZN5human9getNationB5cxx11Ev
03CE0 ; DATA XREF: human::human(void)+10↑o
03CE0 ; human::~human()+10↑o
03CE8 dq offset _ZN5human13ReverseNationB5cxx11Ev
```
- 每個類別的struct最前面(this)都指向自己的vtable,vtable的每個entry分別指向該類別中的virtual function
- 一個繼承父類別的子類別,也會繼承父類別的vtable,如果有override某個函式,則對應的vtable entry會指向override的function(ex.第4行),否則指向父類別的function(ex.第6行)
- ex. `myDerived`繼承`myBase`,並override`func2`

:::warning
IDA tricks
`shift + F1`可以開啟Local types,可以右鍵edit修改struct member
:::
### C++ Polymorphism(多態/多型)