본문으로 바로가기

[pwnable.kr] leg 풀이, write-up

category Wargame/pwnable.kr 2018. 12. 24. 11:10

pwnable.kr --- leg



ssh leg@pwnable.kr 2222 (pw:guest)로 접속하면 leg 문제를 만날 수 있다.


windows 환경의 cmd로 접속한것이 아니라 xshell5로 접속했다.


windows 환경의 cmd로 접속하려면 ssh leg@pwnable.kr -p2222와 같이 접속해야한다.



우선 현재 디렉터리에 어떤 파일이 있는지 확인해보자.







leg라는 의심스러운 elf 파일과 우리가 보고싶은 flag 파일이 있다.


leg elf 파일을 만든 leg.c와 leg.asm 파일은 아래의 경로에서 다운 받을 수 있다.


leg.c의 내용은 아래와 같다.



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
#include <stdio.h>
#include <fcntl.h>
 
int key1(){
    asm("mov r3, pc\n");
}
 
int key2(){
    asm(
    "push    {r6}\n"
    "add    r6, pc, $1\n"
    "bx    r6\n"
    ".code   16\n"
    "mov    r3, pc\n"
    "add    r3, $0x4\n"
    "push    {r3}\n"
    "pop    {pc}\n"
    ".code    32\n"
    "pop    {r6}\n"
    );
}
 
int key3(){
    asm("mov r3, lr\n");
}
 
int main(){
    int key=0;
    printf("Daddy has very strong arm! : ");
    scanf("%d"&key);
    if( (key1()+key2()+key3()) == key ){
        printf("Congratz!\n");
        int fd = open("flag", O_RDONLY);
        char buf[100];
        int r = read(fd, buf, 100);
        write(0, buf, r);
    }
    else{
        printf("I have strong leg :P\n");
    }
    return 0;
}
cs



key 변수를 생성하고 사용자로부터 key의 내용을 입력받는다. 

key1(), key2(), key3()의 함수를 실행 한 뒤 각각의 return 값을 더해 key 값과 비교한다.

성공하면 flag를 알려주고 틀리면 실패한다.


leg.c 소스를 보면 key1(), key2(), key3()를 인라인 어셈블리어로 작성을 했다.

일반적으로 우리가 보아온 intel 계열이 아니라 ARM 계열의 어셈블리어이다.


