Linux/Linux_technic

Strcmp 부분 gdb로 암호 문자열 크랙하기

sosal 2010. 8. 29. 09:22
반응형

/*
 http://sosal.tistory.com/
 * made by so_Sal
 */


가끔 프로그램이, 변수에 어떤 값이 들어가있는지 궁금할 때가 있다.

그때는 심볼이나 디버깅 정보를 삽입하여, gdb에서 watch 명령어나
직접적으로 메모리에 접근하여 정보를 가져오는방법, 레지스터를 확인하는 방법
등이 있는데, 이 포스팅에서 레지스터가 가리키는 문자열 변수에 어떤 값이 들어가있는지
GDB 디버깅 툴을 이용하여 확인하는 방법을 알아보려고 한다.

<간단한 예제를 통해서, 패스워드를 크랙하여보자>
(답) ANUG9LMRKOB^IS_SOSAL


#include<stdio.h>
#include<string.h>

int main(){

    char password[] = "MY_PASSWORD_IS_SOSAL";
    char input[100] = "";

    int length = strlen(password);
    int i,j;

    for(j=0;j<13;j++){
        for(i=0;i<j;i++){
            password[i]--;
        }
    }
    printf("password : ");
    scanf("%s",input);

    if(!strcmp(password,input))
        printf("very good!\n");
    else
        printf("retry!\n");

    return 0;
}

 


물론 위 프로그램을 C언어로 다시 작성하여, password 배열의 값을
출력하면 그만이겠지만, 분석하는 사람이 꼭 프로그래머 이란 법은 없다.
(물론 아이다와 같은 초호화 디버깅 툴이 있지만.. GDB로 한번 해보자)

strcmp() 부분에서, 만들어진 패스워드와 (password) 사용자가 입력한 변수(input)을 비교한다.
비교하기 전에는 각 레지스터에 문자열을 담아 strcmp 함수에 보낼것이다. (매개변수 역할)

strcmp로 보내는 매개변수 (즉 레지스터가 가리키는 변수의 주소)를 체크하면
패스워드를 쉽게 알아낼 수 있게된다.

그럼 gdb로, 어디가 strcmp 부분인지 알아보자.
이부분 역시 아이다와 같은 호화형 디버깅 툴을 쓰면 빨리 찾을 수 있겠지만,
올리디버거나 GDB는 동작중에 제어가 용이하기 때문에 충분히 공부해볼 가치가 있다.

[sosal@localhost binary]$ gdb -q ./strcmp
(no debugging symbols found)

(gdb) disas main <-- disassemble

Dump of assembler code for function main:
0x08048444 <main+0>:    lea    0x4(%esp),%ecx
0x08048448 <main+4>:    and    $0xfffffff0,%esp
0x0804844b <main+7>:    pushl  -0x4(%ecx)
0x0804844e <main+10>:   push   %ebp
0x0804844f <main+11>:   mov    %esp,%ebp
0x08048451 <main+13>:   push   %edi
0x08048452 <main+14>:   push   %ecx
0x08048453 <main+15>:   sub    $0xa0,%esp
0x08048459 <main+21>:   mov    0x8048671,%eax
0x0804845e <main+26>:   mov    %eax,-0x29(%ebp)
............
.. 중략 ..
............
0x08048512 <main+206>:  jle    0x80484e6 <main+162>
0x08048514 <main+208>:  movl   $0x8048650,(%esp)
0x0804851b <main+215>:  call   0x8048338 <printf@plt // <-- 심볼이 살아있기때문에
0x08048520 <main+220>:  lea    -0x8d(%ebp),%eax       //  print함수가 바로 보인다.
0x08048526 <main+226>:  mov    %eax,0x4(%esp)
0x0804852a <main+230>:  movl   $0x804865c,(%esp)
0x08048531 <main+237>:  call   0x8048328 <scanf@plt // scanf 역시 마찬가지..
0x08048536 <main+242>:  lea    -0x8d(%ebp),%eax
0x0804853c <main+248>:  mov    %eax,0x4(%esp)
0x08048540 <main+252>:  lea    -0x29(%ebp),%eax
0x08048543 <main+255>:  mov    %eax,(%esp)
0x08048546 <main+258>:  call   0x8048358 <strcmp@plt> // 역시 strcmp 함수도 보인다.
0x0804854b <main+263>:  test   %eax,%eax
0x0804854d <main+265>:  jne    0x804855d <main+281>
0x0804854f <main+267>:  movl   $0x804865f,(%esp)
0x08048556 <main+274>:  call   0x8048348 <puts@plt>
0x0804855b <main+279>:  jmp    0x8048569 <main+293>
0x0804855d <main+281>:  movl   $0x804866a,(%esp)
0x08048564 <main+288>:  call   0x8048348 <puts@plt>
0x08048569 <main+293>:  mov    $0x0,%eax
0x0804856e <main+298>:  add    $0xa0,%esp
0x08048574 <main+304>:  pop    %ecx
0x08048575 <main+305>:  pop    %edi
0x08048576 <main+306>:  pop    %ebp
0x08048577 <main+307>:  lea    -0x4(%ecx),%esp
0x0804857a <main+310>:  ret
End of assembler dump.

그렇다면.. 우리가 유심히 봐야할곳은
0x08048540 <main+252>:  lea    -0x29(%ebp),%eax
0x08048543 <main+255>:  mov    %eax,(%esp)
0x08048546 <main+258>:  call   0x8048358 <strcmp@plt> // 역시 strcmp 함수도 보인다.
0x0804854b <main+263>:  test   %eax,%eax

이곳이 아니겠는가?,
mov %eax,(%esp)를 보아 하니
esp가 가리키는곳이 곧, 암호화된 패스워드가 존재하는 곳이고,
그 위치를 eax에다 가져놓으니, eax가 패스워드를 넘기는 매개변수라고 볼 수 있겠다.

그럼 main+258 위치에서, eax라는 레지스터에 패스워드가 저장된다는걸 예상 했으니
직접 문자열을 출력해볼까?

일단 main+258에 break-point를 걸고, 그곳에서 멈춰보자.
(gdb) b *main+258            <-- break-point
Breakpoint 1 at 0x8048546
(gdb) r                            <-- run
password : sosal

Breakpoint 1, 0x08048546 in main ()

(gdb) info reg                <-- 레지스터 값 확인
eax            0xbfb3810f       -1078755057
ecx            0xbb4420 12272672
edx            0x0      0
ebx            0xbb3ff4 12271604
.... 이하 생략 .....

드디어 strlen 함수의 매개변수로 넘기는 부분에 도착하했다.
그럼 여기서 당장 레지스터값들의 아스키값을 출력하면
답을 볼 수 있다.