본문 바로가기
스터디/wargames

[USE-Wargames] IO SmashTheStack Level1 문제풀이

by 깝태 2011. 8. 11.

앞으로 글 제목 앞에 문제별로 [USE] [BOF] [FSB] 등등이 나뉘어져 붙을겁니다.
[USE] 는 이런 문제들을 가지고 나뉘어질것이며 BOF 와 FSB 는 말 안해도 아시죠?

IO 워게임을 Level1 부터 다시 풀이해가겠습니다.
기존에 올라와있던 워게임처럼 간단하게 올릴생각은 아니구요, 처음부터 모르는게 있으면 완벽하게 올리려고 합니다. 
편하게 작성하는거라 말투는 수시로 바뀔겁니다 ㅋㅅㅋ

IO 워게임 공식 사이트 : http://io.smashthestack.org:84/

level1@io:~$ cd /levels
 
level1@io:/levels$ ./level01
Usage: ./level01 <password>

서버에 접속해 Level1 문제파일을 실행하면 다음과 같이 뜬다.

level1@io:/levels$ ./level01 aa
Fail.

프로그램을 실행해 인자로 아무값이나 전달해주면 Fail. 이라는 문자열이 뜨면서 종료된다.
디버깅해서 정답을 찾아야한다. 먼저 꼼수를 쓰자면 바이너리 파일에서 아스키 문자열을 확인할 수 있는 명령어가 있다.

level1@io:/levels$ strings level01
/lib/ld-linux.so.2
__gmon_start__
libc.so.6
printf
execl
puts
strncmp
_IO_stdin_used
__libc_start_main
GLIBC_2.0
PTRh
0Y_]
[^_]
[^_]
omgpassword
Usage: %s <password>
Win.
/bin/sh
Fail.

omgpassword 이라는 수상한 문자열이 있다. 한번 정답으로 넘겨줘보자.
 
level1@io:/levels$ ./level01 omgpassword
Win.
sh-4.1$ id
uid=1001(level1) gid=1001(level1) euid=1002(level2) groups=1002(level2),1001(level1),1029(nosu)

정답을 찾았지만 우리는 겨우 strings 명령어 하나 배울려고 이 워게임을 복습하는것이 아니다.
바로 디버깅을 해보자

level1@io:/levels$ gdb -q level01
Reading symbols from /levels/level01...done.
(gdb) disas main
Dump of assembler code for function main:
0x080483f4 <main+0>:    lea    0x4(%esp),%ecx // ECX 에 (ESP+4) 주소값 복사 - ESP+4 -> argc 
0x080483f8 <main+4>:    and    $0xfffffff0,%esp
0x080483fb <main+7>:    pushl  -0x4(%ecx)
 
0x080483fe <main+10>:   push   %ebp
0x080483ff <main+11>:   mov    %esp,%ebp
0x08048401 <main+13>:   push   %edi
0x08048402 <main+14>:   push   %ecx
0x08048403 <main+15>:   sub    $0x30,%esp // 프롤로그 부분 - 변수 48 바이트 선언, 스택 공간 생성
 
0x08048406 <main+18>:   mov    %ecx,-0x20(%ebp) // (EBP-32) 에 ECX 값 복사
0x08048409 <main+21>:   movl   $0x80485c8,-0xc(%ebp) // (EBP-12) 에 0x80485c8 값 복사
/* (gdb) x/s 0x80485c8
0x80485c8:       "omgpassword"
정답이다, 이렇게 디버깅을 해서 바로 찾아낼 수 있다.
그러면 한번 어떻게 가지고노는지 디버깅을 계속 해보자
*/ 
0x08048410 <main+28>:   mov    -0x20(%ebp),%eax // EAX 에 (EBP-32) 값 복사
0x08048413 <main+31>:   cmpl   $0x2,(%eax) // EAX 와 2 비교 
/*
아마도 인자값을 비교하는것 같다. argv[0] argv[1] 일 경우에만 입력받는것 같다. 
그걸 어떻게 알수있냐면 점프하지 않는곳, 즉 아래의 어셈블리어를 보면 "Usage: %s <password>\n"
라는 문자열을 출력하는데 이 문자열은 인자로, 정답으로 아무거나 전달하지 않았을경우나 한개 초과,
두개 이상 전달해줬을경우에만 나타난다. 즉, 인자를 비교하는것으로써 알수있다.

그러면 지금 (EBP-32) 와 EAX 에는 argc 가 들어가 있는 상태일것이다.

level1@io:/levels$ ./level01
Usage: ./level01 <password>
level1@io:/levels$ ./level01 a <---- cmpl   $0x2,(%eax) 성립하는 조건, 참인 경우
Fail.
level1@io:/levels$ ./level01 a a
Usage: ./level01 <password>
*/
0x08048416 <main+34>:   je     0x8048439 <main+69> // 참일경우 (main+69) 로 점프 - 거짓일경우 계속 진행
0x08048418 <main+36>:   mov    -0x20(%ebp),%edx // EDX 에 (EBP-23) 값 복사
0x0804841b <main+39>:   mov    0x4(%edx),%eax // EAX 에 (EDX+4) 값 복사 
0x0804841e <main+42>:   mov    (%eax),%eax // EAX 에 EAX 값 복사
0x08048420 <main+44>:   mov    %eax,0x4(%esp) // (ESP+4) 에 EAX 값 복사
0x08048424 <main+48>:   movl   $0x80485d4,(%esp) // ESP 에 0x80485d4 값 복사
/*
(gdb) x/s 0x80485d4
0x80485d4:       "Usage: %s <password>\n"
*/ 
0x0804842b <main+55>:   call   0x804832c <printf@plt> // printf 함수 호출, 위 문장 출력
0x08048430 <main+60>:   movl   $0x1,-0x1c(%ebp) // (EBP-28) 에 1 복사
0x08048437 <main+67>:   jmp    0x80484b2 <main+190> // (main+190) 으로 점프
// 여기까지가 비교구문이 거짓일경우에 수행하는 문장이다.
 
