September 15, 2022

FlareOn4-greek_to_me

又感觉好久没写WP。–2022.9.15

0x00 Daily Shell Check

无壳32位

image-20220915094603084

0x01 Analyze Main

首先是 sub_401121 函数,第一个知识点就是Socket通信,这里可知接收了数据然后返回

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
SOCKET __cdecl sub_401121(char *buf)
{
SOCKET v2; // esi
SOCKET v3; // eax
SOCKET v4; // edi
struct WSAData WSAData; // [esp+0h] [ebp-1A0h] BYREF
struct sockaddr name; // [esp+190h] [ebp-10h] BYREF

if ( WSAStartup(0x202u, &WSAData) )
return 0;
v2 = socket(2, 1, 6);
if ( v2 != -1 )
{
name.sa_family = 2;
*(_DWORD *)&name.sa_data[2] = inet_addr("127.0.0.1");
*(_WORD *)name.sa_data = htons(2222u);
if ( bind(v2, &name, 16) != -1 && listen(v2, 0x7FFFFFFF) != -1 )// 监听本地端口为2222的套接字
{
v3 = accept(v2, 0, 0); // 堵塞 等待链接
v4 = v3;
if ( v3 != -1 )
{
if ( recv(v3, buf, 4, 0) > 0 ) // 接收长度为4个字节的数据放到buf
return v4;
closesocket(v4);
}
}
closesocket(v2);
}
WSACleanup();
return 0;
}

(更多的Socket通信逆向可以看 DASCTF2022 X SU-login 该题)

随后在主函数,程序进程SMC解密,而这里的解密实际只异或了一个字节,那么只有256种可能性

image-20220915145427065

同时 sub_4011E6 会验证 40107C 地址的数据是否正确解密,如果成功进入了判断会发送 Congratulations!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
int __usercall sub_401008@<eax>(int _EDI@<edi>, _DWORD *a2@<esi>)
{
_BYTE *v2; // eax
char copy_buf; // dl
int _EBX; // ebx
bool v5; // cf
unsigned int v6; // ett
int v7; // edx
int v9; // [esp+10h] [ebp-1Ch]
char buf[4]; // [esp+24h] [ebp-8h] BYREF
SOCKET s; // [esp+28h] [ebp-4h]
int savedregs; // [esp+2Ch] [ebp+0h] BYREF

s = sub_401121(buf);
if ( !s )
return 0;
v2 = &loc_40107C;
copy_buf = buf[0];
do
{
*v2 = (copy_buf ^ *v2) + 34;
++v2;
}
while ( (int)v2 < (int)&loc_40107C + 0x79 ); // 40107C等待SMC解密的数据
if ( (unsigned __int16)sub_4011E6((unsigned __int8 *)&loc_40107C, 0x79u) == 0xFB5E )
{
_EBX = *(_DWORD *)(v9 + 377554449);
__asm { lock xor bl, [edi+61791C4h] }
v5 = __CFADD__(*(_DWORD *)(8 * (_DWORD)a2 + 0xFB5E), -250248954);
*(_DWORD *)(8 * (_DWORD)a2 + 0xFB5E) -= 250248954;
if ( v9 == 1 )
{
v6 = v5 + 427886322;
v5 = MEMORY[0xFB5E] < v6;
MEMORY[0xFB5E] -= v6;
}
__asm { icebp }
*a2 -= v5 + 530171120;
v7 = *(_DWORD *)(v9 - 1 + 494994972);
__outbyte(6u, 0x5Eu);
*(_DWORD *)(v7 - 17) &= 0xF2638106;
MEMORY[0xFB41] &= 0x66199C4u;
*(a2 - 17) &= 0xE6678106;
*(_DWORD *)(8 * (_DWORD)&savedregs + 0xFB5E + 6) &= 0x69D6581u;
*(_DWORD *)(v7 - 14) -= 107715012;
MEMORY[0xFB07] += 278298362;
*(_DWORD *)((char *)a2 - 18) += 1368424186;
*(_DWORD *)(_EBX + 6) -= 116354433;
*(_DWORD *)(v7 - 23) ^= 0x7C738106u;
send(s, "Congratulations! But wait, where's my flag?", 43, 0);
}
else
{
send(s, "Nope, that's not it.", 20, 0);
}
closesocket(s);
return WSACleanup();
}

0x02 GetFlag

那么了解以上,就可以开始去想方法获得我们的flag了,首先是第一个方法,爆破我们的发送过去的值,如果返回Congratulations 就打印我们所爆破的值

