HateIntel
没有环境,arm也不太会,只能ida静态看看反编译的代码,整体相对比较简单,很容易找到:
将输入通过sub_232c
函数,然后之后和byte_3004
的内容逐字节比较。
那跟进sub_232c
函数
循环4次,即对每一位执行4次sub_2494
,跟进看看:
发现由于a2=1
,所以这个sub_2494
本质就是对8bit数字进行一个循环左移操作。
整个总结下来就是,对每一位输入进行循环左移4位,然后和固定值byte_3004
比较,所以写个函数将byte_3004
的内容循环右移就行了,如下:
byte_3004=[0x44,0xF6,0xF5,0x57,0xF5,0xC6,0x96,0xB6,0x56,0xF5,0x14,0x25,0xD4,0xF5,0x96,0xE6,0x37,0x47,0x27,0x57,0x36,0x47,0x96,0x03,0xE6,0xF3,0xA3,0x92,0x00]
def rol(lst_int, k):
lst=list('{:0>8}'.format(bin(lst_int)[2:]))
tmp= lst[k:] + lst[:k]
tmp2=int("0b"+"".join(i for i in tmp),2)
return tmp2
key=""
for i in byte_3004:
key+=chr(rol(i,-4))
print(key)
得到结果Do_u_like_ARM_instructi0n?:)
:
SimpleVM
遇到VM的题目就是坐穿屁股,这个题又做了一下午加一晚上
首先放进ida发现入口地址有点奇怪,
入口地址为0xc023dc
,那先跑起来试试,跑起来之后发现一个奇怪的点:
程序起了两个子进程,用gdb attach
到224
上去之后,然后用gcore把内存转储出来,放进gdb里,然后过了一下发现了其中sub_8048556
有个可显input
字符串,这应该就是主要逻辑了
sub_8048460(1, "Input : ", 8);
。例如这个sub_8048460
void __cdecl sub_8048460(int a1, int a2, int a3)
{
JUMPOUT(0xF7DF6B70);
}
明显是在调用库函数,查看一下进称的maps
计算一下偏移
>>> hex(0xF7DF6B70-0xf7d21000)
'0xd5b70'
ida打开这个/lib/i386-linux-gnu/libc-2.23.so
看看这个偏移上面的函数
所以知道这个sub_8048460
就是write。
然后依次确认了几个关键函数
sub_80489FE = getuid
sub_8048460 = write
sub_8048470 = pipe
sub_8048480 = fork
sub_8048400 = read
然后代码可读性就比较高了,核心就是这一部分
write(1, "Input : ", 8);
pipe(&v4);
if ( pipe((int)&v4) != -1 && (pipe(&v6), v2 != -1) )
{
v8 = fork__();
if ( v8 == -1 )
{
v10 = 0;
for ( i = 1; i <= 6; ++i )
{
v9 = byte_804B06E[i - 1] ^ i; // error!
write(1, &v9, 1);
}
}
else if ( v8 )
{
v14 = 0;
v15 = 0;
v16 = 0;
read(v4, &v14, 9);
read(v4, &dword_804B0A0, 200);
for ( i = 0; i <= 199; ++i )
*(i + 0x804B0A0) ^= 0x20u;
dword_804B0A0 = v14;
dword_804B0A4 = v15;
for ( i = 0; i <= 199; ++i )
*(i + 0x804B0A0) ^= 0x10u;
if ( sub_8048C6D() == 1 )
{
if ( cur_pc )
{
v10 = 0;
for ( i = 1; i <= 9; ++i )
{
v9 = *(&word_804B07A + i - 1) ^ i;//correct
write(1, &v9, 1);
}
}
else
{
v10 = 0;
for ( i = 1; i <= 6; ++i )
{
v9 = *(&dword_804B074 + i - 1) ^ i;// Wrong
write(1, &v9, 1);
}
}
}
else
{
v10 = 0;
for ( i = 1; i <= 6; ++i )
{
v9 = *(&dword_804B074 + i - 1) ^ i; // Wrong
write(1, &v9, 1);
}
}
}
else
{
v11 = 0;
v12 = 0;
v13 = 0;
read(0, &v11, 10);
if ( v13 )
{
v10 = 0;
for ( i = 1; i <= 6; ++i )
{
v9 = *(&dword_804B074 + i - 1) ^ i; // Wrong
write(1, &v9, 1);
}
}
else
{
write(v5, &v11, 9);
for ( i = 0; i <= 199; ++i )
{
v3 = sub_80489AA(*(i + 0x804B0A0), 3);
*(i + 0x804B0A0) = v3;
}
sub_8048410();
write(v5, &dword_804B0A0, 200);
}
}
根据fork()
的返回值不同进行不同的操作,即区分父子进程,子进程的返回值为0,父进程的返回值是新子进程的ID。也就是父子进程进行不同的操作,这里先看子进程,子进程将用户输入写到了v5里面,然后对0x804B0A0
处200字节内存进行了一顿异或操作也写到了v5。
再回来看看父进程,父进程两个read不用说了把,就是等着子进程写过去的数据。然后还是取到0x804B0A0
位置,之后又进行了两波异或操作,然后就是关键的sub_8048C6D
函数是否correct
。
跟进看一下:
出现了,vm的部分,又是屁股坐穿的一个题,为了读了好久之后,还是有点迷糊,而且反编译出的c还有很多错误的地方,而且动态调试很麻烦,先跑起来,然后gdb连接到两个子进程,然后子进程的子进程先c
,然后子进程下断点再c
,然后再输入,然后再跟,调起来头都是晕的。
于是花了一个小时改成了python,同时也是人工纠正了一下反编译中的一些问题。
基本就如下面所示了。基本逻辑就是将我们的输入放在0x804B0A0
开始的200字节的前七个字节,然后开始循环调用如下:
cnt=7
sub_8048C13()
while cnt: #逐位比较,某一位错误即退出while
cnt-=1
sub_8048B92()
sub_8048ABB()
sub_8048B92()
sub_8048B31()
sub_8048BCE()
sub_8048ABB()
sub_8048C22()
逐位调用函数进行对比,前一位正确了才能继续循环校验下一位,核心的校验点
在sub_8048B31
中对比了dword_804B198
和dword_804B194
值,这样只需要倒推这两个值的来源就能计算到正确的值,不过我就懒得算了,知道大概算法之后,动态跟几个点,主要就是每次循环的第一次调用sub_8048B92
,对804B0A0[7]
赋值,比如以校验第一位输入为例:
这里第一次调用sub_8048B92
时,set_code
即将对804B0A0[7]
赋值96^0x10
,
然后到了sub_8048B31
进行比较的时候,dword_804b194
的值为9
,所以第一位输入的ascii码值即为96^9
,具体的逻辑大家可以进去分析。
这里附上当时调试的python代码:
input=[96^9,102^2,21^38,7^45,76^34,99^7,16^120]
tmp1=[]
for i in input:
tmp1.append(i^0x10)
tmp2=[0x1a,0x10,0x1b,0x1a,0x16,0x16,0x10,0x10,0x1b,0x12,0x17,0x70,0x16,0x10,0x17,0x12,0x17,0x19,0x17,0x10,0x17,0x19,0x12,0x12,0x17,0x76,0x16,0x11,0x17,0x12,0x17,0x12,0x17,0x11,0x17,0x19,0x12,0x12,0x17,0x05,0x16,0x12,0x17,0x12,0x17,0x36,0x17,0x12,0x17,0x19,0x12,0x12,0x17,0x17,0x16,0x13,0x17,0x12,0x17,0x3d,0x17,0x13,0x17,0x19,0x12,0x12,0x17,0x5c,0x16,0x14,0x17,0x12,0x17,0x32,0x17,0x14,0x17,0x19,0x12,0x12,0x17,0x73,0x16,0x15,0x17,0x12,0x17,0x17,0x17,0x15,0x17,0x19,0x12,0x12,0x17,0x68,0x16,0x16,0x17,0x12,0x17,0x00,0x17,0x16,0x17,0x19,0x12,0x12,0x10,0x11,0x1b,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10]
dword_804B0A0=tmp1+tmp2
byte_804B0A9=0x1a
cur_pc=0
byte_804B0AA=0x1a
dword_804B198=0
dword_804B194=0
def log(myfunc):
def wrapper():
myfunc()
global byte_804B0A9,cur_pc,byte_804B0AA,dword_804B194,dword_804B198
print("="*15+myfunc.__name__+"="*15)
print("byte_804B0A9: ",hex(byte_804B0A9))
print("cur_pc: ",hex(cur_pc))
print("="*40)
return wrapper
def logs():
global byte_804B0A9,cur_pc,byte_804B0AA,dword_804B194,dword_804B198
print("="*30)
print("byte_804B0A9: ",hex(byte_804B0A9))
print("cur_pc: ",hex(cur_pc))
print("="*30)
def set_pc(): #sub_8048A48
global byte_804B0A9,cur_pc,byte_804B0AA,dword_804B194,dword_804B198
cur_pc=dword_804B0A0[byte_804B0A9^0x10]^0x10
byte_804B0A9 = ((byte_804B0A9^0x10)+1)^0x10
def set_pc_2(): #sub_8048A0B
global byte_804B0A9,cur_pc,byte_804B0AA,dword_804B194,dword_804B198
cur_pc=dword_804B0A0[cur_pc]^0x10
def set_code(): #sub_8048A2F
global byte_804B0A9,cur_pc,byte_804B0AA,dword_804B194,dword_804B198
dword_804B0A0[cur_pc]=dword_804B198^0x10
def sub_8048A92():
global byte_804B0A9,cur_pc,byte_804B0AA,dword_804B194,dword_804B198
byte_804B0A9=((byte_804B0AA^0x10)+cur_pc)^0x10
@log
def sub_8048B92():
global byte_804B0A9,cur_pc,byte_804B0AA,dword_804B194,dword_804B198
set_pc()
dword_804B198 = cur_pc
set_pc()
dword_804B194 = cur_pc
cur_pc = dword_804B198
dword_804B198 = dword_804B194
set_code()
@log
def sub_8048ABB():
global byte_804B0A9,cur_pc,byte_804B0AA,dword_804B194,dword_804B198
dword_804B198 = cur_pc
dword_804B18C = dword_804B194
set_pc()
dword_804B18C = cur_pc
set_pc()
set_pc_2()
dword_804B194 = cur_pc
cur_pc = dword_804B18C
set_pc_2()
dword_804B198 = cur_pc^dword_804B194
cur_pc = dword_804B18C
set_code()
@log
def sub_8048B31():
global byte_804B0A9,cur_pc,byte_804B0AA,dword_804B194,dword_804B198
set_pc()
set_pc_2()
dword_804B198 = cur_pc
set_pc()
set_pc_2()
dword_804B194 = cur_pc
if(dword_804B198 == dword_804B194):
dword_804B198 = 1
else:
dword_804B198 = 0
cur_pc = 8
set_code()
@log
def sub_8048BCE():
global byte_804B0A9,cur_pc,byte_804B0AA,dword_804B194,dword_804B198
dword_804B198 = cur_pc
set_pc()
dword_804B198 = cur_pc
cur_pc=8
set_pc_2()
if(cur_pc==0):
cur_pc = dword_804B198
sub_8048A92()
@log
def sub_8048C13():
set_pc()
sub_8048A92()
@log
def sub_8048C22():
global byte_804B0A9,cur_pc,byte_804B0AA,dword_804B194,dword_804B198
cur_pc=0
set_pc_2()
dword_804B198 = cur_pc
cur_pc=1
set_pc_2()
dword_804B194 = cur_pc
cur_pc = dword_804B198
dword_804B198 = dword_804B194
'''
b *0x8048C70
b *0x8048B92
b *0x8048ABB
b *0x8048B31
b *0x8048BCE
b *0x8048C13
b *0x8048C22
x/bx 0x804b0a9
x/bx 0x804b190
'''
cnt=7;
set_pc()
sub_8048C13()
set_pc()
while cnt: #逐位比较,某一位错误即退出while
cnt-=1
sub_8048B92()
set_pc()
sub_8048ABB()
set_pc()
sub_8048B92()
set_pc()
sub_8048B31()
set_pc()
sub_8048BCE()
set_pc()
sub_8048ABB()
set_pc()
sub_8048C22()
'''
byte_804B0A9 = 0x1a
cur_pc = 0
set_pc()
sub_8048B92:
set_pc();-------------------------------------------------------------------
| cur_pc=dword_804B0A0[byte_804B0A9^0x10]^0x10 |
| byte_804B0A9 = (byte_804B0A9^0x10+1)^0x10 |
----------------------------------------------------------
dword_804B198 = cur_pc;
set_pc();
dword_804B194 = cur_pc;
cur_pc = dword_804B198
dword_804B198 = dword_804B194
set_code();-----------------------------------------------------------
| dword_804B0A0[cur_pc]=dword_804B198^0x10 |
----------------------------------------------------
sub_8048ABB:
dword_804B198 = cur_pc;
dword_804B18C = dword_804B194;
set_pc();
dword_804B18C = cur_pc;
set_pc();
set_pc_2();------------------------------------------------------
| cur_pc=dword_804B0A0[cur_pc]^0x10 |
-----------------------------------------------
dword_804B194 = cur_pc;
cur_pc = dword_804B18C;
set_pc_2();
dword_804B198 = cur_pc^dword_804B194
cur_pc = dword_804B18C
set_code();
sub_8048B31:
set_pc();
set_pc_2();
dword_804B198 = cur_pc;
set_pc();
set_pc_2();
dword_804B194 = cur_pc;
if(dword_804B198 == dword_804B194):
dword_804B198 = 1;
else:
dword_804B198 = 0;
cur_pc = 8;
set_code();
sub_8048BCE:
dword_804B198 = cur_pc;
set_pc();
dword_804B198 = cur_pc;
cur_pc=8;
set_pc_2();
if(cur_pc!=0):
cur_pc = dword_804B198
sub_8048A92();--------------------------------------------------------
| byte_804B0A9=(byte_804B0AA^0x10+cur_pc)^0x10 |
--------------------------------------------------
sub_8048C13:
set_pc();
sub_8048A92();
sub_8048C22:
cur_pc=0;
set_pc_2();
dword_804B198 = cur_pc;
cur_pc=1;
set_pc_2();
dword_804B194 = cur_pc;
cur_pc = dword_804B198
dword_804B198 = dword_804B194
'''
AutoHotkey2
运行了一下发现直接报错程序崩溃,但是这不是系统报出的错误,肯定是程序的自校验出了问题,拖进ida空荡荡的符号表意味着又要开始脱壳….
跟了一会儿发现有点熟悉,确认了下不就是AutoHotkey1一样的吗。。。
同样lordpe+importrec
之后直接去sub_4508C7
函数,
发现和AutoHotkey1一模一样,那这个题意思就很明确了,第一步肯定是想办法绕过或者修改exe通过检测。
还是分为两部分,第一部分如下:
这部分代码的作用就是将文件从开头到倒数第五位计算一个值,然后异或0xaaaaaaaa
,之后和最后四位组成的值进行比较。这绕过很简单动态调试跟到比较那一步,就知道最后四位的值需要等于多少了。
然后下一部分
读取5-8
字节数据作为下一次文件读取的偏移,然后读取16字节数据,和固定的16字节比较,那就很容易了,直接找一下该16字节内容在文件的偏移就可以,
所以修改5-8
字节为\x00\x38\x03\x00
即可,注意这里改了之后要重新调试看最后四位的值,最后8位改为如下之后程序即可正常运行:
查一下bastard son of Eddard Stark
,得到一个人名Jon Snow
,所以最后结果就是jonsnow
x64_lotto
很明确的一个题目,64位,放到ida里面,直接进到主函数wmain
,分为几个部分来说,第一部分如下:
读取我们输入的六个数字,然后和随机的6个数字对比,不一样就继续输入,而且观察一下后面的代码,之后就再也没有用到过输入的数字,那就很简单了,动态调试,直接在while(v3!=6)
判断的时候patch一下通过判断就行了
第二部分如下:
对一段固定的值一顿异或操作之后将结果存在栈上,由于都是固定的值,动态跟一下就知道最后的异或结果。
第三部分如下:
patch一下,绕过对v2的判断,之后动态直接跟到wprintf处就拿到flag了:
flag就是from_GHL2_-_!
CSharp
又是一道.net
的题目,直接上dnspy看反编译的代码,其中比较引人关注的是名叫MetMett
的函数,没有被反编译出来,
再看到form1
这里在初始化的时候对这个函数体进行了一个解密操作,那这样其实动态调试是来的最快的,
两个断点一下,把两次的值复制出来,
解密前:
028D681E0B3FDEFFFFFF0115179B0215901F8FFFFFFF601E492D030115169B0218901F8FFFFFFF601E452D030115169B0216901F8FFFFFFF601E562D030115169B0217901F8FFFFFFF601E4C2D030115169B021E0A901F8FFFFFFF601E2B2D030115169B021D901F8FFFFFFF601FF0FFFFFF2D030115169B0219901F8FFFFFFF601E1C2D030115169B021A901F8FFFFFFF601E302D030115169B021E08901F8FFFFFFF601FE1FFFFFF2D030115169B021C901F8FFFFFFF601FEDFFFFFF2D030115169B021E09901F8FFFFFFF601FA2FFFFFF2D030115169B021B901F8FFFFFFF601E742D030115169B29
解密后:
038E691F0C40DF0000000216189C0316912010000000611F4A2E040216179C0319912033000000611F462E040216179C0317912011000000611F572E040216179C0318912021000000611F4D2E040216179C031F0B912011000000611F2C2E040216179C031E9120900000006120F10000002E040216179C031A912044000000611F1D2E040216179C031B912066000000611F312E040216179C031F099120B50000006120E20000002E040216179C031D9120A00000006120EE0000002E040216179C031F0A9120EE0000006120A30000002E040216179C031C912033000000611F752E040216179C2A
然后就是想看到解密后的代码,直接hex 编辑器上用解密后替换掉解密前的部分,再放进dnspy里反编译,拿到如下代码:
private static void MetMett(byte[] chk, byte[] bt)
{
if (bt.Length == 12)
{
chk[0] = 2;
if ((bt[0] ^ 16) != 74)
{
chk[0] = 1;
}
if ((bt[3] ^ 51) != 70)
{
chk[0] = 1;
}
if ((bt[1] ^ 17) != 87)
{
chk[0] = 1;
}
if ((bt[2] ^ 33) != 77)
{
chk[0] = 1;
}
if ((bt[11] ^ 17) != 44)
{
chk[0] = 1;
}
if ((bt[8] ^ 144) != 241)
{
chk[0] = 1;
}
if ((bt[4] ^ 68) != 29)
{
chk[0] = 1;
}
if ((bt[5] ^ 102) != 49)
{
chk[0] = 1;
}
if ((bt[9] ^ 181) != 226)
{
chk[0] = 1;
}
if ((bt[7] ^ 160) != 238)
{
chk[0] = 1;
}
if ((bt[10] ^ 238) != 163)
{
chk[0] = 1;
}
if ((bt[6] ^ 51) != 117)
{
chk[0] = 1;
}
}
}
那这个就挺清晰的了,算出来之后需要base64解码一下得到最后答案就是dYnaaMic