0 → Linked List
Arvin Liu
Signartures
/ Behaviors
ML
MODEL
Predicted Scores
Benign / Malicious
Learn
Lookup hash
Sig A: Severity 5
Sig D: Severity 7
Maliciousness
Category
XGB LGBM
Behavior A: 11 times
我們會介紹...
前言
- 指標
- 參考
- struct / 基礎class
- Linked LIst
指標
Pointer
指標對初心者來說
非常不好理解
有問題要盡早問喔!
前言
變數的儲存法
Saving Variables
int a = 5;
這短短的一行到底做了什麼?
變數的儲存法
變數的儲存法
它在哪裡? | 它是什麼? | 它放了什麼? |
---|---|---|
第0個櫃子 (0x00) | 放學校的書的櫃子 | 國文課本。 |
第1個櫃子 (0x01) | 放輕小說的櫃子 | 涼宮春日的憂鬱。 |
第2個櫃子 (0x02) | 放本本的櫃子 | ????? |
編號 / 地址。
變數名稱。
具體來說是什麼。
(通常用十六進位, 0x 表示)
你可以想像程式的記憶體 (RAM)
有這樣的一個櫃子,並且附有編號
每個櫃子都會有三個屬性:
- 他是第幾號櫃子?
- 這個櫃子放哪種型態的東西?
- 裡面放了什麼東西?(只能一個)
編號 0
編號 1
編號 2
它在哪裡? | 它是什麼? | 它放了什麼? |
---|---|---|
0x00000 |
int x | 7 |
0x00001 | 空的。 | 沒放東西。 |
0x00002 | 空的。 | 沒放東西。 |
-
找一個沒人用的櫃子。
-
讓別人知道 a 在哪裡。
-
把值放進去。
int a
5
變數的儲存法
int a = 5;
的流程
變數的儲存法
int main() {
int a = 5;
}
編譯 (Compile)
機器語言
用工具解析
組合語言
在 ebp - 4 的
位置上存放 5
movl 的指令:
movl src, dst
將 src 這個數值存入
dst 這個位置。
數值大小為 long (4 bytes)
這裡我使用 objdump
取址「&」和取值「*」
Address of "&" and dereference "*"
& 取址運算子 (拿地址)
* 取值運算子 (拿數值)
取址「&」和取值「*」
這裡說的運算子是什麼?
就相當於你說 -1 的 "-" 一樣,
是一種運算符號
它在哪裡? | 它是什麼? | 它放了什麼? |
---|---|---|
0x00000 | int x | 7 |
0x00004 | float a | 1.1 |
0x00008 | double b | 1.0 |
那麼 &x 就是 0x00000。
&
*
但... &x 的型態是什麼?
不好表示,所以用特殊型態表示 int的地址: int*
所以 & 可以把 int 變成 int* , * 可以把 int* 變回int
所以 & 可以把 float 變成 float* , * 可以把 float* 變回float
取址「&」和取值「*」
那麼你覺得 *(0x00000) 會是什麼?
它在哪裡? | 它是什麼? | 它放了什麼? |
---|---|---|
0x00000 | int x | 7 |
0x00004 | float a | 1.1 |
0x00008 | double b | 1.0 |
&
*
取址「&」和取值「*」
會編譯失敗。 因為0x00000是 int,不是 int*
- *(int *)(0x00000)
- *(float *)(0x00004)
- &*(float *)(0x00004)
- 7
- 1.1
- 0x00004
int* 的 「*」和
取值的 「*」
不要搞混!!
一個是型態的一部分 (拿來當宣告用的),
一個是取值符號(拿來使用,當運算的)。
取址「&」和取值「*」
#include <iostream>
int main(){
int x=777;
std::cout << &x << std::endl;
//std::cout << *(int *)(0x7fffffffdc54) << std::endl;
std::cout << *&x << std::endl;
std::cout << &*&x << std::endl;
std::cout << *&*&x << std::endl;
//std::cout << *(0x7fffffffdc54) << std::endl; // CE
//std::cout << *x << std::endl; // CE
//std::cout << **&*&*&x << std::endl; // CE
}
/* Output -- Answers are here.
0x7fffffffdc54
777
777
0x7fffffffdc54
777
*/
取址「&」和取值「*」- 練習題
Practice Time!
除了練習上面的東西,觀察你變數的地址以外,
試試看 *(int *) 一個隨便給的數字會怎麼樣?
小小提醒:
你們是猜不出0x7fffffffdc54這種東西的,因為每次都不一樣。
所以你們直接註解那行掉就可以囉!
* 小知識:現今電腦都有ASLR(位址空間組態隨機載入)保護機制,所以每一次執行的&x都不一樣。除非你把它關掉,你才可以預先知道&x是什麼。這邊看不懂沒關係,想了解可以自行google看看。
變數地址的變數的宣告
也就是 - 指標宣告
What is Pointer?
指標就是一個變數。
它存著別人的地址。
如果你要存int的地址,
那麼它型態就是 int * 。
Pointer Init
它在哪裡? | 它是什麼? | 它放了什麼? |
---|---|---|
0x00000 | int x | 7 |
0x00008 | (int *) a | 0x00000 |
0x00010 | (int **) b | 0x00008 |
int x = 7;
int *a = &x;
int **b = &a;
std::cout << *a << std::endl;
*a = 5;
std::cout << **b << std::endl;
5
**b = x , *b =a , *a = x
宣告指標的一些坑
int* a, b;
// 上下兩個等價
int *a;
int b;
// 所以宣告兩個指標要
int *a, *b;
小小總結
-
宣告時,在變數前加上一個*,即代表此變
數為一個指標 - 宣告時, *不要跟著型態,會搞混。(int *a,*b;)
-
使用時,在一個變數前加上一個&,其值即
為此變數的位址 -
使用時,在一個指標前加上一個*,其值即
為位在此位址之變數本身
題外話:sizeof
sizeof : 知道一個型態的大小是多少Bytes
1 Byte = 8 bits
1 bit 就是一個 0或1。
所以一個byte 就是 00000000 ~ 11111111
通常bit都會八個一組,我們稱之為byte。
各個型態多少byte(s)?
#include <iostream>
int is_admin = 0;
int ary[160];
int main(){
std::cout << sizeof(char) << std::endl;
std::cout << sizeof(int) << std::endl;
std::cout << sizeof(long long) << std::endl;
std::cout << sizeof(float) << std::endl;
std::cout << sizeof(int *) << std::endl;
std::cout << sizeof(double *) << std::endl;
}
// 1 4 8 4 8 8
Practice: 算兩個地址差多少!
#include <iostream>
int a;
int main(){
char c;
// 想辦法算出 &a - &c
}
hint : 我們知道指標的大小是 8 bytes,和longlong一樣。
answer : (long long) &a - (long long) &c
記憶體區段錯誤?
Segmentation Fault (會吃RE)
亂亂用 *
它在哪裡? | 它是什麼? | 它放了什麼? |
---|---|---|
0x00000 | int x | 7 |
0x00004 | 系統的祕密。 | 不給看&寫>< |
0x00008 | 還沒規劃的地方。 | ??????? |
*(int *)0x00004 或 *(int *)0x00008 因為這兩塊還沒規劃或者電腦不給你看,所以當你在*的時候就會出現記憶體區段錯誤。
(例如程式已停止回應。)
亂亂用 *
所以只要你 * 的位置是系統開給你的,例如宣告變數/陣列,那麼通常就不會出現RE。
你真的懂陣列嘛? part 1
陣列宣告
int a = 5;
它在哪裡? | 它是什麼? | 它放了什麼? |
---|---|---|
0x00000 | 空的。 | 沒放東西。 |
0x00004 | 空的。 | 沒放東西。 |
0x00008 | int a | 5 |
之後程式自己的記憶體會長這樣子:
int a[4] = {0};
那這樣子呢?
陣列宣告
它在哪裡? | 它是什麼? | 它放了什麼? |
---|---|---|
0xFFF00 | a[0] | 0 |
0xFFF04 | a[1] | 0 |
0xFFF08 | a[2] | 0 |
0xFFF0A | a[3] | 0 |
... |
大概會像這樣子:
int a[4] = {0};
編譯器會讓 a 和 &a 都會等於 0xFFF00, 但 *a 是 a[0]。
陣列取值
int a[4] = {0};
a[1] = 1;
追根究底,a[1]是什麼意思?
其實就是 *(a + 1) 的意思。(a 是類似int *,詳情請見上一頁)
那,a + 1 是什麼意思呢?
就是a這個指標的地址 + 1個int的大小。
根據上面的原則。寫a[1]或寫1[a] 都沒有關係喔!
因為 1 + a = a + 1。
一個型態的大小可以用sizeof(type)來看。例如sizeof(int)就會是4,表示4Bytes。
你真的懂陣列嘛? part 2
為什麼陣列開太小會RE啊?
(執行時期錯誤)
例如我們宣告 int ary[160];
但是題目的N可能會到10000,
因此你就會存取到ary[1600]。
我們已經知道,
亂亂存取很容易吃RE。
它在哪裡? | 它是什麼? | 它放了什麼? |
---|---|---|
0x00000 | int x | 7 |
0x00004 | 系統的祕密。 | 不給看&寫>< |
0x00008 | 還沒規劃的地方。 | ??????? |
也就是你戳到0x00004/0x00008的時候,電腦就會因為安全因素讓你RE。
開int ary[160] -> 看ary[1600]
它在哪裡? | 它是什麼? | 它放了什麼? |
---|---|---|
0xF0000 | ary[0]在這裡。 | 0 |
0xF0004 | ary[1]在這裡。 | 0 |
.... | ... | ... |
0xF003C | ary[159]在這裡。 | 0 |
0xF0040 | 還沒用到 | ??? |
... | ... | ... |
0xF0400 | 系統的祕密>///< | 不給尼看>///< |
因為程式沒幫你開到0xF0400,
只要那邊有系統的祕密/還沒規劃,看了就會吃RE!
小小總結
- 因為陣列大小吃RE的原因就是你存取了一個系統沒預料到的地址。
- 陣列的記憶體空間是連續的。
#include <iostream>
int ary[160];
int is_admin = 0;
int main(){
int i,x;
std::cin >> i >> x;
ary[i] = x;
if(is_admin){
std::cout << "How did you do that?\n";
}
}
你要輸入什麼才可以讓程式輸出
"How did you do that" 呢?
Practice 0x01 - Global Flag
#include <iostream>
int is_admin = 0;
int ary[160];
int main(){
int i,x;
std::cin >> i >> x;
ary[i] = x;
if(is_admin){
std::cout << "How did you do that?\n";
}
}
你要輸入什麼才可以讓程式輸出
"How did you do that" 呢?
Practice 0x02 - Global Flag
Practice 0x03 - Struct Corrupt
#include <iostream>
#include <cstring>
struct person{
char name[16];
int is_admin;
};
int main(){
char tmp[16];
struct person peipei;
peipei.is_admin = 0;
std::cin >> tmp ;
strcpy(peipei.name,tmp);
std::cout << "Hi! " << tmp << std::endl;
if(peipei.is_admin){
std::cout << "Wow! Peipei is so Dian!" << std::endl;
}
}
你要輸入什麼才可以讓程式輸出
"Wow! Peipei is so Dian!" 呢?
剛剛的Practice 0x03是一種典型的buffer overflow (BOF)攻擊喔!
為什麼可以征服宇宙?
如果你有程式碼就可以任意改值!無限血量無限金幣...
Challenge 1 - long long strike
#include <iostream>
long long ary[160];
int is_user=0;
int is_admin=0;
int main(){
long long i,x;
std::cin >> i >> x;
ary[i] = x;
if(is_admin == 1){
std::cout << "Why Peipei is so dian?\n";
}
}
你要輸入什麼才可以讓程式輸出
"Why Peipei is so dian?" 呢?
0x00000001????????
例如 0x00000100000000 =
4294967296 (輸入4594967296即可。)
int 和 long long
怎麼存的?
表示成二進位制後逆著寫。
詳情請查詢 little-endian
(big-endian 就是順著寫)
0x00000001????????
longlong 存成
????????10000000
is_user 存 ????????
is_admin 存 10000000
-> is admin 是 00000001 = 1 (逆著)
Function Call & Ptr
Call by Value
void f(int a){
a = 5;
}
int main(){
int a = 10;
f(a);
cout << a << endl;
}
- Call by Value是函數呼叫時,參數是用複製的。
- 因為第二行的a=5是改複製體。所以不會改到main的a。
- 跟指標沒有關係的幾乎都是Call by Value。
Call by Address
void f(int *a){
*a = 5;
}
int main(){
int a = 10;
f(&a);
cout << a << endl;
}
- Call by Address是函數呼叫時,參數用它的指標。
- 因為給的是a的指標,所以f內直接做取值就會改到真正的a。所以輸出就會變5。
- 注意因為給的是地址,接下來用這個參數都要 *
- 其實也是一種call by value,因為address也是複製的。
Call by Address (Ary)
//void f(int a[]){
void f(int *a){
a[1] = 5;
}
int main(){
int a[10]={0};
f(a);
std::cout << a[1] << std::endl;
}
- 我們知道陣列的變數給的是指標。
- 也就是int ary[10]的ary是個指標。
- 那麼,其實它就是一種call by addr。
- 所以a[1]就會是5,會被改到。
- 宣告用int a[]還是int *a都一樣。
Call by Reference
void f(int &a){
a = 5;
}
int main(){
int a = 10;
f(a);
cout << a << endl;
}
- Call by Reference是函數宣告時在變數前面加個&,告訴程式不要複製。(不要Call by Value)。
- 因此這樣一來真正的a就會被直接改到。
Example - Swap
Swap - Call by Value
void f(int a, int b){
int tmp = a ;
a = b;
b = tmp;
}
int main(){
int a = 10, b = 5;
f(a,b);
std::cout << a << "," << b << std::endl;
}
- 行不通,因為f改的都是複製體。
Swap - Call by Address
void f(int *a, int *b){
int tmp = *a ;
*a = *b;
*b = tmp;
}
int main(){
int a = 10, b = 5;
f(&a,&b);
std::cout << a << "," << b << std::endl;
}
- 注意Call by Address給地址,所以第一行要用int *
- 因為只有地址,接下來(2~5行)都要用 * 取值。
- 使用函數的時候,當然就是要傳地址。所以第八行要 &。
Swap - Call by Reference
void f(int &a, int &b){
int tmp = a ;
a = b;
b = tmp;
}
int main(){
int a = 10, b = 5;
f(a,b);
std::cout << a << "," << b << std::endl;
}
- 注意Call by Reference是宣告的時候要給 &。
- 其他照舊。
小小總結
- 如果你只是要改到值,就用call by reference就好。
懶的用的話,放全域變數就好。
- 如果涉及太多指標操作,再用call by address。
- 例如下週的linked list。
- 如果不需要改值,就安心使用call by value吧!看起來會比較簡潔。
指標
By Arvin Liu
指標
從零到鏈結串列 Linked List
- 1,407