[Reversing] Handray (Asm to C)
시작 전에 x86-64 Calling Convention에 의해서 함수에 들어가는 인자를 알아보겠습니다.
가변 인자를 사용해도 되겠지만, 조금 더 이해하기 쉽도록 add 함수에 인자를 8개 넣어서 컴파일 했습니다.
정상적으로 실행이 됩니다.
이제 이 바이너리를 gdb로 돌려봐서 레지스터를 확인해보면
add 함수를 호출하기 전 1, 3, 5, 7, 9, 0xb, 0xd, 0xf가 알맞게 잘 들어가 있습니다.
각 인자들은 차례로 rdi, rsi, rdx, rcx, r8, r9을 사용하며, 이 외에 인자들은 스택을 이용합니다.
main +12로 r9 레지스터를 이용하기 직전으로 가보면 스택에 0xf와 0xd가 차례대로 들어와 있는 것을 확인할 수 있습니다.
Handray 1
0x0000000000001149 <+0>: endbr64
0x000000000000114d <+4>: push rbp
0x000000000000114e <+5>: mov rbp,rsp
0x0000000000001151 <+8>: lea rax,[rip+0xeac] # 0x2004
0x0000000000001158 <+15>: mov rdi,rax
0x000000000000115b <+18>: call 0x1050 <puts@plt>
0x0000000000001160 <+23>: mov eax,0x0
0x0000000000001165 <+28>: pop rbp
0x0000000000001166 <+29>: ret
주어진 코드는 위와 같습니다.
코드가 짧으므로 간단하게 C로 바꿔보면, (편의상 main 함수라고 가정)
main +8에서 rax에 [rip + 0xeac]를 복사합니다. main +15에서 이를 rdi에 넣고, main +18에서 puts 함수를 호출합니다.
int main() {
puts("~~~");
return 0;
}
Handray 2
0x0000000000001189 <+0>: endbr64
0x000000000000118d <+4>: push rbp
0x000000000000118e <+5>: mov rbp,rsp
0x0000000000001191 <+8>: sub rsp,0x20
0x0000000000001195 <+12>: mov rax,QWORD PTR fs:0x28
0x000000000000119e <+21>: mov QWORD PTR [rbp-0x8],rax
0x00000000000011a2 <+25>: xor eax,eax
0x00000000000011a4 <+27>: lea rax,[rbp-0x18]
0x00000000000011a8 <+31>: mov rsi,rax
0x00000000000011ab <+34>: lea rax,[rip+0xe52] # 0x2004
0x00000000000011b2 <+41>: mov rdi,rax
0x00000000000011b5 <+44>: mov eax,0x0
0x00000000000011ba <+49>: call 0x1090 <__isoc99_scanf@plt>
0x00000000000011bf <+54>: mov DWORD PTR [rbp-0x14],0x0
0x00000000000011c6 <+61>: jmp 0x121c <main+147>
0x00000000000011c8 <+63>: mov DWORD PTR [rbp-0x10],0x0
0x00000000000011cf <+70>: jmp 0x11df <main+86>
0x00000000000011d1 <+72>: mov edi,0x20
0x00000000000011d6 <+77>: call 0x1070 <putchar@plt>
0x00000000000011db <+82>: add DWORD PTR [rbp-0x10],0x1
0x00000000000011df <+86>: mov eax,DWORD PTR [rbp-0x18]
0x00000000000011e2 <+89>: sub eax,DWORD PTR [rbp-0x14]
0x00000000000011e5 <+92>: sub eax,0x1
0x00000000000011e8 <+95>: cmp DWORD PTR [rbp-0x10],eax
0x00000000000011eb <+98>: jl 0x11d1 <main+72>
0x00000000000011ed <+100>: mov DWORD PTR [rbp-0xc],0x0
0x00000000000011f4 <+107>: jmp 0x1204 <main+123>
0x00000000000011f6 <+109>: mov edi,0x2a
0x00000000000011fb <+114>: call 0x1070 <putchar@plt>
0x0000000000001200 <+119>: add DWORD PTR [rbp-0xc],0x1
0x0000000000001204 <+123>: mov eax,DWORD PTR [rbp-0x14]
0x0000000000001207 <+126>: add eax,eax
0x0000000000001209 <+128>: cmp DWORD PTR [rbp-0xc],eax
0x000000000000120c <+131>: jle 0x11f6 <main+109>
0x000000000000120e <+133>: mov edi,0xa
0x0000000000001213 <+138>: call 0x1070 <putchar@plt>
0x0000000000001218 <+143>: add DWORD PTR [rbp-0x14],0x1
0x000000000000121c <+147>: mov eax,DWORD PTR [rbp-0x18]
0x000000000000121f <+150>: cmp DWORD PTR [rbp-0x14],eax
0x0000000000001222 <+153>: jl 0x11c8 <main+63>
0x0000000000001224 <+155>: mov eax,0x0
0x0000000000001229 <+160>: mov rdx,QWORD PTR [rbp-0x8]
0x000000000000122d <+164>: sub rdx,QWORD PTR fs:0x28
0x0000000000001236 <+173>: je 0x123d <main+180>
0x0000000000001238 <+175>: call 0x1080 <__stack_chk_fail@plt>
0x000000000000123d <+180>: leave
0x000000000000123e <+181>: ret
jmp, jl, je같은 명령어가 있는 걸 보아 제어문이 포함된 것 같습니다.
0x000000000000118d <+4>: push rbp
0x000000000000118e <+5>: mov rbp,rsp
함수 프롤로그 입니다.
0x0000000000001191 <+8>: sub rsp,0x20
스택을 0x20만큼 할당합니다.
0x0000000000001195 <+12>: mov rax,QWORD PTR fs:0x28
0x000000000000119e <+21>: mov QWORD PTR [rbp-0x8],rax
0x00000000000011a2 <+25>: xor eax,eax
8 Bytes Canary를 활성화하고, RAX를 0으로 초기화합니다.
0x00000000000011a4 <+27>: lea rax,[rbp-0x18]
0x00000000000011a8 <+31>: mov rsi,rax
0x00000000000011ab <+34>: lea rax,[rip+0xe52] # 0x2004
0x00000000000011b2 <+41>: mov rdi,rax
0x00000000000011b5 <+44>: mov eax,0x0
0x00000000000011ba <+49>: call 0x1090 <__isoc99_scanf@plt>
main +27에서 [rbp - 0x18]에 변수 하나를 생성하고, 이를 RSI 레지스터에 넣습니다.
main +34에서 [rip + 0xe52]에 있는 값을 RAX에 넣고, 이를 RDI에 넣습니다.
RAX를 0으로 초기화 하고 scanf() 함수를 호출합니다.
여기까지 봤을 때 C 코드는 아래와 같습니다.
int main() {
int a;
scanf("%d", &a);
return 0;
}
[rbp - 0x18]이 앞으로 호출될 때 DWORD (4 Bytes)로 호출되기 때문에 int 형으로 생각했습니다. (하위 4Bytes만 쓰인 것인지는 모르겠음)
[rip + 0xe52]는 scanf()에서 쓰인 포맷 스트링인 %d 입니다.
0x00000000000011bf <+54>: mov DWORD PTR [rbp-0x14],0x0
0x00000000000011c6 <+61>: jmp 0x121c <main+147>
반복을 위한 초기 변수를 선언하고 0으로 초기화합니다. 이후 main +147로 점프합니다.
int main() {
int a;
scanf("%d", &a);
for (int i = 0;
return 0;
}
0x00000000000011c8 <+63>: mov DWORD PTR [rbp-0x10],0x0
0x00000000000011cf <+70>: jmp 0x11df <main+86>
새로운 지역변수가 선언되고, 0으로 초기화 되는 것을 보아 두 번째 반복문이 선언됐습니다. 이후 main+86으로 점프합니다.
int main() {
int a;
scanf("%d", &a);
for (int i = 0;
for (int j = 0;
return 0;
}
0x00000000000011d1 <+72>: mov edi,0x20
0x00000000000011d6 <+77>: call 0x1070 <putchar@plt>
putchar의 인자로 0x20이 쓰였습니다. 0x20은 공백(' ')입니다.
int main() {
int a;
scanf("%d", &a);
for (int i = 0;
for (int j = 0;
putchar(' ');
return 0;
}
0x00000000000011db <+82>: add DWORD PTR [rbp-0x10],0x1
두 번째 반복 변수에 쓰였던 j를 1씩 더합니다.
int main() {
int a;
scanf("%d", &a);
for (int i = 0;
for (int j = 0; ;j++) {
putchar(' ');
}
return 0;
}
0x00000000000011df <+86>: mov eax,DWORD PTR [rbp-0x18]
0x00000000000011e2 <+89>: sub eax,DWORD PTR [rbp-0x14]
0x00000000000011e5 <+92>: sub eax,0x1
0x00000000000011e8 <+95>: cmp DWORD PTR [rbp-0x10],eax
0x00000000000011eb <+98>: jl 0x11d1 <main+72>
처음에 scanf로 사용자에게 입력받았던 변수 a에서 첫 번째 반복 변수인 i를 뺄셈합니다. 그리고, 이 값에 1을 더 뺍니다.
이후 cmp 명령어를 통해 위에서 연산한 값인 RAX와 두 번째 반복 변수인 j를 비교합니다.
main +98에 있는 jl 명령어는 비교 연산자로 less than 이라는 의미를 가집니다. 즉 부등호 ' < '를 의미합니다.
int main() {
int a;
scanf("%d", &a);
for (int i = 0;
for (int j = 0; j < a - i - 1 ; j++) {
putchar(' ');
}
return 0;
}
0x00000000000011ed <+100>: mov DWORD PTR [rbp-0xc],0x0
0x00000000000011f4 <+107>: jmp 0x1204 <main+123>
[rbp - 0xc]라는 새로운 지역변수가 생기고 0으로 초기화 됐습니다. main +123으로 점프한 다는 것을 보아 세 번째 반복문으로 보입니다.
int main() {
int a;
scanf("%d", &a);
for (int i = 0;
for (int j = 0; j < a - i - 1 ; j++) {
putchar(' ');
}
for (int k = 0;
return 0;
}
0x00000000000011f6 <+109>: mov edi,0x2a
0x00000000000011fb <+114>: call 0x1070 <putchar@plt>
putchar의 인자로 0x2a가 들어왔습니다. 0x2a는 ' * ' 입니다.
int main() {
int a;
scanf("%d", &a);
for (int i = 0;
for (int j = 0; j < a - i - 1 ; j++) {
putchar(' ');
}
for (int k = 0;
putchar('*');
return 0;
}
0x0000000000001200 <+119>: add DWORD PTR [rbp-0xc],0x1
세 번째 반복 변수에 쓰였던 k에 1을 더합니다.
int main() {
int a;
scanf("%d", &a);
for (int i = 0;
for (int j = 0; j < a - i - 1 ; j++) {
putchar(' ');
}
for (int k = 0; ; k++) {
putchar('*');
}
return 0;
}
0x0000000000001204 <+123>: mov eax,DWORD PTR [rbp-0x14]
0x0000000000001207 <+126>: add eax,eax
0x0000000000001209 <+128>: cmp DWORD PTR [rbp-0xc],eax
0x000000000000120c <+131>: jle 0x11f6 <main+109>
첫 번째 반복 변수인 i를 RAX에 넣고, 이를 자기 자신과 더합니다. 즉, 곱하기 2를 했습니다.
이제 RAX를 세 번째 반복 변수인 k와 비교하고, main +109로 점프합니다.
jle 명령어는 less than equal의 뜻을 가진 비교 연산자입니다. 즉, <=입니다.
int main() {
int a;
scanf("%d", &a);
for (int i = 0;
for (int j = 0; j < a - i - 1 ; j++) {
putchar(' ');
}
for (int k = 0; k <= i * 2 ; k++) {
putchar('*');
}
return 0;
}
0x000000000000120e <+133>: mov edi,0xa
0x0000000000001213 <+138>: call 0x1070 <putchar@plt>
putchar의 인자로 0xa를 넣습니다. 0xa를 줄 바꿈 문자 '\n' 입니다.
int main() {
int a;
scanf("%d", &a);
for (int i = 0;
for (int j = 0; j < a - i - 1 ; j++) {
putchar(' ');
}
for (int k = 0; k <= i * 2 ; k++) {
putchar('*');
}
putchar('\n');
return 0;
}
0x0000000000001218 <+143>: add DWORD PTR [rbp-0x14],0x1
첫 번째 반복 변수인 i에 1을 더합니다.
int main() {
int a;
scanf("%d", &a);
for (int i = 0; ; i++) {
for (int j = 0; j < a - i - 1 ; j++) {
putchar(' ');
}
for (int k = 0; k <= i * 2 ; k++) {
putchar('*');
}
putchar('\n');
}
return 0;
}
0x000000000000121c <+147>: mov eax,DWORD PTR [rbp-0x18]
0x000000000000121f <+150>: cmp DWORD PTR [rbp-0x14],eax
0x0000000000001222 <+153>: jl 0x11c8 <main+63>
scanf를 통해 입력한 값인 a를 첫 번째 반복 변수인 i와 비교합니다. 이후 less than ' < ' 연산자로 main +63으로 점프합니다.
int main() {
int a;
scanf("%d", &a);
for (int i = 0; i < a; i++) {
for (int j = 0; j < a - i - 1 ; j++) {
putchar(' ');
}
for (int k = 0; k <= i * 2 ; k++) {
putchar('*');
}
putchar('\n');
}
return 0;
}
최종 코드입니다.
0x0000000000001224 <+155>: mov eax,0x0
0x0000000000001229 <+160>: mov rdx,QWORD PTR [rbp-0x8]
0x000000000000122d <+164>: sub rdx,QWORD PTR fs:0x28
0x0000000000001236 <+173>: je 0x123d <main+180>
0x0000000000001238 <+175>: call 0x1080 <__stack_chk_fail@plt>
0x000000000000123d <+180>: leave
0x000000000000123e <+181>: ret
스택을 보호하기 위해 Canary 값과 비교하고, 스택을 정리합니다.
위와 같이 코드를 작성하고 컴파일하면
별 찍기 프로그램인 것을 알 수 있습니다.
Handray 3
0x00000000000011a9 <+0>: endbr64
0x00000000000011ad <+4>: push rbp
0x00000000000011ae <+5>: mov rbp,rsp
0x00000000000011b1 <+8>: sub rsp,0x90
0x00000000000011b8 <+15>: mov rax,QWORD PTR fs:0x28
0x00000000000011c1 <+24>: mov QWORD PTR [rbp-0x8],rax
0x00000000000011c5 <+28>: xor eax,eax
0x00000000000011c7 <+30>: movabs rax,0x6563696c41
0x00000000000011d1 <+40>: mov edx,0x0
0x00000000000011d6 <+45>: mov QWORD PTR [rbp-0x80],rax
0x00000000000011da <+49>: mov QWORD PTR [rbp-0x78],rdx
0x00000000000011de <+53>: mov DWORD PTR [rbp-0x70],0x0
0x00000000000011e5 <+60>: mov DWORD PTR [rbp-0x6c],0x12
0x00000000000011ec <+67>: mov DWORD PTR [rbp-0x68],0x1
0x00000000000011f3 <+74>: mov QWORD PTR [rbp-0x60],0x626f42
0x00000000000011fb <+82>: mov QWORD PTR [rbp-0x58],0x0
0x0000000000001203 <+90>: mov DWORD PTR [rbp-0x50],0x0
0x000000000000120a <+97>: mov DWORD PTR [rbp-0x4c],0x14
0x0000000000001211 <+104>: mov DWORD PTR [rbp-0x48],0x3
0x0000000000001218 <+111>: mov rax,QWORD PTR [rbp-0x80]
0x000000000000121c <+115>: mov rdx,QWORD PTR [rbp-0x78]
0x0000000000001220 <+119>: mov QWORD PTR [rbp-0x40],rax
0x0000000000001224 <+123>: mov QWORD PTR [rbp-0x38],rdx
0x0000000000001228 <+127>: mov rax,QWORD PTR [rbp-0x74]
0x000000000000122c <+131>: mov rdx,QWORD PTR [rbp-0x6c]
0x0000000000001230 <+135>: mov QWORD PTR [rbp-0x34],rax
0x0000000000001234 <+139>: mov QWORD PTR [rbp-0x2c],rdx
0x0000000000001238 <+143>: mov rax,QWORD PTR [rbp-0x60]
0x000000000000123c <+147>: mov rdx,QWORD PTR [rbp-0x58]
0x0000000000001240 <+151>: mov QWORD PTR [rbp-0x24],rax
0x0000000000001244 <+155>: mov QWORD PTR [rbp-0x1c],rdx
0x0000000000001248 <+159>: mov rax,QWORD PTR [rbp-0x54]
0x000000000000124c <+163>: mov rdx,QWORD PTR [rbp-0x4c]
0x0000000000001250 <+167>: mov QWORD PTR [rbp-0x18],rax
0x0000000000001254 <+171>: mov QWORD PTR [rbp-0x10],rdx
0x0000000000001258 <+175>: mov DWORD PTR [rbp-0x84],0x2
0x0000000000001262 <+185>: mov DWORD PTR [rbp-0x88],0x0
0x000000000000126c <+195>: jmp 0x1384 <main+475>
0x0000000000001271 <+200>: lea rcx,[rbp-0x40]
0x0000000000001275 <+204>: mov eax,DWORD PTR [rbp-0x88]
0x000000000000127b <+210>: movsxd rdx,eax
0x000000000000127e <+213>: mov rax,rdx
0x0000000000001281 <+216>: shl rax,0x3
0x0000000000001285 <+220>: sub rax,rdx
0x0000000000001288 <+223>: shl rax,0x2
0x000000000000128c <+227>: add rax,rcx
0x000000000000128f <+230>: mov rsi,rax
0x0000000000001292 <+233>: lea rax,[rip+0xd6b] # 0x2004
0x0000000000001299 <+240>: mov rdi,rax
0x000000000000129c <+243>: mov eax,0x0
0x00000000000012a1 <+248>: call 0x10b0 <printf@plt>
0x00000000000012a6 <+253>: mov eax,DWORD PTR [rbp-0x88]
0x00000000000012ac <+259>: movsxd rdx,eax
0x00000000000012af <+262>: mov rax,rdx
0x00000000000012b2 <+265>: shl rax,0x3
0x00000000000012b6 <+269>: sub rax,rdx
0x00000000000012b9 <+272>: shl rax,0x2
0x00000000000012bd <+276>: add rax,rbp
0x00000000000012c0 <+279>: sub rax,0x2c
0x00000000000012c4 <+283>: mov eax,DWORD PTR [rax]
0x00000000000012c6 <+285>: mov esi,eax
0x00000000000012c8 <+287>: lea rax,[rip+0xd3f] # 0x200e
0x00000000000012cf <+294>: mov rdi,rax
0x00000000000012d2 <+297>: mov eax,0x0
0x00000000000012d7 <+302>: call 0x10b0 <printf@plt>
0x00000000000012dc <+307>: lea rax,[rip+0xd34] # 0x2017
0x00000000000012e3 <+314>: mov rdi,rax
0x00000000000012e6 <+317>: mov eax,0x0
0x00000000000012eb <+322>: call 0x10b0 <printf@plt>
0x00000000000012f0 <+327>: mov eax,DWORD PTR [rbp-0x88]
0x00000000000012f6 <+333>: movsxd rdx,eax
0x00000000000012f9 <+336>: mov rax,rdx
0x00000000000012fc <+339>: shl rax,0x3
0x0000000000001300 <+343>: sub rax,rdx
0x0000000000001303 <+346>: shl rax,0x2
0x0000000000001307 <+350>: add rax,rbp
0x000000000000130a <+353>: sub rax,0x28
0x000000000000130e <+357>: mov eax,DWORD PTR [rax]
0x0000000000001310 <+359>: cmp eax,0x4
0x0000000000001313 <+362>: je 0x1363 <main+442>
0x0000000000001315 <+364>: cmp eax,0x4
0x0000000000001318 <+367>: ja 0x1373 <main+458>
0x000000000000131a <+369>: cmp eax,0x3
0x000000000000131d <+372>: je 0x1352 <main+425>
0x000000000000131f <+374>: cmp eax,0x3
0x0000000000001322 <+377>: ja 0x1373 <main+458>
0x0000000000001324 <+379>: cmp eax,0x1
0x0000000000001327 <+382>: je 0x1330 <main+391>
0x0000000000001329 <+384>: cmp eax,0x2
0x000000000000132c <+387>: je 0x1341 <main+408>
0x000000000000132e <+389>: jmp 0x1373 <main+458>
0x0000000000001330 <+391>: lea rax,[rip+0xce8] # 0x201f
0x0000000000001337 <+398>: mov rdi,rax
0x000000000000133a <+401>: call 0x1090 <puts@plt>
0x000000000000133f <+406>: jmp 0x1373 <main+458>
0x0000000000001341 <+408>: lea rax,[rip+0xce0] # 0x2028
0x0000000000001348 <+415>: mov rdi,rax
0x000000000000134b <+418>: call 0x1090 <puts@plt>
0x0000000000001350 <+423>: jmp 0x1373 <main+458>
0x0000000000001352 <+425>: lea rax,[rip+0xcd9] # 0x2032
0x0000000000001359 <+432>: mov rdi,rax
0x000000000000135c <+435>: call 0x1090 <puts@plt>
0x0000000000001361 <+440>: jmp 0x1373 <main+458>
0x0000000000001363 <+442>: lea rax,[rip+0xccf] # 0x2039
0x000000000000136a <+449>: mov rdi,rax
0x000000000000136d <+452>: call 0x1090 <puts@plt>
0x0000000000001372 <+457>: nop
0x0000000000001373 <+458>: mov edi,0xa
0x0000000000001378 <+463>: call 0x1080 <putchar@plt>
0x000000000000137d <+468>: add DWORD PTR [rbp-0x88],0x1
0x0000000000001384 <+475>: mov eax,DWORD PTR [rbp-0x88]
0x000000000000138a <+481>: cmp eax,DWORD PTR [rbp-0x84]
0x0000000000001390 <+487>: jl 0x1271 <main+200>
0x0000000000001396 <+493>: mov eax,0x0
0x000000000000139b <+498>: mov rdx,QWORD PTR [rbp-0x8]
0x000000000000139f <+502>: sub rdx,QWORD PTR fs:0x28
0x00000000000013a8 <+511>: je 0x13af <main+518>
0x00000000000013aa <+513>: call 0x10a0 <__stack_chk_fail@plt>
0x00000000000013af <+518>: leave
0x00000000000013b0 <+519>: ret
전체 코드입니다.
0x00000000000011ad <+4>: push rbp
0x00000000000011ae <+5>: mov rbp,rsp
0x00000000000011b1 <+8>: sub rsp,0x90
0x00000000000011b8 <+15>: mov rax,QWORD PTR fs:0x28
0x00000000000011c1 <+24>: mov QWORD PTR [rbp-0x8],rax
0x00000000000011c5 <+28>: xor eax,eax
함수 프롤로그 입니다. 지역변수 할당을 위해 스택을 0x90만큼 할당하였습니다.
또한 스택 보호를 위해 Canary를 설정하였습니다.
0x00000000000011c7 <+30>: movabs rax,0x6563696c41
RAX에 Little Endian 방식으로 'Alice'를 넣음.
movabs 명령어는 Move Absolute로, 64 Bits 레지스터에 64비트 정수의 상수를 바로 넣을 때 사용합니다.
32 Bits 까지는 mov로 되지만, 64 Bits는 movabs를 사용해야 한다고 하네요.
0x00000000000011d1 <+40>: mov edx,0x0
0x00000000000011d6 <+45>: mov QWORD PTR [rbp-0x80],rax
0x00000000000011da <+49>: mov QWORD PTR [rbp-0x78],rdx
0x00000000000011de <+53>: mov DWORD PTR [rbp-0x70],0x0
0x00000000000011e5 <+60>: mov DWORD PTR [rbp-0x6c],0x12
0x00000000000011ec <+67>: mov DWORD PTR [rbp-0x68],0x1
우선, 이렇게 일정한 크기로 여러번 접근하는 것은 구조체를 의심할 수 있습니다.
EDX에 0을 넣습니다. 이후 8 Bytes로 위에서 RAX에 넣었던 Alice를 넣고, 나머지 8 Bytes를 0으로 채웁니다.
즉, 상위 8 Bytes에는 Alice가 들어갔고, 하위 8Bytes에는 0이 들어갔습니다. [rbp - 0x70]에 0을 넣은 것은 널 문자인지 잘 모르겠
습니다.
이후 main +60에 4Bytes로 0x12를 넣고, 같은 크기로 0x1을 넣습니다.
이를 통해서 해당 값들이 구조체라고 했을 때, 각 필드는 16 Bytes의 string, int 타입의 구조체 멤버 2개를 갖고 있음을 알 수 있습니
다.
typedef struct {
char name[16];
int age;
int num;
} People;
int main() {
People person[1] = {
{"Alice", 0x12, 0x1}
}
return 0;
}
구조체 필드 명이나, 멤버, 변수 이름 등은 제가 임의로 지었습니다. num은 아마 학번?으로 생각합니다.
0x00000000000011f3 <+74>: mov QWORD PTR [rbp-0x60],0x626f42
0x00000000000011fb <+82>: mov QWORD PTR [rbp-0x58],0x0
0x0000000000001203 <+90>: mov DWORD PTR [rbp-0x50],0x0
0x000000000000120a <+97>: mov DWORD PTR [rbp-0x4c],0x14
0x0000000000001211 <+104>: mov DWORD PTR [rbp-0x48],0x3
구조체의 두 번째 객체 입니다.
위와 같은 방식으로 "Bob"이라는 이름을 갖고 있고, 나이는 0x14, 학번은 3을 갖고 있습니다.
typedef struct {
char name[16];
int age;
int num;
} People;
int main() {
People person[2] = {
{"Alice", 0x12, 0x1},
{"Bob", 0x14, 0x3}
}
return 0;
}
0x0000000000001218 <+111>: mov rax,QWORD PTR [rbp-0x80]
0x000000000000121c <+115>: mov rdx,QWORD PTR [rbp-0x78]
0x0000000000001220 <+119>: mov QWORD PTR [rbp-0x40],rax
0x0000000000001224 <+123>: mov QWORD PTR [rbp-0x38],rdx
0x0000000000001228 <+127>: mov rax,QWORD PTR [rbp-0x74]
0x000000000000122c <+131>: mov rdx,QWORD PTR [rbp-0x6c]
0x0000000000001230 <+135>: mov QWORD PTR [rbp-0x34],rax
0x0000000000001234 <+139>: mov QWORD PTR [rbp-0x2c],rdx
0x0000000000001238 <+143>: mov rax,QWORD PTR [rbp-0x60]
0x000000000000123c <+147>: mov rdx,QWORD PTR [rbp-0x58]
0x0000000000001240 <+151>: mov QWORD PTR [rbp-0x24],rax
0x0000000000001244 <+155>: mov QWORD PTR [rbp-0x1c],rdx
0x0000000000001248 <+159>: mov rax,QWORD PTR [rbp-0x54]
0x000000000000124c <+163>: mov rdx,QWORD PTR [rbp-0x4c]
0x0000000000001250 <+167>: mov QWORD PTR [rbp-0x18],rax
0x0000000000001254 <+171>: mov QWORD PTR [rbp-0x10],rdx
위 내용들을 복사하여 새로운 변수를 생성합니다.
typedef struct {
char name[16];
int age;
int num;
} People;
int main() {
People person[2] = {
{"Alice", 0x12, 0x1},
{"Bob", 0x14, 0x3}
}
People alice = person[0];
People bob = person[1];
return 0;
}
0x0000000000001258 <+175>: mov DWORD PTR [rbp-0x84],0x2
0x0000000000001262 <+185>: mov DWORD PTR [rbp-0x88],0x0
0x000000000000126c <+195>: jmp 0x1384 <main+475>
반복을 설정합니다. 이후 main +475로 점프합니다.
typedef struct {
char name[16];
int age;
int num;
} People;
int main() {
People person[2] = {
{"Alice", 0x12, 0x1},
{"Bob", 0x14, 0x3}
}
People alice = person[0];
People bob = person[1];
for (int i = 0; 2; ) {
}
return 0;
}
정확한 반복 조건은 나오지 않았기 때문에 저렇게 뒀습니다.
0x0000000000001271 <+200>: lea rcx,[rbp-0x40]
0x0000000000001275 <+204>: mov eax,DWORD PTR [rbp-0x88]
0x000000000000127b <+210>: movsxd rdx,eax
0x000000000000127e <+213>: mov rax,rdx
0x0000000000001281 <+216>: shl rax,0x3
0x0000000000001285 <+220>: sub rax,rdx
0x0000000000001288 <+223>: shl rax,0x2
0x000000000000128c <+227>: add rax,rcx
0x000000000000128f <+230>: mov rsi,rax
0x0000000000001292 <+233>: lea rax,[rip+0xd6b] # 0x2004
0x0000000000001299 <+240>: mov rdi,rax
0x000000000000129c <+243>: mov eax,0x0
0x00000000000012a1 <+248>: call 0x10b0 <printf@plt>
배열의 인덱스를 계산하여 person[i]의 주소를 계산하고, person[i].name의 주소를 구한 후 RSI에 저장합니다.
포맷 스트링 "%s"를 RAX에 저장합니다 [rip + 0xd6b] 이후 printf를 호출합니다.
- movsxd: Move with Sign Extend Doubleword
32비트 정수를 64비트 주소 계산에 사용할 수 있게 확장합니다. 32비트 레지스터를 64비트 레지스터로 옮기되, 부호를 확장합니다.
- shl: Shift Left
왼쪽으로 비트를 옮깁니다. -> 2의 제곱
shl rax, 0x3 --> rax *= 2^3
typedef struct {
char name[16];
int age;
int num;
} People;
int main() {
People person[2] = {
{"Alice", 0x12, 0x1},
{"Bob", 0x14, 0x3}
}
People alice = person[0];
People bob = person[1];
for (int i = 0; 2; ) {
printf("Name: %s\n", person[i].name);
}
return 0;
}
0x00000000000012a6 <+253>: mov eax,DWORD PTR [rbp-0x88]
0x00000000000012ac <+259>: movsxd rdx,eax
0x00000000000012af <+262>: mov rax,rdx
0x00000000000012b2 <+265>: shl rax,0x3
0x00000000000012b6 <+269>: sub rax,rdx
0x00000000000012b9 <+272>: shl rax,0x2
0x00000000000012bd <+276>: add rax,rbp
0x00000000000012c0 <+279>: sub rax,0x2c
0x00000000000012c4 <+283>: mov eax,DWORD PTR [rax]
0x00000000000012c6 <+285>: mov esi,eax
0x00000000000012c8 <+287>: lea rax,[rip+0xd3f] # 0x200e
0x00000000000012cf <+294>: mov rdi,rax
0x00000000000012d2 <+297>: mov eax,0x0
0x00000000000012d7 <+302>: call 0x10b0 <printf@plt>
위의 과정과 동일합니다. 이번엔 Age 멤버에 접근합니다.
typedef struct {
char name[16];
int age;
int num;
} People;
int main() {
People person[2] = {
{"Alice", 0x12, 0x1},
{"Bob", 0x14, 0x3}
}
People alice = person[0];
People bob = person[1];
for (int i = 0; 2; ) {
printf("Name: %s\n", person[i].name);
printf("Age: %d\n", person[i].age);
}
return 0;
}
0x00000000000012dc <+307>: lea rax,[rip+0xd34] # 0x2017
0x00000000000012e3 <+314>: mov rdi,rax
0x00000000000012e6 <+317>: mov eax,0x0
0x00000000000012eb <+322>: call 0x10b0 <printf@plt>
[rip + 0xd34]가 포맷 스트링이라고 생각했지만, printf()를 호출하는데, 인자를 두 개 사용하지 않았습니다.
그래서 [rip + 0xd34]가 문자열 리터럴이라고 생각했고, printf("~~~~\n")은 puts("~~~~")와 같기 때문에, 컴파일러 단에서 바뀐 것으로 보았습니다.
typedef struct {
char name[16];
int age;
int num;
} People;
int main() {
People person[2] = {
{"Alice", 0x12, 0x1},
{"Bob", 0x14, 0x3}
}
People alice = person[0];
People bob = person[1];
for (int i = 0; 2; ) {
printf("Name: %s\n", person[i].name);
printf("Age: %d\n", person[i].age);
puts("~~~~");
}
return 0;
}
0x00000000000012f0 <+327>: mov eax,DWORD PTR [rbp-0x88]
0x00000000000012f6 <+333>: movsxd rdx,eax
0x00000000000012f9 <+336>: mov rax,rdx
0x00000000000012fc <+339>: shl rax,0x3
0x0000000000001300 <+343>: sub rax,rdx
0x0000000000001303 <+346>: shl rax,0x2
0x0000000000001307 <+350>: add rax,rbp
0x000000000000130a <+353>: sub rax,0x28
0x000000000000130e <+357>: mov eax,DWORD PTR [rax]
반복 변수 i값을 기준으로 구조체 배열의 age 멤버를 가리키는 주소를 계산합니다.
최종적으로 eax에 person[i].age 값이 들어갑니다.
0x0000000000001310 <+359>: cmp eax,0x4
0x0000000000001313 <+362>: je 0x1363 <main+442>
....
0x0000000000001363 <+442>: lea rax,[rip+0xccf] # 0x2039
0x000000000000136a <+449>: mov rdi,rax
0x000000000000136d <+452>: call 0x1090 <puts@plt>
0x0000000000001372 <+457>: nop
비교문입니다. eax == 4라면 main +442로 점프합니다.
main +442에는 puts("~~~")를 선언합니다.
typedef struct {
char name[16];
int age;
int num;
} People;
int main() {
People person[2] = {
{"Alice", 0x12, 0x1},
{"Bob", 0x14, 0x3}
}
People alice = person[0];
People bob = person[1];
for (int i = 0; 2; ) {
printf("Name: %s\n", person[i].name);
printf("Age: %d\n", person[i].age);
puts("~~~~");
if (person[i].age == 4) {
puts("~~~");
}
return 0;
}
0x0000000000001315 <+364>: cmp eax,0x4
0x0000000000001318 <+367>: ja 0x1373 <main+458>
0x000000000000131a <+369>: cmp eax,0x3
0x000000000000131d <+372>: je 0x1352 <main+425>
0x000000000000131f <+374>: cmp eax,0x3
0x0000000000001322 <+377>: ja 0x1373 <main+458>
0x0000000000001324 <+379>: cmp eax,0x1
0x0000000000001327 <+382>: je 0x1330 <main+391>
0x0000000000001329 <+384>: cmp eax,0x2
0x000000000000132c <+387>: je 0x1341 <main+408>
0x000000000000132e <+389>: jmp 0x1373 <main+458>
위와 같은 방식으로 C로 바꿔줍니다.
typedef struct {
char name[16];
int age;
int num;
} People;
int main() {
People person[2] = {
{"Alice", 0x12, 0x1},
{"Bob", 0x14, 0x3}
}
People alice = person[0];
People bob = person[1];
for (int i = 0; 2; ) {
printf("Name: %s\n", person[i].name);
printf("Age: %d\n", person[i].age);
puts("~~~~");
if (person[i].age == 4) {
puts("4");
}
else if (person[i].age > 4) {
puts("Over 4");
}
else if (person[i].age == 3) {
puts("3");
}
else if (person[i].age > 3) {
puts("Over 3");
}
else if (person[i].age == 1) {
puts("1");
}
else if (person[i].age == 2) {
puts("2");
}
else {
puts("???");
}
return 0;
}
puts() 안의 내용은 임의로 바꿨습니다.
0x0000000000001373 <+458>: mov edi,0xa
0x0000000000001378 <+463>: call 0x1080 <putchar@plt>
0xa -> '\n'
typedef struct {
char name[16];
int age;
int num;
} People;
int main() {
People person[2] = {
{"Alice", 0x12, 0x1},
{"Bob", 0x14, 0x3}
}
People alice = person[0];
People bob = person[1];
for (int i = 0; 2; ) {
printf("Name: %s\n", person[i].name);
printf("Age: %d\n", person[i].age);
puts("~~~~");
if (person[i].age == 4) {
puts("4");
}
else if (person[i].age > 4) {
puts("Over 4");
}
else if (person[i].age == 3) {
puts("3");
}
else if (person[i].age > 3) {
puts("Over 3");
}
else if (person[i].age == 1) {
puts("1");
}
else if (person[i].age == 2) {
puts("2");
}
else {
puts("???");
}
putchar('\n');
}
return 0;
}
0x000000000000137d <+468>: add DWORD PTR [rbp-0x88],0x1
0x0000000000001384 <+475>: mov eax,DWORD PTR [rbp-0x88]
0x000000000000138a <+481>: cmp eax,DWORD PTR [rbp-0x84]
0x0000000000001390 <+487>: jl 0x1271 <main+200>
반복문의 조건을 설정합니다.
typedef struct {
char name[16];
int age;
int num;
} People;
int main() {
People person[2] = {
{"Alice", 0x12, 0x1},
{"Bob", 0x14, 0x3}
};
People alice = person[0];
People bob = person[1];
for (int i = 0; i < 2; i++) {
printf("Name: %s\n", person[i].name);
printf("Age: %d\n", person[i].age);
puts("~~~~");
if (person[i].age == 4) {
puts("4");
}
else if (person[i].age > 4) {
puts("Over 4");
}
else if (person[i].age == 3) {
puts("3");
}
else if (person[i].age > 3) {
puts("Over 3");
}
else if (person[i].age == 1) {
puts("1");
}
else if (person[i].age == 2) {
puts("2");
}
else {
puts("???");
}
putchar('\n');
}
return 0;
}
최종 코드 입니다.
0x0000000000001396 <+493>: mov eax,0x0
0x000000000000139b <+498>: mov rdx,QWORD PTR [rbp-0x8]
0x000000000000139f <+502>: sub rdx,QWORD PTR fs:0x28
0x00000000000013a8 <+511>: je 0x13af <main+518>
0x00000000000013aa <+513>: call 0x10a0 <__stack_chk_fail@plt>
0x00000000000013af <+518>: leave
0x00000000000013b0 <+519>: ret
두 번째 핸드레이와 같이 스택을 보호하기 위해 Canary 값과 비교하고, 스택을 정리합니다.
컴파일하고 실행한 결과입니다.