제리의 블로그

pwnable.kr leg 본문

Wargame/pwnable.kr

pwnable.kr leg

j3rrry 2018. 9. 17. 23:52

leg - 2 pt


Daddy told me I should study arm.
But I prefer to study my leg!

Download : http://pwnable.kr/bin/leg.c
Download : http://pwnable.kr/bin/leg.asm

ssh leg@pwnable.kr -p2222 (pw:guest)


/ $ id
uid=1000(busy) gid=1000 groups=1000
/ $ ls
bin      dev      flag     linuxrc  root     sys
boot     etc      leg      proc     sbin     usr
/ $ ls -l flag leg
-r--------    1 1001     0               38 Nov 10  2014 flag
---s--x---    1 1001     1000        636419 Nov 10  2014 leg



목차 

1. 소스

leg.c:
#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;
}
key1(), key2(), key3() 을 합한 값을 알아내야 합니다.



leg.asm:
   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
어셈블리어를 분석해보면
각 함수들의 r0 가 r2로 합해져 비교(cmp)됩니다.


이제 각 함수를 디스어셈블링하여 분석할텐데
공통적으로 각 함수들은 r3 의 값을 r0로 한다.
즉, r3 가 함수의 결과이다.



key1:
int key1(){
    asm("mov r3, pc\n");
}
   0x00008cdc <+8>: mov r3, pc
   0x00008ce0 <+12>:    mov r0, r3
key1 함수에서는 pc 값을 r0 로 보내고 있습니다.
PC는 Program Counter 로
intel 계열 레지스터로 치면 Instruction Point 와 같은 역할이라고 볼 수 있습니다.



key2:
int key2(){
    asm(
...
    "mov    r3, pc\n"
    "add    r3, $0x4\n"
...
    );
}
   0x00008d04 <+20>:    mov r3, pc
   0x00008d06 <+22>:    adds    r3, #4
...
   0x00008d10 <+32>:    mov r0, r3
key2 함수에서는 pc 값에 4 를 add 하고 있습니다.



key3:
int key3(){
    asm("mov r3, lr\n");
}
   0x00008d28 <+8>: mov r3, lr
   0x00008d2c <+12>:    mov r0, r3
key3 함수에서는 lr 값을 r0 에 저장합니다.



 key1()  +   key2()   + key3()
= 0x8cdc + 0x8d04 + 4 + 0x8d7c
= 108384
각 함수의 값을 계산해보면 108384 인데



/ $ ./leg
Daddy has very strong arm! : 108384
I have strong leg :P
실패 ㅠ



직접 gdb 로 분석해보고 싶은데
/ $ gdb ./leg
sh: gdb: not found
pwnable.kr 서버에는 gdb 가 없어서
직접 ARM 환경을 구축해서 pc 값을 알아보도록 하자



2. 로컬에서 디버깅 해보기

로컬에 컴파일해본거라 오프셋 정보 등은 다르지만!
(gdb) disas
Dump of assembler code for function key1:
   0x000084c4 <+0>:     push    {r7}
   0x000084c6 <+2>:     add     r7, sp, #0
=> 0x000084c8 <+4>:     mov     r3, pc
   0x000084ca <+6>:     mov     r0, r3
   0x000084cc <+8>:     mov     sp, r7
   0x000084ce <+10>:    pop     {r7}
   0x000084d0 <+12>:    bx      lr
End of assembler dump.
(gdb) i r r3 pc
r3             0x1      1
pc             0x84c8   0x84c8 <key1+4>
(gdb) ni
0x000084ca in key1 ()
(gdb) i r r3 pc
r3             0x84cc   33996
pc             0x84ca   0x84ca <key1+6>
pc+4
처음 예상했던 pc 값보다 +4 인 값이 도출이 되었습니다.



(gdb) disas
Dump of assembler code for function key2:
   0x000084d4 <+0>:     push    {r7}
   0x000084d6 <+2>:     add     r7, sp, #0
=> 0x000084d8 <+4>:     mov     r3, pc
   0x000084da <+6>:     add.w   r3, r3, #4
   0x000084de <+10>:    mov     r0, r3
   0x000084e0 <+12>:    mov     sp, r7
   0x000084e2 <+14>:    pop     {r7}
   0x000084e4 <+16>:    bx      lr
