这几天断断续续的都是事,都没静下来学点东西..

输入在程序内

domo:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include<stdio.h>
void success(){
printf("success\n");
}
void failed(){
printf("failed\n");
}
int main(void){
char name[9];
scanf("%s",name);
if(!strcmp(name,"jsk")){
success();
}else{
failed();
}
return 0;
}

我们需要利用angr框架来自动化扫出jsk。
首先加载二进制程序

1
p = Project("domo",auto_load_libs=False)

接着创建一个状态,默认为程序入口

1
state=p.factory.entry_state()

然后创建模拟器模拟程序执行

1
sm=p.factory.simulation_manager(state)

接着创建限制条件,在IDA中我们发现,

1
2
3
4
5
6
7
8
9
text:00000000004006A8                 jnz     short loc_4006B6
.text:00000000004006AA mov eax, 0
.text:00000000004006AF call success
.text:00000000004006B4 jmp short loc_4006C0
.text:00000000004006B6 ; ---------------------------------------------------------------------------
.text:00000000004006B6
.text:00000000004006B6 loc_4006B6: ; CODE XREF: main+40↑j
.text:00000000004006B6 mov eax, 0
.text:00000000004006BB call failed

程序跳转0x4006b6即是结果错误,那么我们需要程序跳转0x04006AA,
于是约束条件find=0x04006AA ,avoid=0x4006B6

1
res=sm.explore(find=0x04006AA ,avoid=0x4006B6)

打印输出结果

1
2
for pp in res.found:
print pp.posix.dumps(0)

一般res.found的len为发现路径的个数,一般为1,posix.dumps(0)为输入字符
结果:

1
2
3
starssgo@ubuntu:~/CTF/angr/one$ python exp.py 
1
jsk

domoexp:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# -*- coding: utf-8 -*
from angr import *


p = Project("domo",auto_load_libs=False)

state=p.factory.entry_state()

sm=p.factory.simulation_manager(state)

res=sm.explore(find=0x04006AA ,avoid=0x4006B6)

print len(res.found)
for pp in res.found:
print pp.posix.dumps(0)

从命令行输入

例子
拖入IDA,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
int __cdecl main(int argc, const char **argv, const char **envp)
{
int result; // eax

if ( argc == 2 )
{
if ( (unsigned int)verify(argv[1], argv, envp) )
puts("Correct! that is the secret key!");
else
puts("I'm sorry, that's the wrong secret key!");
result = 0;
}
else
{
puts("You need to enter the secret key!");
result = -1;
}
return result;
}

发现当verify()函数返回值不等于0时,我们就得到结果,查看verify函数

1
2
3
4
5
6
7
8
9
10
11
12
_BOOL8 __fastcall verify(__int64 a1)
{
int i; // [rsp+14h] [rbp-4h]

for ( i = 0; *(_BYTE *)(i + a1); ++i )
{
if ( encrypted[i] != ((unsigned __int8)((unsigned __int8)(*(_BYTE *)(i + a1) ^ i) << ((i ^ 9) & 3)) | (unsigned __int8)((signed int)(unsigned __int8)(*(_BYTE *)(i + a1) ^ i) >> (8 - ((i ^ 9) & 3))))
+ 8 )
return 0LL;
}
return i == 23;
}

其中有一个比较复杂的算法,如果手动去算非常麻烦,这个时候就用到了angr,还是先找约束条件
我们发现find=0x400602为计算结果正确的路径,现在我们只需要一个从命令行接受输入的框架。
这里我们用到了claripy模块,
我们首先限制输入的长度最多为100

1
argv1 = claripy.BVS("argv1",100*8)

接着我们将状态为有一个命令行参数

1
initial_state = project.factory.entry_state(args=["./crackme1",argv1])

接下来就是传入模拟器,然后执行

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
import angr
import claripy


def main():
project = angr.Project("./ais3_crackme")

#create an initial state with a symbolic bit vector as argv1
argv1 = claripy.BVS("argv1",100*8) #
initial_state = project.factory.entry_state(args=["./crackme1",argv1])

#create a path group using the created initial state
sm = project.factory.simulation_manager(initial_state)

#symbolically execute the program until we reach the wanted value of the instruction pointer
sm.explore(find=0x400602) #at this instruction the binary will print(the "correct" message)

found = sm.found[0]
#ask to the symbolic solver to get the value of argv1 in the reached state as a string
solution = found.solver.eval(argv1, cast_to=str)

print repr(solution)
solution = solution[:solution.find(b"\x00")]
print solution
if __name__ == '__main__':
main()

输出

我们在前面知道了re.found[0].posix.dumps(0)是打印出输入,那么re.found[0].posix.dumps(1)是不是输出呢,我们测试一下,
还是第一个domo的exp,不过我们最后输出处变成了:

1
2
print pp.posix.dumps(0)
print pp.posix.dumps(1)

我们查看下结果

1
2
3
4
5
starssgo@ubuntu:~/CTF/angr/one$ python exp.py 
1
jsk

starssgo@ubuntu:~/CTF/angr/one$

程序并没有输入任何东西,我们在看下我们的find=0x04006AA,直到路径寻找到时,我们都没有输入字符,我们尝试将find=0x400654,在ida反编译为:

1
2
3
4
5
6
7
.text:0000000000400646                 push    rbp
.text:0000000000400647 mov rbp, rsp
.text:000000000040064A mov edi, offset s ; "success"
.text:000000000040064F call _puts
.text:0000000000400654 nop
.text:0000000000400655 pop rbp
.text:0000000000400656 retn

这时我们再次运行

1
2
3
4
5
6
starssgo@ubuntu:~/CTF/angr/one$ python exp.py 
1
jsk
success

starssgo@ubuntu:~/CTF/angr/one$

可以看到,我们可以获取程序输出。
我们也可以获取寄存器的值,我们将find=0x40064A,此时当路径寻找到的时候,edi=success,

1
2
3
4
5
pp.memory.load(pp.regs.edi,endness='Iend_LE') #获取寄存器的值
#其中endness的值可以有三个
#LE – little endian, least significant byte is stored at lowest address
#BE – big endian, most significant byte is stored at lowest address
#ME – Middle-endian. Yep.

我们更改输出为:

1
2
print pp.posix.dumps(0)
print pp.memory.load(pp.regs.edi,endness='Iend_LE')

查看结果:

1
2
3
4
starssgo@ubuntu:~/CTF/angr/one$ python exp.py 
1
jsk
<BV64 0x73736563637573>

可以看到,得到了寄存器的值,我们将其转为字符串,

1
2
3
print pp.posix.dumps(0)
addr=pp.memory.load(pp.regs.edi,endness='Iend_BE')#更改为大端
print pp.solver.eval(addr,cast_to=str)

查看结果:

1
2
3
1
jsk
success

我们依然获取了输出值,但是不一样的是,我们能得到了寄存器的值。

先写到这里,等学到后面了在更新…
(我讨厌期末考试)
参考链接