0x08048439 <main+69>:   mov    -0xc(%ebp),%eax // EAX 에 (EBP-12) 값 복사 - 현재 EBP-12 "omgpassword" = EAX
0x0804843c <main+72>:   mov    $0xffffffff,%ecx // ECX 에 -1 복사
0x08048441 <main+77>:   mov    %eax,-0x24(%ebp) // (EBP-36) 에 EAX 복사 - 현재 EAX "omgpassword"
0x08048444 <main+80>:   mov    $0x0,%al // EAX 의 하위 ax의 하위 1바이트가 0 으로 복사된다. (0x80485c8 -> 0x8048500)
 
0x08048446 <main+82>:   cld
0x08048447 <main+83>:   mov    -0x24(%ebp),%edi // EDI 에 (EBP-36) 값 복사 - 이제 EDI 에는 EAX 가 들어가있음
// EDI 에 정답이 들어가있는거나 마찬가지
0x0804844a <main+86>:   repnz scas %es:(%edi),%al

// 여기서 cld 와 repnz 의 중요한 설명이 필요하므로 따로 추가하겠다. 
 
0x0804844c <main+88>:   mov    %ecx,%eax // EAX 에 ECX 값 복사 - (-13) 을 EAX 에 저장
0x0804844e <main+90>:   not    %eax // EAX 가 NOT 연산을 해서 -13 => 12 가 된다.
0x08048450 <main+92>:   lea    -0x1(%eax),%edx // EDX 에 (EAX-1) 주소값 복사
0x08048453 <main+95>:   mov    -0x20(%ebp),%ecx // ECX 에 (EBP-32) 값 복사 - argc 전달
0x08048456 <main+98>:   mov    0x4(%ecx),%eax // EAX 에 (ECX+4) 값 복사
0x08048459 <main+101>:  add    $0x4,%eax // EAX + 4 - argc+4 => argv[1] 의 시작주소
0x0804845c <main+104>:  mov    (%eax),%ecx // ECX 에 EAX 값 복사
 
0x0804845e <main+106>:  mov    %edx,0x8(%esp) // (ESP+8) 에 EDX 값 복사 - argv[2]
0x08048462 <main+110>:  mov    -0xc(%ebp),%eax // EAX 에 (EBP-12) 값 복사
0x08048465 <main+113>:  mov    %eax,0x4(%esp) // (ESP+4) 에 EAX 값 복사 - argv[1]
0x08048469 <main+117>:  mov    %ecx,(%esp) // ESP 에 ECX 값 복사 - argv[0]
0x0804846c <main+120>:  call   0x804830c <strncmp@plt> // 아하! strncmp 함수를 수행하기 위해 인자 세개를 받았군요,
/* int strncmp ( const char * str1, const char * str2, size_t num ); 이 인자들을 받아준거다.
120 에 브포를 걸고 edx, eax, ecx 하나하나 확인해보자

(gdb) x/s $edx
0xb:     <Address 0xb out of bounds>
(gdb) x/s $eax
0x80485c8:       "omgpassword"
(gdb) x/s $ecx
0xbfffdea4:      "aaaa" <- 내가 입력한값과 omgpassword 를 비교한다
*/
 
0x08048471 <main+125>:  test   %eax,%eax // EAX 검사 - eflags 값이 바뀌는데 이 또한 자세하게 추가하겠다. 
 
0x08048473 <main+127>:  jne    0x804849f <main+171> // 0x804849f 로 점프 - 실패했을경우
 
0x08048475 <main+129>:  movl   $0x80485ea,(%esp) // ESP 에 0x80485ea 값 복사
/*
(gdb) x/s 0x80485ea
0x80485ea:       "Win."
그러니깐 성공한 경우이다.
*/ 
0x0804847c <main+136>:  call   0x80482fc <puts@plt>
 
0x08048481 <main+141>:  movl   $0x0,0x8(%esp)
0x08048489 <main+149>:  movl   $0x80485ef,0x4(%esp)
0x08048491 <main+157>:  movl   $0x80485f2,(%esp)  
0x08048498 <main+164>:  call   0x80482ec <execl@plt>
// 쉘 띄우는 과정
 
0x0804849d <main+169>:  jmp    0x80484ab <main+183>

0x0804849f <main+171>:  movl   $0x80485fa,(%esp) // ESP 에 0x80485fa 값 복사
0x080484a6 <main+178>:  call   0x80482fc <puts@plt> //puts 함수호출
0x080484ab <main+183>:  movl   $0x0,-0x1c(%ebp) // (EBP-28) 에 0 복시
0x080484b2 <main+190>:  mov    -0x1c(%ebp),%eax // EAX 에 (EBP-28) 복사
0x080484b5 <main+193>:  add    $0x30,%esp // 스택 공간 초기화
 
0x080484b8 <main+196>:  pop    %ecx
0x080484b9 <main+197>:  pop    %edi
0x080484ba <main+198>:  pop    %ebp
0x080484bb <main+199>:  lea    -0x4(%ecx),%esp
0x080484be <main+202>:  ret // 에필로그 
End of assembler dump.

이로서 분석이 끝났다. 정답은 omgpassword 이다.

level1@io:/levels$ ./level01 omgpassword
Win.
sh-4.1$ id
uid=1001(level1) gid=1001(level1) euid=1002(level2) groups=1002(level2),1001(level1),1029(nosu)
 
sh-4.1$ cat /home/level2/.pass
WE5aVWRwYPhX