BREAD (BIOS Reverse Engineering & Advanced Debugger) เป็นดีบักเกอร์ x86 โหมดจริงแบบ 'ฉีดได้' ที่สามารถดีบักโค้ดโหมดจริงตามอำเภอใจ (บน HW จริง) จากพีซีเครื่องอื่นผ่านสายเคเบิลอนุกรม
BREAD เกิดขึ้นจากความพยายามที่ล้มเหลวหลายครั้งในการย้อนกลับวิศวกรรม BIOS รุ่นเก่า เนื่องจากส่วนใหญ่ -- หากไม่ใช่ทั้งหมด -- การวิเคราะห์ BIOS จะดำเนินการแบบคงที่โดยใช้ตัวแยกชิ้นส่วน การทำความเข้าใจ BIOS จึงเป็นเรื่องยากมาก เนื่องจากไม่มีทางทราบค่าของรีจิสเตอร์หรือหน่วยความจำในโค้ดที่กำหนดได้
อย่างไรก็ตาม BREAD ยังสามารถดีบักโค้ดที่กำหนดเองในโหมดจริงได้ เช่น โค้ดที่สามารถบูตได้หรือโปรแกรม DOS ด้วยเช่นกัน
การสาธิตด่วน:
การเปลี่ยนชื่อสตริง CPU ผ่าน BREAD
ดีบักเกอร์นี้แบ่งออกเป็นสองส่วน: ดีบักเกอร์ (เขียนทั้งหมดในชุดประกอบและทำงานบนฮาร์ดแวร์ที่กำลังดีบั๊ก) และบริดจ์ที่เขียนด้วยภาษา C และทำงานบน Linux
ดีบักเกอร์เป็นโค้ดแบบฉีดได้ ซึ่งเขียนในโหมดเรียล 16 บิต และสามารถวางไว้ภายใน BIOS ROM หรือโค้ดโหมดเรียลอื่นๆ ได้ เมื่อดำเนินการ จะตั้งค่าตัวจัดการการขัดจังหวะที่เหมาะสม ทำให้โปรเซสเซอร์อยู่ในโหมดขั้นตอนเดียว และรอคำสั่งบนพอร์ตอนุกรม
ในทางกลับกัน บริดจ์คือการเชื่อมโยงระหว่างดีบักเกอร์และ GDB บริดจ์สื่อสารกับ GDB ผ่าน TCP และส่งต่อคำขอ/การตอบกลับไปยังดีบักเกอร์ผ่านพอร์ตอนุกรม แนวคิดเบื้องหลังบริดจ์คือการขจัดความซับซ้อนของแพ็กเก็ต GDB และสร้างโปรโตคอลที่ง่ายกว่าสำหรับการสื่อสารกับเครื่อง นอกจากนี้ โปรโตคอลที่เรียบง่ายกว่าช่วยให้ขนาดโค้ดสุดท้ายมีขนาดเล็กลง ทำให้ง่ายต่อการฉีดดีบักเกอร์ในสภาพแวดล้อมที่แตกต่างกันต่างๆ
ดังแสดงในแผนภาพต่อไปนี้:
+---------+ simple packets +----------+ GDB packets +---------+
| | --------------- > | | --------------- > | |
| dbg | | bridge | | gdb |
| ( real HW ) | <- -------------- | ( Linux ) | <- -------------- | ( Linux ) |
+---------+ serial +----------+ TCP +---------+
ด้วยการใช้ GDB stub ทำให้ BREAD มีคุณสมบัติมากมายที่พร้อมใช้งานทันที รองรับคำสั่งต่อไปนี้:
การทำวิศวกรรมย้อนกลับไบนารีดิบ เช่น BIOS ใน GDB หมายความว่าไม่มีสัญลักษณ์ดั้งเดิมโดยอัตโนมัติ อย่างไรก็ตาม ในขณะที่กระบวนการ RE ดำเนินไป ผู้ใช้/โปรแกรมเมอร์/แฮ็กเกอร์จะมีความเข้าใจที่ดีขึ้นในบางส่วนของโค้ด และเครื่องมือวิเคราะห์แบบคงที่ เช่น IDA, Cutter, Ghidra และอื่นๆ อนุญาตให้เพิ่มคำอธิบายประกอบ ความคิดเห็น คำจำกัดความของฟังก์ชัน และอีกมากมาย การปรับปรุงเหล่านี้ช่วยเพิ่มประสิทธิภาพการทำงานของผู้ใช้อย่างมาก
ด้วยเหตุนี้ จึงมีสคริปต์ Python ที่แสดงร่วมกันในโปรเจ็กต์ที่เรียกว่า symbolify.py
เมื่อพิจารณารายการสัญลักษณ์ (ป้ายกำกับที่อยู่) จะสร้างไฟล์ ELF ขั้นต่ำพร้อมสัญลักษณ์เหล่านี้ ELF นี้สามารถโหลดลงใน GDB ได้ในภายหลัง และใช้เพื่อทำให้กระบวนการแก้ไขจุดบกพร่องง่ายขึ้นอย่างมาก
ไฟล์สัญลักษณ์สามารถมีช่องว่าง บรรทัดว่าง ความคิดเห็น (#) และความคิดเห็นในบรรทัดที่อยู่ ที่อยู่สามารถอยู่ในรูปแบบทศนิยมหรือฐานสิบหก และป้ายกำกับ/สัญลักษณ์ (คั่นด้วยอักขระช่องว่างหนึ่งตัวขึ้นไป) สามารถอยู่ในรูปแบบ [a-z0-9_]+ ดังเช่นใน (ตัวอย่างจริงสามารถพบได้ใน symbols/ami_ipm41d3 ข้อความ):
#
# This is a comment
#
0xdeadbeef my_symbol1
0x123 othersymbol # This function does xyz
# Example with decimal address
456 anotherone
ตัวอย่างเช่น เมื่อพิจารณาถึงไฟล์สัญลักษณ์ที่มีอยู่ใน symbols/ami_ipm41d3.txt ผู้ใช้สามารถทำสิ่งต่อไปนี้:
$ ./simbolify.py symbols/ami_ipm41d3.txt ip41symbols.elf
จากนั้นโหลดใน GDB เช่นเดียวกับใน:
(gdb) add-symbol-file ip41symbols.elf 0
add symbol table from file "ip41symbols.elf" at
.text_addr = 0x0
(y or n) y
Reading symbols from ip41symbols.elf...
(No debugging symbols found in ip41symbols.elf)
(gdb) p cseg_
cseg_change_video_mode_logo cseg_get_cpuname
(gdb) p cseg_
โปรดทราบว่าแม้แต่การเติมข้อความอัตโนมัติของ GDB ก็ทำงานได้อย่างที่คาดไว้ น่าทึ่งมากใช่ไหม
เท่าไหร่? ใช่. เนื่องจากโค้ดที่กำลังดีบั๊กไม่ทราบว่ากำลังดีบั๊กอยู่ โค้ดดังกล่าวอาจรบกวนการทำงานของดีบักเกอร์ได้หลายวิธี เช่น:
การข้ามโหมดป้องกัน: หากรหัสที่แก้ไขข้อบกพร่องเปลี่ยนเป็นโหมดที่ได้รับการป้องกัน โครงสร้างสำหรับตัวจัดการการขัดจังหวะ ฯลฯ จะมีการเปลี่ยนแปลง และตัวดีบักเกอร์จะไม่ถูกเรียกใช้ที่จุดนั้นในโค้ดอีกต่อไป อย่างไรก็ตาม อาจเป็นไปได้ว่าการกลับไปสู่โหมดจริง (การเรียกคืนสถานะก่อนหน้าทั้งหมด) จะทำให้ดีบักเกอร์ทำงานได้อีกครั้ง
การเปลี่ยนแปลง IDT: หากรหัสที่แก้ไขข้อบกพร่องเปลี่ยน IDT หรือที่อยู่พื้นฐานด้วยเหตุผลใดก็ตาม ตัวจัดการดีบักเกอร์จะไม่ถูกเรียกใช้อย่างถูกต้อง
สแต็ก: BREAD ใช้สแต็กและถือว่ามีอยู่จริง! ไม่ควรแทรกลงในตำแหน่งที่ยังไม่ได้กำหนดค่าสแต็ก
สำหรับการดีบัก BIOS มีข้อจำกัดอื่น ๆ เช่น: ไม่สามารถดีบักโค้ด BIOS ได้ตั้งแต่เริ่มต้น (bootblock) เนื่องจากจำเป็นต้องมีการตั้งค่าขั้นต่ำ (เช่น RAM) เพื่อให้ BREAD ทำงานได้อย่างถูกต้อง อย่างไรก็ตาม เป็นไปได้ที่จะดำเนินการ "warm-reboot" โดยการตั้งค่า CS:EIP เป็น F000:FFF0
ในสถานการณ์สมมตินี้ สามารถติดตามการเริ่มต้น BIOS ได้อีกครั้ง เนื่องจาก BREAD ได้รับการโหลดอย่างถูกต้องแล้ว โปรดทราบว่า "เส้นทางโค้ด" ของการเริ่มต้น BIOS ระหว่างวอร์มรีบูตอาจแตกต่างจากการโคลด์รีบูต และขั้นตอนการดำเนินการอาจไม่เหมือนกันทุกประการ
การสร้างต้องใช้ GNU Make, คอมไพเลอร์ C (เช่น GCC, Clang หรือ TCC), NASM และเครื่อง Linux เท่านั้น
ดีบักเกอร์มีสองโหมดการทำงาน: การโพล (ค่าเริ่มต้น) และแบบอินเทอร์รัปต์:
โหมดโพลเป็นวิธีที่ง่ายที่สุด และควรทำงานได้ดีในสภาพแวดล้อมที่หลากหลาย อย่างไรก็ตาม เนื่องจากลักษณะการโพล จึงมีการใช้งาน CPU สูง:
$ git clone https://github.com/Theldus/BREAD.git
$ cd BREAD/
$ make
โหมดตามการขัดจังหวะจะปรับการใช้งาน CPU ให้เหมาะสมโดยการใช้การขัดจังหวะ UART เพื่อรับข้อมูลใหม่ แทนที่จะสำรวจข้อมูลอย่างต่อเนื่อง ซึ่งส่งผลให้ CPU ยังคงอยู่ในสถานะ 'หยุด' จนกว่าจะได้รับคำสั่งจากดีบักเกอร์ และด้วยเหตุนี้ จึงป้องกันไม่ให้ใช้ทรัพยากรของ CPU ถึง 100% อย่างไรก็ตาม เนื่องจากการขัดจังหวะไม่ได้เปิดใช้งานเสมอไป โหมดนี้จึงไม่ได้ตั้งค่าเป็นตัวเลือกเริ่มต้น:
$ git clone https://github.com/Theldus/BREAD.git
$ cd BREAD/
$ make UART_POLLING=no
การใช้ BREAD ต้องใช้สายเคเบิลอนุกรมเท่านั้น (และใช่ เมนบอร์ดของคุณ มี ส่วนหัว COM โปรดตรวจสอบคู่มือ) และฉีดโค้ดในตำแหน่งที่เหมาะสม
หากต้องการฉีด ต้องทำการเปลี่ยนแปลงเพียงเล็กน้อยใน dbg.asm (src ของดีบักเกอร์) ต้องเปลี่ยนรหัส 'ORG' ของรหัสและวิธีที่รหัสควรส่งคืน (มองหา " >> CHANGE_HERE <<
" ในรหัสเพื่อดูสถานที่ที่จำเป็นต้องเปลี่ยน)
การใช้ AMI เก่าเป็นตัวอย่าง โดยที่โมดูลดีบักเกอร์จะถูกวางในตำแหน่งโลโก้ BIOS ( 0x108200
หรือ FFFF:8210
) และคำแนะนำต่อไปนี้ใน ROM ได้ถูกแทนที่ด้วยการเรียกโมดูลแบบไกล:
...
00017EF2 06 push es
00017EF3 1E push ds
00017EF4 07 pop es
00017EF5 8BD8 mov bx , ax - ┐ replaced by: call 0xFFFF : 0x8210 (dbg.bin)
00017EF7 B8024F mov ax , 0x4f02 - ┘
00017EFA CD10 int 0x10
00017EFC 07 pop es
00017EFD C3 ret
...
แพตช์ต่อไปนี้ก็เพียงพอแล้ว:
diff --git a/dbg.asm b/dbg.asm
index caedb70..88024d3 100644
--- a/dbg.asm
+++ b/dbg.asm
@@ -21,7 +21,7 @@
; SOFTWARE.
[BITS 16]
- [ORG 0x0000] ; >> CHANGE_HERE <<
+ [ORG 0x8210] ; >> CHANGE_HERE <<
%include "constants.inc"
@@ -140,8 +140,8 @@ _start:
; >> CHANGE_HERE <<
; Overwritten BIOS instructions below (if any)
- nop
- nop
+ mov ax, 0x4F02
+ int 0x10
nop
nop
สิ่งสำคัญคือต้องทราบว่าหากคุณได้เปลี่ยนแปลงคำสั่งบางอย่างภายใน ROM ของคุณเพื่อเรียกใช้โค้ดดีบักเกอร์ คำสั่งเหล่านั้นจะต้องได้รับการกู้คืนก่อนที่จะส่งคืนจากดีบักเกอร์
เหตุผลในการเปลี่ยนคำสั่งทั้งสองนี้ก็คือ คำสั่งเหล่านั้นจะถูกดำเนินการก่อนที่ BIOS จะแสดงโลโก้บนหน้าจอ ซึ่งขณะนี้คือตัวดีบักเกอร์ เพื่อให้แน่ใจว่ามีประเด็นสำคัญบางประการ:
การค้นหาตำแหน่งที่ดีในการเรียกใช้โปรแกรมดีบักเกอร์ (โดยที่ BIOS ได้เริ่มต้นการทำงานเพียงพอแล้ว แต่ไม่สายเกินไป) อาจเป็นเรื่องที่ท้าทาย แต่ก็เป็นไปได้
หลังจากนี้ dbg.bin
ก็พร้อมที่จะแทรกลงในตำแหน่งที่ถูกต้องใน ROM
การดีบักโปรแกรม DOS ด้วย BREAD ค่อนข้างยุ่งยากเล็กน้อย แต่เป็นไปได้:
dbg.asm
เพื่อให้ DOS เข้าใจว่าเป็นโปรแกรม DOS ที่ถูกต้อง:times
)int 0x20
)แพตช์ต่อไปนี้เน้นไปที่สิ่งนี้:
diff --git a/dbg.asm b/dbg.asm
index caedb70..b042d35 100644
--- a/dbg.asm
+++ b/dbg.asm
@@ -21,7 +21,10 @@
; SOFTWARE.
[BITS 16]
- [ORG 0x0000] ; >> CHANGE_HERE <<
+ [ORG 0x100]
+
+ times 40*1024 db 0x90 ; keep some distance,
+ ; 40kB should be enough
%include "constants.inc"
@@ -140,7 +143,7 @@ _start:
; >> CHANGE_HERE <<
; Overwritten BIOS instructions below (if any)
- nop
+ int 0x20 ; DOS interrupt to exit process
nop
สร้างฟล็อปปี้อิมเมจ FreeDOS (หรือ DOS) ที่สามารถบูตได้โดยมีเพียงเคอร์เนลและเทอร์มินัล: KERNEL.SYS
และ COMMAND.COM
เพิ่มโปรแกรมที่จะดีบั๊กและ DBG.COM
( dbg.bin
) ลงในฟล็อปปี้อิมเมจนี้ด้วย
ควรดำเนินการขั้นตอนต่อไปนี้หลังจากสร้างภาพ:
bridge
ไว้แล้ว (ดูคำแนะนำในส่วนถัดไป)DBG.COM
DBG.COM
ดำเนินต่อไปจนกว่าจะเสร็จสิ้นสิ่งสำคัญคือต้องทราบว่า DOS จะไม่ลบอิมเมจกระบวนการหลังจากออก เป็นผลให้สามารถกำหนดค่าดีบักเกอร์ได้เช่นเดียวกับโปรแกรม DOS อื่น ๆ และสามารถตั้งค่าเบรกพอยต์ที่เหมาะสมได้ จุดเริ่มต้นของดีบักเกอร์เต็มไปด้วย NOP ดังนั้นจึงคาดว่ากระบวนการใหม่จะไม่เขียนทับหน่วยความจำของดีบักเกอร์ ทำให้สามารถทำงานต่อได้แม้ว่าจะดูเหมือนว่า "เสร็จสิ้น" แล้วก็ตาม ซึ่งช่วยให้ BREAD สามารถดีบักโปรแกรมอื่นๆ รวมถึง DOS ได้ด้วย
Bridge เป็นกาวระหว่างดีบักเกอร์และ GDB และสามารถใช้งานได้หลากหลายวิธี ไม่ว่าจะบนฮาร์ดแวร์จริงหรือเครื่องเสมือน
พารามิเตอร์ของมันคือ:
Usage: ./bridge [options]
Options:
-s Enable serial through socket, instead of device
-d <path> Replaces the default device path (/dev/ttyUSB0)
(does not work if -s is enabled)
-p <port> Serial port (as socket), default: 2345
-g <port> GDB port, default: 1234
-h This help
If no options are passed the default behavior is:
./bridge -d /dev/ttyUSB0 -g 1234
Minimal recommended usages:
./bridge -s (socket mode, serial on 2345 and GDB on 1234)
./bridge (device mode, serial on /dev/ttyUSB0 and GDB on 1234)
หากต้องการใช้กับฮาร์ดแวร์จริง เพียงเรียกใช้โดยไม่มีพารามิเตอร์ ทางเลือก คุณสามารถเปลี่ยนเส้นทางอุปกรณ์ด้วยพารามิเตอร์ -d
:
./bridge
หรือ ./bridge -d /path/to/device
)Single-stepped, you can now connect GDB!
จากนั้นเปิด GDB: gdb
สำหรับใช้ในเครื่องเสมือน ลำดับการดำเนินการจะเปลี่ยนแปลงเล็กน้อย:
./bridge
หรือ ./bridge -d /path/to/device
)make bochs
หรือ make qemu
)Single-stepped, you can now connect GDB!
จากนั้นเปิด GDB: gdb
ในทั้งสองกรณี ตรวจสอบให้แน่ใจว่าได้เรียกใช้ GDB ภายในโฟลเดอร์รูท BRIDGE เนื่องจากมีไฟล์เสริมในโฟลเดอร์นี้เพื่อให้ GDB ทำงานได้อย่างถูกต้องในแบบ 16 บิต
BREAD เปิดกว้างสำหรับชุมชนเสมอและยินดีที่จะยอมรับการมีส่วนร่วม ไม่ว่าจะเกี่ยวกับปัญหา เอกสาร การทดสอบ คุณสมบัติใหม่ การแก้ไขข้อบกพร่อง การพิมพ์ผิด และอื่นๆ ยินดีต้อนรับเข้าสู่ต่างประเทศ
BREAD ได้รับอนุญาตภายใต้ใบอนุญาต MIT เขียนโดย Davidson Francis และผู้ร่วมให้ข้อมูลคนอื่นๆ (หวังว่า)
เบรกพอยต์ถูกนำมาใช้เป็นเบรกพอยต์ของฮาร์ดแวร์ ดังนั้นจึงมีจำนวนเบรกพอยต์ที่พร้อมใช้งานจำนวนจำกัด ในการใช้งานปัจจุบัน มีเพียง 1 เบรกพอยต์ที่ใช้งานอยู่ในแต่ละครั้ง!
จุดเฝ้าดูฮาร์ดแวร์ (เช่น เบรกพอยต์) ยังรองรับทีละจุดเท่านั้น
โปรดทราบว่าการลงทะเบียนการแก้ไขข้อบกพร่องไม่ทำงานตามค่าเริ่มต้นบน VM สำหรับ bochs จะต้องคอมไพล์ด้วยแฟล็ก --enable-x86-debugger=yes
สำหรับ Qemu จะต้องทำงานโดยเปิดใช้งาน KVM: --enable-kvm
( make qemu
ทำสิ่งนี้แล้ว)