ENFJ_Blog
Arm gdbserver (-sh: ./gdbserver: not found fixed error) 본문
개요
embedded 기기를 분석하기 위해서는 gdb를 사용하기는 힘들다
바로 크기 때문이다. gdb의 크기는 9.9M이다.
하지만 펌웨어 플래시 메모리에 크기는 10M를 넘지 않는 펌웨어도 존재한다. 그렇기에 gdb의 크기는 부담이 되며 사용할 수 없게 된다. 그 문제를 해결한 것이 gdbserver이다. gdbserver의 크기는 399K 이다. gdbserver는 원격에서 gdb를 돌릴 수 있게 해주는 역할만 하기 때문에 상대적으로 부담이 덜하다.
그렇기에 임베디드 펌웨어에서는 gdbserver를 이용하여 동적분석을 진행한다.
이 gdbserver를 사용하는 과정에서 발생한 오류를 해결한 과정을 공유하기 위해 이 글을 작성했다.
*참고로 답은 굉장히 간단하니 바쁜 사람은 밑에서 어떻게 고쳤는지 보길 바란다.*
gdbserver 설치 및 복사 방법
테스트를 위해 진행한 만큼 실제 하드웨어를 사용하지는 않았고, 컴퓨터 VM으로 qemu를 사용해 에뮬레이팅을 하였다.
먼저 에물레이팅을 위해 qemu를 사용하였다. 이 부분은 많은 자료가 존재하기에 생략하도록 하겠다.
gdbserver를 파일 시스템에 복사하여 qemu 에뮬레이팅을 하는 방식을 사용하였는데
gdbserver는 buildroot를 사용하여 얻었다.
1. Buildroot 설치
buildroot 공식 사이트를 이용하여 wget을 통해 압축 파일을 가져왔다.
wget https://buildroot.org/downloads/buildroot-2025.02.tar.gz
tar -zxvf buildroot-2025.02.tar.gz
이를 이용하여 Buildroot를 설치 하였고
make menuconfig
를 해서 타겟인 임베디드 펌웨어에 맞게 커널과, 아키텍쳐를 설정해줬다.
여기서 toolchain 설정 부분에서 binuntils 버전과 gcc 버전이 달랐지만 크게 신경쓰지 않아도 되는 듯하여 신경쓰지 않고 빌드하였다.
target option과 toolchain 커널 버전을 설정하였으면, target package에서 gdb를 추가하여야 하는데, 바로는 추가가 안되고 toolchain에서 enable C++을 추가하면 gdb가 설정된다.
그리고 make 를 통해 빌드하였다.
그리고 밑과 같은 명령어를 통해 gdbserver 삽입, 파일시스템 재구성, qemu 에뮬래이팅을 하였다
cp ~/embedded_vuln/buildroot-2025.02/output/target/usr/lib/* ~/embedded_vuln/_Target_Firmware.bin.extracted/squashfs-root/lib/
cp ~/embedded_vuln/buildroot-2025.02/output/target/lib/* ~/embedded_vuln/_Target_Firmware.bin.extracted/squashfs-root/lib/
mksquashfs _Target_Firmware.bin.extracted/squashfs-root/* ./new_fs -b 131072 -comp xz
qemu-system-arm -kernel ./kernel_img -initrd ./new_fs -M virt -nographic -append "root=/dev/ram" -netdev user,id=eth0,hostfwd=tcp::9999-:9999 -device virtio-net-device,netdev=eth0 -m 256M
여기서 파일 시스템 재구성은 기존 파일 시스템 크기를 바탕으로 하였으니 참고해야한다.
근데 이게 잘 될 수도 있지만 난 문제가 하나 발생하였다.
# ls
app etc lib32 mnt root sys var
bin gdbserver linuxrc opt run tmp
dev lib media proc sbin usr
# ./gdbserver
-sh: ./gdbserver: not found
위와 같이 gdbserver가 존재하여도 못찾겠다는 의미였다. 실행권한은 주어진 것은 확인하였고, 무엇 때문에 안되는지 모르는 상태였다.
2. libc와 ld 문제
결국 발견한 것은 libc와 ld 문제였다. host에서 file 명령어를 통해 linking된 파일을 살펴보게 되면
$ file gdbserver
gdbserver: ELF 32-bit LSB pie executable, ARM, EABI5 version 1 (GNU/Linux), dynamically linked, interpreter /lib/ld-linux-armhf.so.3, for GNU/Linux 6.1.0, stripped
여기서는 문제점을 발견할 수는 없지만
파일 시스템의 라이브러리를 보게 되면
$ ls ld-linux.so.3
ld-linux.so.3
이와 같이 로더가 다른 모습을 볼 수 있다. 그러면 로더를 같게 하기 위해 두 가지 방법이 존재한다고 생각했다.
1. gdbserver와 같은 로더를 파일 시스템에 추가하여 새로 합친다.
2. patchelf를 이용하여 로더를 바꾼다.
미리 답을 말하자면 둘 다 실패했다.
ld 추가
아까 file 명령어를 통해 gdbserver의 원래 ld는 ld-linux-armhf.so.3라는 것을 알 수 있다. 이 파일은 아까 gdbserver를 가져왔던 buildroot에서 /usr/lib 위치가 아닌 /lib 위치에 존재하기에 그 파일을 가져왔다.
그 후 실행하면
# ./gdbserver
./gdbserver: error while loading shared libraries: /lib/libm.so.6: internal error
이런식으로 libm.so.6에서 에러가 나온다고 한다. 그래서 ld에서 가져온 lib를 가져왔는데
에러가 뜨는 libc를 다 가져왔었다. 에러가 뜨던 libc중 일부는 에뮬레이터를 실행하는데 자체에서 에러가 났다.
한마디로 에뮬레이터에서 에러가 나거나 gdbserver를 실행하는데 저렇게 에러가 뜨는 것이다.
patchelf를 이용하여 로더 교체
기존에 있는 로더는 ld-linux.so.3 이다.
그렇기 때문에 밑과 같은 명령어를 사용하여 바이너리를 패치할 수 있다.
$ patchelf --set-interpreter /lib/ld-linux.so.3 gdbserver
그리고 파일을 다시 합쳐서 qemu를 실행한 뒤 gdb를 실행하면...
# ./gdbserver
./gdbserver: error while loading shared libraries: /lib/libstdc++.so.6: internal error
와 같이 libc 에러가 뜬다.
그래서 gdbserver에서 libc를 교체하지 않아서 뜨나? 하고 생각이 들어서 libc도 교체해보았다.
$ patchelf --replace-needed libc.so.6 /lib/libstdc++.so.6 gdbserver
그런데도!
# ./gdbserver
./gdbserver: error while loading shared libraries: /lib/libstdc++.so.6: internal error
똑같은 에러가 뜬다.
혹시 patchelf 명령어 중간에 libc.so.6을 고쳐야 하나? 싶어서 고쳐보고 실행해보았다.
$ patchelf --replace-needed libstdc++.so.6 /lib/libstdc++.so.6 gdbserver
위에 에러 뜬거와 똑같은 에러가 뜬다.
BuildRoot를 변경하고 똑같은 과정을 거쳐도 같은 에러가 나왔다.
그래서 새로운 방법을 사용했는데 확실한 방법이다.
Statically-link gdbserver
dynamic linking으로 인한 오류이기 때문에 바이너리 내에 정적으로 링킹되어 있는 gdbserver를 구하면 된다.
용량문제가 있을거라고 잠깐 생각했지만, 생각해보니까 라이브러리도 복사해 오는걸 보니까 똑같다고 생각되었다.
Statically-link gdbserver를 인터넷에 검색하고 다운로드하여 사용했다. (아예 gdbserver 코드를 정적 컴파일해도 될 듯하다)