현장에 설치된 장비를 개선해야 했다. 소스가 없고 만든 곳과 연락이 안 되며 장비는 돌고 있다. 그것이 유일한 자료다.
Table of contents
Open Table of contents
밖에서부터 봤다
안을 열기 전에 밖에서 관측할 수 있는 것을 다 봤다.
통신 무엇을 주고받는지 캡처한다
전원 소비 전력이 동작에 따라 달라져 언제 뭘 하는지 힌트가 된다
표시 LED 와 화면이 상태를 알려준다
시간 몇 초마다 무엇을 하는지 주기를 잰다
밖에서 알 수 있는 것이 생각보다 많았고 이것만으로 대략 어떤 상태들이 있고 어떤 순서로 도는지가 잡혔다. 그리고 이것은 장비를 안 건드린다.
펌웨어를 꺼냈다
밖에서 안 되는 부분이 남아서 펌웨어를 봤다. 읽어내는 방법이 몇 가지 있었는데 JTAG 같은 디버그 포트와 메모리 칩 직접 읽기다.
읽는 것만으로도 장비가 망가질 수 있어서 여분 장비로 먼저 했다. 현장 장비는 안 건드렸다.
문자열부터 봤다
펌웨어 이미지를 얻고 나서 hexdump 로 한 번 훑고 가장 먼저 한 것이 문자열 추출이었다.
$ strings firmware.bin | less
여기서 많이 나왔다.
오류 메시지 → 어떤 상황을 다루는지
명령 이름 → 어떤 기능이 있는지
버전 문자열 → 언제 빌드됐는지
경로 → 개발 환경의 흔적
분해하기 전에 strings 출력만으로 구조가 상당히 잡혔다.
다만 strings 는 네 글자 이상 이어지는 ASCII 구간만 내보내는 것이 기본이다. 두세 글자짜리 명령 이름이나 상태 코드는 그대로 두면 안 보이므로 -n 으로 기준을 낮춰 한 번 더 돌려야 했다.
알려진 패턴과 실험
이미지 안에서 알아볼 수 있는 조각을 찾았다. libc 함수의 특징적인 코드와 CRC-16 표 같은 상수 배열과 프로토콜 상수가 그것이다.
아는 것이 하나 보이면 그 주변이 풀린다. 체크섬 상수를 찾으니 그것을 쓰는 함수가 보이고 그 함수를 부르는 곳이 통신 처리부였다. objdump 로 훑으면서 memcpy 나 strcmp 같은 호출을 찾는 것도 실마리가 됐다.
추측한 것을 확인하려면 실험이 필요했다. hexdump 로 바꿀 자리를 짚고 여분 장비에서 값을 넣어 동작을 봤다.
이 바이트가 주기 설정 같다 → 바꿔 보니 주기가 변함 ✓
이 값이 임계값 같다 → 바꿔도 변화 없음 ✗
틀린 추측이 절반이었다. 그래도 실험이 빠르니 여러 번 돌렸고 틀린 추측으로 현장 장비를 건드렸으면 그것이 멈췄을 것이다.
검증 — 세 부류로 나눈 문서
알아낸 주소와 뜻을 그때그때 적었다.
[확인됨]
0x0400 주기 설정 (단위: 100ms)
0x0404 재시도 횟수
[추측]
0x0408 임계값? (바꿔도 관측 변화 없음 — 다른 조건 필요할 수도)
[모름]
0x0410~0x04FF 용도 불명
0x0400 처럼 주소를 적고 세 부류로 나눈 것이 유용했다. 나중에 다시 볼 때 어디부터 파야 하는지 바로 알고 이 구분이 없으면 나중에 추측을 사실로 읽는다.
이 작업은 재미있어서 계속 파고 싶어지고 안 풀린 부분이 신경 쓰인다. 그런데 원래 목적은 특정 기능 하나를 개선하는 것이었다.
그 기능에 필요한 만큼만 알아내고 멈췄고 나머지는 문서에 모름으로 남겼다. 전부 이해하려 하면 끝이 없으니 필요한 만큼이 어디까지인지 정하고 시작하는 것이 나았다.
정리
- 소스가 없으면 밖에서 관측할 수 있는 것부터 본다
- 통신과 전력과 표시와 주기로 상태와 순서가 잡힌다
- 안을 볼 때는
JTAG를 쓰든 무엇을 쓰든 여분 장비로 한다 - 펌웨어를 얻으면
strings가 먼저다 - 기본이 네 글자 이상이라
-n으로 한 번 더 돌린다 - 아는 조각 하나가 보이면 주변이 풀린다
objdump에서libc함수와CRC-16상수를 먼저 찾는다- 추측은 절반쯤 틀리므로 여분 장비에서 바꿔 보며 확인한다
- 문서를 확인됨과 추측과 모름 셋으로 나눈다
- 어디까지 필요한지 정하고 시작하지 않으면 끝이 없다