本想用这个漏洞学习一下syzkaller的,但是最后环境什么的终于搭建好了,但是跑了好久,没跑出这个漏洞,最后不得已,只能关了,下次等熟悉了,在写专门写一篇
一、漏洞情况分析
看一下比较权威的网站关于这个漏洞的描述:
Linux内核UFO到非UFO路径转换时的内存崩溃问题,在构建一个UFO数据包时,内核会使用MSG_MORE __ip_append_data()函数来调用ip_ufo_append_data()并完成路径的添加。但是在这两个send()调用的过程中,添加的路径可以从UFO路径转换为非UFO路径,而这将导致内存崩溃的发生。为了防止UFO数据包长度超过MTU,非UFO路径的copy = maxfraglen – skb->len将会变成false,并分配新的skb。这将会出发程序计算fraggap = skb_prev->len – maxfraglen的值,并将copy = datalen – transhdrlen – fraggap设置为false。
我们把这段话的流程步骤来分一下
Linux内核UFO到非UFO路径转换时的内存崩溃问题,在构建一个UFO数据包时,内核会使用MSG_MORE __ip_append_data()函数来调用ip_ufo_append_data()并完成路径的添加。但是在这两个send()调用的过程中,添加的路径可以从UFO路径转换为非UFO路径,而这将导致内存崩溃的发生。
为了防止UFO数据包长度超过MTU,非UFO路径的copy = maxfraglen – skb->len将会变成false,并分配新的skb。
这将会出发程序计算fraggap = skb_prev->len – maxfraglen的值,并将copy = datalen – transhdrlen – fraggap设置为false。
1、Linux内核UFO到非UFO路径转换时的内存崩溃问题,在构建一个UFO数据包时,内核会使用MSG_MORE __ip_append_data()函数来调用ip_ufo_append_data()并完成路径的添加。但是在这两个send()调用的过程中,添加的路径可以从UFO路径转换为非UFO路径,而这将导致内存崩溃的发生。
首先来分析这个第一句话
UFO和非UFO :
UFO是修改传统的udp包在传输层的传输方式,传统的udp包在网络上传输的数据包不能大于mtu,当用户发送大于mtu的数据报文时,通常会在传输层(或者在特殊情况下在IP层分片,比如ip转发或ipsec时)就会按mtu大小进行分段,防止发送出去的报文大于mtu,为提升该操作的性能,新的网卡硬件基本都实现了UFO功能,可以使分段(或分片)操作在网卡硬件完成,此时用户态就可以发送长度大于mtu的包,而且不必在协议栈中进行分段(或分片)。
UFO到非UFO路径转换:
这句话,是漏洞利用的描述。看一下漏洞利用代码
1 2 3 4 | int s = socket(PF_INET, SOCK_DGRAM, 0 );
int rv = send(s, buffer , size, MSG_MORE); / / MSG_MORE 标志位,表示发送的包使用的是ufo模式
rv = setsockopt(s, SOL_SOCKET, SO_NO_CHECK, &val, sizeof(val)); 通过修改标志位,导致系统如果再次发送的时候,ufo模式,变成了非ufo模式
send(s, buffer , 1 , 0 );
|
这段代码,调用了两次send函数,第一次通过MSG_MORE,标志这个发送的包使用ufo模式,然后通过setsockopt,设置SO_NO_CHECK标志位,将socket的发包模式强行改为非ufo模式,从ufo变成了非ufo模式,然后发送了一个非ufo的包
2、为了防止UFO数据包长度超过MTU,非UFO路径的copy = maxfraglen – skb->len将会变成false,并分配新的skb。
(第一张可能不太清楚,结合第二张看,这是别人画的,我直接拿来用了,怕大家看的麻烦)
如上图,是sk_buff的内存结构图,header的数据区是可以存储,frags[*]里面的这些也是可以存储的,header的数据区很小,应该不是很大,如果一次发送很大的数据,应该是存储到fargs里面的。sk_buff,和skb_shared_info,虽然不是一个结构题,但是内存是相邻的。
ufo是通过skb发送是不需要分包的,能发送超过mtu的数据。
非ufo,每个skb的大小的数据,最大应该也只有当前网卡的mtu的大小,这个数值是可以动态设置的。超过这个mtu大小,就会在ip_ufo_append_data这个函数中被分包
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | while (length > 0 ) {
/ * Check if the remaining data fits into current packet. * /
copy = mtu - skb - > len ; / / 当前skb 还可以copy多少数据 skb - > len = 3512
if (copy < length)
copy = maxfraglen - skb - > len ; / / copy = 对齐的最大的拷贝字节 - skb中已经存在的数据
if (copy < = 0 ) { / / copy< = 0 表示该分配一个新的sk_buff,因为最后一个已被完全填满
char * data; / / copy< = 0 表明有些数据必须从当前ip片段中删除,并移至新片段
unsigned int datalen;
unsigned int fraglen;
unsigned int fraggap;
unsigned int alloclen;
struct sk_buff * skb_prev;
.......
}
|
copy = mtu- 上一个传输数据进来的skb的数据大小,如果上一个传输进来的数据,大于mtu的大小,所以会被分包,进入copy<=0,分配一个skb,将数据拷贝到新分配的数据中。如果上一个分配的是ufo,那么他的数据,是不会分包的,必然超过mtu的大小,然后再通过非ufo发送的时候,会去除原来ufo传输进来的数据,这个时候,会将ufo的包,当成非ufo,进行分包(重新分配一个skb,承载数据)。
3、这将触发程序计算fraggap = skb_prev->len – maxfraglen的值,并将copy = datalen – transhdrlen – fraggap设置为false。
fraggap = ufo传输进来的skb的 - 对齐的最大的拷贝字节,如果fragga不等于0,就会触发拷贝逻辑,将ufo skb的数据拷贝到新分配的skb中。
1 2 3 4 5 6 | copy = datalen - transhdrlen - fraggap; / / 传输层首部和上个SKB多出的数据已复制,接着复制剩下的数据 / / copy = 1480 - 0 - 2012 = - 532
if (copy > 0 && getfrag( from , data + transhdrlen, offset, copy, fraggap, skb) < 0 ) {
err = - EFAULT;
kfree_skb(skb);
goto error;
}
|
二、exp以及漏洞具体分析
前面,指示分析了linux网络驱动出现问题的位置,可能不太好懂,我们结合漏洞利用代码来分析一下,就会很好理解了。
这个漏洞,他的描述中没有写,但是,poc是通过ufo切换到非ufo,然后构造缓冲区溢出。覆盖了函数指针,然后触发这个被覆盖的函数的调用逻辑,进行提权。这里我们从后往前分析
1、被覆盖的函数指针,和缓冲区溢出的位置。
被覆盖的函数指针:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | struct skb_shared_info {
unsigned char nr_frags;
__u8 tx_flags;
unsigned short gso_size;
unsigned short gso_segs;
unsigned short gso_type;
struct sk_buff * frag_list;
struct skb_shared_hwtstamps hwtstamps;
u32 tskey;
__be32 ip6_frag_id;
atomic_t dataref;
void * destructor_arg; / / 这个结构体的这个指针 ,这个指针会在skb被释放的时候,主动调用调用
skb_frag_t frags[MAX_SKB_FRAGS];
};
|
缓冲区溢出的位置(看名字,整个函数好像也只有这个一地方有copy)
1 2 3 4 5 6 7 8 9 | if (fraggap) {
skb - >csum = skb_copy_and_csum_bits(
skb_prev, maxfraglen,
data + transhdrlen, fraggap, 0 );
skb_prev - >csum = csum_sub(skb_prev - >csum,
skb - >csum);
data + = fraggap;
pskb_trim_unique(skb_prev, maxfraglen);
}
|
2、如何造成溢出,如何溢出到函数指针的位置
通过ufo到非uof的方式,来构造溢出环境。mtu为1500。
第一次调用send(s, buffer, size, MSG_MORE);,会调用到__ip_append_data的 ip_ufo_append_data函数,将传入的所有数据放到skb的缓冲区中。
第二次调用send(s, buffer, 1, 0);由于已经设置过标志位了,所以直接越过了ip_ufo_append_data函数,接着往下走非ufo的逻辑了。poc中,传入的数据是3484,到ip_append_data后,upd头先加+8,ip头在+20,=3512
下面的代码是__ip_append_data代码片段,在第二次发送send的时候,才会进入这里(下面代码包括注释摘自 https://cloud.tencent.com/developer/article/1396155 我怕有的朋友看麻烦,就放上了,代码各种注释已经写的很清楚了)``
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 | if (!skb) / / 前面已经传入一次数据了,所以,这个skb肯定不是空的
goto alloc_new_skb;
while (length > 0 ) {
/ * Check if the remaining data fits into current packet. * /
copy = mtu - skb - > len ; / / 当前skb 还可以copy多少数据 skb - > len = 3512
if (copy < length)
copy = maxfraglen - skb - > len ; / / copy = 对齐的最大的拷贝字节 - skb中已经存在的数据
if (copy < = 0 ) { / / copy< = 0 表示该分配一个新的sk_buff,因为最后一个已被完全填满
char * data; / / copy< = 0 表明有些数据必须从当前ip片段中删除,并移至新片段
unsigned int datalen;
unsigned int fraglen;
unsigned int fraggap;
unsigned int alloclen;
struct sk_buff * skb_prev;
alloc_new_skb:
skb_prev = skb;
if (skb_prev)
fraggap = skb_prev - > len - maxfraglen;
else
fraggap = 0 ;
datalen = length + fraggap; / / datalen = 1 + 2012 = 2013
if (datalen > mtu - fragheaderlen) / / 如果剩余的数据一个分片不够容纳,则根据MTU重新计算本次可发送的数据长度
datalen = maxfraglen - fragheaderlen; / / datalen = 1500 - 20 = 1480
fraglen = datalen + fragheaderlen; / / 根据本次复制的数据长度以及IP首部长度,计算三层首部及数据的总长度
if ((flags & MSG_MORE) &&
!(rt - >dst.dev - >features&NETIF_F_SG))
alloclen = mtu; / / 如果后续还有数据输出且网络设备不支持聚合分散I / O,则将MTU作为分配SKB的长度
else
alloclen = fraglen; / / 否则按数据的长度(包括IP首部)分配SKB的空间即可
alloclen + = exthdrlen; / / alloclen = 1500 + 0 = 1500
if (datalen = = length + fraggap)
alloclen + = rt - >dst.trailer_len;
if (transhdrlen) { / / 根据是否存在传输层首部,确定用何种方法分配SKB
skb = sock_alloc_send_skb(sk,
alloclen + hh_len + 15 ,
(flags & MSG_DONTWAIT), &err);
} else {
skb = NULL;
if (atomic_read(&sk - >sk_wmem_alloc) < =
2 * sk - >sk_sndbuf)
skb = sock_wmalloc(sk,
alloclen + hh_len + 15 , 1 ,
sk - >sk_allocation);
if (unlikely(!skb))
err = - ENOBUFS;
}
if (!skb)
goto error;
/ *
* Fill in the control structures
* /
skb - >ip_summed = csummode; / / 填充用于校验的控制信息
skb - >csum = 0 ;
skb_reserve(skb, hh_len); / / 为数据报预留用于存放二层首部、三层首部和数据的空间,并设置SKB中指向三层和四层的指针
/ * only the initial fragment is time stamped * /
skb_shinfo(skb) - >tx_flags = cork - >tx_flags;
cork - >tx_flags = 0 ;
skb_shinfo(skb) - >tskey = tskey;
tskey = 0 ;
/ *
* Find where to start putting bytes.
* /
data = skb_put(skb, fraglen + exthdrlen);
skb_set_network_header(skb, exthdrlen);
skb - >transport_header = (skb - >network_header +
fragheaderlen);
data + = fragheaderlen + exthdrlen; / / data = 20 + 0 = 20
if (fraggap) { / / 如果上一个SKB的数据超过 8 字节对齐MTU,则将超出数据和传输层首部复制到当前SKB,重新计算校验和 fraggap = 2012
skb - >csum = skb_copy_and_csum_bits( / / 并以 8 字节对齐MTU为长度截取上一个SKB的数据
skb_prev, maxfraglen,
data + transhdrlen, fraggap, 0 );
skb_prev - >csum = csum_sub(skb_prev - >csum,
skb - >csum);
data + = fraggap;
pskb_trim_unique(skb_prev, maxfraglen);
}
copy = datalen - transhdrlen - fraggap; / / 传输层首部和上个SKB多出的数据已复制,接着复制剩下的数据 / / copy = 1480 - 0 - 2012 = - 532
if (copy > 0 && getfrag( from , data + transhdrlen, offset, copy, fraggap, skb) < 0 ) {
err = - EFAULT;
kfree_skb(skb);
goto error;
}
offset + = copy; / / 完成本次复制数据,计算下次需复制数据的地址及剩余数据的长度。传输层首部已经复制
length - = datalen - fraggap; / / 因此需要将传输层首部的transhdrlen置为 0 ,同时IPsec首部长度exthdrlen也置为 0 / / length = 2013 - 1480 = 533
transhdrlen = 0 ;
exthdrlen = 0 ;
csummode = CHECKSUM_NONE;
/ *
* Put the packet on the pending queue.
* /
__skb_queue_tail(queue, skb); / / 将复制完数据的SKB添加到输出队列的尾部,接着复制剩下的数据
continue ;
}
|
skb_copy_and_csum_bits这个函数,会拷贝skb数据,到缓冲区,上面传入的缓冲区,刚好是另一个skb的数据区。
如上图,是sk_buff的内存结构图,data的数据区是可以存储,frags[*]里面的这些也是可以存储的,data的数据区很小,应该不是很大,如果一次发送很大的数据,应该是存储到fargs里面的。sk_buff,和skb_shared_info,虽然不是一个结构题,但是内存是相邻的。
skb_copy_and_csum_bits函数,是将数据拷贝到新申请的skb的data数据区了,而这个data,跟skb_shared_info数据是相邻的,这就意味着,如果数据超过data的大小,就能将数据覆盖到skb_shared_info的位置。skb_shared_info就是那个我们要溢出的指针函数所在的内存位置。(这里不具体写了,相关资料里有,这里比较简单)
3、找到发送溢出的位置了,如何溢出到指定的位置
我们直接看poc,发送了3484个字节,而skb_shared_info的位置,在3165,也就是说,需要3164个字节都是来覆盖data数据,mtu=1500,udphead = 8,iphead = 20,3164-1500+8+20=1692,第一个skb填满数据后,开始在新分配的skb进行益处1692+20 = 1712(不需要udp头了),从这里可以推测出data大小= 1712
我们也可以按照代码里里面的算,传入数据3484 + 8+20 = 3512 ,第一个skb所有数据长度。3512+1500=2012 2012-(3484-3164)+20 = 1712。
实际过程中,我们申请了1531个字节,但是返回1728个字节(头部16个字节,从head开始算的),tail=1516 , end = 1728。
所以,1712刚好能覆盖data所有地址,后续溢出的就是skb_shared_info
三、环境搭建
vm虚拟机+ubuntu,参考这个
https://www.anquanke.com/post/id/92755#h2-6
https://cloud.tencent.com/developer/article/1396155
我使用的是linux mint主机,里面装了vm虚拟机,没有使用双机调试,但是gdb附加远程主机的时候,使用桥接的虚拟机的ip不能连接。
然后我试试了本机的ip,然后成功了
target remote localhost:8864
也可以使用qemu 来调试,你和可以使用上面的虚拟机安装的源码,或者自己下载对应的linux版本。不过根文件系统不能太简陋,必须具备一些功能。
本来是想用这个漏洞学习syzkaller的,但是最后出了点问题,不过,我发现他制作文件系统的工具 debootstrap,有兴趣的可以了解下,我用他提供的教程制作了镜像,用于syzkaller测试,但是竟然,连接都失败了,后来解决了一大堆问题,还是无法连接,后来我发现,linux 内核和文件系统不兼容,debootstrap是Debian 提供的,制作的是根文件系统有版本的,后来我找到了这个,然后就一直用这个了
在这里下载镜像 wheezy.img
https://github.com/google/syzkaller/blob/master/docs/syzbot.md
四、漏洞利用技术
1、rop
学术解释:ROP的全称为Return-oriented programming(返回导向编程),这是一种高级的内存攻击技术可以用来绕过现代操作系统的各种通用防御。
个人技术理解解释:
搜索内存中后缀有ret指令的代码片段,构成一段payload,将他们的地址push到一个自定义的栈中,当能控制ip时,进行栈迁移,利用ret指令的特性,连续的执行代码片段
rop条件:
1.后缀有ret的代码片段
2.ip
3.栈迁移
找了个小demo学习了一下
源码地址:https://github.com/vnik5287/kernel_rop
在这个漏洞中,或者在这个demo中,ret代码段,我们可以搜索出来,ip,我们能拿到,那栈迁移那
栈迁移:
我们有ip,但是我们应该只能控制调用到什么地址,让他执行,但是我们如何给sp设置值,又设置成什么值,我们可以构造一个栈进行rop,但是我们只能控制一个被调用地址
如果想要进行栈迁移,先用我们的猪脑子,想一想,自己该怎么办
想想,好像,不太可能有类似的,即使是有条转接指令mov rxx,0xfffxx ,然后 mov rsp,rxx,这样也不行,你这个地址,得多幸运,才能找到啊。
看看具体实例,大神们如何做哪:
1.先找 xchg eax, esp ; ret
2.通过这条指令地址算出栈的地址
3.然后mmap算出来的地址,在这个地址上构造栈。
4.控制的地址是xchg eax, esp ; ret的地址
开始看,我直接懵了,栈迁移那, xchg eax, esp,开始没想通,eax的值,怎么就变成你自己栈的位置了
看我们控制的位置的汇编,call rax 想了想,跳转地址,也就是rax的地址,不就是我控制的吗
rax = (xchg eax, esp ; ret) 的地址 esp=rax低32位,我们申请的栈的位置就是rax低32位的地址
这就已经很明白了,我们跳转到 xchg eax, esp ; ret,rax是可以控制的,也就是说,第一条指令进行了栈迁移,ret进入了rop
这个我们要考虑call的寄存器,最好调用的是call rax我们就 xchg eax, esp 如果是 call rbx ,我们就需要 xchg ebx, esp
总结一下rop利用过程。
首先,找到我们控制ip的位置,反汇编,看看是通过什么方式过来的,是call xxx比如rax
因为我们是可以控制rax的,知道rax的值,当call过去以后,rax的值是不变的,所以我们可以让rax = xchg eax, esp ; ret 这个片段的内存的地址,这样,esp=rax低32位了。我们是事先知道rax的数值的,所以我们可以在rax低32位的地址申请内存,作为rop的栈。
2、 namespace
这个网上查一查。资料基本一大堆,而这个漏洞,需要mtu = 1500,也可以设置成别的数,不过剩下的数据可能要自己计算。同时为了漏洞能够触发,必须找到也给能建立连接,可以让系统进入发送udp包逻辑的网络地址,这无疑,lo最合适,别的地址,ip我们基本没法百分比保证,但是,lo mtu基本都是 65535,设置他的mtu是需要root权限的。是不是很尬尴。在poc里面,巧妙的利用namespace。
我手工测试过namespace,namespace本身也有root一说的,而且,必须用root创建的namespace,才是root的namespace,才能设置网卡,用户创建的,是没有权限的,那么poc中是如何做到的
1 2 3 4 | if (!write_file( "/proc/self/uid_map" , "0 %d 1\n" , real_uid)) {
perror( "[-] write_file(/proc/self/uid_map)" );
exit(EXIT_FAILURE);
}
|
我测试了,这行代码,就可以在不使用root创建namespace的情况下,将namespace的权限变成root的,可以设置网卡了。
五、gdb自动化脚本
gdb也算是老朋友的,经常一调试就是一天,但是,问题在于,敲命令,键盘都敲烂了,各种数据也要自己记录,跑错一次,本次调试全部作废,时间浪费了很多,于是就研究了一下脚本,然后,真香。
官方文档
https://sourceware.org/gdb/wiki/PythonGdbTutorial
没找到中文的,自己看了三天。。。
下面是多线程调试的例子
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 | void br( int a6){
printf( "%s" ,a6);
printf( "main thread lwpid = %u\n" , syscall(SYS_gettid));
}
void * nbi(void * arg)
{
while ( 1 ){
sleep( 1 );
br( 6 );
}
}
int main(){
pthread_t tid,tid2,tid3;
printf( "feafea" );
printf( "main thread lwpid = %u\n" , syscall(SYS_gettid));
pthread_create(&tid, NULL, nbi, NULL);
pthread_create(&tid2, NULL, nbi, NULL);
pthread_create(&tid3, NULL, nbi, NULL);
pthread_join(tid, NULL);
pthread_join(tid2, NULL);
pthread_join(tid3, NULL);
return 0 ;
}
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 | gdb.execute( 'file a.out' , to_string = True )
class MyBreakpoint(gdb.Breakpoint):
def __init__( self , spec, command = ""):
super (MyBreakpoint, self ).__init__(spec, gdb.BP_BREAKPOINT,internal = False )
self .command = command
print (spec)
def stop ( self ):
gdb.write( 'MyBreakpoint\n' )
print ( self .location)
print ( self .number)
print ( self .hit_count)
print ( self .thread)
t = gdb.selected_thread()
print (t.name)
print (t.num)
print (t.global_num)
print (t.ptid)
print (gdb.newest_frame() = = gdb.selected_frame ())
top = gdb.selected_frame()
someVector = top.read_var( 'a6' )
print (someVector)
print (top.name())
print (top.function())
print ( hex (top.pc()))
print (top.older())
print (gdb.invalidate_cached_frames())
print (top.find_sal())
print ( hex (top.read_register( "rcx" )))
return True
gdb.execute( "start" )
MyBreakpoint( 'pygdbtst.c:9 thread 3' )
gdb.execute( 'run' )
|
放几个入门例子
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | bp1 = gdb.Breakpoint( "myFunc()" )
def stopHandler(stopEvent):
for b in stopEvent.breakpoints:
if b = = bp1:
print "myFunc() breakpoint"
else :
print "Unknown breakpoint"
/ / gdb.execute( "continue" )
gdb.events.stop.connect (stopHandler) / / 注册gdb 调试器stop事件的函数
gdb.execute( "run" )
|
1 2 3 4 5 6 7 8 9 | gdb.execute( 'file a.out' , to_string = True ) / / 直接gdb进入,然后选择要执行的文件
class MyBreakpoint(gdb.Breakpoint): / / 继承断掉类
def stop ( self ):
print ( 'MyBreakpoint\n' )
gdb.write( 'MyBreakpoint\n' )
return False / / 继续执行
return True / / 停下来,等待用户交互
MyBreakpoint( 'br' ) / / 给br函数下断点
gdb.execute( 'run' ) / / 执行gdb的命令
|
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 | / / 添加一个gdb命令,这个是gdb的demo,官方文档中有
class _LPrintfBreakpoint(gdb.Breakpoint):
def __init__( self , spec, command):
super (_LPrintfBreakpoint, self ).__init__(spec, gdb.BP_BREAKPOINT,
internal = False )
self .command = command
print ( "init" )
def stop( self ):
print ( "stop" )
gdb.execute( self .command)
return False
class _LPrintfCommand(gdb.Command):
def __init__( self ):
super (_LPrintfCommand, self ).__init__( 'main' , / / main命令,直接在gdb终端输入
gdb.COMMAND_DATA,
gdb.COMPLETE_SYMBOL)
def invoke( self , arg, from_tty): / / mian命令执行函数
print ( "invoke" )
(remaining, locations) = gdb.decode_line(arg)
if remaining is None :
raise gdb.GdbError( 'printf format missing' )
remaining = remaining.strip( ',' )
if locations is None :
raise gdb.GdbError( 'no matching locations found' )
spec = arg[ 0 : - len (remaining)]
_LPrintfBreakpoint(spec, 'printf ' + remaining)
_LPrintfCommand()
|
这个是cve调试的时候使用的,效果很好
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 | from __future__ import with_statement
import gdb
import sys
class MyBreakpoint(gdb.Breakpoint):
def __init__( self , spec, command = ""):
super (MyBreakpoint, self ).__init__(spec, gdb.BP_BREAKPOINT,internal = False )
self .command = command
print ("")
self .is_entry = False
self .queue = 0
self .islevel = 0
def stop ( self ):
t = gdb.selected_thread()
thread_file = "file_thread" + str (t.num)
top = gdb.selected_frame()
thread_file_fd = open (thread_file, "a+" )
function_name = top.name()
thread_file_fd.write(function_name + "\t" )
thread_file_fd.write( "hit_count" + str ( self .hit_count) + "\n" )
if "__ip_append_data" = = function_name:
length = top.read_var( 'length' )
print ( "length:" , length)
if length = = 3492 :
self .islevel = 1
print ( "\n" )
return False
if self .islevel ! = 0 :
self .islevel = self .islevel + 1
sk_buff_head = gdb.parse_and_eval( 'queue' )
self .queue = sk_buff_head
print ( "next:" + str (sk_buff_head[ "next" ]))
print ( "prev:" + str (sk_buff_head[ "prev" ]))
print ( "qlen:" + str (sk_buff_head[ "qlen" ]))
print ( "\n" )
return False
if "ip_generic_getfrag" = = function_name:
if self .islevel = = 2 :
self .islevel = 0
print ( "ip_generic_getfrag" )
try :
print ( hex ( self .queue))
sk_buff_head = self .queue
print ( "next:" + str (sk_buff_head[ "next" ]))
print ( "prev:" + str (sk_buff_head[ "prev" ]))
print ( "qlen:" + str (sk_buff_head[ "qlen" ]))
print ( "\n" )
except Exception as e:
print (e)
return False
thread_file_fd.close()
return False
def start_test():
gdb.execute( 'file a.out' , to_string = True )
gdb.execute( "start" )
MyBreakpoint( 'br' )
def start_cve():
gdb.execute( "target remote localhost:8864" )
MyBreakpoint( '__ip_append_data' )
MyBreakpoint( 'ip_generic_getfrag' )
if __name__ = = '__main__' :
start_cve()
|
问题:
gdb +vm 调试,不能设置多余四个断点,到了就会报错,不知为什么,跟cpu或者内核个数没关系,
相关网站:
https://www.freebuf.com/vuls/154300.html
https://www.anquanke.com/post/id/92755#h2-6
https://cloud.tencent.com/developer/article/1396155 这个界面在百度没搜到,是在google 上搜出来的
分享一个网站,gdb调试的例子很全面
https://wizardforcel.gitbooks.io/100-gdb-tips/content/index.html
望,大佬不要吝啬口水,多喷
[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课