풀이
2개의 소스 코드가 주어진다.
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;
}
leg.asm
(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)
처음보는 asm 관련 문제가 나와서 좀 고민을 많이 했다.
일단 차근차근 봐보도록 하자.
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);
}
먼저 main 함수에서는 key 값을 입력받고, key1, key2, key3의 리턴 값이 key 값과 같으면 flag를 출력한다.
당연하게도 문제를 풀기 위해서는, 함수의 리턴 값이 뭔지 알아야 한다.
일단 asm을 봐보면, 함수의 리턴 값을 r0에 저장하는 듯하다. 그렇다면 key1, key2, key3에서의 r0 값을 알면..?
먼저 key1부터 봐보자.
int key1(){
asm("mov r3, pc\n");
}
(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.
해당 코드에서는 2줄만 보면 된다.
0x00008cdc mov r3, pc
0x00008ce0 mov r0, r3
처음 보는 pc라는 레지스터가 있어, 뭔지 찾아봤다.
PC: 다음 실행할 코드의 주소를 저장한다. (EIP) & 다음 인출(Fetch) 될 명령어의 주소를 가지고 있는 레지스터
출처: https://technote.kr/310[TechNote.kr]
찾아보니 CPU에는 4단계 명령어 처리 구조가 존재한다고 한다. pc 레지스터에는 Fetch 될 명령어의 주소를 가지고 있으므로, 해당 위치에서 두 단계 뒤 주소가 pc에 들어간다고 한다.
그렇다면, 0x8cdc에서 해당 구문을 처리하니, pc에 들어가는 값은 +8한 주소인 0x8ce4이다.
그렇게 되면? r3에는 0x8ce4 값이 들어가고 r0에 다시 0x8ce4 값이 들어가니까? key1에서의 값은 0x8ce4이다.
다음은 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.
r0에 무슨 값이 들어가나 보면 최종적으로 r3의 값이 r0에 들어간다.
r3값이 처음으로 정의되는 구문을 보자.
0x00008d04 mov r3, pc
그러나 여기서 주의할 점이 있다. 답이 계속 틀려 정보를 모으게 되었는데..
ARM에는 ARM mode와 Thumb mode가 있다.
bx 명령어를 이용해 ARM에서는 ARM mode <-> Thumb mode를 왔다 갔다 할 수 있다고 한다. 정말 어렵다...
ARM mode는 32bit를 사용하고, Thumb mode는 16bit를 사용하니 PC가 두 단계가 아니라 한 단계 뒤를 가리킨다.
그러므로 key2에서는 pc값이 0x8d08이고, adds r3, #4 구문을 실행해 4를 더하니, 최종 값은 0x8d0c이다.
제일 쉬운 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.
여기서는 딱 하나만 알아도 된다.
0x00008d28 mov r3, lr
lr 레지스터에는 리턴 어드레스가 들어간다고 하니까? key3 함수가 종료되고 난 후에 주소를 넣어주면 된다.
main에서 보면 알 수 있듯이, 다음 주소는 0x8d80이다.
최종적으로 key1, key2, key3의 리턴 값을 더하면 0x8ce4 + 0x8d0c + 0x8d80 = 0x1a770이다.
key값은 정수로 받으니, 0x1a770 -> 108,400 해주면 flag를 얻을 수 있다.
이번 문제를 풀기 위해서 정말 많은(?) 지식이 필요한 거 같다.
'Wargame > pwnable.kr' 카테고리의 다른 글
[Pwnable.kr] shellshock 풀이 (1pt) (0) | 2020.07.21 |
---|---|
[Pwnable.kr] mistake 풀이 (1pt) (0) | 2020.07.20 |
[Pwnable.kr] input2 풀이 (4pt) (0) | 2020.07.20 |
[Pwnable.kr] random 풀이 (1pt) (0) | 2020.07.19 |
[Pwnable.kr] passcode 풀이 (10pt) (0) | 2020.07.19 |