BREAD(BIOS Reverse Engineering & Advanced Debugger)는 직렬 케이블을 통해 다른 PC에서 임의의 리얼 모드 코드(실제 HW에서)를 디버깅할 수 있는 '주입 가능한' 리얼 모드 x86 디버거입니다.
BREAD는 레거시 BIOS를 리버스 엔지니어링하려는 여러 번의 실패한 시도에서 탄생했습니다. 전부는 아니더라도 대부분의 BIOS 분석이 디스어셈블러를 사용하여 정적으로 수행된다는 점을 고려하면 주어진 코드에서 레지스터나 메모리의 값을 알 수 있는 방법이 없기 때문에 BIOS를 이해하는 것이 매우 어렵습니다.
그럼에도 불구하고 BREAD는 부팅 가능한 코드나 DOS 프로그램과 같은 임의의 코드를 리얼 모드에서 디버깅할 수도 있습니다.
빠른 데모:
BREAD를 통해 CPU 문자열 이름 변경
이 디버거는 디버거(완전히 어셈블리로 작성되고 디버깅 중인 하드웨어에서 실행됨)와 C로 작성되고 Linux에서 실행되는 브리지의 두 부분으로 나뉩니다.
디버거는 16비트 리얼 모드로 작성된 주입 가능한 코드이며 BIOS ROM 또는 기타 리얼 모드 코드 내에 배치될 수 있습니다. 실행되면 적절한 인터럽트 핸들러를 설정하고 프로세서를 단일 단계 모드로 설정하며 직렬 포트에서 명령을 기다립니다.
반면에 브리지는 디버거와 GDB 사이의 링크입니다. 브리지는 TCP를 통해 GDB와 통신하고 직렬 포트를 통해 디버거에 요청/응답을 전달합니다. 브리지 뒤에 있는 아이디어는 GDB 패킷의 복잡성을 제거하고 시스템과 통신하기 위한 더 간단한 프로토콜을 설정하는 것입니다. 또한 프로토콜이 단순해지면 최종 코드 크기가 더 작아지고 다양한 환경에 디버거를 더 쉽게 삽입할 수 있습니다.
다음 다이어그램에 표시된 대로:
+---------+ simple packets +----------+ GDB packets +---------+
| | --------------- > | | --------------- > | |
| dbg | | bridge | | gdb |
| ( real HW ) | <- -------------- | ( Linux ) | <- -------------- | ( Linux ) |
+---------+ serial +----------+ TCP +---------+
GDB 스텁을 구현함으로써 BREAD는 즉시 사용 가능한 많은 기능을 갖습니다. 다음 명령이 지원됩니다.
GDB에서 BIOS와 같은 원시 바이너리를 리버스 엔지니어링하면 자동으로 원래 기호가 없음을 의미합니다. 그러나 RE 프로세스가 진행됨에 따라 사용자/프로그래머/해커는 코드의 특정 부분을 더 잘 이해하게 되며 IDA, Cutter, Ghidra 등과 같은 정적 분석 도구를 사용하면 주석, 설명, 기능 정의를 추가할 수 있습니다. 그리고 더. 이러한 향상된 기능은 사용자의 생산성을 크게 향상시킵니다.
이를 염두에 두고 프로젝트에 symbolify.py
라는 동반 Python 스크립트가 있습니다. 기호 목록(주소 라벨)이 주어지면 이러한 기호가 추가된 최소 ELF 파일을 생성합니다. 이 ELF는 나중에 GDB에 로드되어 디버깅 프로세스를 크게 단순화하는 데 사용될 수 있습니다.
기호 파일에는 공백, 빈 줄, 주석(#) 및 주소 줄의 주석이 포함될 수 있습니다. 주소는 10진수 또는 16진수 형식일 수 있으며 레이블/기호(하나 이상의 공백 문자로 구분)는 다음과 같이 [a-z0-9_]+ 형식일 수 있습니다(실제 예는 Symbols/ami_ipm41d3에서 찾을 수 있습니다. txt):
#
# 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 디버깅의 경우 다음과 같은 다른 제한 사항이 있습니다. BREAD가 올바르게 작동하려면 최소 설정(예: RAM)이 필요하므로 아주 시작(부팅 블록)부터 BIOS 코드를 디버깅하는 것은 불가능합니다. 그러나 CS:EIP를 F000:FFF0
으로 설정하면 "웜 재부팅"을 수행할 수 있습니다. 이 시나리오에서는 BREAD가 이미 올바르게 로드되었으므로 BIOS 초기화를 다시 따를 수 있습니다. 웜 재부팅 중 BIOS 초기화의 "코드 경로"는 콜드 재부팅과 다를 수 있으며 실행 흐름도 정확히 동일하지 않을 수 있습니다.
빌드에는 GNU Make, C 컴파일러(예: GCC, Clang 또는 TCC), NASM 및 Linux 시스템만 필요합니다.
디버거에는 폴링(기본값) 및 인터럽트 기반의 두 가지 작동 모드가 있습니다.
폴링 모드는 가장 간단한 접근 방식이며 다양한 환경에서 잘 작동합니다. 그러나 폴링 특성으로 인해 CPU 사용량이 높습니다.
$ git clone https://github.com/Theldus/BREAD.git
$ cd BREAD/
$ make
인터럽트 기반 모드는 지속적으로 폴링하는 대신 UART 인터럽트를 활용하여 새 데이터를 수신함으로써 CPU 활용도를 최적화합니다. 이로 인해 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의 올바른 위치에 삽입될 준비가 됩니다.
BREAD를 사용하여 DOS 프로그램을 디버깅하는 것은 약간 까다롭지만 가능합니다.
dbg.asm
편집합니다.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
커널과 터미널( KERNEL.SYS
및 COMMAND.COM
만 포함하는 부팅 가능한 FreeDOS(또는 DOS) 플로피 이미지를 만듭니다. 또한 이 플로피 이미지에 디버깅할 프로그램과 DBG.COM
( dbg.bin
)을 추가합니다.
이미지를 생성한 후에는 다음 단계를 수행해야 합니다.
bridge
이미 열린 상태에서 부팅합니다(지침은 다음 섹션 참조).DBG.COM
실행합니다.DBG.COM
프로세스가 완료될 때까지 계속되도록 허용합니다.DOS는 종료된 후에 프로세스 이미지를 지우지 않는다는 점에 유의하는 것이 중요합니다. 결과적으로 디버거는 다른 DOS 프로그램처럼 구성될 수 있으며 적절한 중단점을 설정할 수 있습니다. 디버거의 시작 부분은 NOP로 채워져 있으므로 새 프로세스가 디버거의 메모리를 덮어쓰지 않고 "완료"된 것처럼 보이는 후에도 계속 작동할 수 있을 것으로 예상됩니다. 이를 통해 BREaD는 DOS 자체를 포함한 다른 프로그램을 디버깅할 수 있습니다.
브리지는 디버거와 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
.두 경우 모두 BRIDGE 루트 폴더 내에서 GDB를 실행해야 합니다. 이 폴더에는 GDB가 16비트에서 제대로 작동하기 위한 보조 파일이 있기 때문입니다.
BREAD는 항상 커뮤니티에 열려 있으며 문제, 문서, 테스트, 새로운 기능, 버그 수정, 오타 등의 기여를 기꺼이 받아들입니다. 참여를 환영합니다.
BREAD는 MIT 라이선스에 따라 라이선스가 부여됩니다. Davidson Francis와 (희망적으로) 다른 기여자들이 작성했습니다.
중단점은 하드웨어 중단점으로 구현되므로 사용 가능한 중단점 수가 제한되어 있습니다. 현재 구현에서는 한 번에 단 하나의 활성 중단점만 사용됩니다! ↩
하드웨어 감시점(예: 중단점)도 한 번에 하나씩만 지원됩니다. ↩
디버그 레지스터는 VM에서 기본적으로 작동하지 않습니다. Boch의 경우 --enable-x86-debugger=yes
플래그를 사용하여 컴파일해야 합니다. Qemu의 경우 KVM이 활성화된 상태에서 실행해야 합니다: --enable-kvm
( make qemu
이미 이 작업을 수행하고 있습니다). ↩