#include<stdio.h>#include<stdlib.h>#include<stdint.h>#include<string.h>#include<ctype.h>#include<limits.h>staticvolatileunsignedcharsink;staticunsignedchar*read_input(intargc,char**argv,size_t*out_size){FILE*fp=NULL;if(argc>=2){fp=fopen(argv[1],"rb");if(!fp){perror("fopen");exit(1);}}else{fp=stdin;}size_tcap=1024;size_tlen=0;unsignedchar*buf=malloc(cap);if(!buf){perror("malloc");exit(1);}while(1){if(len==cap){cap*=2;unsignedchar*tmp=realloc(buf,cap);if(!tmp){free(buf);perror("realloc");exit(1);}buf=tmp;}size_tn=fread(buf+len,1,cap-len,fp);len+=n;if(n==0){break;}}if(fp!=stdin){fclose(fp);}unsignedchar*exact=malloc(len==0?1:len);if(!exact){free(buf);perror("malloc exact");exit(1);}if(len>0){memcpy(exact,buf,len);}free(buf);*out_size=len;returnexact;}/*
* Bug #1: OOB Read
*/staticvoidbug_oob_read_escape(constunsignedchar*data,size_tsize){for(size_ti=0;i<size;i++){if(data[i]=='\\'){sink=data[i+1];// OOB Read if the last byte of input is backslash '\\'
}}}/*
* Bug #2: Stack Buffer Overflow
*/staticvoidbug_stack_overflow_tokenizer(constunsignedchar*data,size_tsize){chartoken[16];size_tj=0;for(size_ti=0;i<size;i++){unsignedcharc=data[i];if(c==' '||c=='\t'||c=='\n'||c==':'||c==';'){if(j>0){sink=token[0];}j=0;continue;}token[j++]=(char)c;// Don't check length of token
}if(j>0){sink=token[0];}}/*
* Bug #3: Heap buffer overflow
*/staticvoidbug_heap_overflow_length_field(constunsignedchar*data,size_tsize){intdeclared_len=0;size_ti=0;while(i<size&&isdigit(data[i])){declared_len=declared_len*10+(data[i]-'0');i++;}if(i>=size||data[i]!=':'){return;}i++;if(declared_len<=0||declared_len>1024){return;}size_tpayload_len=size-i;char*buf=malloc((size_t)declared_len);if(!buf){return;}/*
* allocated size = declared_len
* copy size = payload_len
*/memcpy(buf,data+i,payload_len);// heap-buffer-overflow
sink=buf[0];free(buf);}/*
* Bug #4: Use-after-free
*/staticvoidbug_use_after_free_state_machine(constunsignedchar*data,size_tsize){char*state=malloc(16);if(!state){return;}memset(state,0,16);for(size_ti=0;i<size;i++){if(data[i]=='#'){free(state);/*
* Not set state = NULL.
*/continue;}if(isalnum(data[i])){state[0]=(char)data[i];}}intsaw_hash=0;for(size_ti=0;i<size;i++){if(data[i]=='#'){saw_hash=1;break;}}if(!saw_hash){free(state);}}/*
* Bug #5: Double free
*/staticvoidbug_double_free_reset(constunsignedchar*data,size_tsize){char*buf=malloc(32);if(!buf){return;}intfreed=0;for(size_ti=0;i<size;i++){if(data[i]=='#'){free(buf);// 1
if(freed){free(buf);// 2
}freed=1;}}if(!freed){free(buf);}}/*
* Bug #6: Null pointer dereference
*/staticvoidbug_null_deref_bad_bracket_state(constunsignedchar*data,size_tsize){charlocal[8];char*current=local;intdepth=0;for(size_ti=0;i<size;i++){if(data[i]=='['){depth++;}elseif(data[i]==']'){depth--;if(depth<0){current=NULL;}}if(current){current[0]=(char)data[i];}else{current[0]='X';// null pointer dereference
}}sink=local[0];}intmain(intargc,char**argv){size_tsize=0;unsignedchar*data=read_input(argc,argv,&size);if(size==0){free(data);return0;}bug_oob_read_escape(data,size);bug_stack_overflow_tokenizer(data,size);bug_heap_overflow_length_field(data,size);bug_double_free_reset(data,size);bug_use_after_free_state_machine(data,size);bug_null_deref_bad_bracket_state(data,size);free(data);return0;}
Các nhóm bug chính trong source:
#
Function
Bug
1
bug_oob_read_escape()
Out-of-bounds read
2
bug_stack_overflow_tokenizer()
Stack buffer overflow
3
bug_heap_overflow_length_field()
Heap buffer overflow
4
bug_use_after_free_state_machine()
Use-after-free
5
bug_double_free_reset()
Double free
6
bug_null_deref_bad_bracket_state()
Null pointer dereference
0x02 Build binary
1. Generating BBtargets
DGF cần biết trước các vị trí đích trong chương trình nhằm tập trung khai thác. Các vị trí này được mô tả trong file BBtargets.txt, mỗi dòng có định dạng:
1
<source-file>:<line-number>
Trong demo này, các target được chọn là các dòng chứa thao tác lỗi bộ nhớ trong src.c, bao gồm:
Bug
Line
Out-of-bounds read
src.c:79
Stack buffer overflow
src.c:102
Heap buffer overflow
src.c:143
Use-after-free
src.c:170
Double free
src.c:203
Null pointer dereference
src.c:237
File BBtargets.txt có thể được tạo tự động bằng cách tìm các pattern tương ứng trong source code:
Ở quá trình này, AFLGo nhúng thông tin khoảng cách vào binary thông qua tham số -distance. Thông tin này cho phép fuzzer biết đường thực thi của mỗi input đang gần hay xa các vị trí mục tiêu. Ngoài ra, AddressSanitizer được bật nhằm phát hiện các lỗi an toàn bộ nhớ.
#!/bin/bash
set -euo pipefail
BIN="${BIN:-./obj-dist/src}"CRASH_DIR="${CRASH_DIR:-out/crashes}"LOG_DIR="${LOG_DIR:-asan-replay-logs}"if[ ! -x "$BIN"];thenecho"[-] Binary not found or not executable: $BIN"exit1fiif[ ! -d "$CRASH_DIR"];thenecho"[-] Crash directory not found: $CRASH_DIR"exit1fimkdir -p "$LOG_DIR"echo"[+] Binary : $BIN"echo"[+] Crash dir : $CRASH_DIR"echo"[+] Log dir : $LOG_DIR"echocount=0for crash in "$CRASH_DIR"/id:*;doif[ ! -f "$crash"];thencontinueficount=$((count +1))base="$(basename "$crash"| tr ':,''__')"log="$LOG_DIR/$base.log"input_hex="$LOG_DIR/$base.hex"echo"============================================================"echo"[+] Replaying #$count: $crash"echo"[+] Log: $log" xxd -g 1"$crash" > "$input_hex"ASAN_OPTIONS=abort_on_error=1:symbolize=1:detect_leaks=0\
"$BIN""$crash" > "$log" 2>&1||trueecho"[+] Input hex:" head -n 5"$input_hex"echo"[+] ASAN summary:" grep -E \
"ERROR: AddressSanitizer|SUMMARY: AddressSanitizer|AddressSanitizer:|SEGV|heap-buffer-overflow|stack-buffer-overflow|heap-use-after-free|double-free|attempting double-free"\
"$log"| head -n 12||echo" [!] No ASAN summary found"echodoneecho"============================================================"echo"[+] Total replayed crashes: $count"echo"[+] Logs saved in: $LOG_DIR"echoecho"[+] Unique ASAN summaries:"grep -R "SUMMARY: AddressSanitizer""$LOG_DIR" 2>/dev/null |\
sed 's|.*/||'|\
sort | uniq -c ||trueechoecho"[+] Unique ASAN error types:"grep -R "ERROR: AddressSanitizer""$LOG_DIR" 2>/dev/null |\
sed -E 's/.*ERROR: AddressSanitizer: //'|\
sort | uniq -c ||true
Kết quả thu được như sau:
0x05 Analysis
Kết quả replay cho thấy fuzzer đã sinh nhiều testcase gây crash từ seed ban đầu A.
1. Out-of-bounds read
Crash đầu tiên có input là ký tự backslash \. Input chỉ có một byte, vì vậy khi parser gặp data[i] == '\\', nó đọc tiếp:
1
sink=data[i+1];
Với size = 1 và i = 0, chương trình đọc data[1], nằm ngoài vùng hợp lệ. ASAN báo:
1
2
==2104661==ERROR: AddressSanitizer: heap-buffer-overflow
SUMMARY: AddressSanitizer: heap-buffer-overflow /home/fz/MOD-AFLGO/examples/demo/src.c:79:20 in bug_oob_read_escape
ASAN gọi chung là heap-buffer-overflow, nhưng bản chất thao tác là read vì chương trình đọc từ data[i + 1].
2. Stack buffer overflow
Nhiều testcase khác nhau đều được ASAN gom về cùng lỗi:
1
SUMMARY: AddressSanitizer: stack-buffer-overflow src.c:102:20 in bug_stack_overflow_tokenizer
Đoạn code lỗi:
1
2
3
4
chartoken[16];size_tj=0;...token[j++]=(char)c;
Mảng token chỉ có 16 phần tử hợp lệ:
1
token[0] ... token[15]
Khi fuzzer sinh input có token liên tiếp dài hơn 16 byte, j tăng đến 16 và chương trình ghi vào:
1
token[16]
Đây là ghi vượt ra ngoài mảng cục bộ trên stack.
3. Heap buffer overflow
Một testcase có input bắt đầu bằng:
1
38 3a ...
0x38 là ký tự 8, 0x3a là :. Input có dạng gần với:
1
8:<payload>
Trong source, parser lấy declared_len từ phần trước dấu :, sau đó cấp phát:
1
char*buf=malloc((size_t)declared_len);
Nhưng lại copy toàn bộ payload thực tế:
1
memcpy(buf,data+i,payload_len);
Nếu payload_len > declared_len, memcpy sẽ ghi vượt ra khỏi vùng heap đã cấp phát.
4. Null Pointer
Một số testcase chứa ký tự ] gây:
1
2
Illegal instruction
[!] No ASAN summary found
Ví dụ input ]. Theo logic source, ký tự ] trước [ làm depth giảm xuống âm, sau đó current = NULL. Chương trình tiếp tục ghi:
1
current[0]='X';
Đây là lỗi null pointer dereference. Tuy nhiên, trong binary hiện tại, compiler có thể tối ưu nhánh undefined behavior thành trap instruction, dẫn tới SIGILL / Illegal instruction.