Reversing.kr Writeup(16-20)

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 attach224上去之后,然后用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_804B198dword_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