본문 바로가기
스터디/wargames

SmashTheStack : level1@io.smashthestack 문제풀이

by 깝태 2011. 7. 20.

제 개인사이트에 작성한 문서라 문서 말투가 일기체입니다.
모두 수정할 수 없기에 이해해주시고 봐주시면 감사하겠습니다

-------------------------------------------------------------------------------------------

SmashTheStack 워게임은 제목을 봐서는 Stack 을 공격하는 스택 버퍼 오버플로우와 관련이 많다고 생각하겠지만

여러 분류로 나뉘어져 있는것 같다. 내가 풀고있는 IO 이 서버에서는 FTZ 초기레벨과 같이 기본 해킹 문제를

다루고 있는것 같다.


나의 숙적, 버퍼 오버플로우와 대면할때는 달라져 있겠지.


이미 문제를 풀어 팀에서는 과제로 제출했지만 좀 더 디테일하게 분석을 해 올릴 생각이며,

내가 민망하게끔 기초스러운것이라도 알게된게 있다면 기재할 생각이다. 그래서 아마 하나의 문서를 작성할때

오랜 시간이 걸릴것으로 예정되며 또한 이 문서의 길이도 엄청나게 길어질 것이라 생각된다.


SSH - level1@io.smashthestack.org -p2224


처음 서버를 접속한 자라면 먼저 영문으로 작성된 긴 가이드를 읽을 수 있다.

문제를 푸는 방법에 대해 나와있으며 요약하자면


level1@io:/levels$ cd /levels 

// 문제폴더

level1@io:/levels$ ls -al

// 문제확인

level1@io:/levels$ level01

// 문제확인

level1@io:/levels$ cat /home/level2/.pass

// 권한 획득 시 키값 얻기 (쉘 권한에서만 가능)


level1@io:/levels$ ./level01

Usage: ./level01 <password>


level1@io:/levels$ ./level01 aaaa

Fail.


프로그램을 실행하면 패스워드를 입력하라는 메세지가 나오고 아무런 문자나 입력하면 Fail. 이라는 문자열이 뜬다.
소스는 당연히 없을터이니 GDB 를 이용해 프로그램을 분석해서 문제를 풀라는 말이었다.

문제를 푸는 방법은 쉽게 다가가기 -> 어렵게 다가가기 이다.

먼저 트릭을 사용해 보았다. 리눅스에서는 strings 명령어를 통해 바이너리 파일의 문자열을 확인할 수 없다.
대게 vi, cat 를 사용할 수 없을 경우 많이 사용된다.

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-3.2$ id
uid=1001(level1) gid=1001(level1) euid=1002(level2) groups=1001(level1),1029(nosu)

sh-3.2$ cat /home/level2/.pass
WE5aVWRwYPhX

쉘을 획득했지만 얻는게 있어야 재미도 있기에 GDB 로 문제를 열고 분석해보겠다.

level1@io:/levels$ gdb -q level01

(gdb) disas main
Dump of assembler code for function main:
0x080483f4 <main+0>:    lea    0x4(%esp),%ecx // ESP+4 => argc 주소값을 ECX 에 복사 - 0xbfffdd10 주소가 들어가있음
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 // EDI, ECX 차례대로 푸시
/*
EDI 는 ESI 레지스터가 가리키는 주소의 데이터가 복사되며
ECX 는 반복 명령어 사용시 반복 카운트로 사용 됨
*/
0x08048403 <main+15>:   sub    $0x30,%esp // 0x30 => 48 바이트 변수 선언, 스택의 길이 0x30
0x08048406 <main+18>:   mov    %ecx,-0x20(%ebp) 
// (EBP-32) 에 ECX 의 값을 복사 - (EBP-32) 에도 argc 주소 0xbfffdd10 가 들어가있음.
0x08048409 <main+21>:   movl   $0x80485c8,-0xc(%ebp) // (EBP-12) 에 0x80485c8 주소값을 복사
/*
(gdb) x/s 0x80485c8
0x80485c8:       "omgpassword"
정답이 (EBP-12) 안에 들어가있다. 과연 프로그램이 
어떻게 갖고노는지 아직 알 수 없으니, 찬찬히 알아가보자.
*/
0x08048410 <main+28>:   mov    -0x20(%ebp),%eax // EAX 에 (EBP-32) 의 값을 복사 => 0xbfffdd10 들어감
0x08048413 <main+31>:   cmpl   $0x2,(%eax) // EAX 와 2 와 비교한다.
0x08048416 <main+34>:   je     0x8048439 <main+69> 
// 참이면 0x8048439 이 곳으로 점프 - 아마도 인자의 개수를 비교하는것같다.
/*
그래서 무조건 ./level01 argv[1] 이렇게 입력해주지 않으면 오류가 뜨는것이라고 추측 가능하다.
참이 아니라면 밑에서 계속 된다.
*/
0x08048418 <main+36>:   mov    -0x20(%ebp),%edx // EDX 에 (EBP-32) 값 복사 - EDX 에는 0xbfffdd10 들어감
0x0804841b <main+39>:   mov    0x4(%edx),%eax // EAX 에 (EDX+4) 값 복사 - argv[0] 의 주소 복사
0x0804841e <main+42>:   mov    (%eax),%eax // EAX 에 EAX 복사 - 별 의미없다.
0x08048420 <main+44>:   mov    %eax,0x4(%esp) // (ESP+4) 에 EAX 값 복사 - argv[0] 의 주소 복사
0x08048424 <main+48>:   movl   $0x80485d4,(%esp) // ESP 에 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> // 0x80484b2 이곳으로 점프~

