Llvm “防”反向 pass


#1

Hi Hackers,

反向工程、防反向是永恒的博弈艺术 :slight_smile:

引子:我这两天学习如何编写一个LLVM PASS 纯练手写了一个LLVM “防”反向工程 PASS——ARE(Anti Reverse Engeering) 很KISS(Keep It Simple and Stupid),请大大们轻轻拍砖,目前70+行,实现了:

  • 修改函数名称
	.text
	.file	"test-are.c"
	.globl	login                   # -- Begin function login
	.p2align	4, 0x90
	.type	login,@function
login:                                  # @login
	.cfi_startproc
# BB#0:
	pushq	%rbp
.Lcfi0:
	.cfi_def_cfa_offset 16
.Lcfi1:
	.cfi_offset %rbp, -16
	movq	%rsp, %rbp
.Lcfi2:
	.cfi_def_cfa_register %rbp
	subq	$32, %rsp
	movabsq	$.L.str, %rax
	movabsq	$.L.str.1, %rcx
	movq	%rdi, -24(%rbp)
	movq	%rsi, -16(%rbp)
	movq	%rax, %rdi
	movq	%rcx, %rsi
	movl	$6, %edx
	movb	$0, %al
	callq	printf
	cmpq	$0, -24(%rbp)
	je	.LBB0_2
# BB#1:
	cmpq	$0, -16(%rbp)
	jne	.LBB0_3
.LBB0_2:
	movl	$-1, -4(%rbp)
	jmp	.LBB0_4
.LBB0_3:
	movl	$0, -4(%rbp)
.LBB0_4:
	movl	-4(%rbp), %eax
	addq	$32, %rsp
	popq	%rbp
	retq
.Lfunc_end0:
	.size	login, .Lfunc_end0-login
	.cfi_endproc
                                        # -- End function
	.globl	logout                  # -- Begin function logout
	.p2align	4, 0x90
	.type	logout,@function
logout:                                 # @logout
	.cfi_startproc
# BB#0:
	pushq	%rbp
.Lcfi3:
	.cfi_def_cfa_offset 16
.Lcfi4:
	.cfi_offset %rbp, -16
	movq	%rsp, %rbp
.Lcfi5:
	.cfi_def_cfa_register %rbp
	subq	$16, %rsp
	movabsq	$.L.str.2, %rax
	movabsq	$.L.str.1, %rsi
	movl	%edi, -4(%rbp)
	movl	-4(%rbp), %ecx
	movq	%rax, %rdi
	movl	$14, %edx
	movb	$0, %al
	callq	printf
	xorl	%eax, %eax
	addq	$16, %rsp
	popq	%rbp
	retq
.Lfunc_end1:
	.size	logout, .Lfunc_end1-logout
	.cfi_endproc
                                        # -- End function
	.globl	main                    # -- Begin function main
	.p2align	4, 0x90
	.type	main,@function
main:                                   # @main
	.cfi_startproc
# BB#0:
	pushq	%rbp
.Lcfi6:
	.cfi_def_cfa_offset 16
.Lcfi7:
	.cfi_offset %rbp, -16
	movq	%rsp, %rbp
.Lcfi8:
	.cfi_def_cfa_register %rbp
	subq	$32, %rsp
	movabsq	$.L.str.4, %rax
	movabsq	$.L.str.5, %rcx
	movabsq	$.L.str.3, %rdx
	movl	$0, -8(%rbp)
	movl	%edi, -4(%rbp)
	movq	%rsi, -24(%rbp)
	movq	%rdx, -16(%rbp)
	movq	%rax, %rdi
	movq	%rcx, %rsi
	movb	$0, %al
	callq	printf
	xorl	%edi, %edi
	xorl	%esi, %esi
	callq	login
	movl	$4294967295, %edi       # imm = 0xFFFFFFFF
	callq	logout
	xorl	%eax, %eax
	addq	$32, %rsp
	popq	%rbp
	retq
.Lfunc_end2:
	.size	main, .Lfunc_end2-main
	.cfi_endproc
                                        # -- End function
	.type	.L.str,@object          # @.str
	.section	.rodata.str1.1,"aMS",@progbits,1
