一些信号量函数的学习
信号量的注册在init函数中
v2.sa_handler = (__sighandler_t)sub_400E1D;
v2.sa_flags = 4;
sigaction(34, &v2, &v1);
sigaction()
函数定义为
int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);
sigaction 结构体定义如下
struct sigaction {
void (*sa_handler) (int);
sigset_t sa_mask;
int sa_flags; // 用来设置信号处理的相关操作
void (*sa_restorer) (void);
}
关于 sa_flags
的定义如下 (from linux/include/uapi/asm-generic/signal-defs.h
)
#ifndef SA_NOCLDSTOP
#define SA_NOCLDSTOP 0x00000001
#endif
#ifndef SA_NOCLDWAIT
#define SA_NOCLDWAIT 0x00000002
#endif
#ifndef SA_SIGINFO
#define SA_SIGINFO 0x00000004
#endif
/* 0x00000008 used on alpha, mips, parisc */
/* 0x00000010 used on alpha, parisc */
/* 0x00000020 used on alpha, parisc, sparc */
/* 0x00000040 used on alpha, parisc */
/* 0x00000080 used on parisc */
/* 0x00000100 used on sparc */
/* 0x00000200 used on sparc */
#define SA_UNSUPPORTED 0x00000400
#define SA_EXPOSE_TAGBITS 0x00000800
/* 0x00010000 used on mips */
/* 0x00800000 used for internal SA_IMMUTABLE */
/* 0x01000000 used on x86 */
/* 0x02000000 used on x86 */
/*
* New architectures should not define the obsolete
* SA_RESTORER 0x04000000
*/
#ifndef SA_ONSTACK
#define SA_ONSTACK 0x08000000
#endif
#ifndef SA_RESTART
#define SA_RESTART 0x10000000
#endif
#ifndef SA_NODEFER
#define SA_NODEFER 0x40000000
#endif
#ifndef SA_RESETHAND
#define SA_RESETHAND 0x80000000
#endif
题目中使用的是 SA_SIGINFO
,对信号处理程序提供了附加信息:一个指向 siginfo
结构的指针以及一个指向上下文标识符的指针
比如上述部分就是将 sub_400E1D
函数注册为34信号的处理函数
主程序中,为 2
信号注册了一个处理函数,这个函数是用于对最终加密结果进行判断的,随后进行了一个初始化,接下来进入一个死循环,等待子程序发送 2
信号
接下来需要了解是如何读取指令的
子程序中调用了函数 sub_400A0D(dword_4019C0, s1);
,其中,第一个参数是vm的指令码,s1是输入的 flag
这个函数是由 while
循环和 switch
语句实现的
v41 = a1[(unsigned __int8)qword_6030C8[20]];
if ( v41 == 23 )
break;
++qword_6030C8[20];
这里可以看出来 a1
存储了vm程序的机器码,qword_6030C8[20]
存放的是 eip
switch ( v41 )
{
case 0:
case 8:
case 9:
case 10:
case 12:
case 13:
case 14:
case 17:
case 19:
case 20:
v2 = qword_6030C8[20];
qword_6030C8[20] = v2 + 1;
*v42 = a1[v2];
break;
default:
break;
}
这个 switch
中判断指令码是否为 0, 8, 9 ...
,如果是,将会进行 eip+1
的操作,并再从机器码中取出一位,说明这些指令含有参数
接下来选择几个指令,分析是如何传参的
case 0:
v3 = getppid();
sigqueue(v3, 34, (const union sigval)v42);
break;
0
指令发送了 34
信号量,将参数设置为 sigval
进行传递,这个指令仅传递了一个指令参数
case 1:
val = qword_6030C8 + 16;
v4 = getppid();
sigqueue(v4, 34, (const union sigval)val);
break;
case 2:
vala = qword_6030C8 + 17;
v5 = getppid();
sigqueue(v5, 34, (const union sigval)vala);
break;
case 3:
valb = qword_6030C8 + 18;
v6 = getppid();
sigqueue(v6, 34, (const union sigval)valb);
break;
1, 2, 3
三条指令区别仅在于使用的全局变量地址不同(可以猜测出来是三个不同的寄存器)
case 8:
valf = qword_6030C8 + 16;
v11 = getppid();
sigqueue(v11, 37, (const union sigval)valf);
break;
根据之前的分析,8
指令理应含有参数,但只传递了寄存器一个参数,因此观察处理函数
int __fastcall sub_400F16(__int64 a1, siginfo_t *a2)
{
*(_BYTE *)a2->_sifields._timer.si_sigval.sival_ptr += s1[79];
return semop(semid, &stru_6030BE, 1uLL);
}
发现使用了 s1[79]
这个变量,而 vm 函数中含有语句 v42 = s1 + 79;
因此,参数通过 s1[79]
这个全局变量传递到了处理函数中
还有一类指令只有kill
case 21:
v24 = getppid();
kill(v24, 46);
break;
这类指令仅传递了全局变量
最后经过对每条指令的分析,以及动调查看内存,确定 qword_6030C8[19]
是 esp
,*(_QWORD *)qword_6030C8
模拟了栈,*((_QWORD *)qword_6030C8 + 1)
是输入的开始地址,qword_6030C8[16, 17, 18]
是三个寄存器,qword_6030C8[21]
是跳转用的 ZF
标志位
Writeup
先手动反汇编
code = [
17, 52, 0, 42, 5, 16, 20, 9, 23, 0, 36, 5, 3, 17, 29, 6, 0,
0, 5, 3, 17, 64, 6, 0, 72, 5, 17, 29, 23, 14, 1, 21, 4, 15,
1, 22, 2, 0, 0, 4, 3, 5, 16, 20, 50, 5, 9, 2, 19, 29, 5, 18,
21, 4, 16, 20, 61, 10, 1, 19, 52, 3, 4, 18, 14, 1, 21, 4, 7,
1, 22, 2, 0, 0, 4, 3, 5, 16, 20, 85, 5, 9, 1, 19, 64, 5, 18
]
sub_400E1D = "push({});"
sub_400E78 = "pop({});"
sub_400F16 = "{} += {};"
sub_400FA8 = "{} -= {};"
eip = 0 # 20
eax = 0 # 16
ebx = 0 # 17
ecx = 0 # 18
edx = 0 # 19
memory = [2] * 0x1000
memory2 = [1] * 50
while eip < len(code):
cur_op = code[eip]
if cur_op == 0:
cur_arg = code[eip + 1]
print ("_%02X:" % (eip), sub_400E1D.format(cur_arg))
edx += 1
memory[edx] = cur_arg
eip += 1
elif cur_op == 1:
cur_arg = "eax"
print ("_%02X:" % (eip), sub_400E1D.format(cur_arg))
edx += 1
memory[edx] = eax
elif cur_op == 2:
cur_arg = "ebx"
print ("_%02X:" % (eip), sub_400E1D.format(cur_arg))
edx += 1
memory[edx] = ebx
elif cur_op == 3:
cur_arg = "ecx"
print ("_%02X:" % (eip), sub_400E1D.format(cur_arg))
edx += 1
memory[edx] = ecx
elif cur_op == 4:
cur_arg = "eax"
edx -= 1
print ("_%02X:" % eip, sub_400E78.format(cur_arg))
eax = memory[edx]
elif cur_op == 5:
cur_arg = "ebx"
edx -= 1
print ("_%02X:" % eip, sub_400E78.format(cur_arg))
ebx = memory[edx]
elif cur_op == 6:
cur_arg = "ecx"
edx -= 1
print ("_%02X:" % eip, sub_400E78.format(cur_arg))
ecx = memory[edx]
elif cur_op == 7:
eax += ebx
print ("_%02X:" % eip, "eax += ebx;")
elif cur_op == 8:
cur_arg = code[eip + 1]
print ("_%02X:" % eip, sub_400F16.format("eax", cur_arg))
eax += cur_arg
eip += 1
elif cur_op == 9:
cur_arg = code[eip + 1]
print ("_%02X:" % eip, sub_400F16.format("ebx", cur_arg))
ebx += cur_arg
eip += 1
elif cur_op == 10:
cur_arg = code[eip + 1]
print ("_%02X:" % eip, sub_400F16.format("ecx", cur_arg))
ecx += cur_arg
eip += 1
elif cur_op == 11:
eax -= ebx
print ("_%02X:" % eip, "eax -= ebx;")
elif cur_op == 12:
cur_arg = code[eip + 1]
print ("_%02X:" % eip, sub_400FA8.format("eax", cur_arg))
eax -= cur_arg
eip += 1
elif cur_op == 13:
cur_arg = code[eip + 1]
print ("_%02X:" % eip, sub_400FA8.format("ebx", cur_arg))
ebx -= cur_arg
eip += 1
elif cur_op == 14:
cur_arg = code[eip + 1]
print ("_%02X:" % eip, sub_400FA8.format("ecx", cur_arg))
eip += 1
elif cur_op == 15:
eax ^= ebx
print ("_%02X:" % eip, f"eax ^= ebx;")
elif cur_op == 16:
zf = (eax == ebx)
print ("_%02X:" % eip, f"zf = (eax == ebx);")
elif cur_op == 17:
cur_arg = code[eip + 1]
print ("_%02X:" % eip, f"push(eip+2); eip = {hex(cur_arg)};")
memory[edx] = eip
edx += 1
eip += 1
elif cur_op == 18:
edx -= 1
print ("_%02X:" % eip, f"pop(eip);")
elif cur_op == 19:
cur_arg = code[eip + 1]
print ("_%02X:" % eip, f"eip = {hex(cur_arg)};")
eip += 1
elif cur_op == 20:
cur_arg = code[eip + 1]
print ("_%02X:" % eip, f"if zf: eip = {hex(cur_arg)};")
eip += 1
elif cur_op == 21:
memory[edx] = memory2[ecx]
print ("_%02X:" % eip, f"push(memory2[ecx]);")
edx += 1
elif cur_op == 22:
edx -= 1
memory2[ecx] = memory[edx]
print ("_%02X:" % eip, f"pop(memory2[ecx]);")
elif cur_op == 23:
print ("_%02X:" % eip, "break;")
eip += 1
拿到反汇编结果
_00: push(eip+2); eip = 0x34;
_02: push(42);
_04: pop(ebx);
_05: zf = (eax == ebx);
_06: if zf: eip = 0x9;
_08: break;
_09: push(36);
_0B: pop(ebx);
_0C: push(ecx);
_0D: push(eip+2); eip = 0x1d;
_0F: pop(ecx);
_10: push(0);
_12: pop(ebx);
_13: push(ecx);
_14: push(eip+2); eip = 0x40;
_16: pop(ecx);
_17: push(72);
_19: pop(ebx);
_1A: push(eip+2); eip = 0x1d;
_1C: break;
_1D: ecx -= 1;
_1F: push(memory2[ecx]);
_20: pop(eax);
_21: eax ^= ebx;
_22: push(eax);
_23: pop(memory2[ecx]);
_24: push(ebx);
_25: push(0);
_27: pop(eax);
_28: push(ecx);
_29: pop(ebx);
_2A: zf = (eax == ebx);
_2B: if zf: eip = 0x32;
_2D: pop(ebx);
_2E: ebx += 2;
_30: eip = 0x1d;
_32: pop(ebx);
_33: pop(eip);
_34: push(memory2[ecx]);
_35: pop(eax);
_36: zf = (eax == ebx);
_37: if zf: eip = 0x3d;
_39: ecx += 1;
_3B: eip = 0x34;
_3D: push(ecx);
_3E: pop(eax);
_3F: pop(eip);
_40: ecx -= 1;
_42: push(memory2[ecx]);
_43: pop(eax);
_44: eax += ebx;
_45: push(eax);
_46: pop(memory2[ecx]);
_47: push(ebx);
_48: push(0);
_4A: pop(eax);
_4B: push(ecx);
_4C: pop(ebx);
_4D: zf = (eax == ebx);
_4E: if zf: eip = 0x55;
_50: pop(ebx);
_51: ebx += 1;
_53: eip = 0x40;
_55: pop(ebx);
_56: pop(eip);
发现其实是调用了几个函数,分别在0x34、0x40、0x1D
0x34处的函数判断了长度,0x1D处的函数从后往前异或数字,每次加2,0x40处的函数从后往前进行加法,数字每次加1
所以最后的加密算法为:首先从后往前 ^36, ^38, ^40,随后从后往前+0, +1, +2…,最后从后往前 ^72, ^74, ^76…
反向解密即可
s2 = [
0xA3, 0xD8, 0xAC, 0xA9, 0xA8, 0xD6, 0xA6, 0xCD, 0xD0, 0xD5,
0xF7, 0xB7, 0x9C, 0xB3, 0x31, 0x2D, 0x40, 0x5B, 0x4B, 0x3A,
0xFD, 0x57, 0x42, 0x5F, 0x58, 0x52, 0x54, 0x1B, 0x0C, 0x78,
0x39, 0x2D, 0xD9, 0x3D, 0x35, 0x1F, 0x09, 0x41, 0x40, 0x47,
0x42, 0x11
]
flag = ''
x = 36
y = 0
z = 72
for i in s2[::-1]:
flag += chr(((i ^ z) - y) ^ x)
x += 2
y += 1
z += 2
print (flag[::-1])
# 'flag{Now_Y0u_Know_th4_Signa1_0f_Linux!!!!}'