Crack Socket

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import sys
import os
import time
import socket
TCP_IP = '127.0.0.1'
TCP_PORT = 2222
BUFFER_SIZE = 1024
for i in range (0,256):
os.startfile(sys.argv[1])
time.sleep(0.1)
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((TCP_IP, TCP_PORT))
s.send(chr(i))
data = s.recv(BUFFER_SIZE)
s.close()
if 'Congratulations' in data:
print(i)
break

从这里我们可以直接知道我们要的值是0xA2,但不能直接拿到flag,估计是在SMC解密后的内容,于是我们可以IDA调试,随后这里发送字节过去

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import sys
import os
import time
import socket
TCP_IP = '127.0.0.1'
TCP_PORT = 2222
BUFFER_SIZE = 1024

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((TCP_IP, TCP_PORT))
print(type(b'\xA2'))
s.send(b'\xA2')
data = s.recv(BUFFER_SIZE)
print(data)
s.close()

调试至此,GetFlag!

image-20220915145940474

unicorn

那么还有个方法就是 unicorn 模拟执行

参考文章:

https://bbs.pediy.com/thread-223473-1.htm

那么unicorn的本意就是帮我们模拟执行代码,稍稍回忆两个点

那么与之前思路相同,我们模拟执行 sub_4011E6 的代码让该函数验证我们的 loc_40107C(我们尝试解密后的) 是否正确

  1. 先取出我们需要的数据并转成字节
1
2
3
4
5
6
7
8
9
10
11
12
13
CHECKSUM_CODE = binascii.unhexlify(
'55 8B EC 51 8B 55 0C B9 FF 00 00 00 89 4D FC 85 D2 74 51 53 8B 5D 08 56 57 '
'6A 14 58 66 8B 7D FC 3B D0 8B F2 0F 47 F0 2B D6 0F B6 03 66 03 F8 66 89 7D '
'FC 03 4D FC 43 83 EE 01 75 ED 0F B6 45 FC 66 C1 EF 08 66 03 C7 0F B7 C0 89 '
'45 FC 0F B6 C1 66 C1 E9 08 66 03 C1 0F B7 C8 6A 14 58 85 D2 75 BB 5F 5E 5B '
'0F B6 55 FC 8B C1 C1 E1 08 25 00 FF 00 00 03 C1 66 8B 4D FC 66 C1 E9 08 66 '
'03 D1 66 0B C2 8B E5 5D'.replace(' ', ''))
ENCODED_BYTES = binascii.unhexlify(
'33 E1 C4 99 11 06 81 16 F0 32 9F C4 91 17 06 81 14 F0 06 81 15 F1 C4 91 1A '
'06 81 1B E2 06 81 18 F2 06 81 19 F1 06 81 1E F0 C4 99 1F C4 91 1C 06 81 1D '
'E6 06 81 62 EF 06 81 63 F2 06 81 60 E3 C4 99 61 06 81 66 BC 06 81 67 E6 06 '
'81 64 E8 06 81 65 9D 06 81 6A F2 C4 99 6B 06 81 68 A9 06 81 69 EF 06 81 6E '
'EE 06 81 6F AE 06 81 6C E3 06 81 6D EF 06 81 72 E9 06 81 73 7C'.replace(' ', ''))
  1. 那么我们模拟执行的代码如下
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
def emulate_checksum(decoded_bytes):
# establish memory addresses for checksum code, stack, and decoded bytes
address = 0x400000 # 运行地址
stack_addr = 0x410000 # 堆栈地址
dec_bytes_addr = 0x420000 # loc_40107C的数据
# write checksum code and decoded bytes into memory
mu = Uc(UC_ARCH_X86, UC_MODE_32) # 要启动的架构与位数
mu.mem_map(address, 2 * 1024 * 1024) # 加载的地址与大小
mu.mem_write(address, CHECKSUM_CODE) # 将 sub_4011E6 的数据写入运行地址
mu.mem_write(dec_bytes_addr, decoded_bytes)
# place the address of decoded bytes and size on the stack
mu.reg_write(UC_X86_REG_ESP, stack_addr) # 堆栈地址
mu.mem_write(stack_addr + 4, struct.pack('<I', dec_bytes_addr)) # 填好 sub_4011E6 的参数
mu.mem_write(stack_addr + 8, struct.pack('<I', 0x79))
# emulate and read result in AX
mu.emu_start(address, address + len(CHECKSUM_CODE)) # 开始执行
checksum = mu.reg_read(UC_X86_REG_AX) # 读取结果,注意最后的返回值在ax中
return checksum
  1. 最后利用 capstone 打印出SMC后的语句
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
import binascii
import struct
from unicorn import *
from unicorn.x86_const import *
from capstone import *


