OpenWrt
OpenWrt는 와이파이 라우터용 리눅스 기반 임베디드 펌웨어이다. 방화벽, 패킷포워딩 등 리눅스의 강력한 기능과 더불어 전체 파일 시스템과 필요에 맞게 확장 설치가 가능한패키지 매니저를 제공한다. 유명한 아키텍처라 QEMU와 VirtualBox에서 부드럽게 구동할 수 있다.
OpenWrt를 실행하기 위한 ARM 아키텍처 에뮬레이팅
OpenWrt에서 미리 컴파일된 파일을 제공하므로 이를 사용한다.
위에서부터 커널 이미지 다운로드, 파일시스템, 파일시스템 추출 과정이다.
wget -q https://downloads.openwrt.org/releases/21.02.3/targets/armvirt/32/openwrt-21.02.3-armvirt-32-zImage -O zImage
wget -q https://downloads.openwrt.org/releases/21.02.3/targets/armvirt/32/openwrt-21.02.3-armvirt-32-rootfs-squashfs.img.gz -O rootfs-squashfs.img.gz
gunzip rootfs-squashfs.img.gz
필요한 경우 qemu-system-arm을 설치해야 한다.
이제 qemu 바이너리를 실행한다.
qemu-system-arm -M virt-2.9 -kernel zImage -no-reboot -nographic -nic user -nic user -drive file=rootfs-squashfs.img,if=virtio,format=raw -append "root=/dev/vda"
로딩되다 멈추면 enter를 한 번 눌러 메인 인터페이스에 진입할 수 있다. 파일이 잘 동작하는지 테스트하기 위한 실행으로 Ctrl + A + X를 눌러 QEMU를 종료한다.
커널로부터 디버그 파일을 다운로드한다.
wget -q https://downloads.openwrt.org/releases/21.02.3/targets/armvirt/32/kernel-debug.tar.zst
sudu apt install zstd
tar --use-compress-program=unzstd -xvf kernel-debug.tar.zst
cd debug
debug 폴더에는 vmlinux 파일이 있는데, 모든 symbol 정보를 포함한 압축되지 않은 커널이다. 이 파일로부터 symbol을 추출한다.
nm vmlinux > kallsyms
vmlinux를 nm으로 추출할 수도 있다.
TriforceAFL 설치
퍼징 대상이 될 시스템을 구축했으므로, 퍼저를 설치할 차례다. 여기서는 TriforceAFL을 이용한다. TriforceAFL이 ARM 아키텍처에서 동작하려면 조금 수정이 필요하다.
docker run --rm -it -v $(pwd)/armfuzz:/krn moflow/afl-triforce /bin/bash
triforceLinuxSyscallFuzzer 폴더에서 이미지가 시작되면 다음 명령을 실행한다. 이제 ARM 아키텍처에서 실행 가능한 수정 버전을 갖고 있다.
cd /TriforceAFL
git pull
make clean
make $(nprocs)
다음으로 ARM 아키텍처 컴파일에 필요한 툴체인을 설치한다. 마찬가지로 실행 중인 이미지 내에서 입력한다.
apt update
apt install gcc-arm-linux-gnueabi g++-arm-linux-gnueabi
드라이버가 x86 아키텍처용이므로 interruption 명령어를 ARM 아키텍처용으로 수정해야 한다.
apt install vim
cd /TriforceLinuxSyscallFuzzer
vim aflCall.c
다음 내용을 찾는다.
static inline u_long
aflCall(u_long a0, u_long a1, u_long a2)
{
u_long ret;
asm(".byte 0x0f, 0x24"
: "=a"(ret)
: "D"(a0), "S"(a1), "d"(a2)
);
return ret;
}
다음과 같이 바꾼다.
static inline u_long
aflCall(u_long a0, u_long a1, u_long a2)
{
u_long ret;
register long r0 asm ("r0")= a0;
register long r1 asm ("r1") = a1;
register long r2 asm ("r2") = a2;
asm("swi 0x4c4641"
: "=r"(r0)
: "r"(r0), "r"(r1), "r"(r2)
);
ret = (u_long)r0;
return ret;
}
ARM 명세의 swi는 software interrupt의 약자로, 이 명령어를 0x4c4641 값으로 변경한 것이다.
a0, a1, a2 입력 파라미터는 r0, r1, r2 레지스터에 매핑된다.
출력값은 r0에 저장되고 ret 변수에 지정된다. 마지막으로 r0값은 u_long 타입으로 캐스팅되고 반환된다.
QEMU는 intermediate representation을 생성하기 위해 translator를 사용한다. 이 경우 코드는 인터럽트의 특정 번호를 감지하기 위해 수정된다. 0x4c4641은 아스키 문자열로 "FLA"를 의미한다. TriforceALF은 이를 AFL을 위한 호출을 생성하는 데 사용한다.
이제 드라이버와 다른 바이너리를 컴파일한다.
make clean
CC=arm-linux-gnueabi-gcc make
다음 과정은 입력값을 생성하고 새로운 cpio 파일을 만드는 것이다. 이 파일을 생성하기 위해 OpenWrt에서 제공하는 default-rootfs 파일을 사용한다. 도커 시스템에 이미 포함되어 있다. krn폴더에 있는 파일을 복사한다.
그런데 복사가 되지 않아 확인해보니 krn 폴더에는 아무것도 없었다. 직접 파일을 다운로드 한다.
krn 폴더는 도커 내부가 아니라 외부에 있는 armfuzz 파일 내부를 나타낸다.
따라서 새로운 터미널을 열어 다음 명령을 통해 armfuzz 폴더 내에 파일을 다운로드 받는다.
cd armfuzz
sudo wget --no-check-certificate https://downloads.openwrt.org/releases/21.02.3/targets/armvirt/32/openwrt-21.02.3-armvirt-32-default-rootfs.tar.gz
cd /TriforcelinuxSyscallFuzzer
mkdir openwrt-rootfs
cp ../krn/openwrt-21.02.3-armvirt-32-default-rootfs.tar.gz openwrt-rootfs
cd openwrt-rootfs
tar -xvzf openwrt-21.02.3-armvirt-32-default-rootfs.tar.gz
rm openwrt-21.02.3-armvirt-32-default-rootfs.tar.gz
cp ../driver bin/driver
find . -print0 | cpio --null -ov --format=newc > ../openwrt-rootfs.cpio
이제 퍼저에 사용될 입력값을 생성한다. makefile로 수행하고, krn 폴더의 . 이미 inputs 파일이 만들어져 있을 수 있다.
cd ..
make inputs
다음으로, 초반에 생성했던 zImage와 kallSyms를 krn 폴더로 옮기고 작업을 수행한다. 옮기는 작업은 sudo와 cp를 이용한다.
옮긴 파일을 도커 이미지 내에서 kern 폴더로 옮긴다.
cp ../krn/zImage kern/bzImage
cp ../krn/kallSyms kern/kallsyms
OpenWrt 시스템을 시작한다.
../TriforceAFL/qemu-system-arm -M virt -kernel kern/bzImage -initrd openwrt-rootfs.cpio -m 200M -nographic -no-reboot
에뮬레이트된 OpenWrt 내에서 확인한 driver와 도커시스템 내에서 driver의 md5값이 일치함을 확인할 수 있다.
에뮬레이션을 실행하고 /bin/driver를 입력하면 시스템이 정상적으로 드라이버를 설치하고 실행하게 된다.
ARM OpenWrt에서 TriforceAFL 실행하기
우리 시스템에서 OpenWrT를 실행하려면 runFuzz 스크립트를 수정해야 한다.
cd /TriforceLinuxSyscallFuzzer
vim runFuzz
파일의 끝에 다음과 같은 코드가 적혀 있다.
$AFL/afl-fuzz $FARGS -t 500+ -i $INP -o outputs -QQ -- \
$AFL/afl-qemu-system-trace \
-L $AFL/qemu_mode/qemu/pc-bios \
-kernel $KERN/bzImage -initrd ./fuzzRoot.cpio.gz \
-m 64M -nographic -append "console=ttyS0" \
-aflPanicAddr "$PANIC" \
-aflDmesgAddr "$LOGSTORE" \
-aflFile @@
이를 다음과 같이 수정한다.
$AFL/afl-fuzz $FARGS -t 500+ -i $INP -o outputs -QQ -- \
$AFL/qemu-system-arm -M virt \
-kernel $KERN/bzImage -initrd ./openwrt-rootfs.cpio \
-m 200M -nographic -append "console=ttyS0" \
-aflPanicAddr "$PANIC" \
-aflDmesgAddr "$LOGSTORE" \
-aflFile @@
또 openwrt-rootfs 폴더의 init 파일을 수정해야 한다.
vim openwrt-rootfs/init
다음 내용을
exec switch_root $NEW_ROOT /sbin/init
다음과 같이 수정한다.
exec /bin/driver
rootfs 파일을 다시 생성한다.
cd openwrt-rootfs
find . -print0 | cpio --null -ov --format=newc > ../openwrt-rootfs.cpio
퍼징을 실행하기 전, AFL의 몇가지 문제를 수정해야 한다. 새로운 터미널을 열고 다음을 입력한다. 모든 CPU 코어의 성능 프로파일이 "performance"로 변경된다.
나의 경우에는 CPU0, CPU1 등 폴더에 cpufreq 폴더가 존재하지 않아 실행하지 않았다. 가상환경에서 실행하는 경우 존재하지 않을 수 있다고 파악했다.
cd /sys/devices/system/cpu
echo performance | sudo tee cpu*/cpufreq/scaling_governor
또한 echo core > /proc/sys/kernel/core_pattern을 수정하라는 메세지가 뜨면 새 터미널에서 다음과 같이 입력하면 된다.
sudo su
echo core > /proc/sys/kernel/core_pattern
exit
그러면 inputs 폴더의 입력값이 들어가면서 퍼징이 시작된다.
input을 수정해 crash 발생시키기
다음을 입력해 새로운 인풋 파일을 생성한다.
./gen2.py
ls -lah gen2-inputs
다음으로 runFuzz script를 수정한다.
vim runFuzz
# hokey arg parsing, sorry!
if [ "x$1" = "x-C" ] ; then # continue
INP="-"
shift
else
INP=inputs
fi
위 내용에 "INP-gen2-inputs"를 추가해 다음과 같이 바꾼다.
# hokey arg parsing, sorry!
if [ "x$1" = "x-C" ] ; then # continue
INP="-"
shift
else
INP=inputs
fi
INP=gen2-inputs
그리고 같은 파일에서 다음 내용을 찾는다.
# run fuzzer and qemu-system
export AFL_SKIP_CRASHES=1
$AFL/afl-fuzz $FARGS -t 500+ -i $INP -o outputs -QQ -- \
$AFL/qemu-system-arm -M virt \
-kernel $KERN/bzImage -initrd ./openwrt-rootfs.cpio \
-m 200M -nographic -append "console=ttyS0" \
-aflPanicAddr "$PANIC" \
-aflDmesgAddr "$LOGSTORE" \
-aflFile @@
200m을 64m으로 바꾼다.
# run fuzzer and qemu-system
export AFL_SKIP_CRASHES=1
$AFL/afl-fuzz $FARGS -t 500+ -i $INP -o outputs -QQ -- \
$AFL/qemu-system-arm -M virt \
-kernel $KERN/bzImage -initrd ./openwrt-rootfs.cpio \
-m 64M -nographic -append "console=ttyS0" \
-aflPanicAddr "$PANIC" \
-aflDmesgAddr "$LOGSTORE" \
-aflFile @@
"mv output output-bk/" 이전 퍼징 결과가 있다면 수행한다.
crash를 발생시키는 퍼징을 시작한다.
mv output output-bk/
./runFuzz -M 0
이것으로 ARM 아키텍처에서 실행되는 OpenWrt 시스템 상에서 리눅스 syscall interface를 퍼징하기 위한TriforceLinuxSyscallFuzzer를 세팅해보았다.
'보안 > fuzzing' 카테고리의 다른 글
libAFL 개념 정리 (1) | 2024.05.20 |
---|---|
[번역] A Look at AFL++ Under The Hood (0) | 2024.04.12 |
[하드웨어 해킹] shannon 퍼징 (0) | 2024.01.22 |
[하드웨어 해킹] QEMU 수정하기 (0) | 2024.01.21 |
[하드웨어 해킹] CVE-2011-0531 재현하기(실패) (0) | 2024.01.19 |