fallnight
資工二甲
113屆 儲備會長
特選仔
女婕思
C黑魔法是啥
小試身手 - 判斷奇偶
運算子的優先順序
for-loop 的各種寫法
區域 & 全域變數
Random (亂數)
C的安全問題
你平時寫程式不會注意的小細節
遇到特定的測資就會出錯
可能沒學過的酷酷的寫法
遇到沒寫完善的code就可以鑽它的漏洞做壞壞的事(x
寫個可以判斷輸入的整數是奇數還是偶數的程式吧
if(n % 2 == 1)
// 奇數
else
// 偶數if(n % 2 != 0)
// 奇數
else
// 偶數直接丟個負數給它試試吧 你會知道誰比較好的
#include<stdio.h>
int main()
{
int n;
printf("請輸入n: ");
scanf("%d",&n);
if(n % 2 != 0)
{
printf("%d是奇數\n",n);
}
else
{
printf("%d是偶數\n",n);
}
return 0;
}#include<stdio.h>
int main()
{
int n;
printf("請輸入n: ");
scanf("%d",&n);
if(n % 2 == 1)
{
printf("%d是奇數\n",n);
}
else
{
printf("%d是偶數\n",n);
}
return 0;
}#include<stdio.h>
int main()
{
printf(" 5 %% 2 = %d\n" , 5 % 2);
printf("-5 %% 2 = %d\n" , -5 % 2);
printf(" 5 %% -2 = %d\n" , 5 % -2);
return 0;
}#include<stdio.h>
int main()
{
printf(" 5 %% 2 = %d\n" , 5 % 2);
printf("-5 %% 2 = %d\n" , -5 % 2);
printf(" 5 %% -2 = %d\n" , 5 % -2);
return 0;
}a = (a / b) * b + a % b
=> (a % b) = a - (a / b) * b
整數型態的 a 等於 (a / b) * b + a % b
移項得知
a 除以 b 的餘數是 (a % b) = a - (a / b) * b
(a % b) = a - (a / b) * b
// a=-5, b=2
-5 % 2 = -5 - (-5 / 2) * 2
= -5 - (-2) * 2
= -5 - (-4)
= -5 + 4
= -1
// a= 5, b=-2
5 % -2 = 5 - (5 / -2) * (-2)
= 5 - (-2) * (-2)
= 5 - 4
= 1
if(n % 2 == 1)
// 奇數
else
// 偶數if(n % 2 != 0)
// 奇數
else
// 偶數if(n % 2 == 0)
// 偶數
else
// 奇數#include<stdio.h>
int main()
{
int n;
while(1)
{
printf("請輸入n: ");
scanf("%d",&n);
if(n % 2 == 0)
{
printf("%d是偶數\n",n);
}
else
{
printf("%d是奇數\n",n);
}
}
return 0;
}一樣先來個小試身手時間!
int a = -1, b = 1, c;
c = a+++b;
printf("a, b, c = %d, %d, %d\n", a, b, c);int a = -1, b = 1, c;
c = a+++b;
printf("a, b, c = %d, %d, %d\n", a, b, c);圖片來源: https://en.cppreference.com/w/c/language/operator_precedence
Operator Precedence
如果遇到同一種運算子就由左往右
int a = -1, b = 1, c;
c = a+++b;
printf("a, b, c = %d, %d, %d\n", a, b, c);c = a+++b
=> c = (a++)+b
=> c = -1 + 1 = 0
=> a = a+1 = -1 + 1 = 0
=> b = 1
=> a = 0, b=1, c=0
#include <stdio.h>
int main()
{
for(int i=1;0<i<10;i++){
printf("NISRA");
}
return 0;
}for(int i=1;0<i<10;i++){
printf("NISRA");
}0 < i < 10
=> (0 < i) < 10
因為 i 的初始值直接從大於 0 的 1 開始,所以 0 < i 永遠是True = 1
=> 1 < 10
所以迴圈的條件判斷永遠都是True,造成無限迴圈
for(int i=1;0<i && i<10;i++){
printf("NISRA");
}0 < i < 10
=> 0 < i && i < 10
//i = 10
=> True && False
=> False,迴圈結束
#include <stdio.h>
int main()
{
int a = 10, b = 20, c = 30;
if (c > b > a)
printf("True\n");
else
printf("False\n");
}#include <stdio.h>
int main()
{
int a = 10, b = 20, c = 30;
if (c > b > a)
printf("True\n");
else
printf("False\n");
}c > b > a
=> (c > b) > a
=> (30 > 20) > 10
=> (True) > 10 = 1 > 10
=> False
int a = 10, b = 20, c = 30;
if (c > b > a)
printf("True\n");
else
printf("False\n");c > b > a
=> c > b && b > a
=> 30 > 20 && 20 > 10
=> True && True
=> True
又一樣先來個小試身手時間!
#include <stdio.h>
int main()
{
for(int i = 9; i >= 0; i--){
printf("%d\n",i);
}
}1
3
2
就是一些沒什麼意義但看起來很厲害的寫法
#include <stdio.h>
int main()
{
for(int i = 10; 0 <= --i;){
printf("%d\n",i);
}
}#include <stdio.h>
int main()
{
for(int i = 10; 0 <= --i;){
printf("%d\n",i);
}
}1
3
2
#include <stdio.h>
int main()
{
for(int i = 10;i-->0;){
printf("%d\n",i);
}
}#include <stdio.h>
int main()
{
for(int i = 10;i-->0;){
printf("%d\n",i);
}
}1
2
#include <stdio.h>
int main()
{
for(int i = 10; 0 <= ~~ --i;){
printf("%d\n",i);
}
}#include <stdio.h>
int main()
{
for(int i = 10; 0 <= ~~ --i;){
printf("%d\n",i);
}
}1
2
#include <stdio.h>
int main()
{
for (int i = 10, j = 0; i > 0 && j < 10; i--, j++){
printf("%d %d\n",i,j);
}
}1
3
2
By 歷屆學長姐的留言
又又一樣先來個小試身手時間!
int x=0;
int getNum(){
int x=1214;
{
return x;
}
}int x=0;
int getNum(){
int x=1214;
{
return x;
}
}由下往上、由內到外,遇到的第一個
所以 x 會回傳1214
int x=0;
int getNum(){
int x=1214;
{
extern int x;
return x;
}
}int main(){
{
int x=0;
}
printf("%d\n",x);
}int main(){
{
int x=0;
}
printf("%d\n",x);
}跟 printf 相同的{ }內沒有 x 被宣告,所以出現錯誤
終於沒有小試身手了(x
產生亂數的函式
但亂數並不是隨機產生
而是由一個亂數種子,透過複雜的數學演算法算出很像亂數的數值
#include <stdio.h>
#include <stdlib.h> // C 的亂數標頭檔
/* #include <cstdlib>
如果是C++則是引入這個標頭檔 */
int main(){
int x = rand(); //產生亂數
printf("x = %d\n",x);
return 0;
}#include <stdio.h>
#include <stdlib.h>
int main(){
for(int i=0;i<10;i++){
printf("%d\n",rand());
}
}基於一個亂數種子,透過複雜的數學演算法算出很像亂數的數值
因為沒有特別去設定這次的亂數種子是什麼,所以執行結果都一樣
#include <stdio.h>
#include <stdlib.h>
int main(){
for(int i=0;i<10;i++){
printf("%d\n",rand());
}
}#include <stdio.h>
#include <stdlib.h>
#include <time.h> // C 的時間相關的標頭檔
/* #include <ctime>
如果是C++則是引入這個標頭檔 */
int main(){
srand(time(NULL)); //初始化時間種子
printf("%d\n",rand());
return 0;
}利用srand()函式設定
可以設定成以當下時間為種子去做亂數計算
交作業都來不及了誰還會想到安全問題
#include <stdio.h>
#include <string.h>
int main()
{
char input[10];
printf("Give me some input: ");
scanf("%s", input);
printf("%s\n", input);
return 0;
}#include <stdio.h>
#include <string.h>
int main()
{
char input[10];
printf("Give me some input: ");
scanf("%s", input);
printf("%s\n", input);
return 0;
}#include <stdio.h>
#include <string.h>
int main()
{
char input[10];
printf("Give me some input: ");
scanf("%9s", input);
printf("%s\n", input);
return 0;
}%s -> %9s
如果沒有限制,輸入過大的值會覆蓋到其他變數原本的值 -> Buffer Overflow
input字元陣列的宣告長度是10
但字串結尾要有結束符號 \0
所以是 9 個字元 + 1 個結束符號 = 10個陣列元素
| N | I | S | R | A | B | L | A | B | \0 |
|---|
input[10]
9
結尾
緩衝區溢位
輸入的內容超過預先留好的記憶體大小
圖片來源: https://www.cloudflare.com/zh-tw/learning/security/threats/buffer-overflow/
kernel space
stack
data
heap
code
簡易的記憶體佈局(Memory Layout)的示意圖
系統保留
區域變數
動態分配
程式全域變數
程式碼
高位址
低位址
kernel space
stack
data
heap
code
系統保留
區域變數
動態分配
程式全域變數
程式碼
高位址
低位址
kernel space
stack
data
heap
code
系統保留
區域變數
動態分配
程式全域變數
程式碼
高位址
低位址
kernel space
stack
data
heap
code
系統保留
區域變數
動態分配
程式全域變數
程式碼
動態分配的空間
C
malloc / free
C++
new / delete
高位址
低位址
kernel space
stack
data
heap
code
系統保留
區域變數
動態分配
程式全域變數
程式碼
高位址
低位址
kernel space
stack
data
heap
code
系統保留
區域變數
動態分配
程式全域變數
程式碼
高位址
低位址
kernel space
stack
data
heap
code
系統保留
區域變數
動態分配
程式全域變數
程式碼
#include <stdio.h>
int global = 87; // data
int main()
{
int a = 10; // stack
}高位址
低位址
暫存器
是一個臨時儲存區域
用於儲存指令、變數和變數的值、計算後的結果
x86是intel發明的指令集,我們的電腦處理器就會去執行x86的這些指令
EIP
Instruction Pointer
下一個執行的 instruction 之位址
ESP
Stack Pointer
儲存 Stack 頭位址
EBP
Base Pointer
儲存 Stack 基底(base)位址
kernel space
stack
data
heap
code
系統保留
區域變數
動態分配
程式全域變數
程式碼
EBP
ESP
EIP
#include<stdio.h>
int main()
{
char a[4];
scanf("%4s",&a);
printf("%s",a);
return 0;
}| index | value |
|---|---|
| EBP+0x8 | ... |
| EBP+0x4 | ... |
| EBP | ???? |
| EBP-0x4 | ???? |
| EBP-0x8 | ???? |
| EBP-0xC | ???? |
| EBP-0x10 | ???? |
EIP
#include<stdio.h>
int main()
{
char a[4];
scanf("%4s",&a);
printf("%s",a);
return 0;
}| index | value |
|---|---|
| EBP+0x8 | ... |
| EBP+0x4 | ... |
| EBP | EBP |
| EBP-0x4 | ???? |
| EBP-0x8 | ???? |
| EBP-0xC | ???? |
| EBP-0x10 | ???? |
EIP
#include<stdio.h>
int main()
{
char a[4];
scanf("%4s",&a);
printf("%s",a);
return 0;
}| index | value |
|---|---|
| EBP+0x8 | ... |
| EBP+0x4 | ... |
| EBP | EBP |
| EBP-0x4 | ???? |
| EBP-0x8 | ???? |
| EBP-0xC | ???? |
| EBP-0x10 | ???? |
EIP
a [ ]
#include<stdio.h>
int main()
{
char a[4];
scanf("%4s",&a);
printf("%s",a);
return 0;
}| index | value |
|---|---|
| EBP+0x8 | ... |
| EBP+0x4 | ... |
| EBP | EBP |
| EBP-0x4 | dcba |
| EBP-0x8 | ???? |
| EBP-0xC | ???? |
| EBP-0x10 | ???? |
EIP
a [ ]
#include<stdio.h>
int main()
{
char a[4];
scanf("%4s",&a);
printf("%s",a);
return 0;
}| index | value |
|---|---|
| EBP+0x8 | ... |
| EBP+0x4 | ... |
| EBP | EBP |
| EBP-0x4 | dcba |
| EBP-0x8 | ???? |
| EBP-0xC | ???? |
| EBP-0x10 | ???? |
EIP
#include <stdio.h>
#include <string.h>
int main()
{
char pwd[8] = "NISRA";
char input[8];
printf("Give me some input: ");
scanf("%s", input);
if (strcmp(pwd, "admin") == 0)
printf("Yes you pass it!\n\n");
else
printf("No, keep trying.\n\n");
return 0;
}在不改動程式碼的前提下
輸入 input
印出 "Yes you pass it!"
#include <stdio.h>
#include <string.h>
int main()
{
char pwd[8] = "NISRA";
char input[8];
printf("Give me some input: ");
scanf("%s", input);
if (strcmp(pwd, "admin") == 0)
printf("Yes you pass it!\n\n");
else
printf("No, keep trying.\n\n");
return 0;
}| index | value |
|---|---|
| EBP+0x8 | ... |
| EBP+0x4 | ... |
| EBP | ???? |
| EBP-0x4 | ???? |
| EBP-0x8 | ???? |
| EBP-0xC | ???? |
| EBP-0x10 | ???? |
EIP
#include <stdio.h>
#include <string.h>
int main()
{
char pwd[8] = "NISRA";
char input[8];
printf("Give me some input: ");
scanf("%s", input);
if (strcmp(pwd, "admin") == 0)
printf("Yes you pass it!\n\n");
else
printf("No, keep trying.\n\n");
return 0;
}| index | value |
|---|---|
| EBP+0x8 | ... |
| EBP+0x4 | ... |
| EBP | EBP |
| EBP-0x4 | ???? |
| EBP-0x8 | ???? |
| EBP-0xC | ???? |
| EBP-0x10 | ???? |
EIP
#include <stdio.h>
#include <string.h>
int main()
{
char pwd[8] = "NISRA";
char input[8];
printf("Give me some input: ");
scanf("%s", input);
if (strcmp(pwd, "admin") == 0)
printf("Yes you pass it!\n\n");
else
printf("No, keep trying.\n\n");
return 0;
}| index | value |
|---|---|
| EBP+0x8 | ... |
| EBP+0x4 | ... |
| EBP | EBP |
| EBP-0x4 | 0x00000041 |
| EBP-0x8 | 0x5253494E |
| EBP-0xC | ???? |
| EBP-0x10 | ???? |
EIP
#include <stdio.h>
#include <string.h>
int main()
{
char pwd[8] = "NISRA";
char input[8];
printf("Give me some input: ");
scanf("%s", input);
if (strcmp(pwd, "admin") == 0)
printf("Yes you pass it!\n\n");
else
printf("No, keep trying.\n\n");
return 0;
}| index | value |
|---|---|
| EBP+0x8 | ... |
| EBP+0x4 | ... |
| EBP | EBP |
| EBP-0x4 | \x00\x00\x00A |
| EBP-0x8 | RSIN |
| EBP-0xC | ???? |
| EBP-0x10 | ???? |
EIP
位元組存放順序(byte ordering)
資料在記憶體中放的順序
e.g. 0x12345678
little-endian \x78 \x56 \x34 \x12
big-endian \x12 \x34 \x56 \x78
低位址
高位址
LSB
MSB
x86 是 little-endian
一個字元等於 1 byte
1 byte = 8 bits
Register 是 32-bit = 4 byte
NISRA 存在EBP內,所以一行只能容納 4 個字元
A
R S I N
低位址
高位址
#include <stdio.h>
#include <string.h>
int main()
{
char pwd[8] = "NISRA";
char input[8];
printf("Give me some input: ");
scanf("%s", input);
if (strcmp(pwd, "admin") == 0)
printf("Yes you pass it!\n\n");
else
printf("No, keep trying.\n\n");
return 0;
}| index | value |
|---|---|
| EBP+0x8 | ... |
| EBP+0x4 | ... |
| EBP | EBP |
| EBP-0x4 | \x00\x00\x00A |
| EBP-0x8 | RSIN |
| EBP-0xC | ???? |
| EBP-0x10 | ???? |
EIP
#include <stdio.h>
#include <string.h>
int main()
{
char pwd[8] = "NISRA";
char input[8];
printf("Give me some input: ");
scanf("%s", input);
if (strcmp(pwd, "admin") == 0)
printf("Yes you pass it!\n\n");
else
printf("No, keep trying.\n\n");
return 0;
}| index | value |
|---|---|
| EBP+0x8 | ... |
| EBP+0x4 | ... |
| EBP | EBP |
| EBP-0x4 | \x00\x00\x00n |
| EBP-0x8 | imda |
| EBP-0xC | bbbb |
| EBP-0x10 | aaaa |
EIP
覆蓋到 pwd[]
#include <stdio.h>
#include <string.h>
int main()
{
char pwd[8] = "NISRA";
char input[8];
printf("Give me some input: ");
scanf("%s", input);
if (strcmp(pwd, "admin") == 0)
printf("Yes you pass it!\n\n");
else
printf("No, keep trying.\n\n");
return 0;
}| index | value |
|---|---|
| EBP+0x8 | ... |
| EBP+0x4 | ... |
| EBP | EBP |
| EBP-0x4 | \x00\x00\x00n |
| EBP-0x8 | imda |
| EBP-0xC | bbbb |
| EBP-0x10 | aaaa |
EIP
input [ ]
pwd [ ]
https://blog.gtwang.org/programming/c-cpp-rand-random-number-generation-tutorial-examples/#google_vignette
https://hackmd.io/@Ben1102/B1gfGLT3u
https://ithelp.ithome.com.tw/articles/10188599
https://hackmd.io/@mushding/assembly_language
https://www.eagletek.com.tw/post/register-in-cpu?srsltid=AfmBOooF2MVABCyP1Rfk334LJCFAkoybJTZpVatjJceDhXFcchuolNQb
https://www.ithome.com.tw/tech/56880