CHECKSUM_CODE = binascii.unhexlify(
'55 8B EC 51 8B 55 0C B9 FF 00 00 00 89 4D FC 85 D2 74 51 53 8B 5D 08 56 57 '
'6A 14 58 66 8B 7D FC 3B D0 8B F2 0F 47 F0 2B D6 0F B6 03 66 03 F8 66 89 7D '
'FC 03 4D FC 43 83 EE 01 75 ED 0F B6 45 FC 66 C1 EF 08 66 03 C7 0F B7 C0 89 '
'45 FC 0F B6 C1 66 C1 E9 08 66 03 C1 0F B7 C8 6A 14 58 85 D2 75 BB 5F 5E 5B '
'0F B6 55 FC 8B C1 C1 E1 08 25 00 FF 00 00 03 C1 66 8B 4D FC 66 C1 E9 08 66 '
'03 D1 66 0B C2 8B E5 5D'.replace(' ', ''))
ENCODED_BYTES = binascii.unhexlify(
'33 E1 C4 99 11 06 81 16 F0 32 9F C4 91 17 06 81 14 F0 06 81 15 F1 C4 91 1A '
'06 81 1B E2 06 81 18 F2 06 81 19 F1 06 81 1E F0 C4 99 1F C4 91 1C 06 81 1D '
'E6 06 81 62 EF 06 81 63 F2 06 81 60 E3 C4 99 61 06 81 66 BC 06 81 67 E6 06 '
'81 64 E8 06 81 65 9D 06 81 6A F2 C4 99 6B 06 81 68 A9 06 81 69 EF 06 81 6E '
'EE 06 81 6F AE 06 81 6C E3 06 81 6D EF 06 81 72 E9 06 81 73 7C'.replace(' ', ''))

def decode_bytes(i):
decoded_bytes = []
for byte in ENCODED_BYTES:
decoded_bytes.append(((int(byte) ^ i) + 34) & 0xFF)
return bytes(decoded_bytes)

def emulate_checksum(decoded_bytes):
# establish memory addresses for checksum code, stack, and decoded bytes
address = 0x400000 # 运行地址
stack_addr = 0x410000 # 堆栈地址
dec_bytes_addr = 0x420000 # loc_40107C的数据
# write checksum code and decoded bytes into memory
mu = Uc(UC_ARCH_X86, UC_MODE_32) # 要启动的架构与位数
mu.mem_map(address, 2 * 1024 * 1024) # 加载的地址与大小
mu.mem_write(address, CHECKSUM_CODE) # 将 sub_4011E6 的数据写入运行地址
mu.mem_write(dec_bytes_addr, decoded_bytes)
# place the address of decoded bytes and size on the stack
mu.reg_write(UC_X86_REG_ESP, stack_addr) # 堆栈地址
mu.mem_write(stack_addr + 4, struct.pack('<I', dec_bytes_addr)) # 填好 sub_4011E6 的参数
mu.mem_write(stack_addr + 8, struct.pack('<I', 0x79))
# emulate and read result in AX
mu.emu_start(address, address + len(CHECKSUM_CODE)) # 开始执行
checksum = mu.reg_read(UC_X86_REG_AX) # 读取结果,注意最后的返回值在ax中
return checksum

for i in range(0, 256):
decoded_bytes = decode_bytes(i)
checksum = emulate_checksum(decoded_bytes)
if checksum == 0xFB5E:
print('Checksum matched with byte %X' % i)
print('Decoded bytes disassembly:')
md = Cs(CS_ARCH_X86, CS_MODE_32)
for j in md.disasm(decoded_bytes, 0x40107C):
print("0x%x:\t%s\t%s" % (j.address, j.mnemonic, j.op_str))
break

GetFlag!!

image-20220915151325227

Angr

那么该题当然也能 Angr 解题

参考文章

https://blog.csdn.net/qq_42680294/article/details/112976617

思路与unicorn一样,从我们的 sub_4011E6 开始模拟执行,再验证该函数返回值是否为 0xFB5E

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
#!/usr/bin/env python3

import angr
import sys
import binascii

