1. 문제
비밀번호를 찾으라는데 유저이름까지 나온다. 유저이름과 비번을 맞추면 될듯
따로 패킹된건 없다
2. 접근방법
int __cdecl __noreturn main(int argc, const char **argv, const char **envp)
{
_DWORD *v3; // eax
unsigned int i; // ecx
int v5; // eax
char *v6; // esi
int v7; // eax
v3 = VirtualAlloc(0, 0x100u, 0x3000u, 4u);
for ( i = 0; i < 0x100; i += 4 )
v3[i] = 8921743;
passswd = (int)(v3 + 168);
v5 = 87;
v6 = &byte_403138;
do
{
v5 = printf(std::cout, v5);
LOBYTE(v5) = *++v6;
}
while ( (_BYTE)v5 );
std::ostream::operator<<(std::cout, std::endl);
v7 = std::ostream::operator<<(std::cout, std::endl);
std::ostream::operator<<(v7, std::endl);
sub_401720(std::cout, "Username: ");
std::operator>><char,std::char_traits<char>>(std::cin, name);
sub_401720(std::cout, "Password: ");
std::istream::operator>>(std::cin, &input_passwd);
sub_401000();
std::ios::clear(std::cin + *(_DWORD *)(std::cin + 4), 0, 0);
std::istream::ignore(std::cin, 255, 10);
std::istream::get(std::cin);
exit(0);
}
중요 함수는 위 3개이다. 이름, 비번을 입력받는데 요놈들은 전역변수에 선언되어 있다. sub_401000
함수에서 실제 아디랑 비번 비교하는 듯
int sub_401000()
{
...
v0 = strcmp(name, byte_4031F8) == 0;
v44 = input_passwd == *(_DWORD *)passswd; // 비번 8921743
std::ostream::operator<<(std::cout, std::endl);
if ( !strcmp(name, "DonaldDuck") )
{
v1 = std::ostream::operator<<(std::cout, std::endl);
std::ostream::operator<<(v1, std::endl);
v2 = printf(std::cout, 0x49);
v3 = printf(v2, 32);
v4 = printf(v3, 99);
v5 = printf(v4, 97);
v6 = printf(v5, 110);
v7 = printf(v6, 39);
v8 = printf(v7, 116);
v9 = printf(v8, 32);
v10 = printf(v9, 98);
v11 = printf(v10, 101);
v12 = printf(v11, 108);
v13 = printf(v12, 105);
v14 = printf(v13, 101);
v15 = printf(v14, 118);
v16 = printf(v15, 101);
v17 = printf(v16, 32);
v18 = printf(v17, 121);
v19 = printf(v18, 111);
v20 = printf(v19, 117);
v21 = printf(v20, 32);
v22 = printf(v21, 102);
v23 = printf(v22, 101);
v24 = printf(v23, 108);
v25 = printf(v24, 116);
v26 = printf(v25, 32);
v27 = printf(v26, 102);
v28 = printf(v27, 111);
v29 = printf(v28, 114);
v30 = printf(v29, 32);
v31 = printf(v30, 116);
v32 = printf(v31, 104);
v33 = printf(v32, 97);
v34 = printf(v33, 116);
v35 = printf(v34, 33);
v36 = printf(v35, 32);
v37 = printf(v36, 120);
printf(v37, 68);
v38 = std::ostream::operator<<(std::cout, std::endl);
std::ostream::operator<<(v38, std::endl);
std::ostream::operator<<(std::cout, std::endl);
}
if ( v0 && v44 ) // name의 첫 바이트 0이여야 하고 비번을 잘 입력해야함
{
v39 = 87;
v40 = &success;
do
{
v39 = printf(std::cout, v39); // 성공 루틴
LOBYTE(v39) = *++v40;
}
while ( (_BYTE)v39 );
}
else
{
v41 = 83;
v42 = &fail; // sorry 출력 부분
do
{
v41 = printf(std::cout, v41);
LOBYTE(v41) = *++v42;
}
while ( (_BYTE)v41 );
}
return std::ostream::operator<<(std::cout, std::endl);
}
성공 메시지가 출력되려면 v0과 v44가 참이여야 한다. 요 두개의 변수는 맨 위에서 설정된다.
v0 = strcmp(name, byte_4031F8) == 0;
v44 = input_passwd == *(_DWORD *)passswd; // 비번 8921743
byte_4031f8
요놈은 0이여서 결국 name의 첫바이트가 널바이트인지 비교를 한다. 이름에 널바이트를 주게도면 결과값은 0이고 어셈으로는 " cmp dl, [ecx] " 요 부분이다. ecx에 들은 값이 널바이트여서 dl이 널이면 zero flag는 1이 된다.
그리도 아래에서 setz bl을 통해 현재 제로 플래그가 1이면 bl에 1을 넣고 0이면 bl에 0을 넣는다.
결국 bl은 name의 첫바이트가 널인지 아닌지에 따라서 결정된다. 그 밑에 sete 부분은 cmp eax, [ecx] 의 결과로 제로플래그에 따라 결과가 달라진다. 즉 저부분이 비밀번호 확인 루틴이다.
3. 풀이
약간 문제가 좀 그렇다.. 비밀번호는 박혀있는 값을 입력하면 된다. 걍 찾으면 되는데 8921743
요거다. 유저이름은 DonaldDack
으로 추정되긴 하는데 저걸 입력하면 아까 Name의 첫 바이트가 널이여야 한다는 조건을 통과하지 못해서 성공 메시지가 안나온다.
그래서 리눅스에서 pwntools 를 이용해서 익스 코드를 짤때 널바이트를 넣을수 있었는데 그 아이디가 떠올라서 윈도우용 pwntools을 검색해봤다. 찾아보니 winpwn 이라는 모듈이 있었고 이를 이용해서 익스코드를 짯다.
디버깅은 windbg, windbg preview, mingw gdb, x64dbg 를 이용해서 attach가 가능하다
from winpwn import *
context.debugger = "windbgx"
context.arch = "i386"
context.log_level = "debug"
p = process("09.exe")
windbgx.attach(p, script="bp 0xe613e6")
#p.recv(100)
p.recvuntil("Username: ")
p.sendline("\x00")
p.recvuntil("Password: ")
# p.recv(30)
p.send("8921743")
p.interactive()
근데 위 코드도 안된다. ㅅㅂ아ㅣ럼니아러;ㄴㅇ미ㅏㅓㄹ 삽질을 존나했는데
Username에 입력을 받고, "Password: "가 출력되는 함수를 확인해보았을 때
저 0xe613e6 함수가 Password 를 출력해주는 부분이다. 근데 저함수가 호출되도 로그에는 Password 문자열이 안나왔다 ;;
결국 Username에는 널바이트가 들어갔다고 쳐도 Password에는 값이 안들어간 상태로 검증을 하게 된다.
v0 = strcmp(name, byte_4031F8) == 0;
v44 = input_passwd == *(_DWORD *)passswd; // 비번 8921743
std::ostream::operator<<(std::cout, std::endl);
if ( !strcmp(name, "DonaldDuck") )
{
저 부분에서 안나왔던 "Password: " 문자열이 출력된다. ;;
에고
4. 몰랐던 개념
그래도 winpwn이라는 좋은 기능을 얻었다.
'워게임 > CodeEngn' 카테고리의 다른 글
[CodeEngn] Advance RCE L10 (0) | 2021.02.05 |
---|---|
[CodeEngn] Advance RCE L08 (0) | 2021.02.02 |
[CodeEngn] Advance RCE L07 (0) | 2021.02.02 |
[CodeEngn] Advance RCE L06 (0) | 2021.01.30 |
[CodeEngn] Advance RCE L05 (0) | 2021.01.29 |
Uploaded by Notion2Tistory v1.1.0