.L.str:
	.asciz	"DEBUG: %s, line %d\n"
	.size	.L.str, 20

	.type	.L.str.1,@object        # @.str.1
.L.str.1:
	.asciz	"test-are.c"
	.size	.L.str.1, 11

	.type	.L.str.2,@object        # @.str.2
.L.str.2:
	.asciz	"DEBUG: %s, line %d: ID %d\n"
	.size	.L.str.2, 27

	.type	.L.str.3,@object        # @.str.3
.L.str.3:
	.asciz	"Vml5Z0pFZGk9UHg2a2dPY0loZW49S3cxN3dVQUFBPT0"
	.size	.L.str.3, 44

	.type	.L.str.4,@object        # @.str.4
.L.str.4:
	.asciz	"Hello world: %s\n"
	.size	.L.str.4, 17

	.type	.L.str.5,@object        # @.str.5
.L.str.5:
	.asciz	"6xxzcQMhb4WgKX0EUkwG747K"
	.size	.L.str.5, 25


	.ident	"Fedora clang version 6.0.0 (trunk 311323) (based on LLVM 6.0.0svn-r311323)"
	.section	".note.GNU-stack","",@progbits

将敏感的函数名,例如login、logout,修改成“无意义”的:

	.text
	.file	"test-are.c"
	.globl	dragonball1             # -- Begin function dragonball1
	.p2align	4, 0x90
	.type	dragonball1,@function
dragonball1:                            # @dragonball1
	.cfi_startproc
# BB#0:
	pushq	%rbp
.Lcfi0:
	.cfi_def_cfa_offset 16
.Lcfi1:
	.cfi_offset %rbp, -16
	movq	%rsp, %rbp
.Lcfi2:
	.cfi_def_cfa_register %rbp
	subq	$32, %rsp
	movabsq	$.L.str, %rax
	movabsq	$.L.str.1, %rcx
	movq	%rdi, -24(%rbp)
	movq	%rsi, -16(%rbp)
	movq	%rax, %rdi
	movq	%rcx, %rsi
	movl	$6, %edx
	movb	$0, %al
	callq	printf
	cmpq	$0, -24(%rbp)
	je	.LBB0_2
# BB#1:
	cmpq	$0, -16(%rbp)
	jne	.LBB0_3
.LBB0_2:
	movl	$-1, -4(%rbp)
	jmp	.LBB0_4
.LBB0_3:
	movl	$0, -4(%rbp)
.LBB0_4:
	movl	-4(%rbp), %eax
	addq	$32, %rsp
	popq	%rbp
	retq
.Lfunc_end0:
	.size	dragonball1, .Lfunc_end0-dragonball1
	.cfi_endproc
                                        # -- End function
	.globl	dragonball2             # -- Begin function dragonball2
	.p2align	4, 0x90
	.type	dragonball2,@function
dragonball2:                            # @dragonball2
	.cfi_startproc
# BB#0:
	pushq	%rbp
.Lcfi3:
	.cfi_def_cfa_offset 16
.Lcfi4:
	.cfi_offset %rbp, -16
	movq	%rsp, %rbp
.Lcfi5:
	.cfi_def_cfa_register %rbp
	subq	$16, %rsp
	movabsq	$.L.str.2, %rax
	movabsq	$.L.str.1, %rsi
	movl	%edi, -4(%rbp)
	movl	-4(%rbp), %ecx
	movq	%rax, %rdi
	movl	$14, %edx
	movb	$0, %al
	callq	printf
	xorl	%eax, %eax
	addq	$16, %rsp
	popq	%rbp
	retq
.Lfunc_end1:
	.size	dragonball2, .Lfunc_end1-dragonball2
	.cfi_endproc
                                        # -- End function
	.globl	main                    # -- Begin function main
	.p2align	4, 0x90
	.type	main,@function
main:                                   # @main
	.cfi_startproc
# BB#0:
	pushq	%rbp
.Lcfi6:
	.cfi_def_cfa_offset 16
