C黑魔法

$whoami

  • Du  
  • 輔大資工三乙
  • 輔大資訊安全研究會(NISRA)111屆副會長
  • AIS3 2023 學員
  • 行政院國家資通安全會 112 年網路攻防演練攻擊手資格

Agenda

  • C 的黑魔法
  • C 的安全問題

Warning

C 的黑魔法

判斷奇偶

Lab 0x0

寫一個判斷奇偶數的程式

左邊還是右邊

if(n % 2 == 1)
// 奇數
else
// 偶數
if(n % 2 != 0)
// 奇數
else
// 偶數

測試 -5 這個測資

左邊還是右邊

if(n % 2 == 1)
// 奇數
else
// 偶數
if(n % 2 != 0)
// 奇數
else
// 偶數

-5 是偶數

-5 是奇數

為什麼

試試看編譯執行下面的程式碼

#include <stdio.h>

int main() {
    printf("\n");
    printf("-5 %% 2 = %d\n" , -5 % 2);
    printf(" 5 %% -2 = %d\n" , 5 % -2);

    return 0;
}

結果.....

為什麼???

你真的懂 % (取餘數) 嗎 ?

C99 define: a / b

C99 define: a / b

a = (a / b) * 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
// 偶數

n = -5
n % 2 = -1
-5 是偶數

n = -5
n % 2 = -1
-5 是奇數

C 的黑魔法

運算子優先順序

輸出結果是 ???

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);

為什麼 ???

Operator Precedence

  • 運算子有優先順序

看回來原本的問題

int a = -1, b = 1, c;
c = a+++b;
printf("a, b, c = %d, %d, %d\n", a, b, c);

執行的結果是 ??? 好像哪裡怪怪的

#include <stdio.h>
int main()
{
    for(int i=1;0<i<10;i++){
    	printf("NISRA");
    }
    return 0;
}

結果 - 無窮迴圈 

為什麼 ???

Operator Precedence

  • 運算子有優先順序
    • 遇到同優先級運算子時看Associativity

回來看看

#include <stdio.h>
int main()
{
    for(int i=1;0<i<10;i++){
    	printf("NISRA");
    }
    return 0;
}

第一次迴圈:i=1 → (0<i)=1 → 1<10

第二次迴圈:i=2 → (0<i)=1 → 1<10

.......

舉個

#include <stdio.h>
int main()
{
    int a = 10, b = 20, c = 30;
    if (c > b > a)
        printf("True\n");
    else
        printf("False\n");
}

你覺得會輸出什麼

False ! ! !

c > b > a
          (c > b) > a   
          (30>20)>a            1 > a

               1 > 30           0

C 的黑魔法

for-loop各種寫法

先來個簡單的lab

使用for-loop印出右圖

#include <stdio.h>
int main()
{
    for(int i = 9; i >= 0; i--){
        printf("%d\n",i);
    }
}

1

2

4

3

還能怎麼寫???

#include <stdio.h>
int main()
{
    for(int i = 10; 0 <= --i;){
        printf("%d\n",i);
    }
}

#include <stdio.h>
int main()
{
    for(int i = 10;i-->0;){
        printf("%d\n",i);
    }
}

#include <stdio.h>
int main()
{
    for(int i = 10; 0 <= ~~ --i;){
        printf("%d\n",i);
    }
}

再來一個簡單的lab

再來個簡單的lab

使用for-loop印出右圖

再來個簡單的lab

使用for-loop印出右圖

#include <stdio.h>
int main()
{
    for (int i = 10, j = 0; i > 0 && j < 10; i--, j++){
        printf("%d %d\n",i,j);
    }
}

小結論

珍惜生命,不要亂寫噁心別人

C 的黑魔法

Scope 

return 的值是???

int x=0;
int getNum(){
    int x=1214;
    {
    	return x;
    }
}

變數的可視範圍

○ 由下而上,由內而外,遇到的第一個

int x=0;
int getNum(){
    int x=1214;
    {
    	return x;
    }
}

如果想要讀取全域變數 ???

int x=0;
int getNum(){
	int x=1214;
    {
    	extern int x;
    	return x;
    }
}

如果想要讀取全域變數

○ 使用 extern

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);
}

C 的黑魔法

Random

輸入 key 印出 You got it

#include <stdio.h>
int main()
{
    unsigned int random, key = 0;
    random = rand();
    printf("Give secret number: ");
    scanf("%d", &key);
    if( (key ^ random) == 0xdeadbeef ){
    printf("You got it!!\n");
    return 0;
    }
    printf("No, keep trying.\n");
    return 0;
}

輸入 key 印出 You got it

#include <stdio.h>
int main()
{
    unsigned int random, key = 0;
    random = rand();
    printf("Give secret number: ");
    scanf("%d", &key);

    if( (key ^ random) == 0xdeadbeef ){
    printf("You got it!!\n");
    return 0;
    }
    printf("No, keep trying.\n");
    return 0;
}