0x08048439 <main+69>:   mov    -0xc(%ebp),%eax 
// EAX 에 (EBP-12) 값 복사 - (EBP-12) 는 정답이 들어가있으므로 EAX 에 정답이 들어감 
0x0804843c <main+72>:   mov    $0xffffffff,%ecx // ECX 에 -1 복사 - NOT 으로 플러스로 변하기때문에 카운터용으로
0x08048441 <main+77>:   mov    %eax,-0x24(%ebp) // (EBP-36) 에 EAX 값 복사 - (EBP-36) 에 정답 복사
0x08048444 <main+80>:   mov    $0x0,%al // AL 에 0 복사

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

0x0804844c <main+88>:   mov    %ecx,%eax // EAX 에 ECX 복사

0x0804844e <main+90>:   not    %eax // -1 을 부호바꿔서 +1 로 - 12

0x08048450 <main+92>:   lea    -0x1(%eax),%edx // EDX 에 (EAX-1) 주소값 복사 
0x08048453 <main+95>:   mov    -0x20(%ebp),%ecx 
// ECX 에 (EBP-32) 값 복사 - ECX 에 argc 주소 0xbfffdd10 가 들어가있음.
0x08048456 <main+98>:   mov    0x4(%ecx),%eax // EAX 에 (ECX+4) 값 복사 - EAX 에 argv[0] 들어감
0x08048459 <main+101>:  add    $0x4,%eax // EAX + 4 - argv[1] 들어감
0x0804845c <main+104>:  mov    (%eax),%ecx // ECX 에 EAX 값 복사 - ECX 에 argv[1] 들어감
0x0804845e <main+106>:  mov    %edx,0x8(%esp) // (ESP+8) 에 EDX 값 복사 - 세번째 인자 전달
0x08048462 <main+110>:  mov    -0xc(%ebp),%eax // EAX 에 (EBP-12) 복사 - EAX 에 정답 omgpassword 들어감
0x08048465 <main+113>:  mov    %eax,0x4(%esp) 
// (ESP+4) 에 EAX 값 복사 - 두번째 인자 전달 - 바로 위의 과정덕에 정답이 나오게됨
0x08048469 <main+117>:  mov    %ecx,(%esp) // ESP 에 ECX 값 복사 - 첫번째 인자 argv[1] 들어감
/*
ESP 를 참조한다는것에 생각을 하면
%edx,0x8(%esp) -> %eax,0x4(%esp) -> %ecx,(%esp) 이 것은
세 개의 인자를 넣는것을 알 수 있다. 원래는 차례대로 나와야 되지만
얼핏 들어본바로는 인텔에서는 함수 호출시 인자가 거꾸로 전달된다고 한다는것을 들어본것 같다.

주소값이 4바이트 이니깐 4바이트씩 증가하고 있다.
*/
0x0804846c <main+120>:  call   0x804830c <strncmp@plt> // 아마도 이 함수에 세 개의 인자가 전달되어서 이 함수에서 비교하나 보다
// strncmp 는 문자열 비교 함수
/*
(gdb) x/s $edx
0xb:     <Address 0xb out of bounds>
(gdb) x/s $eax
0x80485c8:       "omgpassword"
(gdb) x/s $ecx
0xbfffdea1:      "aaaaa"

중요한 부분이기에 확인해보았더니 eax 에서 정답이 나왔다.

0x08048471 <main+125>:  test   %eax,%eax // EAX 가 0 인지 확인

0x08048473 <main+127>:  jne    0x804849f <main+171> // 0x804849f 로 점-점-점-ㅍ- ㅡ
0x08048475 <main+129>:  movl   $0x80485ea,(%esp) // ESP 에 0x80485ea 값 복사 - 0x80485ea:       "Win."
0x0804847c <main+136>:  call   0x80482fc <puts@plt> // Puts 함수 호출
0x08048481 <main+141>:  movl   $0x0,0x8(%esp) // (ESP+8) 에 0 복사

0x08048489 <main+149>:  movl   $0x80485ef,0x4(%esp) // (ESP+4) 에 0x80485ef 값 복사
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)
0x080484a6 <main+178>:  call   0x80482fc <puts@plt> // omgpassword 입력을 못해서 틀렸을경우 Fail. 출력

0x080484ab <main+183>:  movl   $0x0,-0x1c(%ebp) // (EBP-1) 에 0 복사
0x080484b2 <main+190>:  mov    -0x1c(%ebp),%eax // EAX 에 (EBP-28) 값 복사
0x080484b5 <main+193>:  add    $0x30,%esp // ESP+30 스택 공간 초기화

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.

이로서 분석이 끝났다. 내가 이렇게 직접 GDB 로 문제를 분석해보는건 오랜만에 처음이라 힘든 작업이었으나
나의 발전을 위한 과정이라 생각해 이렇게 분석을 100% 하고나니 뿌듯하게 느껴진다.

왜 프로그램을 실행하면 저렇게 나오는지, 왜 정답이 나오는지 이제 100% 알겠다.
앞으로 문제풀때 확실히 이 경험이 도움이 될거라 나는 믿는다. 첫 번째 복습차로 이렇게 복습을 했지만
앞으로도 계속 복습을 해 나가면서 발전해가리라 믿는다.