.Lcfi7:
	.cfi_offset %rbp, -16
	movq	%rsp, %rbp
.Lcfi8:
	.cfi_def_cfa_register %rbp
	subq	$32, %rsp
	movabsq	$.L.str.4, %rax
	movabsq	$.L.str.5, %rcx
	movabsq	$.L.str.3, %rdx
	movl	$0, -8(%rbp)
	movl	%edi, -4(%rbp)
	movq	%rsi, -24(%rbp)
	movq	%rdx, -16(%rbp)
	movq	%rax, %rdi
	movq	%rcx, %rsi
	movb	$0, %al
	callq	printf
	xorl	%edi, %edi
	xorl	%esi, %esi
	callq	dragonball1
	movl	$4294967295, %edi       # imm = 0xFFFFFFFF
	callq	dragonball2
	xorl	%eax, %eax
	addq	$32, %rsp
	popq	%rbp
	retq
.Lfunc_end2:
	.size	main, .Lfunc_end2-main
	.cfi_endproc
                                        # -- End function
	.type	.L.str,@object          # @.str
	.section	.rodata.str1.1,"aMS",@progbits,1
.L.str:
	.asciz	"DEBUG: %s, line %d\n"
	.size	.L.str, 20

	.type	.L.str.1,@object        # @.str.1
.L.str.1:
	.asciz	"test-are.c"
	.size	.L.str.1, 11

	.type	.L.str.2,@object        # @.str.2
.L.str.2:
	.asciz	"DEBUG: %s, line %d: ID %d\n"
	.size	.L.str.2, 27

	.type	.L.str.3,@object        # @.str.3
.L.str.3:
	.asciz	"Vml5Z0pFZGk9UHg2a2dPY0loZW49S3cxN3dVQUFBPT0"
	.size	.L.str.3, 44

	.type	.L.str.4,@object        # @.str.4
.L.str.4:
	.asciz	"Hello world: %s\n"
	.size	.L.str.4, 17

	.type	.L.str.5,@object        # @.str.5
.L.str.5:
	.asciz	"6xxzcQMhb4WgKX0EUkwG747K"
	.size	.L.str.5, 25


	.ident	"Fedora clang version 6.0.0 (trunk 311323) (based on LLVM 6.0.0svn-r311323)"
	.section	".note.GNU-stack","",@progbits

TODO 还未实现的功能:

  • 使常量“人类”不可读,直接修改LLVM IR的Constant:
; ModuleID = 'test-are-mod.bc'
source_filename = "test-are.c"
target datalayout = "e-m:e-i64:64-f80:128-n8:16:32:64-S128"
target triple = "x86_64-redhat-linux"

@.str = private unnamed_addr constant [20 x i8] c"DEBUG: %s, line %d\0A\00", align 1
@.str.1 = private unnamed_addr constant [11 x i8] c"test-are.c\00", align 1
@.str.2 = private unnamed_addr constant [27 x i8] c"DEBUG: %s, line %d: ID %d\0A\00", align 1
@.str.3 = private unnamed_addr constant [44 x i8] c"Vml5Z0pFZGk9UHg2a2dPY0loZW49S3cxN3dVQUFBPT0\00", align 1
@.str.4 = private unnamed_addr constant [17 x i8] c"Hello world: %s\0A\00", align 1
@.str.5 = private unnamed_addr constant [25 x i8] c"6xxzcQMhb4WgKX0EUkwG747K\00", align 1

; Function Attrs: noinline nounwind optnone uwtable
define i32 @dragonball1(i8*, i8*) #0 {
  %3 = alloca i32, align 4
  %4 = alloca i8*, align 8
  %5 = alloca i8*, align 8
  store i8* %0, i8** %4, align 8
  store i8* %1, i8** %5, align 8
  %6 = call i32 (i8*, ...) @printf(i8* getelementptr inbounds ([20 x i8], [20 x i8]* @.str, i32 0, i32 0), i8* getelementptr inbounds ([11 x i8], [11 x i8]* @.str.1, i32 0, i32 0), i32 6)
  %7 = load i8*, i8** %4, align 8
  %8 = icmp ne i8* %7, null
  br i1 %8, label %9, label %12

