x86中参数都是保存在栈上,但在x64中前六个参数依次保存在RDI, RSI, RDX, RCX, R8和 R9寄存器里,如果还有更多的参数的话才会保存在栈上。这样传参就有点难受,但是程序在编译过程中会加入一些通用函数用来进行初始化操作(比如加载libc.so的初始化函数),所以虽然很多程序的源码不同,但是初始化的过程是相同的,因此针对这些初始化函数,我们可以提取一些通用的gadgets加以使用,从而达到我们想要达到的效果。
__libc_csu_init函数(1-3参数)
3参数
输入 objdump -d 文件名 :反汇编文件中的需要执行指令的那些section
找到 -- 有以下代码
1.执行gad1
Copy . text : 000000000040089A pop rbx #必须为0
. text : 000000000040089B pop rbp #必须为1
. text : 000000000040089C pop r12 #call(由于下面call指令的寻址方式为间接寻址,所以此处应为got表地址)
. text : 000000000040089E pop r13 #arg3
. text : 00000000004008A0 pop r14 #arg2
. text : 00000000004008A2 pop r15 #arg1
. text : 00000000004008A4 retn —— > #to gad2
2.再执行gad2
Copy . text : 0000000000400880 mov rdx , r13
. text : 0000000000400883 mov rsi , r14
. text : 0000000000400886 mov edi , r15
. text : 0000000000400889 call qword ptr [ r12 + rbx * 8 ] call
. text : 000000000040088D add rbx , 1
. text : 0000000000400891 cmp rbx , rbp
. text : 0000000000400894 jnz short loc_400880
. text : 0000000000400896 add rsp , 8
. text : 000000000040089A pop rbx
. text : 000000000040089B pop rbp
. text : 000000000040089C pop r12
. text : 000000000040089E pop r13
. text : 00000000004008A0 pop r14
. text : 00000000004008A2 pop r15
. text : 00000000004008A4 retn —— > #构造一些垫板(7*8=56byte)就返回了
这样的话
Copy r13 =rdx =arg3
r14 =rsi =arg2
r15 =edi =arg1
r12 =call address
1-2参数
还有一个老司机才知道的x64 gadgets,就是 pop rdi,ret的gadgets。这个gadgets还是在这里,但是是由opcode错位产生的。
如上的例子中4008A2、4008A4两句的字节码如下
意思是pop r15,ret,但是恰好pop rdi,ret的opcode如下
因此如果我们指向0x4008A3就可以获得pop rdi,ret的opcode,从而对于单参数函数可以直接获得执行,这是1个参数的情况。
与此类似的,还有0x4008A1处的
那么这个有什么用呢?我们知道x64传参顺序是rdi,rsi,rdx,rcx。
所以rsi是第二个参数,我们可以在rop中配合pop rdi,ret来使用pop rsi,pop r15,ret,这样就可以轻松的调用2个参数的函数。
_dl_runtime_resolve(6参数)
我们把_dl_runtime_resolve
反编译可以得到:
Copy 0x7ffff7def200 <_dl_runtime_resolve>: sub rsp,0x38
0x7ffff7def204 <_dl_runtime_resolve+4>: mov QWORD PTR [rsp],rax
0x7ffff7def208 <_dl_runtime_resolve+8>: mov QWORD PTR [rsp+0x8],rcx
0x7ffff7def20d <_dl_runtime_resolve+13>: mov QWORD PTR [rsp+0x10],rdx
0x7ffff7def212 <_dl_runtime_resolve+18>: mov QWORD PTR [rsp+0x18],rsi
0x7ffff7def217 <_dl_runtime_resolve+23>: mov QWORD PTR [rsp+0x20],rdi
0x7ffff7def21c <_dl_runtime_resolve+28>: mov QWORD PTR [rsp+0x28],r8
0x7ffff7def221 <_dl_runtime_resolve+33>: mov QWORD PTR [rsp+0x30],r9
0x7ffff7def226 <_dl_runtime_resolve+38>: mov rsi,QWORD PTR [rsp+0x40]
0x7ffff7def22b <_dl_runtime_resolve+43>: mov rdi,QWORD PTR [rsp+0x38]
0x7ffff7def230 <_dl_runtime_resolve+48>: call 0x7ffff7de8680 <_dl_fixup>
0x7ffff7def235 <_dl_runtime_resolve+53>: mov r11,rax
0x7ffff7def238 <_dl_runtime_resolve+56>: mov r9,QWORD PTR [rsp+0x30]
0x7ffff7def23d <_dl_runtime_resolve+61>: mov r8,QWORD PTR [rsp+0x28]
0x7ffff7def242 <_dl_runtime_resolve+66>: mov rdi,QWORD PTR [rsp+0x20]
0x7ffff7def247 <_dl_runtime_resolve+71>: mov rsi,QWORD PTR [rsp+0x18]
0x7ffff7def24c <_dl_runtime_resolve+76>: mov rdx,QWORD PTR [rsp+0x10]
0x7ffff7def251 <_dl_runtime_resolve+81>: mov rcx,QWORD PTR [rsp+0x8]
0x7ffff7def256 <_dl_runtime_resolve+86>: mov rax,QWORD PTR [rsp]
0x7ffff7def25a <_dl_runtime_resolve+90>: add rsp,0x48
0x7ffff7def25e <_dl_runtime_resolve+94>: jmp r11
从0x7ffff7def235开始,就是这个通用gadget的地址了。通过这个gadget我们可以控制rdi,rsi,rdx,rcx, r8,r9的值。但要注意的是_dl_runtime_resolve
()在内存中的地址是随机的。所以我们需要先用information leak得到_dl_runtime_resolve
()在内存中的地址。那么_dl_runtime_resolve
()的地址被保存在了哪个固定的地址呢?
通过反编译level5程序我们可以看到email protected 这个函数使用PLT [0] 去查找write函数在内存中的地址,函数jump过去的地址*0x600ff8其实就是_dl_runtime_resolve
()在内存中的地址了。所以只要获取到0x600ff8这个地址保存的数据,就能够找到_dl_runtime_resolve
()在内存中的地址:
Copy 0000000000400420 <[email protected]>:
400420: ff 35 ca 0b 20 00 pushq 0x200bca(%rip) # 600ff0 <_GLOBAL_OFFSET_TABLE_+0x8>
400426: ff 25 cc 0b 20 00 jmpq *0x200bcc(%rip) # 600ff8 <_GLOBAL_OFFSET_TABLE_+0x10>
40042c: 0f 1f 40 00 nopl 0x0(%rax)
gdb-peda$ x/x 0x600ff8
0x600ff8 <_GLOBAL_OFFSET_TABLE_+16>: 0x00007ffff7def200
gdb-peda$ x/21i 0x00007ffff7def200
0x7ffff7def200 <_dl_runtime_resolve>: sub rsp,0x38
0x7ffff7def204 <_dl_runtime_resolve+4>: mov QWORD PTR [rsp],rax
0x7ffff7def208 <_dl_runtime_resolve+8>: mov QWORD PTR [rsp+0x8],rcx
0x7ffff7def20d <_dl_runtime_resolve+13>: mov QWORD PTR
[rsp+0x10],rdx
另一个要注意的是,想要利用这个gadget,我们还需要控制rax的值,因为gadget是通过rax跳转的:
Copy 0x7ffff7def235 <_dl_runtime_resolve+53>: mov r11,rax
……
0x7ffff7def25e <_dl_runtime_resolve+94>: jmp r11
所以我们接下来用ROPgadget查找一下libc.so中控制rax的gadget:
Copy ROPgadget --binary libc.so.6 --only "pop|ret" | grep "rax"
0x000000000001f076 : pop rax ; pop rbx ; pop rbp ; ret
0x0000000000023950 : pop rax ; ret
0x000000000019176e : pop rax ; ret 0xffed
0x0000000000123504 : pop rax ; ret 0xfff0
0x0000000000023950刚好符合我们的要求。有了pop rax和_dl_runtime_resolve这两个gadgets,我们就可以很轻松的调用想要的调用的函数了。