[pwnable.kr] leg 풀이
analysis
문세 설명을 보니 ARM 문제이다.
asm 파일이랑 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;
}
이건 C 언어 코드인데 어차피 asm 파일을 봐야할 것 같다.
여기서는 플래그 조건만을 확인하고자 한다. key1, key2, key3 함수가 있는데, 해당 함수들의 반환값을 모두 합한 게 우리가 입력하는 key면 플래그를 출력해준다.
어셈블리 파일은 gdb에서 disass 한 걸 그대로 붙여넣었다.
(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.
main 함수이다. main 함수는 사실 C 언어에서 분석했기 때문에 그닥 볼게 없다. 이후 함수들을 위주로 분석하자.
그 전에 짚어갈 점이 있다.
ARM 아키텍처에서는 레지스터를 r 이후 숫자들로 사용한다.
용도를 조금 정리해보자면,
r0 ~ r3 | 함수 인자, r0는 return value |
r4 ~ r12 | 변수용 레지스터, r11은 이전 arm 프레임 포인터 |
r13 | stack pointer |
r14 | LR (link register), RET 역할 |
r15 | pc(program counter), fetch될 명령어 주소 |
ARM에 공부해보다가 pc값이 바로 다음 명령어를 가리키는 amd 아키텍처와 다르게, arm에서는 다음다음 명령어를 가리킨다.
그 이유로는 명령어를 수행할 때 fetch(읽고) -> decode(해석하고) -> execute(실행하고) 과정을 거치는데, 명령어를 두 번 실행하면
fetch -> decode -> execute 다음 명령어를 fetch -> decode -> execute 해서 총 6번의 과정을 거쳐야 하는데
arm에서는 이를 합쳐서 4번의 과정을 수행하기 때문이다.
실행하는 명령어가 A B C D 순서라고 할 때,
A(fetch)
B(fetch) -> A(decode)
C(fetch) -> B(decode) -> A(execute)
D(fetch) -> C(decode) -> B(execute)
로 들어간다.
따라서 A가 실행되고, B가 실행되는데에 4번으로 줄어든다. 여기서 성능저하를 해결하기 위해 하드웨어적 방법을 사용했다고는 하는데 아직 그건 잘 모르겠고, 중요하게 봐야할 부분으로 A가 execute될 때, fetch할 명령어는 C이다.
따라서 pc가 다다음 명령어를 가리킨다.
Solution
다시 돌아가서 key1 함수를 보자.
(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.
arm에 익숙하지는 않지만 어느정도 해석은 가능하다. 어차피 r0가 어떤 값이 세팅되는 지를 볼 것이므로 mov r0 , r3부분을 보고 잘 따라가면 된다.
r0에 r3을 넣는데, r3에는 이전에 pc를 넣는다.
pc는 다다음 주소니까 0x08cdc + 0x8 = 0x08ce4가 r3에 들어가고 결국 r0도 0x08ce4이다.
key1 == 0x08ce4
key2는 스택을 사용하는 것 같지만 여기서도 r0만 보면 된다.
r0는 r3값이 들어간다.
r3에는 4가 더해지고 그 이전에 pc를 대입한다.
r3에 pc가 들어가면 다다음 명령어인 0x08d08이 들어가고 그 다음 + 4로 0x08d0c가 들어갈 것이다.
따라서 r0는 0x08d0c 이다.
key2 == 0x08d0c
key3에서는 r0에 r3가 들어가는데 r3에는 lr이 들어간다. lr은 위 레지스터 설명과 같이 RET역할을 한다.
어셈블리에서 branch 이전에 돌아갈 주소를 넣기 때문에 key3를 호출한 main 함수에서 봐야한다.
main 함수에서 branch 하게 되면 ret로 0x08d80을 넣고 갈 것이므로 key3는 0x08d80이 된다.
따라서 전체 key1 + key2 + key3 = 0x08ce4 + 0x08d0c + 0x08d80인 0x1a770이다. 이걸 이제 10진수로 바꾸어서 넣어주자. 108400
풀었당.