End of assembler dump.
(gdb) i r r3 pc
r3             0x84cc   33996
pc             0x84d8   0x84d8 <key2+4>
(gdb) ni
0x000084da in key2 ()
(gdb) i r r3 pc
r3             0x84dc   34012
pc             0x84da   0x84da <key2+6>
(gdb) ni
0x000084de in key2 ()
(gdb) i r r3
r3             0x84e0   34016
(pc+4)+4



   0x00008534 <+60>:    bl      0x84e8 <key3>
   0x00008538 <+64>:    mov     r3, r0
(gdb) disas
Dump of assembler code for function key3:
   0x000084e8 <+0>:     push    {r7}
   0x000084ea <+2>:     add     r7, sp, #0
=> 0x000084ec <+4>:     mov     r3, lr
   0x000084ee <+6>:     mov     r0, r3
   0x000084f0 <+8>:     mov     sp, r7
   0x000084f2 <+10>:    pop     {r7}
   0x000084f4 <+12>:    bx      lr
End of assembler dump.
(gdb) i r r3 lr
r3             0x84e0   34016
lr             0x8539   34105
(gdb) ni
0x000084ee in key3 ()
(gdb) i r r3 lr
r3             0x8539   34105
lr             0x8539   34105
lr 는 Link Register 로
함수가 다 수행된 후
돌아갈 주소를 저장하는 레지스터입니다.
개인적으로 0x8538 이 이상적이라고 생각하는데 +1 이 되어있습니다.



3. Flag

(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
key1 = 0x8ce0 + 4



(gdb) disas key2
...
   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
key2 = 0x8d04 + 4 + 4



int key3(){
    asm("mov r3, lr\n");
}
   0x00008d7c <+64>:    bl  0x8d20 <key3>
  0x00008d80 <+68>:    mov r3, r0
key3 = 0x8d7c + 5



>>> 0x8ce0 + 4 + 0x8d04 + 4 + 4 + 0x8d80 + 1
108401
/ $ ./leg
Daddy has very strong arm! : 108401
I have strong leg :P
/ $ ./leg
Daddy has very strong arm! : 108400
Congratz!
My daddy has a lot of ARMv5te muscle!
그러나 제가 계산한 것과 -1 차이가 났다.
아무래도 lr 레지스터의 값이 0x8d80 이 맞나보다.
(그런데 로컬에서는 왜...)



4. 참고

PC(프로그램 카운터) == r15
각 명령어에 대해 1워드 (4바이트) 씩 증가하거나 Thumb 상태에서 실행되는 명령어의 크기만큼 증가합니다.

명령어 dest, src
http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.dui0204ik/CEGIBCCG.html

lr == r14
lr 은 RET.
sp == r13
http://www.jkelec.co.kr/img/lecture/arm_arch/arm_arch_4.html

함수의 반환값은 r0 에 담긴다.

cpu는 명령을 실행할 때 fetch > decode > execute 과정을 거칩니다.

예전에는

 fetch

decode 

execute 

fetch 

decode 

execute 

2개의 명령어를 실행하려면 6 cycle 이 필요했는데
pipe line 으로 인해

 fetch

decode 

execute 

 

 

 fetch

decode 

execute 

효율적이게 처리할 수 있게 되었습니다.


PC 는 fetch 를 가리키고 있습니다.
ARM 은 32bit 가 1 word 이므로 실행하고 있는 코드의 +8 byte (2 word) 를 가리키고 있습니다.
(seungmin4239's leg writeup - http://blog.naver.com/PostView.nhn?blogId=seungmin4239&logNo=220921113078)

ARM 레지스터 : http://forum.falinux.com/zbxe/index.php?document_srl=571055&mid=lecture_tip
instruction pointer vs program counter : https://stackoverflow.com/questions/15739489/program-counter-and-instruction-register?rq=1
instruction piplining - Wikipedia : https://en.wikipedia.org/wiki/Instruction_pipelining


'Wargame > pwnable.kr' 카테고리의 다른 글

pwnable.kr Rookies echo2 - 50 pt  (0) 2018.12.14
pwnable.kr Rookies crypto1 - 120pt (CBC mode. byte-at-a-time decryption)  (0) 2018.12.06
pwnable.kr asm  (0) 2018.09.04
pwnable.kr syscall  (0) 2018.09.04
pwnable.kr fix  (0) 2018.09.03
Comments