前言:在新生基础知识 1 我们学习了 ret2text 的利用手法,当然初步学习 pwn 一定是有很多很多并不理解的东西,文章写得过于详细便会显得啰嗦繁杂,写的过于简略又会导致跳步过大而难以理解。因此文章主要是用来讲解一些题目的基本思路,细节问题可以私聊。另外这是老学长曾经编写的博客,大家也可以去拜读:https://www.yuque.com/cyberangel/rg9gdm/glzdgr,这篇文章是讲述 ret2shellcode 的,也正好是我们这篇文章要讲述的内容,可以都看看,互补缺漏。
本来准备讲述 ret2shellcode 的时候是准备在靶场上找一道例题的,不过在最近打比赛的时候遇到了一道基于某计算器程序的真实漏洞,一时开始思考可不可以编写一些贴近新生认知的题目,让他们在学习 pwn 的时候可以更深刻得理解 pwn 的内涵,是用来干什么的,题目漏洞是从哪里出来的。

后来思考了一下,很多新生都学过了 C 语言的基础知识,于是这里挑了一道大家熟知的算法题去当做 pwn 题目
事实上即使存在 pwn 漏洞,作为只考虑样例和超时情况的算法题确实可以 AC:/ 洛谷:杨辉三角 /

AC 代码如下:
1 | // gcc main.c -o main -fno-stack-protector -no-pie -z execstack -Wall -Wextra |
Ret2Shellcode:
说了挺多,现在终于进入正题了。 ret2shellcode 核心逻辑非常直接:当程序存在栈溢出漏洞,且目标内存区域,通常是栈内存,具有可执行权限Executable, 简称 X 权限时,攻击者将自定义的机器指令(Shellcode)写入栈中,然后通过覆盖函数的返回地址,让程序跳转到这段 Shellcode 的起始位置去执行,从而获取系统权限
使用条件:
由于我们是要将 shellcode 写道栈当中,因此程序编译时不能开启 NX (No-eXecute))。如果开启了 NX,栈上的数据就会被标记为不可执行,跳转过去程序会直接 Segmentation Fault Crash。
那么什么是 shellcode?
shellcode 在逆向题目中也有出现, 是一段可执行的汇编二进制代码指令,主要用于如缓冲区溢出漏洞利用。
"\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\xb0\x0b\xcd\x80"就是一串简短的 x32 系统中调用 execve("/bin/sh")的 shellcode。
事实上由于纯数字写出来过于繁琐,pwntools 集成了一种可以直接利用汇编转换字节码的方式:
1 | shellcode = asm(""" |
如上 asm 就可以把汇编文本自动编译成可以直接注入内存的十六进制字节串。
那么问题又来了?什么是汇编语言?汇编语言又如何去调用后门函数?
汇编语言:
关键寄存器(以 x86-64 为例)
- 通用寄存器(存数据与参数):
RAX:通常用于存放函数的返回值或系统调用号。RDI,RSI,RDX,RCX,R8,R9:在 Linux 64 位下按顺序用于传递函数的前 6 个参数。
- 指针寄存器(追踪执行与内存):
RIP:指令指针,指向 下一个要执行的指令地址。RSP:栈顶指针,时刻指向当前内存栈的顶部。RBP:栈帧基址指针,指向当前函数栈帧的底部(用于定位局部变量)。
常用操作指令
mov a, b:数据传输(将b的值赋给a)。xor/and/or a, b:逻辑运算(xor eax, eax常用作快速清零)。push x:入栈(RSP地址减小,把x放入栈顶)。pop x:出栈(把栈顶数据放入x,RSP地址增加)。jmp addr:无条件跳转到指定地址执行。
这里简单进行罗列,大概了解一些寄存器知识和基础传参调用即可。
我的建议是遇到什么学什么,等到有一定基础之后可以去阅读《汇编语言第四版》王爽编著的这本书。这本书已经放到招新群群文件当中了。这个也可以去看:https://blog.csdn.net/hanmo22357/article/details/127883179
题目:杨辉三角:
上面没有使用大量篇幅去讲述汇编语言,网上有很多很多资料,自行查找即可,也可以私聊询问。
1 | // gcc main.c -o main -fno-stack-protector -no-pie -z execstack -Wall -Wextra |
这个就是算法题目 AC 的源码,实际上这道题目也是存在 pwn 漏洞的。我们把他编译成二进制文件
考虑到不存在充足的 gadgets 去跳转栈地址,我们这里直接把栈地址个送出来printf("%p\n", (void *)s);
其实一开始是想补充一个 jmp rsp 之类的 gadgets 的。
1 | gcc main.c -o main -fno-stack-protector -no-pie -z execstack -Wall -Wextra |
通过这行命令去编译,我们随后也会把编译好的二进制文件发放在群里。
解题思路
直接打开 ida 找到 main 函数,利用 F5 反汇编:

我们发现 scanf(“%s”,s)` 没有限制长度,可以覆盖 main() 的返回地址;在最开始也会泄露 s 的栈地址。
atoi(s) 存在一个特性,只看开头那段十进制数字,遇到第一个非数字就停,不会检查后面的内容,但是后面的内容仍会当做字符串存入缓冲区,有点类似 \x00 截断的特性。
那么我们可以利用 scanf 读取一个长字符串进行栈溢出,在 1X 后面写一段 shellcode,然后跳转执行 shellcode

运行一下发现的确可以截取到栈地址。直接截取:
1 | from pwn import* |

构造 shellcode:
1 | shellcode = asm(""" |
shellcode 就是利用汇编语言触发后门函数的一段汇编指令,如上,已添加注释
构造 payload:
之后只要我们能够利用 1X 去截断 atoi 函数,然后在后面接上 shellcode,利用缓冲区溢出劫持返回地址到 shellcode 的地址,就可以执行 shellcode 从而获取 shell:
1 | from pwn import* |

说些什么吧!