Bash Readline 有用的快捷键
Bash Ctrl+x 一族有许多方便快捷的指令,类似 Emacs 的按键,记录一下。
clear-display (M-C-l)🔗 类似clear指令,达成普通Ctrl+l不消除的 Terminal Scrollbuffertranspose-words (M-t)🔗 可以在两个单词之间,按下进行单词交换。形成单词整体向后移动的效果complete-filename (M-/)🔗 触发文件名补全,甚至还有complete-into-braces (M-{)能自动计算出最短形式。用possible-filename-completions (C-x /)能显示列表。
相似的还有complete-variable (M-$)和complete-command (M-!)等,明确指定要补全的种类undo (C-_ or C-x C-u)🔗 Ctrl+ 下划线容易和窗口缩放冲突,用另一个unix-line-discard (C-u)🔗 在行尾时清除整行insert-comment (M-#)🔗 把当前行变成注释,相当于取消执行,但保留在可视范围内。方便复制backward-kill-word (M-DEL)🔗 普通的unix-word-rubout (C-w)是删除整个不带空格的单词,但这个可以删除 camel case 部分
程序运行在 GDB 与 Shell 中的栈地址有所不同
在进行 Binary Exploitation 章节的挑战时,经常需要通过 GDB + 关闭 ASLR 等方式获取栈上返回地址。但断点调试中地址总是和实际运行中的存在 16 字节或者更多的差异。
实际是 GDB 会以完整路径启动,并且使用的环境变量有所不同,通过比较,相比终端环境多了 LINES 和 COLUMNS 两个变量
可以设置初始化脚本来解决
unset env LINES
unset env COLUMNS在配合上固定环境空间,先导出,然后在另一个环境使用
env > runtime.env
env -i $(cat runtime.env | xargs) gdb ./your_program如果环境变量带空格?或许得
{ env -0; printf "%s\0" "$@"; } | xargs -0 env -i之类的,没试验过
另一个原因是程序参数,也会占用栈的空间
pwntools 会把 ANSI Escape Sequence 原样输出
用 pwntools 写脚本操作比较 fancy 会输出进度条的程序,会遇到输出中一些 ANSI escape code 没有正确输出,反而以反斜杠形式原样输出了。
$ python test.py
[+] Starting local process '/usr/bin/bash': pid 5992
\x1b[2KWorking... ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 100% 0:00:01
\x1b[?25h[*] Stopped process '/usr/bin/bash' (pid 5992)
$ bat test.py -p
import pwn
import time
from rich.progress import track
with pwn.process(["bash"]) as io:
for _ in track(range(10)):
time.sleep(0.1)这里输出中奇怪的内容是 \x1b[2K 和 \x1b[?25h,如果启动的是 reverse bash shell,它的 readline 还可能有 x1b[?2004h,\x1b[200C\x01\x1b[6D\x01 等控制光标的指令,均无法正确渲染。
问题并非出在终端模拟器或者程序输出上,而是目前 pwntools 4.15.0 的 term.put 支持不全,直接或间接触发 pwn.term.init() 后,会替换 sys.stdout,并对对于不认识 ANSI escape sequence 的就原样输出。
ssh.interactive()doesn’t interpret ansi escape codes in output · 议题 #2703 · Gallopsled/pwntools
这里有个 issue 描述了同样的现象,反正是 Python 脚本,手动修改库代码能临时解决问题。
不过走另一条路,用 2026-04-05 > pwntools 读取程序输出的同时也记录在日志文件中 的 tee 直接输出到 stdout 就能绕过这层限制。
def tee(process: pwn.process):
orig_send_raw = process.send_raw
orig_recv_raw = process.recv_raw
output = sys.__stdout__.buffer # type: ignore sys.stdout is replaced by pwn.term
def send_raw(data):
output.write(data)
output.flush()
return orig_send_raw(data)
def recv_raw(numb):
data = orig_recv_raw(numb) or b"" # orig may return str('')
output.write(data)
output.flush()
return data
process.send_raw = send_raw
process.recv_raw = recv_raw
with pwn.process(["/challenge/run"]) as io:
tee(io)
io.recvuntil(b"\r")
io.info("Starting the hacking script...")
io.sendline(b"python3 /tmp/pwnsolver.py hack")
io.recvrepeat(timeout=None) # type: ignorepwntools 在本地通过 SSH 远程解题
在终端中通过 SSH 连接到 200ms+ 遥远的服务器,在上面使用 tmux + vim 远程编程的实在是延迟较高。
即便有内嵌在网页上的 VSCode ,或者本地 VSCode 安装 SSHfs 插件,体验都差强人意。
一种快速调试的方式是,把 challenge 下载下来本地运行,下面这几种我都采用过,各有各的优势
- scp 直接拉取文件
- 访问 pwncollege/intro-to-cybersecurity-dojo: Intro to Cybersecurity 在线预览源码
- 拉取 pwncollege/challenges: pwn.college challenges 使用
pwnshop工具在本地 Docker 启动题目容器
今天想到一种更流水线的模式,自动上传文件在远程执行。比如这是解决 intro-to-cybersecurity-dojo / integrated-security / secure-chat-1 的框架:
- 在开发设备上运行脚本,会 SSH 到 challenge 机器,并 SFTF 把当前文件传上去启动运行
- 脚本检测当前是 challenge 机器,进行解题
- 这题有特殊要求,需要在
/challenge/run启动的全新 shell 中进行操作 - 所以
boot()中在 shell 中运行hack()
- 这题有特殊要求,需要在
- 这里的 tee 用来解决 pwntools 会把 ANSI Escape Sequence 原样输出 问题
- 使用
io.recvrepeat()不断触发 tee 的打印操作 - 使用
io.wait(),io.shutdown()来在 EOF 后停止程序 - 在 Python 3.14 上运行如果抛出
opcode LOAD_SMALL_INT not allowed错误,需要手动合并 https://github.com/Gallopsled/pwntools/pull/2627 中对 safeeval.py 的变更
import pwn
import os
import sys
remote_script = "/tmp/pwnsolver.py"
def tee(process: pwn.tube):
orig_send_raw = process.send_raw
orig_recv_raw = process.recv_raw
output = sys.__stdout__.buffer # type: ignore sys.stdout is replaced by pwn.term
def send_raw(data, *args, **kwargs):
output.write(data)
output.flush()
return orig_send_raw(data, *args, **kwargs)
def recv_raw(numb, *args, **kwargs):
data = orig_recv_raw(numb, *args, **kwargs) or b"" # orig may return str('')
output.write(data)
output.flush()
return data
process.send_raw = send_raw
process.recv_raw = recv_raw
def main():
# raw to skip checksec
ssh = pwn.ssh(user="hacker", host="dojo.pwn.college", raw=True)
local_script = os.path.abspath(__file__)
ssh.upload(local_script, remote_script)
argv = ["/run/dojo/bin/python3", remote_script]
# requires patch from https://github.com/Gallopsled/pwntools/pull/2627 on Python 3.14+
io: pwn.tubes.ssh.ssh_process
io = ssh.process(argv, argv[0], aslr=True) # type: ignore
tee(io)
# keep triggering tee's recv_raw until remote process joins
io.recvrepeat()
io.wait()
def boot():
with pwn.process(["/challenge/run"]) as io:
tee(io)
# wait for signal of bootup completion
io.recvuntil(b"\r")
io.info("Starting the hacking script...")
io.sendline(f"python3 {remote_script} hack".encode())
# keep triggering tee's recv_raw until it receives the stop signal
io.recvuntil(b"pwn.college{")
# send EOF to stop the process
io.shutdown()
io.info("Done! Check the output above for the flag.")
def hack():
pass
if __name__ == "__main__":
if sys.argv[1:2] == ["hack"]:
hack()
elif os.environ.get("DOJO_AUTH_TOKEN"):
boot()
else:
main()