이번 포스팅에서는 가장 기본인 hello world 프로그램을 MIPS아키텍쳐에서 리버싱을 해본 것을 다루어보도록하겠습니다.
추후 분기문, 반복문 등 기본적인 구문에 대해서 하나씩 추가해 볼 생각입니다.
MIPS에서 사용하는 레지스터에 대한 설명과 기본적인 명령어에 대한 설명은 아래 이곳을 참조하였습니다.
[참조] http://logos.cs.uic.edu/366/notes/mips%20quick%20tutorial.htm
1. C소스
몇 줄안되지만 리버싱해볼 프로그램을 먼저 살펴보겠습니다.
1 2 3 4 5 6 7 8 9 10 11 | //hello.c #include <stdio.h> int main(int argc, char *argv[]){ printf("argc = %d\n", argc); printf("argv[0] = %s\n", argv[0]); printf("argv[1] = %s\n", argv[1]); return 0; } |
argc = 2
argv[0] = /root/hello
argv[1] = hi
2. Disassemble Code
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 | 0x00400640 <+0>: addiu sp,sp,-32 0x00400644 <+4>: sw ra,28(sp) 0x00400648 <+8>: sw s8,24(sp) 0x0040064c <+12>: move s8,sp 0x00400650 <+16>: sw a0,32(s8) 0x00400654 <+20>: sw a1,36(s8) 0x00400658 <+24>: lui v0,0x40 0x0040065c <+28>: addiu v0,v0,2144 0x00400660 <+32>: move a0,v0 0x00400664 <+36>: lw a1,32(s8) 0x00400668 <+40>: jal 0x400500 <printf@plt> 0x0040066c <+44>: move at,at 0x00400670 <+48>: lui v0,0x40 0x00400674 <+52>: addiu v1,v0,2160 0x00400678 <+56>: lw v0,36(s8) 0x0040067c <+60>: lw v0,0(v0) 0x00400680 <+64>: move a0,v1 0x00400684 <+68>: move a1,v0 0x00400688 <+72>: jal 0x400500 <printf@plt> 0x0040068c <+76>: move at,at 0x00400690 <+80>: lui v0,0x40 0x00400694 <+84>: addiu v1,v0,2176 0x00400698 <+88>: lw v0,36(s8) 0x0040069c <+92>: addiu v0,v0,4 0x004006a0 <+96>: lw v0,0(v0) 0x004006a4 <+100>: move a0,v1 0x004006a8 <+104>: move a1,v0 0x004006ac <+108>: jal 0x400500 <printf@plt> 0x004006b0 <+112>: move at,at 0x004006b4 <+116>: move v0,zero 0x004006b8 <+120>: move sp,s8 0x004006bc <+124>: lw ra,28(sp) 0x004006c0 <+128>: lw s8,24(sp) 0x004006c4 <+132>: addiu sp,sp,32 0x004006c8 <+136>: jr ra 0x004006cc <+140>: move at,at |
1 2 3 4 | 0x00400640 <+0>: addiu sp,sp,-32 0x00400644 <+4>: sw ra,28(sp) 0x00400648 <+8>: sw s8,24(sp) 0x0040064c <+12>: move s8,sp |
레지스터의 값을 메모리에 Word사이즈만큼(4byte) 저장하는 명령어 입니다.
여기서 주의 깊게 보아야할 부분은
sw source_register, destination_memory 로 표현된다는 점이에요.
ra (Return Address)
말그대로 리턴 주소를 가지고 있는 레지스터입니다.
28(sp)
이런 표현은 sp + 28을 뜻합니다. +28을 스택포인터의 오프셋으로 사용해서 스택에 접근하겠다는 뜻이지요
그래서 종합적으로 두 번째 라인은 어떻게 해석되느냐!
현재 리턴 주소를 sp+28에 저장해라 ~ 라는 뜻이 되겠습니다.
세 번째 라인은 sw s8, 24(sp) 입니다.
두 번째 라인과 비슷한 형태로 저장할 값만 바뀌었네요, s8를 sp+24에 저장해라! 가 되겠죠?
s8은 mips에서 프레임 포인터 레지스터입니다. x86에서 ebp와 같다고 생각하시면 될거 같네요.
네 번째 라인은 move s8, sp에요! move는 말그대로 값을 이동시키는 명령어 이고 x86에서 mov와 같습니다.
그러니까 s8 = sp가 되겠죠?
그럼 종합적으로 위의 네 라인이 무슨 일을 했는지 봅시다!
스택의 크기를 증가 시켰고, 현재 리턴어드레스와 프레임 포인터를 메모리에 저장했습니다.
그리고 프레임포인터에 스택포인터 값을 넣어주었죠.
네, 맞습니다. 스택프레임을 구성하는 구문이지요? 익숙한 x86으로 보면
push ebp
mov ebp, esp
sub esp, 0x20
함수 시작과 동시에 이루어지는 이 것과 비슷하게 움직이고 있습니다.
이제 두 번째 부분을 볼까요?
1 2 3 4 5 6 7 | 0x00400650 <+16>: sw a0,32(s8) 0x00400654 <+20>: sw a1,36(s8) 0x00400658 <+24>: lui v0,0x40 0x0040065c <+28>: addiu v0,v0,2144 0x00400660 <+32>: move a0,v0 0x00400664 <+36>: lw a1,32(s8) 0x00400668 <+40>: jal 0x400500 <printf@plt> |
여기서 새롭게 보이는 명령어는 lui, lw, jal 정도가 있네요? 나머지는 위에 본 것들라 수월할거에요
두 번째 부분 첫 째 라인은 sw a0, 32(s8) 입니다.
a0 ~ a3 레지스터는 서브 루틴을 호출 할 때 파라미터들을 저장하는 레지스터입니다.
그럼 자연스레, main함수의 파라미터들이 a0~a3에 위치하고 있다고 생각해볼 수 있겠네요,
여기서는 s8+32에 a0의 값을 저장하고 있네요, s8은 아까 위에서 ebp랑 같은 프레임 포인터라고 했죠?
직접 a0에 어떤 값이 들어 있는 지 한번 찍어보도록 합시다.
레지스터의 용도에 따르면 a0에는 메인함수의 첫 번째 파라미터인 argc가 들어 있어야할 거에요.
브레이크 포인트를 걸고 나서 hi라는 argument를 주고 실행을 한 후 값을 찍어보니까!
0x2가 나왔어요. 파라미터를 하나 주고(./hello hi) 실행 했으니까, argc가 2일테고... 오호, 그럼 a0에 argc(0x2)가 들어있는게 맞고,
이 값을 스택에 저장해 주겠다는 뜻이 되겠네요.
같은 방식으로 a1의 값을 확인하면
argv[0]의 포인터가 들어있는 것을 알 수 있습니당
음? 그럼 a2에는 프로그램 실행 환경변수의 포인터 값이 들어 있어야 겠죠?
확인해보니까 맞네요 ㅎ;;
여하튼! 다시 본론으로 돌아오면, 두 번째 라인에서는 s8+36에(스택에) argv[] 포인터 값을 넣어주었어요,,
세 번째 라인은 lui v0, 0x40 으로 lui연산을 하고 있습니다. lui연산은 해당 레지스터의 상위 두바이트에 값을 로드하는 명령어인데요,
따라서 v0 = 0x00400000 와 같이 표현해 볼 수 있겠습니다.
네 번째 라인에서는 addiu v0, 2144 이고,
v0 = v0+2144 니까
v0 = 0x00400000 + 2144
v0 = 0x400860 이 되겠습니다.
다섯 번째 라인에서는 move a0, v0
a0 = v0 이고, 네 번째 라인에서 연산한 결과를 a0에 넣어주고있어요, 0x400860에는 어떤 값이 들어 있는 것일까요?
오, 프로그램에서 처음으로 호출 하는 printf의 첫 번째 파라미터가 들어 있었네요,
위에서 a0~a3은 서브루틴에서 함수호출을 하기전 파라미터가 셋팅되는 레지스터라고 했었죠?
이제 printf를 호출 할 준비를 하나보네요.
여섯 번째 라인!
lw a1, 32(s8) 은 Load Word 명령어로 레지스터를 Destination으로 메모리에서 값을 로드해오는 명령어 입니다.
따라서, a1 = s8+32 가 될거고, s8+32에는 첫 번째 라인에서 값을 넣어준 argc가 들어 있을 거에요.
두 번째 부분의 마지막라인인 일곱 번째 라인은
jal 0x400500 <printf@plt> 네요, jal은 옆에 <printf@plt>라고 써있는 걸 보고 눈치 채셨겠지만,
x86에서 call과 비슷한 명령어 입니다. Jump and Link이고, 동작은 해당 함수 주소로 Jump! 하고 다음 명령어 주소를
ra레지스터에 셋팅해줘서 Link하는 명령어 입니다.
printf를 호출 하는데 첫 번째 파라미터에는 a0의 값 "argc = %d\n"
두 번째 파라미터에는 a1의 값 0x2를 가지고 호출을 하게 되겠지요!
두 번째 이하 부분도 비슷한 형식으로 printf를 호출 하고 있기에 설명은 생략하겠습니다.
혹시 포스팅 중 부족한 부분이나, 틀린 부분은 피드백 주시면 감사하겠습니다. 질문도 좋아요~ :)
'Study > IoT' 카테고리의 다른 글
MIPS 리버싱 기초-2(if, bof) (0) | 2017.09.14 |
---|---|
firmware 분석 환경 구축하기 (11) | 2017.09.06 |