; <label>:9:                                      ; preds = %2
  %10 = load i8*, i8** %5, align 8
  %11 = icmp ne i8* %10, null
  br i1 %11, label %13, label %12

; <label>:12:                                     ; preds = %9, %2
  store i32 -1, i32* %3, align 4
  br label %14

; <label>:13:                                     ; preds = %9
  store i32 0, i32* %3, align 4
  br label %14

; <label>:14:                                     ; preds = %13, %12
  %15 = load i32, i32* %3, align 4
  ret i32 %15
}

declare i32 @printf(i8*, ...) #1

; Function Attrs: noinline nounwind optnone uwtable
define i32 @dragonball2(i32) #0 {
  %2 = alloca i32, align 4
  store i32 %0, i32* %2, align 4
  %3 = load i32, i32* %2, align 4
  %4 = call i32 (i8*, ...) @printf(i8* getelementptr inbounds ([27 x i8], [27 x i8]* @.str.2, i32 0, i32 0), i8* getelementptr inbounds ([11 x i8], [11 x i8]* @.str.1, i32 0, i32 0), i32 14, i32 %3)
  ret i32 0
}

; Function Attrs: noinline nounwind optnone uwtable
define i32 @main(i32, i8**) #0 {
  %3 = alloca i32, align 4
  %4 = alloca i32, align 4
  %5 = alloca i8**, align 8
  %6 = alloca i8*, align 8
  store i32 0, i32* %3, align 4
  store i32 %0, i32* %4, align 4
  store i8** %1, i8*** %5, align 8
  store i8* getelementptr inbounds ([44 x i8], [44 x i8]* @.str.3, i32 0, i32 0), i8** %6, align 8
  %7 = call i32 (i8*, ...) @printf(i8* getelementptr inbounds ([17 x i8], [17 x i8]* @.str.4, i32 0, i32 0), i8* getelementptr inbounds ([25 x i8], [25 x i8]* @.str.5, i32 0, i32 0))
  %8 = call i32 @dragonball1(i8* null, i8* null)
  %9 = call i32 @dragonball2(i32 -1)
  ret i32 0
}

attributes #0 = { noinline nounwind optnone uwtable "correctly-rounded-divide-sqrt-fp-math"="false" "disable-tail-calls"="false" "less-precise-fpmad"="false" "no-frame-pointer-elim"="true" "no-frame-pointer-elim-non-leaf" "no-infs-fp-math"="false" "no-jump-tables"="false" "no-nans-fp-math"="false" "no-signed-zeros-fp-math"="false" "no-trapping-math"="false" "stack-protector-buffer-size"="8" "target-cpu"="x86-64" "target-features"="+fxsr,+mmx,+sse,+sse2,+x87" "unsafe-fp-math"="false" "use-soft-float"="false" }
attributes #1 = { "correctly-rounded-divide-sqrt-fp-math"="false" "disable-tail-calls"="false" "less-precise-fpmad"="false" "no-frame-pointer-elim"="true" "no-frame-pointer-elim-non-leaf" "no-infs-fp-math"="false" "no-nans-fp-math"="false" "no-signed-zeros-fp-math"="false" "no-trapping-math"="false" "stack-protector-buffer-size"="8" "target-cpu"="x86-64" "target-features"="+fxsr,+mmx,+sse,+sse2,+x87" "unsafe-fp-math"="false" "use-soft-float"="false" }

!llvm.module.flags = !{!0}
!llvm.ident = !{!1}

!0 = !{i32 1, !"wchar_size", i32 4}
!1 = !{!"Fedora clang version 6.0.0 (trunk 311323) (based on LLVM 6.0.0svn-r311323)"}

将敏感的常量,例如SKEY,变成“人类”不可读的:

	.text
	.file	"test-are.c"
	.globl	dragonball1             # -- Begin function dragonball1
	.p2align	4, 0x90
	.type	dragonball1,@function
dragonball1:                            # @dragonball1
	.cfi_startproc
# BB#0:
	pushq	%rbp