p = angr.Project('greek_to_me.exe', load_options={'auto_load_libs': False}) # 加载一个二进制文件
f2 = None

ENCODED_BYTES = binascii.unhexlify(
'33 E1 C4 99 11 06 81 16 F0 32 9F C4 91 17 06 81 14 F0 06 81 15 F1 C4 91 1A '
'06 81 1B E2 06 81 18 F2 06 81 19 F1 06 81 1E F0 C4 99 1F C4 91 1C 06 81 1D '
'E6 06 81 62 EF 06 81 63 F2 06 81 60 E3 C4 99 61 06 81 66 BC 06 81 67 E6 06 '
'81 64 E8 06 81 65 9D 06 81 6A F2 C4 99 6B 06 81 68 A9 06 81 69 EF 06 81 6E '
'EE 06 81 6F AE 06 81 6C E3 06 81 6D EF 06 81 72 E9 06 81 73 7C'.replace(' ', ''))

# Interate through all of the possible byte values to find the correct "user" input to de-mask the flag
for buf in range(0, 256):

print(("Trying buf = {0}".format(buf)))

# Variable to store the bits written to disk using IDA
asm = None
# Store the output from the first de-obfuscation routine
b2 = []
# Read in bytes written to file from IDA
# with open('greek_to_me_buffer.asm', 'rb') as f:
# asm = f.read()

# Re-implement loc_401039
dl = buf
for byte in ENCODED_BYTES:
#bl = ord(byte)
bl = int(byte)
bl = bl ^ dl
bl = bl & 0xff
bl = bl + 0x22
bl = bl & 0xff
b2.append(bl)

# Set up angr to "run" sub_4011E6
s = p.factory.blank_state(addr = 0x4011E6) # 从0x4011E6开始执行
# 设置 sub_4011E6 的参数
s.mem[s.regs.esp+4:].dword = 1 # Angr memory location to hold the xor'ed and add'ed bytes
s.mem[s.regs.esp+8:].dword = 0x79 # Length of ASM

# Copy bytes output from loc_401039 into address 0x1 so Angr can run it

#asm = ''.join(map(lambda x: chr(x), b2))

asm = bytes(b2)

# 将 loc_40107C 的数据放入该地址
s.memory.store(1, s.se.BVV(int.from_bytes(asm, byteorder='big', signed=False), 0x79 * 8 ))

# Create a simulation manager...
#import pdb; pdb.set_trace()
simgr = p.factory.simulation_manager(s)

# Tell Angr where to go, though there is only one way through this function,
# we just need to stop after ax is set
simgr.explore(find = 0x401268)

# Once ax is set, check to see if the value in ax matches the comparison value
for found in simgr.found:
#import pdb; pdb.set_trace()
print((' ax = %s' % hex(found.solver.eval(found.regs.ax))))
# Comparison check
if hex(found.solver.eval(found.regs.ax)) == '0xfb5e':
# Upon success, dump the asm
code = ("%x" % found.solver.eval_upto(found.memory.load(1, 0x79), 1)[0])
print(('\n Winner is: {0}\n\n'.format(buf)))
print((' %s' % code))
bl = None
dl = None
flag = []
# Using capstone, interpret the ASM
from capstone import *
md = Cs(CS_ARCH_X86, CS_MODE_32)
code = binascii.unhexlify(code)
for i in md.disasm(code, 0x1000):
flag_char = None
#print(i.op_str)
# The if statements do the work of interpreting the ASCII codes to their value counterpart
try:
if i.op_str.split(',')[0].startswith("byte ptr"):
flag_char = chr(int(i.op_str.split(',')[1], 16))
if i.op_str.split(',')[0].startswith('bl'):
bl = chr(int(i.op_str.split(',')[1], 16))
if i.op_str.split(',')[0].startswith('dl'):
dl = chr(int(i.op_str.split(',')[1], 16))
except:
if i.op_str.split(',')[1].strip() == 'dl':
flag_char = dl
if i.op_str.split(',')[1].strip() == 'bl':
flag_char = bl

if (flag_char):
flag.append(flag_char.strip())

print((" 0x%x\t%s\t%s\t%s" %(i.address, i.mnemonic, i.op_str, flag_char)))

print('\n\n')
print((''.join(flag)))

sys.exit(0)

GetFlag!!!

image-20220915153324745

DASCTF X SU
🍬
HFCTF2022
🍪

About this Post

This post is written by P.Z, licensed under CC BY-NC 4.0.