일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |
- Memory Management
- 운영체제
- 동기화문제
- exec
- higunnew
- 백트래킹
- 삼성기출
- 시뮬레이션
- paging
- Deadlock
- 백준
- BOJ
- 스케줄링
- 완전탐색
- 구현
- segmentation
- 삼성리서치
- 프로세스
- 데드락
- 알고리즘
- fork
- dfs
- samsung research
- 김건우
- pwnable.kr
- Brute Force
- ascii_easy
- 가상메모리
- 컴공복전
- BFS
- Today
- Total
gunnew의 잡설
pwnable.kr 8. leg (ARM Assembly) 본문
어떻게 매번 모르는 개념만 나올 수 있을까. ARM assembly를 알아야만 풀 수 있는 문제이다. 뭐 이미 Intel assembly를 알고 있어서 어느 정도는 이해할 수는 있었으나 내가 알던 개념과 조금은 달랐다. 가령 PC register에는 다음 instruction이 아니라 다다음 instruction이 담겨있다는 것과 같은 것 말이다...
코드부터 보자.
#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[101];
int r = read(fd, buf, 100);
write(0, buf, r);
}
else{
printf("I have strong leg :P\n");
}
return 0;
}
우리가 입력하는 정수 key값과 (key1() + key2() + key3())의 값이 같아야 한다. 그러면 각 함수가 반환하는 값을 살펴보아야 한다. 이때 leg.asm의 main함수를 살펴보자.
아무래도 key1 호출이 끝난 뒤 r0를 r4로 옮기고,
mov r4, r0
key2 호출이 끝난뒤 r0를 r3로 옮긴후 add 둘을 더하고
mov r3, r0
add r4, r4, r3
key3 호출이 끝난 뒤 r0을 r3으로 옮기고 r2에 r3과 r4를 더한 값을 저장한 후에
mov r3, r0
add r2, r4, r3
r11 - 16의 주소에 있는 값을 r3으로 옮긴 후 r2와 r3을 비교하는 것으로 보아
r2에는 key1() + key2() + key3()이 들어있고, r3에는 우리가 입력한 key값이 들어가 있을 것이다.
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.
mov r3, pc
mov r0 r3
을 통해 pc의 값이 r0에 들어갔다는 것을 알 수 있다.
그런데 pc에는 무슨 값이 들어있을까?
요약하면 PC는 pipe lining을 통해 다다음 주소를 담고 있다. ( decode 다음에는 원래 execute인데 pipe line을 통해 fetch로 이어서 instruction 수행 가능)
1 | 2 | 3 | 4 | 5 | 6 |
fetch | decode | execute | fetch | decode | execute |
fetch | decode | execute | fetch | decode | |
fetch | decode | execute | fetch | ||
fetch | decode | execute |
따라서 위에서
0x00008cdc <+8>: mov r3, pc
0x00008ce0 <+12>: mov r0, r3
0x00008ce4 <+16>: sub sp, r11, #0
여기서 r0에는 0x00008ce4가 들어간다.
key2() |
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"
);
}
(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.
main에서 key2 호출이 끝난뒤 r0를 r3로 옮긴후 add 둘을 더한다는 것을 명심하자!
mov r3, r0
add r4, r4, r3
따라서 r0에 어떤 값이 들어가는지 봐야한다.
+ 20에서 pc의 값이 r3로 들어가고,
+ 22에서 r3의 값이 4가 증가되고
+ 32에서 r3의 값이 r0으로 들어간다.
+ 20에서 r3로 들어가는 pc의 값은 0x00008d08이기 때문에
최종적으로 r0에는 0x00008d0c가 된다.
key3() |
int key3(){
asm("mov r3, lr\n");
}
(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.
key3 호출이 끝난 뒤 r0을 r3으로 옮기고 r2에 r3과 r4를 더한 값을 저장한다는 것을 상기하자.
mov r3, r0
add r2, r4, r3
r0에는 lr의 값이 들어간다.
LR, the Link Register
Register R14 is used to store the return address from a subroutine. At other times, LR can be used for other purposes.
When a BL or BLX instruction performs a subroutine call, LR is set to the subroutine return address. To perform a subroutine return, copy LR back to the program counter. This is typically done in one of two ways, after entering the subroutine with a BL or BLX instruction:
• Return with a BX LR instruction.
• On subroutine entry, store LR to the stack with an instruction of the form: PUSH {,LR} and use a matching instruction to return: POP {,PC}
LR은 main에서 돌아갈 주소이므로 0x00008d80 (<+68>)이 되며 r0에는 이 값이 담긴다.
자 이제
- 여기서 r0에는 0x00008ce4가 들어간다.
- 최종적으로 r0에는 0x00008d0c가 된다.
- LR은 main에서 돌아갈 주소이므로 0x00008d80 (<+68>)이 되며 r0에는 이 값이 담긴다.
이 정보를 이용하여 모두 더하고 key값을 찾자
108400을 key값으로 입력하면 통과될 것이다.
'System Security' 카테고리의 다른 글
pwnable.kr 10. shellshock (0) | 2020.02.14 |
---|---|
pwnable.kr 9. mistake (Operator priority) (0) | 2020.02.12 |
pwnable.kr 7. input (Various I/O in Linux) (0) | 2020.02.06 |
pwnable.kr 6. random (0) | 2020.02.05 |
pwnable.kr 5. passcode (0) | 2020.02.04 |