.Lcfi0:
	.cfi_def_cfa_offset 16
.Lcfi1:
	.cfi_offset %rbp, -16
	movq	%rsp, %rbp
.Lcfi2:
	.cfi_def_cfa_register %rbp
	subq	$32, %rsp
	movabsq	$.L.str, %rax
	movabsq	$.L.str.1, %rcx
	movq	%rdi, -24(%rbp)
	movq	%rsi, -16(%rbp)
	movq	%rax, %rdi
	movq	%rcx, %rsi
	movl	$6, %edx
	movb	$0, %al
	callq	printf
	cmpq	$0, -24(%rbp)
	je	.LBB0_2
# BB#1:
	cmpq	$0, -16(%rbp)
	jne	.LBB0_3
.LBB0_2:
	movl	$-1, -4(%rbp)
	jmp	.LBB0_4
.LBB0_3:
	movl	$0, -4(%rbp)
.LBB0_4:
	movl	-4(%rbp), %eax
	addq	$32, %rsp
	popq	%rbp
	retq
.Lfunc_end0:
	.size	dragonball1, .Lfunc_end0-dragonball1
	.cfi_endproc
                                        # -- End function
	.globl	dragonball2             # -- Begin function dragonball2
	.p2align	4, 0x90
	.type	dragonball2,@function
dragonball2:                            # @dragonball2
	.cfi_startproc
# BB#0:
	pushq	%rbp
.Lcfi3:
	.cfi_def_cfa_offset 16
.Lcfi4:
	.cfi_offset %rbp, -16
	movq	%rsp, %rbp
.Lcfi5:
	.cfi_def_cfa_register %rbp
	subq	$16, %rsp
	movabsq	$.L.str.2, %rax
	movabsq	$.L.str.1, %rsi
	movl	%edi, -4(%rbp)
	movl	-4(%rbp), %ecx
	movq	%rax, %rdi
	movl	$14, %edx
	movb	$0, %al
	callq	printf
	xorl	%eax, %eax
	addq	$16, %rsp
	popq	%rbp
	retq
.Lfunc_end1:
	.size	dragonball2, .Lfunc_end1-dragonball2
	.cfi_endproc
                                        # -- End function
	.globl	main                    # -- Begin function main
	.p2align	4, 0x90
	.type	main,@function
main:                                   # @main
	.cfi_startproc
# BB#0:
	pushq	%rbp
.Lcfi6:
	.cfi_def_cfa_offset 16
.Lcfi7:
	.cfi_offset %rbp, -16
	movq	%rsp, %rbp
.Lcfi8:
	.cfi_def_cfa_register %rbp
	subq	$32, %rsp
	movabsq	$.L.str.4, %rax
	movabsq	$.L.str.5, %rcx
	movabsq	$.L.str.3, %rdx
	movl	$0, -8(%rbp)
	movl	%edi, -4(%rbp)
	movq	%rsi, -24(%rbp)
	movq	%rdx, -16(%rbp)
	movq	%rax, %rdi
	movq	%rcx, %rsi
	movb	$0, %al
	callq	printf
	xorl	%edi, %edi
	xorl	%esi, %esi
	callq	dragonball1
	movl	$4294967295, %edi       # imm = 0xFFFFFFFF
	callq	dragonball2
	xorl	%eax, %eax
	addq	$32, %rsp
	popq	%rbp
	retq
.Lfunc_end2:
	.size	main, .Lfunc_end2-main
	.cfi_endproc
                                        # -- End function
	.type	.L.str,@object          # @.str
	.section	.rodata.str1.1,"aMS",@progbits,1
.L.str:
	.asciz	"DEBUG: %s, line %d\n"
	.size	.L.str, 20

	.type	.L.str.1,@object        # @.str.1
.L.str.1:
	.asciz	"test-are.c"
	.size	.L.str.1, 11

	.type	.L.str.2,@object        # @.str.2
.L.str.2:
	.asciz	"DEBUG: %s, line %d: ID %d\n"
	.size	.L.str.2, 27

	.type	.L.str.3,@object        # @.str.3