提示:

A ^ B = C
A = B ^ C

Random真的有亂數嗎

執行幾遍看看

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

不管執行幾次結果都是.....

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

如果真的想做到很亂的亂數的效果


○ 加上srand(time(NULL)); 初始化

#include <stdio.h>
int main()
{
    unsigned int random, key = 0;
    srand(time(NULL));
    random = rand();
    printf("Give secret number: ");
    scanf("%d", &key);
    if( (key ^ random) == 0xdeadbeef ){
    printf("You got it!!\n");
    return 0;
    }
    printf("No, keep trying.\n");
}

key ^ random = 0xdeadbeef;
key = random ^ 0xdeadbeef;

#include <stdio.h>
int main()
{
    unsigned int random, key = 0;
    random = rand();
    printf("key= %d\n",41^ 0xdeadbeef);
    printf("Give secret number: ");
    scanf("%d", &key);
    if( (key ^ random) == 0xdeadbeef ){
    printf("You got it!!\n");
    return 0;
    }
    printf("No, keep trying.\n");
    return 0;
}

再回來看剛剛的lab

key ^ random = 0xdeadbeef;
key = random ^ 0xdeadbeef;

#include <stdio.h>
int main()
{
    unsigned int random, key = 0;
    random = rand();
    printf("key= %d\n",random^ 0xdeadbeef);
    printf("Give secret number: ");
    scanf("%d", &key);
    if( (key ^ random) == 0xdeadbeef ){
    printf("You got it!!\n");
    return 0;
    }
    printf("No, keep trying.\n");
    return 0;
}

另一個解法

C 的黑魔法

          宗教戰爭

while(1){
/* Do something */
}
for(i = 0 ; i < 10 ; i++){
/* Do something */
}

while(1)
{
/* Do something */
}
for(i = 0 ; i < 10 ; i++)
{
/* Do something */
}

左邊還是右邊

C 的安全問題

 

寫扣得交作業都來不及了誰還會想到安全問題

Lab Time

在不改動程式碼的前提

輸入 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;
}

我沒有要講Lab怎麼解

先來看看下一頁的程式碼有什麼問題

有什麼問題

#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;
}

沒有限制輸入長度

Buffer Overflow

  • 緩衝區溢位
    • 輸入超過 buffer 的資料
  • 可能造成
    • 破壞程式執行
    • 執行期間竄改程式
    • 取得系統控制權
#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;
}

為什麼是9

#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;
}

Memory Layout

系統保留

區域變數

動態分配

程式全域變數

程式碼

高位址

低位址

Memory Layout

系統保留

區域變數

動態分配

程式全域變數

程式碼

  • 程式碼區段(codesection)
  • 又稱為 text section

Memory Layout

系統保留

區域變數

動態分配

程式全域變數

程式碼

  • 存放著程式的全域變數
  • 已初始化 / 未初始化

Memory Layout

系統保留

區域變數

動態分配

程式全域變數

程式碼

  • 動態分配的空間
    •  C
      • malloc / free
    •  C++
      •  new / delete

Memory Layout

系統保留

區域變數

動態分配

程式全域變數

程式碼

  • 區域變數

Memory Layout

系統保留

區域變數

動態分配

程式全域變數

程式碼

  • 系統保留的空間

Memory Layout

系統保留

區域變數

動態分配

程式全域變數

程式碼

#include <stdio.h>

int global = 87;  // data
int main()
{
    int a = 10;  // stack
}

Registers of x86

  • EIP
    • Instruction Pointer
    • 下一個執行的 instruction 之位址
  • ESP
    • Stack Pointer: 儲存 Stack 頭位址
  • EBP
    • Base Pointer: 儲存 Stack 基底(base)位址

Registers of x86

EBP

ESP

EIP

高位址

低位址

回來看一開始的Lab

#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 0x 00000041
EBP-0x8 0x 5253494E
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

#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

為什麼反過來

Endian

  • 位元組存放順序(byte ordering)
  • 資料在記憶體中放的順序
  • 最常見的有兩種,分別是 Big-Endian 與 Little-Endian

Endian

  • MSB / LSB
    • most significant bit / least significant bit
    • 最左側 / 最右側的位元組
  • Big endian
    • MSB 存在最低的位址
  • Little endian
    • LSB 存在最低的位址

Endian

e.g. 0x12345678

  • little-endian                      \x78 \x56 \x34 \x12
  • big-endian                         \x12 \x34 \x56 \x78

低位址

高位址

#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\x00A
EBP-0x8 RSIN
EBP-0xC bbbb
 EBP-0x10 aaaa

EIP

如果輸入aaaabbbb

#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

如果輸入aaaabbbbadmin

覆蓋到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

如果輸入aaaabbbbadmin

pwd[]

input[]

C黑魔法

By zonghao

C黑魔法

  • 100