좀 더 자세히 분석하기 위해 leg.asm을 보자.



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
(gdb) disass main
Dump of assembler code for function main:
   0x00008d3c <+0>:    push    {r4, r11, lr}
   0x00008d40 <+4>:    add    r11, sp, #8
   0x00008d44 <+8>:    sub    sp, sp, #12
   0x00008d48 <+12>:    mov    r3, #0
   0x00008d4c <+16>:    str    r3, [r11, #-16]
   0x00008d50 <+20>:    ldr    r0, [pc, #104]    ; 0x8dc0 <main+132>
   0x00008d54 <+24>:    bl    0xfb6c <printf>
   0x00008d58 <+28>:    sub    r3, r11, #16
   0x00008d5c <+32>:    ldr    r0, [pc, #96]    ; 0x8dc4 <main+136>
   0x00008d60 <+36>:    mov    r1, r3
   0x00008d64 <+40>:    bl    0xfbd8 <__isoc99_scanf>
   0x00008d68 <+44>:    bl    0x8cd4 <key1>
   0x00008d6c <+48>:    mov    r4, r0
   0x00008d70 <+52>:    bl    0x8cf0 <key2>
   0x00008d74 <+56>:    mov    r3, r0
   0x00008d78 <+60>:    add    r4, r4, r3
   0x00008d7c <+64>:    bl    0x8d20 <key3>
   0x00008d80 <+68>:    mov    r3, r0
   0x00008d84 <+72>:    add    r2, r4, r3
   0x00008d88 <+76>:    ldr    r3, [r11, #-16]
   0x00008d8c <+80>:    cmp    r2, r3
   0x00008d90 <+84>:    bne    0x8da8 <main+108>
   0x00008d94 <+88>:    ldr    r0, [pc, #44]    ; 0x8dc8 <main+140>
   0x00008d98 <+92>:    bl    0x1050c <puts>
   0x00008d9c <+96>:    ldr    r0, [pc, #40]    ; 0x8dcc <main+144>
   0x00008da0 <+100>:    bl    0xf89c <system>
   0x00008da4 <+104>:    b    0x8db0 <main+116>
   0x00008da8 <+108>:    ldr    r0, [pc, #32]    ; 0x8dd0 <main+148>
   0x00008dac <+112>:    bl    0x1050c <puts>
   0x00008db0 <+116>:    mov    r3, #0
   0x00008db4 <+120>:    mov    r0, r3
   0x00008db8 <+124>:    sub    sp, r11, #8
   0x00008dbc <+128>:    pop    {r4, r11, pc}
   0x00008dc0 <+132>:    andeq    r10, r6, r12, lsl #9
   0x00008dc4 <+136>:    andeq    r10, r6, r12, lsr #9
   0x00008dc8 <+140>:            ; <UNDEFINED> instruction: 0x0006a4b0
   0x00008dcc <+144>:            ; <UNDEFINED> instruction: 0x0006a4bc
   0x00008dd0 <+148>:    andeq    r10, r6, r4, asr #9
End of assembler dump.
 
(gdb) disass key1
Dump of assembler code for function key1:
   0x00008cd4 <+0>:    push    {r11}        ; (str r11, [sp, #-4]!)
   0x00008cd8 <+4>:    add    r11, sp, #0
   0x00008cdc <+8>:    mov    r3, pc
   0x00008ce0 <+12>:    mov    r0, r3
   0x00008ce4 <+16>:    sub    sp, r11, #0
   0x00008ce8 <+20>:    pop    {r11}        ; (ldr r11, [sp], #4)
   0x00008cec <+24>:    bx    lr
End of assembler dump.
 
(gdb) disass key2
Dump of assembler code for function key2:
   0x00008cf0 <+0>:    push    {r11}        ; (str r11, [sp, #-4]!)
   0x00008cf4 <+4>:    add    r11, sp, #0
   0x00008cf8 <+8>:    push    {r6}        ; (str r6, [sp, #-4]!)
   0x00008cfc <+12>:    add    r6, pc, #1
   0x00008d00 <+16>:    bx    r6
   0x00008d04 <+20>:    mov    r3, pc
   0x00008d06 <+22>:    adds    r3, #4
   0x00008d08 <+24>:    push    {r3}
   0x00008d0a <+26>:    pop    {pc}
   0x00008d0c <+28>:    pop    {r6}        ; (ldr r6, [sp], #4)
   0x00008d10 <+32>:    mov    r0, r3
   0x00008d14 <+36>:    sub    sp, r11, #0
   0x00008d18 <+40>:    pop    {r11}        ; (ldr r11, [sp], #4)
   0x00008d1c <+44>:    bx    lr
End of assembler dump.
 
(gdb) disass key3
Dump of assembler code for function key3:
   0x00008d20 <+0>:    push    {r11}        ; (str r11, [sp, #-4]!)
   0x00008d24 <+4>:    add    r11, sp, #0
   0x00008d28 <+8>:    mov    r3, lr
   0x00008d2c <+12>:    mov    r0, r3
   0x00008d30 <+16>:    sub    sp, r11, #0
   0x00008d34 <+20>:    pop    {r11}        ; (ldr r11, [sp], #4)
   0x00008d38 <+24>:    bx    lr
End of assembler dump.
(gdb) 
cs



우리가 주의 깊게 볼 부분은 main의 key1(), key2(), key3()로 branch 되는 부분

key1(), key2(), key3() 함수의 내용이다.


우선 일반적으로 intel 계열과 ARM 계열의 branch 및 call을 통해 eax, r0를 통해 return 값을 전달한다.

이를 증명하는 부분은 key1(), key2(), key3()의 branch 이후 r0의 값을 다른 레지스터에 저장하는것을 확인할 수 있다.


key1() 함수의 내용을 보면 아래와 같다. 



1
2
3
4
5
6
7
8
9
10
(gdb) disass key1
Dump of assembler code for function key1:
   0x00008cd4 <+0>:    push    {r11}        ; (str r11, [sp, #-4]!)                        
   0x00008cd8 <+4>:    add    r11, sp, #0
   0x00008cdc <+8>:    mov    r3, pc
   0x00008ce0 <+12>:    mov    r0, r3
   0x00008ce4 <+16>:    sub    sp, r11, #0
   0x00008ce8 <+20>:    pop    {r11}        ; (ldr r11, [sp], #4)
   0x00008cec <+24>:    bx    lr
End of assembler dump.
cs



우리가 주의깊게 살펴볼 부분은 아래의 두 줄이다.


mov r3, pc

mov r0, r3


그렇다면 pc는 무엇일까?


pc는 program counter의 약어로 ARM에서 다음에 수행해야할 명령어의 주소를 가리킨다.

그렇다면 0x00008ce0가 다음에 실행될 주소니까 pc에 0x00008ce0가 들어갈 것으로 생각한다.

하지만 컴퓨터 구조를 공부하면 알듯이 pipe line에 의해서 0x00008ce4 부분이 fetch될 부분이다.

pipe line의 개념은 다른 장에서 설명할 계획이다. 우선 pc는 실제로 현재 실행되고 있는 명령의 다다음 명령이라고 생각하면 쉬울것이다.


key2() 함수의 내용을 보면 아래와 같다.



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
(gdb) disass key2
Dump of assembler code for function key2:
   0x00008cf0 <+0>:    push    {r11}        ; (str r11, [sp, #-4]!)                        
   0x00008cf4 <+4>:    add    r11, sp, #0
   0x00008cf8 <+8>:    push    {r6}        ; (str r6, [sp, #-4]!)
   0x00008cfc <+12>:    add    r6, pc, #1
   0x00008d00 <+16>:    bx    r6
   0x00008d04 <+20>:    mov    r3, pc
   0x00008d06 <+22>:    adds    r3, #4
   0x00008d08 <+24>:    push    {r3}
   0x00008d0a <+26>:    pop    {pc}
   0x00008d0c <+28>:    pop    {r6}        ; (ldr r6, [sp], #4)
   0x00008d10 <+32>:    mov    r0, r3
   0x00008d14 <+36>:    sub    sp, r11, #0
   0x00008d18 <+40>:    pop    {r11}        ; (ldr r11, [sp], #4)
   0x00008d1c <+44>:    bx    lr
End of assembler dump.
cs



우리가 주의깊게 살펴볼 부분은 아래의 세 줄이다.


mov r3, pc

adds r3, #4

mov r0, r3


pipe line에 의해 pc 값은 0x00008d08이고 add 명령에 의해 4를 더하니 최종적으로 r3값은 0x00008d0c이다.

이것을 r0로 return 한다.


key3() 함수의 내용을 보면 아래와 같다. 



1
2
3
4
5
6
7
8
9
10
11
(gdb) disass key3
Dump of assembler code for function key3:
   0x00008d20 <+0>:    push    {r11}        ; (str r11, [sp, #-4]!)                        
   0x00008d24 <+4>:    add    r11, sp, #0
   0x00008d28 <+8>:    mov    r3, lr
   0x00008d2c <+12>:    mov    r0, r3
   0x00008d30 <+16>:    sub    sp, r11, #0
   0x00008d34 <+20>:    pop    {r11}        ; (ldr r11, [sp], #4)
   0x00008d38 <+24>:    bx    lr
End of assembler dump.
(gdb) 
cs



우리가 주의깊게 살펴볼 부분은 아래의 두 줄이다.


mov r3, lr

mov r0, r3


우선 lr 레지스터에 대해 알아보자.

lr 레지스터는 link register함수 호출 후 돌아갈 주소를 저장하는 레지스터이다.

key3()가 branch 된 후 돌아갈 함수의 주소는 main의 0x00008d80이다. 


이로서 key1(), key2(), key3()의 return 값은 각각 아래와 같다.


key1(): 0x00008ce4

key2(): 0x00008d0c

key3(): 0x00008d80


key1() + key2() + key3() = 0x0001a770


10진수로 바꾸면 108400이다.


따라서 우리가 입력한 key 값으로 108400을 넣으면 된다.