.L.str.3:
	.asciz	"Vml5Z0pFZGk9UHg2a2dPY0loZW49S3cxN3dVQUFBPT0"
	.size	.L.str.3, 44

	.type	main.buf,@object        # @main.buf
	.section	.rodata,"a",@progbits
main.buf:
	.ascii	"\0224Vx"    # 人类不可读
	.size	main.buf, 4

	.type	.L.str.4,@object        # @.str.4
	.section	.rodata.str1.1,"aMS",@progbits,1
.L.str.4:
	.asciz	"Hello world: %s\n"
	.size	.L.str.4, 17

	.type	.L.str.5,@object        # @.str.5
.L.str.5:
	.asciz	"6xxzcQMhb4WgKX0EUkwG747K"
	.size	.L.str.5, 25


	.ident	"Fedora clang version 6.0.0 (trunk 311323) (based on LLVM 6.0.0svn-r311323)"
	.section	".note.GNU-stack","",@progbits

我在Linux进行了测试,大家可以在Mac上编译安装,期待大家的反馈,谢谢!

注:ARE(归属于 DragonEgg 子项目)使用GPLv2许可证。

Regards,
Leslie Zhai - a LLVM developer


#2

你们都懂LLVM


#3

大致看了下。Function重命名需要判断是否为declaration,我似乎没看到这部分代码。
参见我的博客 http://mayuyu.io/2017/06/02/LLVMHacking-0x1/


#4

不懂,看起来是分享,我给换了下类别。
先赞,后看。


#5

你需要实现的常量文本加密的话我这边用的做法是把原来的GV CDA替换成加密的然后在所有的Instrution类User那里插入allocainst后解密


#6

当然我不清楚GCC那边啥样所以不过是我个人意见而已


#7

Hi Akko,

搜索到大大的7天精通llvm代码混淆系列 orz

  1. 我对OC不熟悉,还得向大大们学习 :slight_smile: 我的主要方向是静态分析,例如检查memset调用时的空引用解析 LLVM后端,例如AVR芯片
  2. 我对”CFG的混淆“持观望态度:应该有benchmark对比CFG混淆后影响ELF尺寸、性能等问题。

期待大大们的反馈,谢谢!

Regards,
Leslie Zhai - a LLVM developer


#8

拜读~


#9

GCC v4.6之后(可能更早一点的版本)支持插件了 :slight_smile: LLVM PASS还得放在llvm/lib/Transforms/下,可能我不够深入,DragonEgg就是一个GCC插件,目前完成了GCC Frontend -> GIMPLE -> LLVM IR的”翻译“ http://llvmweekly.org/issue/190


#10

你说的都没错。但目前混淆的目的就是为了拿体积和性能换攻击者的开销


#11

我不大看得懂,我认识的好多人也看不懂LLVM相关的东西。楼主作为一个LLVM developer,能否用相对直白的语言,发一系列帖子,来介绍一下LLVM和相关的一些概念,如LLVM在开发中的应用等,作一个科普教育呢?
我想对我等LLVM小白来说,应该是非常受用的。
Anyway,谢谢楼主的分享!


#12

我在看你的博客 :slight_smile: http://mayuyu.io/2017/06/02/LLVMHacking-0x1/ 中的代码貌似没有体现判断declaration?可能我看漏了,请大大提示一下,谢谢!


#13

就是F.empty()
IR的函数分为declaration和definition
前者代表实现在当前TU外部由IR的Linker或者目标文件的Native Linker查找。这种函数你重命名了找不到当然就会崩


#14

http://frozengene.github.io/


#15

Kaleidoscope过一遍就差不多知道了


#16

多谢提示!我的测试用例考虑得不够充分。。。


#17

然后控制流平坦化这里好像LLVM自带一个Pass了?在Transforms/Utils里面


#18

感谢 https://github.com/llvm-mirror/llvm/blob/master/lib/Transforms/Utils/FlattenCFG.cpp

哈哈 :slight_smile: 我follow了你的github


#19

Kaleidoscope 就是这个卜 https://llvm.org/docs/tutorial/


#20

yEP