前言
继续ctf的旅程
开始攻防世界web高手进阶区的9分题
本文是Triangle的writeup
解题过程
进入界面
惯例源码+御剑
源码里有发现3个js文件源码
1、源码分析
index界面
就是简单的login
secret.js
index里用到的三个函数都在这个js里定义了
function test_pw(e, _) {
var t = stoh(atob(getBase64Image("eye")))
, r = 4096
, m = 8192
, R = 12288
, a = new uc.Unicorn(uc.ARCH_ARM,uc.MODE_ARM);
a.reg_write_i32(uc.ARM_REG_R9, m),
a.reg_write_i32(uc.ARM_REG_R10, R),
a.reg_write_i32(uc.ARM_REG_R8, _.length),
a.mem_map(r, 4096, uc.PROT_ALL);
for (var o = 0; o < o1.length; o++)
a.mem_write(r + o, [t[o1[o]]]);
a.mem_map(m, 4096, uc.PROT_ALL),
a.mem_write(m, stoh(_)),
a.mem_map(R, 4096, uc.PROT_ALL),
a.mem_write(R, stoh(e));
var u = r
, c = r + o1.length;
return a.emu_start(u, c, 0, 0),
a.reg_read_i32(uc.ARM_REG_R5)
}
function enc_pw(e) {
var _ = stoh(atob(getBase64Image("frei")))
, t = 4096
, r = 8192
, m = 12288
, R = new uc.Unicorn(uc.ARCH_ARM,uc.MODE_ARM);
R.reg_write_i32(uc.ARM_REG_R8, r),
R.reg_write_i32(uc.ARM_REG_R9, m),
R.reg_write_i32(uc.ARM_REG_R10, e.length),
R.mem_map(t, 4096, uc.PROT_ALL);
for (var a = 0; a < o2.length; a++)
R.mem_write(t + a, [_[o2[a]]]);
R.mem_map(r, 4096, uc.PROT_ALL),
R.mem_write(r, stoh(e)),
R.mem_map(m, 4096, uc.PROT_ALL);
var o = t
, u = t + o2.length;
return R.emu_start(o, u, 0, 0),
htos(R.mem_read(m, e.length))
}
function get_pw() {
for (var e = stoh(atob(getBase64Image("templar"))), _ = "", t = 0; t < o3.length; t++)
_ += String.fromCharCode(e[o3[t]]);
return _
}
看了看
就很卧槽
都用unicorn框架仿真ARM
这得逆向了啊
util.js
定义了get_pw()
函数里用到的三个函数
function stoh(t){
return t.split("").map(function(t){
return t.charCodeAt(0)
})
}
function htos(t){
return String.fromCharCode.apply(String,t)
}
function getBase64Image(t){
var e=document.getElementById(t),a=document.createElement("canvas");
a.width=e.width,a.height=e.height;
var n=a.getContext("2d");
n.drawImage(e,0,0);
var r=a.toDataURL("image/png");
return r.replace(/^data:image\/(png|jpeg);base64,/,"")
}
unicorn.js非常大
是JavaScript框架的源码
在secret.js中用于仿真ARM
思路
- 根据index,
get_pw()
应该输出一个固定值 enc_pw
和test_pw
函数仿真ARM,需要逆向了解它在干嘛- 然后想办法满足index里的login
2、输出get_pw()
尝试直接在console输出get_pw()
得到XYzaSAAX_PBssisodjsal_sSUVWZYYYb
这样util就不用管了
3、逆向了解 enc_pw
和test_pw
函数
要逆向enc_pw
和test_pw
函数
先想办法得到16进制
再转换为ARM汇编
工具:Online HEX to ARM Converter
逆向enc_pw
仿照enc_pw
构造getARM1()
函数
又其输出是10进制的数组
构造转换为16进制的函数toHexString()
function getARM1(){
var x = stoh(atob(getBase64Image("frei")));
var output = new Array();
for(var i = 0; i < o2.length ; i++){
output[i] = x[o2[i]];
}
return output;
}
//Looking at o2, we observe that our output will be in integers.
//Lets try converting them to hex values.
function toHexString(byteArray) {
return Array.from(byteArray, function(byte) {
return ('0' + (byte & 0xFF).toString(16)).slice(-2);
}).join('')
}
输出toHexString(getARM1())
得到
0800a0e10910a0e10a20a0e10030a0e30050a0e30040d0e5010055e30100001a036003e2064084e0064084e2015004e20040c1e5010080e2011081e2013083e2020053e1f2ffffba0000a0e30010a0e30020a0e30030a0e30040a0e30050a0e30060a0e30070a0e30090a0e300a0a0e3
转换为ARM
得到
mov r0, r8 //r8根据secret.js,赋值了r=8192,在这里赋值给r0
mov r1, sb //sb是静态基址寄存器,与r9同义,根据secret.js,赋值了m=12288,这里赋值给r1
mov r2, sl //sl是堆栈限制寄存器,与r10同义,根据secret.js,赋值了输入e的长度,这里赋值给r2
mov r3, #0 //r3初始化为0
mov r5, #0 //r5初始化为0
ldrb r4, [r0] //将存储器地址为R0的字节数据读入寄存器R4,并将R4的高24位清零,这里在rom中是0x14
cmp r5, #1 //将R5的值与1相减,结果存在标志位中
bne #0x28 //根据标志位的结果,判断R5与1是否相等,若不相等则跳转到0x28处
and r6, r3, #3 //将R3和3相与结果传入R6,相当于截取R3二进制最后两位传入R6
add r4, r4, r6 //将R4 与R6相加的结果传入R4
add r4, r4, #6 //R4加6,这里在rom中是0x28
and r5, r4, #1 //将R4和1相与的结果传入R5,若R4为偶数则R5=0反之R5=1
strb r4, [r1] //将R4的低8位传入以R1为基址的存储器地址中
add r0, r0, #1 //R0加一
add r1, r1, #1 //R1加一
add r3, r3, #1 //R3加一
cmp r3, r2 //将R3与R2相减,结果存在标志位中
blt #0x14 //根据标志位的结果判断R3是否小于R2若小于则跳转到0x14处,即若计数器小于输入密码长度则继续循环
mov r0, #0 //这里往下都是清零
mov r1, #0
mov r2, #0
mov r3, #0
mov r4, #0
mov r5, #0
mov r6, #0
mov r7, #0
mov sb, #0
mov sl, #0
类似的
逆向test_pw
function getARM2(){
var x = stoh(atob(getBase64Image("eye")));
var output = new Array();
for(var i = 0; i < o1.length ; i++){
output[i] = x[o1[i]];
}
return output;
}
0900a0e10a10a0e10830a0e10040a0e30050a0e300c0a0e30020d0e50060d1e5056086e201c004e200005ce30000000a036046e2060052e10500001a010080e2011081e2014084e2030054e1f1ffffba0150a0e30000a0e30010a0e30020a0e30030a0e30040a0e30060a0e30070a0e30080a0e30090a0e300a0a0e300c0a0e3
mov r0, sb //向r0赋值m = 8192
mov r1, sl //向r1赋值R = 12288
mov r3, r8 //向r3赋值输入值的长度
mov r4, #0 //初始化r4
mov r5, #0 //初始化r5
mov ip, #0 //初始化IP
ldrb r2, [r0] //将存储器地址为R0的字节数据读入寄存器R2,并将R2的高24位清零,此处在rom中是0x18
ldrb r6, [r1] //将存储器地址为R1的字节数据读入寄存器R6,并将R6的高24位清零
add r6, r6, #5 //将R6加5的结果传入R6
and ip, r4, #1 //将R4与1相与的结果传入IP
cmp ip, #0 //判断IP与0是否相等
beq #0x34 //如果IP==0,即R4是偶数,将会跳转到0x34处
sub r6, r6, #3 //else,即如果IP!=0,即R4是奇数,将R6减3的结果传入R6
cmp r2, r6 //判断R2与R6是否相等,此处在rom中是0x34
bne #0x54 //如果R2与R6不相等则跳转到0x54
add r0, r0, #1 //R0加一
add r1, r1, #1 //R1加一
add r4, r4, #1 //R4加一
cmp r4, r3 //比较R4与R3的大小
blt #0x18 //如果R4小于R3则跳转到0x18
mov r5, #1 //设置r5为1
mov r0, #0 //这里往下都是清零,此处在rom中是0x54
mov r1, #0
mov r2, #0
mov r3, #0
mov r4, #0
mov r6, #0
mov r7, #0
mov r8, #0
mov sb, #0
mov sl, #0
mov ip, #0
哎
没接触过ARM汇编
难顶
去现学下
标注在上面
整理一下
enc_pw
函数的流程如下
用python表示就是
def enc_pw(e):
res = ''
f = 0
for i, c in enumerate(e):
c = ord(c)
if f == 1:
c += i & 3
c += 6
f = c & 1
res += chr(c)
return res
test_pw
函数的流程如下:
用python表示就是
def test_pw(e, t):
for i, (c, d) in enumerate(zip(e, t)):
c, d = ord(c), ord(d)
c += 5
if i & 1:
c -= 3
if c != d:
return 0
return 1
4、满足login
搞定了enc_pw
和test_pw
函数的python表示
那再搞个满足index里login的输入
逆着搞一下就是了
import string
def enc_pw(e):
res = ''
f = 0
for i, c in enumerate(e):
c = ord(c)
if f == 1:
c += i & 3
c += 6
f = c & 1
res += chr(c)
return res
encrypted = 'XYzaSAAX_PBssisodjsal_sSUVWZYYYb' # get_pw()的输出结果
flag = ''
# 逆向test_pw和index的login
for i, c in enumerate(encrypted):
c = ord(c)
c -= 5
if i & 1:
c += 3
for d in string.printable:
if enc_pw(flag + d)[i] == chr(c):
flag += d
break
print flag
得到flag
结语
这是逆向啊
还是ARM汇编
现学现卖
知识点
- unicorn框架
- ARM汇编
参考
几个wp里都是用php语言的脚本