PWN BASIC
一些指令
objdump -d -M intel (文件名) #查看汇编源码(加intel是想看ASM版本的,默认是AT&T)
readelf -a (文件名) #查看elf文件头信息等
ldd (文件名) #看binary链接到了哪些函数库
nano /root/.gdbinit #同时安装了pwndbg和peda,在这里面注释切换
ps -a #查看所有进程,找到目标进程,在gdb里用attach + 进程id调试
#与之配合的,可以在exp相应位置加个空的input()暂停
context.arch = ‘amd64’ #exp里指明是x64的架构,x86的话写’i386’
#context(os=’linux’, arch=’amd64’, log_level=’debug’)
readelf -a libc-2.27.so | grep “puts” #可以找到当前版本puts的偏移
strings libc-2.27.so -tx | grep ‘/bin/sh’ #x是代表16进制,找到目标字符串的偏移
libc = cdll.LoadLibrary(‘libc.so.6’) #加载指定版本的libc文件(py语句,需要from ctypes import *)然后就可以使用比如libc.srand(0), libc.rand()
p = process([‘./pwn’],env={“LD_PRELOAD”:”./libc-2.23.so”})
#改变环境参数,使用给定libc来运行
一些笔记
基础
·映射到内存之后,stack是从0x7f开头的地址往低位延伸,heap是从0x60开头的地址往高位延伸
·x64调用栈参数入栈顺序:
一般调用:rdi, rsi, rdx, rcx, r8, r9, stack
系统调用:rdi, rsi, rdx, r10, r8, r9, stack(系统调用号存在rax寄存器)
·Stack Frame
function prologue
例子:比如说
4004e7: push rbp
4004e8: mov rbp, rsp
4004eb: sub rsp, 0x10 (这里让rsp减掉0x10空出来的区域可以放local variable)
400522: call 4004e7
400527: …
在0x400522调用0x4004e7时,push进去的其实是0x400527(也就是0x400522的下一句)
保存return地址是call来完成的
所以其实流程是:先是被call(push return address & 更新rip),然后是上面那三步,最后传参
main函数在被加载之前,其实还有一个return address(比如说__libc_start_main+231)
function epilogue
例子:
400513: leave
400514: ret
leave差不多就是:
mov rsp, rbp
pop rbp (因为pop指令,rsp又会自增0x8,刚好就回到了之前的存进来的rbp
然后ret就把当前rsp指向的return address pop到rip里面去,rsp就回 到了原栈顶)
关于lazy binding机制
got表一开始存的并不是真正的libc函数的地址,就拿puts来说,在调用puts的时候其实是去call puts函数plt表上的地址,而puts的plt表(也就是图上0x400550那里),就是一系列去访问真正的puts的指令。其中第一条jmp QWORD PTR [0x601018]其实就是跳转到puts的got表。
puts的got表一开始存的是0x400550那里调过来的那条指令的下一条的地址(puts@plt+6)。然后push 0x0是一个它的index号(之后会用到)。
接着就会jmp到一个plt都会jmp到的地方,调用<_dl_runtime_resolve_xsave>函数来进行绑定。
然后puts@got就被更新成了libc上真正调用puts的地址。之后再次调用puts@plt时,jmp到puts@got就会是真实地址了,不需要再做之后繁琐的步骤了。
在lazybiding的过程中可以看出来,got表其实是可写的,如果能改写被调用的libc函数的got表内容,(比如把上面那个例子0x601018那里的内容改成system),就达到了GOT Hijacking的效果。
Use PLT as Gadget
在利用plt调用函数时(例如system@plt),有时需要多塞一个ret,保证地址最低位是0x10,而不是0x08。在调用system@plt的时候,会用到一个128bit的寄存器,如果地址没有以0x10结尾,就会crash掉。(栈对齐)
ret2libc
泄露libc的地方有很多,比如stack上(像main函数之前会有一个return address 是__libc_start_main+231这种),heap上的arena信息,got表。
(更多利用方式参考后面的文章)