-
-
[原创]【2019看雪CTF】Q2赛季 第五题 丛林的秘密 WP
-
2019-7-2 00:13 5452
-
【2019看雪CTF】Q2赛季 第五题 丛林的秘密 WP
安卓题,主要java层代码如下:
static { System.loadLibrary("gogogo"); } public MainActivity() { super(); this.u = gogogoJNI.sayHello(); } static EditText access$000(MainActivity arg0) { return arg0.eText1; } static TextView access$100(MainActivity arg0) { return arg0.txView1; } protected void onCreate(Bundle arg3) { super.onCreate(arg3); this.setContentView(2131296284); this.eText1 = this.findViewById(2131165238); this.txView1 = this.findViewById(2131165322); this.findViewById(2131165318).loadUrl(this.u); this.findViewById(2131165318).getSettings().setJavaScriptEnabled(true); this.button1 = this.findViewById(2131165218); this.button1.setOnClickListener(new View$OnClickListener() { public void onClick(View arg2) { if(gogogoJNI.check_key(MainActivity.this.eText1.getText().toString()) == 1) { MainActivity.this.txView1.setText("Congratulations!"); } else { MainActivity.this.txView1.setText("Not Correct!"); } } }); }
有native代码,java层调用了其两个函数sayHello
和check_key
。sayHello
函数返回字串'http://127.0.0.1:8000' ,而check_key
似乎不像校验函数:
signed int __cdecl Java_com_example_assemgogogo_gogogoJNI_check_key(JNIEnv *a1, int a2, int a3) { const char *v3; // eax const char *v4; // esi const char *v5; // ebp unsigned int v6; // edi v3 = (*a1)->GetStringUTFChars(a1, (jstring)a3, 0); if ( v3 ) { v4 = v3; ((void (__cdecl *)(JNIEnv *))(*a1)->ReleaseStringUTFChars)(a1); srand(0x32u); v5 = "d584a68d4e213d88w511v48e61g8d6e8"; v6 = 0; while ( v4[v6] == (rand() % 128 != *v5) ) { ++v6; ++v5; if ( v6 > 0x1F ) { close(sock_fd_g); return 1; } } } return 0; }
再看java层中的loadUrl
和setJavaScriptEnabled
,似乎有猫腻。再细看native代码。
在JNI_OnLoad
函数中调用了地址为B30
的函数,解码了大量数据,并建立了socket,监听8000端口,模拟web服务,有客户端连接后将解码的数据发送到客户端。
数据解码后为html内容,内嵌WebAssembly,html部分内容为(去除了WebAssembly部分):
<!DOCTYPE html> <html><head> <meta http-equiv="content-type" content="text/html; charset=UTF-8"> <meta charset="utf-8"> <style> body { background-color: rgb(255, 255, 255); } </style> </head> <script> ... ... ... function check_flag(){ var value = document.getElementById("key_value").value; if(value.length != 32) { document.getElementById("tips").innerHTML = "Not Correct!"; return; } instance.exports.set_input_flag_len(value.length); for(var ii=0;ii<value.length;ii++){ instance.exports.set_input_flag(value[ii].charCodeAt(),ii); } var ret = instance.exports.check_key(); if (ret == 1){ document.getElementById("tips").innerHTML = "Congratulations!" } else{ document.getElementById("tips").innerHTML = "Not Correct!" } } </script> <body> <div>Key: <input id="key_value" type="text" name="key" style="width:60%" ;="" value=""> <input type="submit" value="check" onclick="check_flag()"></div> <div> <label id="tips"></label></div> </body></html
原来这才是真正的校验交互接口,校验逻辑自然在WebAssembly里。
将WebAssembly写入到文件,转成c再编译,最后反编,得到check_flag
的伪代码如下:
__int64 check_key() { unsigned int v0; // ST00_4 if ( ++wasm_rt_call_stack_depth > 0x1F4u ) wasm_rt_trap(7LL); o(1024u, 1025u, 1026u, 1027u); oo(1028u, 1029u, 1030u, 1031u); ooo(1032u, 1033u, 1034u, 1035u); oooo(1036u, 1037u, 1038u, 1039u); ooooo(0x410u, 0x411u, 0x412u, 0x413u); oooooo(0x414u, 0x415u, 0x416u, 0x417u); ooooooo(0x418u, 0x419u, 0x41Au, 0x41Bu); oooooooo(0x41Cu, 0x41Du, 0x41Eu, 0x41Fu); v0 = xxx(); --wasm_rt_call_stack_depth; return v0; }
先调用了8个函数对输入的32字节进行异或处理,然后调用xxx
函数进行最终校验,xxx
函数中通过32个32元一次代数方程进行校验,直接解方程得到
y = [83,48,109,51,116,105,109,101, 95,108,49,116,116,49,101,95, 99,48,100,101,95,49,115,95, 117,115,51,102,117,108,51,51]
即:S0m3time_l1tt1e_c0de_1s_us3ful33
32字节异或的常量值为:
x = [0x18, 0x09, 0x03, 0x6b, 0x01, 0x5a, 0x32, 0x57, 0x30, 0x5d, 0x40, 0x46, 0x2b, 0x46, 0x56, 0x3d, 0x02, 0x43, 0x17, 0x00, 0x32, 0x53, 0x1f, 0x26, 0x2a, 0x01, 0x00, 0x10, 0x10, 0x1e, 0x40, 0x00]
直接异或得到原始输入:
>>> for i in range(32): ... l.append(x[i]^y[i]) ... >>> print ''.join(map(chr,l)) K9nXu3_2o1q2_w3bassembly_r3vers3
[培训]二进制漏洞攻防(第3期);满10人开班;模糊测试与工具使用二次开发;网络协议漏洞挖掘;Linux内核漏洞挖掘与利用;AOSP漏洞挖掘与利用;代码审计。
赞赏
他的文章
看原图