一些指令

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表。

1637821373564.png

puts的got表一开始存的是0x400550那里调过来的那条指令的下一条的地址(puts@plt+6)。然后push 0x0是一个它的index号(之后会用到)。

1637821433924.png

接着就会jmp到一个plt都会jmp到的地方,调用<_dl_runtime_resolve_xsave>函数来进行绑定。

然后puts@got就被更新成了libc上真正调用puts的地址。之后再次调用puts@plt时,jmp到puts@got就会是真实地址了,不需要再做之后繁琐的步骤了。

1637821473142.png

在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表。


(更多利用方式参考后面的文章)