개요
저번에 시도했으나 풀지 못했던 hack_bin 문제를 이번에 해결해 간단히 소개한다.
문제 상황
.
├── hack_bin
└── key
32비트 리눅스 환경이다. c파일이 주어지지 않았으므로 IDA를 이용해 바이너리를 열어 보자.
int __cdecl main(int a1, char **a2)
{
char dest[256]; // [esp+10h] [ebp-100h] BYREF
if ( a1 != 3 )
{
puts("Give me the argument");
exit(1);
}
strcpy(&s1, a2[1]);
if ( strcmp(&s1, "H4ck 7h1s b1n4ry") )
return puts("Fail!!");
puts("Well DONE!!");
strncpy(dest, a2[2], 0x101u);
return puts(dest);
}
다행히 깔끔하게 디컴파일된다.
바이너리를 실행하고 첫 번째 인자로 H4ck 7h1s b1n4ry를 줘야 할 것 같다. 이후 두 번째 인자로 받은 문자열을 그대로 출력한다.
취약점이 어디에 있지..?
우선 strcpy(&s1, a2[1]);의 src address는 0x0804A060이다. bss 영역이므로 eip를 덮어쓸 수 없다. 일단 무한정 길게 쓸 수 있긴 하지만.
BOF가 발생하는 부분은 strncpy이다. 딱 1바이트만큼 초과해서 받는다.
만약 dest 위에 바로 saved ebp가 존재한다면 하위 1바이트를 덮어써서 dest 영역으로 끌어들일 수 있겠으나…
and esp, 0FFFFFFF0h
main 함수 프롤로그에 위처럼 스택 프레임을 정렬하는 명령이 있으므로 불가능하다.
GDB를 이용해 동적 분석한 결과 해당 주소에는 쓰레기 값이 들어있었다.
정답은 다른 곳에
1바이트 초과 입력이 그냥 있는 것은 아닐 것이라 생각해서 그 부분만 계속 파고 있었는데, 핵심은 다른 곳에 있었다.
s1이 존재하는 bss 영역은 다음과 같이 생겼다.
.bss:0804A060 ; char s1
.bss:0804A060 s1 db ? ; DATA XREF: dtor_001+1C↑r
.bss:0804A060 ; dtor_001+26↑w ...
.bss:0804A061 byte_804A061 db ? ; DATA XREF: dtor_001+45↑r
.bss:0804A062 byte_804A062 db ? ; DATA XREF: dtor_001+54↑r
.bss:0804A063 byte_804A063 db ? ; DATA XREF: dtor_001+63↑r
.bss:0804A064 byte_804A064 db ? ; DATA XREF: dtor_001+72↑r
.bss:0804A065 byte_804A065 db ? ; DATA XREF: dtor_001+81↑r
.bss:0804A066 byte_804A066 db ? ; DATA XREF: dtor_001+8C↑r
.bss:0804A067 byte_804A067 db ? ; DATA XREF: dtor_001+97↑r
.bss:0804A068 byte_804A068 db ? ; DATA XREF: dtor_001+A2↑r
.bss:0804A069 byte_804A069 db ? ; DATA XREF: dtor_001+AD↑r
.bss:0804A06A byte_804A06A db ? ; DATA XREF: dtor_001+B8↑r
.bss:0804A06B byte_804A06B db ? ; DATA XREF: dtor_001+C3↑r
.bss:0804A06C byte_804A06C db ? ; DATA XREF: dtor_001+CE↑r
.bss:0804A06D byte_804A06D db ? ; DATA XREF: dtor_001+D9↑r
.bss:0804A06E byte_804A06E db ? ; DATA XREF: dtor_001+E4↑r
.bss:0804A06F byte_804A06F db ? ; DATA XREF: dtor_001+EF↑r
.bss:0804A070 ; char command[84]
.bss:0804A070 command db 54h dup(?) ; DATA XREF: dtor_001+FA↑o
.bss:0804A070 _bss ends
16바이트의 s1 영역 이후에 command라고 적힌 84바이트짜리 영역이 있다. 이름부터 무척 수상해 보이길래 이 주소를 참조하는 곳을 찾아봤다.
mov dword ptr [esp], offset command ; command
call _system
?! command에 있는 문자열을 인자로 주면서 무려 system 함수를 호출하고 있다!
해당 코드가 있는 함수 전체를 디컴파일해보자.
int dtor_001()
{
int result; // eax
int i; // [esp+1Ch] [ebp-Ch]
for ( i = 0; i <= 15; ++i )
*(&s1 + i) ^= 0x12u;
result = (unsigned __int8)s1;
if ( s1 == 72 )
{
result = (unsigned __int8)byte_804A061;
if ( byte_804A061 == 52 )
{
result = (unsigned __int8)byte_804A062;
if ( byte_804A062 == 99 )
{
result = (unsigned __int8)byte_804A063;
if ( byte_804A063 == 107 )
{
result = (unsigned __int8)byte_804A064;
if ( byte_804A064 == 32 )
{
result = (unsigned __int8)byte_804A065;
if ( byte_804A065 == 55 )
{
result = (unsigned __int8)byte_804A066;
if ( byte_804A066 == 104 )
{
result = (unsigned __int8)byte_804A067;
if ( byte_804A067 == 49 )
{
result = (unsigned __int8)byte_804A068;
if ( byte_804A068 == 115 )
{
result = (unsigned __int8)byte_804A069;
if ( byte_804A069 == 32 )
{
result = (unsigned __int8)byte_804A06A;
if ( byte_804A06A == 98 )
{
result = (unsigned __int8)byte_804A06B;
if ( byte_804A06B == 49 )
{
result = (unsigned __int8)byte_804A06C;
if ( byte_804A06C == 110 )
{
result = (unsigned __int8)byte_804A06D;
if ( byte_804A06D == 52 )
{
result = (unsigned __int8)byte_804A06E;
if ( byte_804A06E == 114 )
{
result = (unsigned __int8)byte_804A06F;
if ( byte_804A06F == 121 )
result = system(command);
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
return result;
}
세상에, 정답이다. s1이 가리키는 문자열을 0x12와 xor 한 후, 그 결과가 H4ck 7h1s b1n4ry이면 시스템 함수를 호출한다.
이상한 점은 이 함수는 main함수에서 호출하지 않는다는 것. 문득 문제 페이지에 있던 문장이 생각났다.
판사님.. 제 main()에는 버그가 없습니다!
코드는 main함수에서만 실행되는 게 아니니 dtor_001()을 다른 어딘가에서 호출하는 걸지도 모른다. 프로그램 초기화나 종료 과정에서 뭘 하는지는 모르지만 믿져야 본전. 거두절미하고 페이로드를 작성해서 확인해보면 될 일이다.
magic = b'H4ck 7h1s b1n4ry'
binsh = b'/bin/sh'
magic_crypt = bytes([i^0x12 for i in magic])
payload = magic_crypt+binsh
p = s.process(["/home/hack_bin/hack_bin", payload, 'A'])
p.interactive()
p.close()
이렇게 쉘을 땄다.
여담
bof를 이용하는 것도 아니고, rop도 아니었다. 오히려 bomblab에 훨씬 가까운 문제랄까.
알고 보면 매우 간단한데 main 함수에만 집중하다가 한참이 걸렸다. 풀었으니 망정이지.
그럼 다음에,
Gabriel-Dropout
at 17:06