코드 개선사항
이전에 짰던 gBS 탐지 스크립트는 아래와 같은 문제를 가지고 있었다.
- ghidra가 SMM 런타임 함수를 찾지 못한다.
이는 entry부터 찾아들어가는 ghidra의 특성과 달리 SMI를 통해 강제로 점프하여 들어오는 곳이라 참조가 끊겨 생기는 문제로 추측한다. - 부팅 중에는 gBS가 실행되어도 된다.
하지만 기존 코드는 모든 함수를 다 돌아버리면서 부팅용 함수든 런타임용 함수든 전부 다 검사해버려 오탐(False Positive)가 발생한다. - 나중에 자동화 과정에서 이게 SMM 단에서 실행되는지, 일반 DXE 드라이버인지 알 수 없다.
개선 로직
개선 로직은 다음과 같이 고민했다.
- 먼저 검사 로직을 수행하기 전에 해당 프로그램의 모든 바이너리를 순회한다.
- 순회한 각 코드의 P-Code를 뽑아내고, 뽑아낸 P-Code에 간접 호출(CALLIND)가 있는지 찾는다.
- 만약 간접 호출이 있고, 0xE0 오프셋을 더한다면 SMM 핸들러를 등록하는 것으로 간주한다.
- 만약 해당 함수가 등록되어있지 않으면 등록을 해준다.
- 이후 gBS 주소 찾기 및 Taint Analysis를 수행한다.
이 때 모든 함수를 찾던 기존 Taint Analysis가 아닌 찾아낸 핸들러들을 대상으로 한다.
왜?
사실 굉장히 휴리스틱한 접근이였다.
먼저 어떻게 함수들을 찾았는지다. SMI이 울렸을 때 실행되는 함수들은 gSmst->SmmHandlerRegister에 저장되어 있다. 이 때 이는 gSmst에서 0xE0만큼 떨어져 있다. 원래라면 추적을 두번, 세번, 네번 이어 가야 gSmst를 찾아낼 수 있지만, 일단 빠르게 결과부터 보기 위해 0xE0을 더해서 호출하는 로직만 찾아서 찾는, 일종의 찍기를 한 것이다.(……)
다음으로 런타임 함수들만 잡는 방법이였다. 바이너리 자체에는 어떤게 부팅 중 실행되는지, 어떤게 런타임에 실행되는지 알 수 없다. 하지만 부팅 중에 실행되지 않는 함수들은 entry에 연결되지 않는다는 점을 생각했다. 그러면 어디에 저장되는가. 사실 이 저장되는 위치가 바로 gSmst->SmmHandlerRegister다! 이 안에 있는 모든 CALL들을 가져와 리스트, 또는 이터레이터 형태로 저장 후 순회하는 방식으로 바꾸면 된다.