$ file unbreakable-enterprise-product-activation
unbreakable-enterprise-product-activation: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=bccabd9737a6b3a73b3fdaca22fdf383497801cc, stripped
void __fastcall __noreturn main(int a1, char **a2, char **a3)
{
if ( a1 != 2 )
errx(1, "./unbreakable_enterprise_product_activation product-key", a3);
strncpy(&dest, a2[1], 0x43uLL);
sub_4027F0(&dest);
sub_402830();
sub_402870();
sub_4028C0();
sub_402910();
sub_402940();
sub_402980();
sub_4029C0();
sub_402A00();
sub_402A40();
sub_402A70();
sub_402AB0();
sub_402AF0();
sub_402B20();
sub_402B50();
sub_402B90();
sub_402BC0();
sub_402BF0();
sub_402C40();
sub_402C80();
sub_402CC0();
sub_402CF0();
sub_402D40();
sub_402D90();
sub_402DD0();
sub_402E10();
sub_402E60();
sub_402EA0();
sub_402EE0();
sub_402F10();
sub_402F50();
sub_402F90();
sub_402FD0();
sub_403010();
sub_403050();
sub_403090();
sub_4030E0();
sub_403120();
sub_403160();
sub_4031A0();
sub_4031D0();
sub_403210();
sub_403250();
sub_403290();
sub_4032D0();
sub_403320();
sub_403350();
sub_403390();
sub_4033D0();
sub_403410();
sub_403440();
sub_400830();
}
⇒ 입력 최대 크기 = 0x43
void __noreturn sub_400830()
{
puts("Thank you - product activated!");
exit(0);
}
모든 함수가 main으로 리턴된 후 마지막으로 sub_400830() 함수가 호출되면서 Thank you ~ 를 출력해준다.
IDA로 스트링을 확인해봤을 때 Product activation failure를 출력하는 부분이 있다.
__int64 sub_402870()
{
__int64 result; // rax
result = (unsigned __int8)byte_6042E3
+ (unsigned __int8)byte_6042E4
- (unsigned __int8)byte_6042D3
- (unsigned __int8)byte_6042C3
- (unsigned int)(unsigned __int8)byte_6042EC;
if ( byte_6042C2 != byte_6042E3 + byte_6042E4 - byte_6042D3 - byte_6042C3 - byte_6042EC )
sub_400850(0x101u);
return result;
}
해당 출력은 sub_400850() 함수에서 해주는데, 이 함수는 main() 함수에서 조건이 맞지 않을 경우 호출된다. 다른 함수들도 확인해보면 sub_400850() 함수가 호출되는걸 확인할 수 있었다.
Thank you를 출력해주는 경로를 찾아야 하고, failure를 출력하는 경로는 피해야 한다.
따라서 angr의 simulation managers를 사용한다.
Simulation managers 사용에 앞서 argument를 줘야 하므로 angr solver engine의 bitvector를 사용한다.
>>> bv = state.solver.BVV(0x1234, 32)
>>> bv
<BV32 0x1234>
>>> state.solver.eval(bv)
4660
0x1234 값을 갖는 32bit bitvector 생성할 수 있다.
symbolic bitvector를 생성하는 BVS도 사용할 수 있다.
#!/usr/bin/python
import angr
# load binary
p = angr.Project('./unbreakable-enterprise-product-activation', auto_load_libs=False)
# max length from strncpy is 0x43
input_size = 0x43
# initialize argv1
state = p.factory.entry_state()
argv1 = state.solver.BVS('argv1', 8*input_size)
# initial state with argument input
state = p.factory.entry_state(args=[p.filename, argv1])
# default is 60 symbolic bytes. so increase size
state.libc.symbolic_bytes = input_size + 1
# argv1 start with CTF{
state.add_constraints(argv1.chop(8)[0]=='C')
state.add_constraints(argv1.chop(8)[1]=='T')
state.add_constraints(argv1.chop(8)[2]=='F')
state.add_constraints(argv1.chop(8)[3]=='{')
simgr = p.factory.simgr(state)
# 0x400830: success message path, 0x400850: failure path
simgr.explore(find=0x400830, avoid=0x400850)
print(simgr.found[0].solver.eval(argv1, cast_to=bytes))
플래그가 CTF{로 시작하므로 argv1에 CTF{를 넣고 simulation manager를 통해 이동해야 하는 경로로 0x400830 주소를 주고 피해야 하는 경로로 0x400850 주소를 준다.
explore 후 found stash에 플래그 찾은 상태에 대한 주소(0x400830)이 저장되므로, 해당 상태에서 dumps로 문자열을 확인해보면 플래그를 얻을 수 있다.
[angr_ctf] 01_angr_avoid (0) | 2022.05.14 |
---|---|
[angr_ctf] 00_angr_find (0) | 2022.05.14 |
[DefCamp CTF 2015 Quals] Entry Language (Reverse 100) (0) | 2022.05.11 |
[angr] fauxware (0) | 2022.05.09 |
[QWBCTF 2018] core writeup (kernel exploit) (0) | 2022.03.22 |