假设我有以下功能:
void personalData()
{
char password[30];
puts("Please enter your password");
fgets(password, 6, stdin);
if(correctPassword(password) != 0) {
puts("Try again later !");
}
else {
puts("Hello master");
}
}
int correctPassword(char password[5])
{
int i = 0;
char desiredPassword[5] = {0x12, 0x13, 0x14, 0x15, 0x16};
char hash[5] = {0x22, 0x23, 0x24, 0x25, 0x26};
for(i = 0; i < 5; ++i) {
password[i] ^= hash[i];
}
return strncmp(password, desiredPassword, 5);
}
int main() {
personalData();
return 0;
}
我的目标是使用 gdb 查找我应为password
选择的密码,以便函数personalData打印 Hello master 。我尝试使用断点,每个部分都有许多分解,但我不知道在哪里寻找实际结果。我也试着查看strncmp但没有结果。
我不应该更改源代码。
答案 0 :(得分:2)
我的目标是使用gdb查找我应该为密码选择的密码,以便函数personalData打印Hello master。
您没有说明您正在使用的平台。我们假设Linux / x86_64。该平台很重要,因为调用约定取决于平台。这里的答案可以很容易地针对不同的平台进行调整。
让我们考虑优化二进制(通常更难)的情况。反汇编personalData
显示:
0x000000000040074d <+45>: callq 0x400560 <fgets@plt>
0x0000000000400752 <+50>: xor %eax,%eax
0x0000000000400754 <+52>: mov %rsp,%rdi
0x0000000000400757 <+55>: callq 0x400680 <correctPassword>
0x000000000040075c <+60>: test %eax,%eax
0x000000000040075e <+62>: jne 0x400780 <personalData+96>
这告诉我们,在从stdin
读取密码后,我们会调用correctPassword
并根据correctPassword
返回0还是非零来更改控制权。接下来的两条指令:
0x0000000000400760 <+64>: mov $0x400851,%edi
0x0000000000400765 <+69>: callq 0x400530 <puts@plt>
正在打印一些输出。如果correctPassword
返回0
并且没有进行跳转,那么会打印什么?
(gdb) x/s 0x400851
0x400851: "Hello master"
所以我们的目标是让correctPassword
返回0.让我们看看它的反汇编:
(gdb) disas correctPassword
....
0x0000000000400673 <+99>: callq 0x4004c0 <strncmp@plt>
0x0000000000400678 <+104>: add $0x28,%rsp
0x000000000040067c <+108>: retq
这告诉我们correctPassword
返回strncmp
返回的内容,即返回我们想要的0 IFF 我们的密码与第一个N
字符相匹配&# 39; s为strncmp
- 与...同在。是时候在strncmp
上设置断点了:
(gdb) break strncmp
Breakpoint 1 at 0x4004c0
(gdb) run
Starting program: /tmp/a.out
Please enter your password
aaaaaaaaa
上面我输入了9个字符的密码,就像初步猜测一样。
Breakpoint 1, __strncmp_ssse3 () at ../sysdeps/x86_64/multiarch/../strcmp.S:174
174 ../sysdeps/x86_64/multiarch/../strcmp.S: No such file or directory.
我碰巧安装了GLIBC调试符号,实际上可以检查GLIBC源代码和源代码级参数,但是你可能没那么奢侈,所以我会使用Linux/x86_64
calling convention代替。通过它,您可以看到strncmp
,RDI
和RSI
寄存器中传递了RDX
的3个参数。他们的价值观是什么?
(gdb) p/x $rdi
$1 = 0x7fffffffdd50
(gdb) p/x $rsi
$2 = 0x7fffffffdd20
(gdb) p/x $rdx
$3 = 0x5
好的,所以只比较密码的前5个字符,之后的任何字符都会被忽略。
正在比较的字符串是什么?
(gdb) x/s $rdi
0x7fffffffdd50: "CBEDG"
(gdb) x/s $rsi
0x7fffffffdd20: "\022\023\024\025\026"
嗯,这两根弦都不像我们的&#34; aaa ......&#34;密码。让我们尝试使用不同的密码:
(gdb) run
Starting program: /tmp/a.out
Please enter your password
bbbbb
Breakpoint 1, __strncmp_ssse3 () at ../sysdeps/x86_64/multiarch/../strcmp.S:174
174 ../sysdeps/x86_64/multiarch/../strcmp.S: No such file or directory.
(gdb) x/s $rdi
0x7fffffffdd50: "@AFGD"
(gdb) x/s $rsi
0x7fffffffdd20: "\022\023\024\025\026"
我们现在可以立即看到$rsi
序列没有改变,并且可以假设"\022\023\024\025\026"
是预期的密码。
我们还看到第一个a
已转换为C
,b
转换为@
。从这里我们可以采用以下两种方式之一:我们可以尝试更多的角色并猜测输入是什么 - &gt;混淆密码算法是,或者我们可以更多而简单地看一下反汇编&#34; read&#34;它
反汇编显示:
0x0000000000400622 <+18>: movb $0x12,(%rsp)
...
0x000000000040062a <+26>: movb $0x13,0x1(%rsp)
0x000000000040062f <+31>: movb $0x14,0x2(%rsp)
0x0000000000400634 <+36>: movb $0x15,0x3(%rsp)
0x0000000000400639 <+41>: movb $0x16,0x4(%rsp)
0x000000000040063e <+46>: movb $0x22,0x10(%rsp)
0x0000000000400643 <+51>: movb $0x23,0x11(%rsp)
0x0000000000400648 <+56>: movb $0x24,0x12(%rsp)
0x000000000040064d <+61>: movb $0x25,0x13(%rsp)
0x0000000000400652 <+66>: movb $0x26,0x14(%rsp)
因为我们知道&#34;目标&#34;字符串为\022\023...
,从0x4006322
到0x400639
的说明只是初始化目标字符串(请注意0x12
== \022
),这是公平的猜测。 。也许从0x40063e
开始的指令与模糊处理有关?进一步研究反汇编,我们看到:
0x0000000000400626 <+22>: cmp $0x5,%rax
...
0x0000000000400657 <+71>: je 0x40066b <correctPassword+91>
0x0000000000400659 <+73>: movzbl 0x10(%rsp,%rax,1),%edx
0x000000000040065e <+78>: xor %dl,(%rdi,%rax,1)
0x0000000000400661 <+81>: add $0x1,%rax
0x0000000000400665 <+85>: cmp $0x5,%rax
0x0000000000400669 <+89>: jne 0x400659 <correctPassword+73>
这是一个固定行程计数为5的循环,在循环中我们从一个缓冲区加载一个字符,并用另一个缓冲区中的字符加载XOR
该值。密码的第一个字符与XOR
进行0x22
的可能性有多大?
(gdb) p/c 'a' ^ 0x22
$5 = 67 'C'
(gdb) p/o 0x12
$6 = 022
(gdb) p/c 'b' ^ 0x22
$7 = 64 '@'
看起来很有前途! (当然,您可以通过在适当的指令上设置断点来确认混淆过程之前和之后各种缓冲区的内容。)
作为我们猜测的最终确认,最后一个字符是XOR
0x26
。{/ p>
(gdb) p/c 'a' ^ 0x26
$8 = 71 'G' # matches last char of 'aaa...' guess
(gdb) p/c 'b' ^ 0x26
$9 = 68 'D' # matches last char of 'bbb...' guess
最后,要构建正确的密码,我们需要采取&#34; target&#34;字符串并在其上执行相同的XOR
s序列:
(gdb) p/c 022 ^ 0x22
$10 = 48 '0'
(gdb) p/c 023 ^ 0x23
$11 = 48 '0'
... etc.
因此,正确的密码为00000
。让我们看看是否有效:
(gdb) disable
(gdb) run
Starting program: /tmp/a.out
Please enter your password
00000
Hello master
[Inferior 1 (process 45643) exited normally]
QED。
答案 1 :(得分:0)
由于哈希数组可能未知(但可从GDB访问),因此您需要将密码设置为hash[n] ^ desiredPassword[n]
。由于xor的反函数是xor,结果应该是desiredPassword。
以下是演示代码:
#include <stdio.h>
char hash[] = {0x23, 0x45, 0x55, 0xbb, 0xdd}; /* Some unknown values */
void personalData()
{
char password[30];
puts("Please enter your password");
fgets(password, 6, stdin);
if(correctPassword(password) != 0) {
puts("Try again later !");
}
else {
puts("Hello master");
}
}
int correctPassword(char password[5])
{
int i = 0;
char desiredPassword[5] = {0x12, 0x13, 0x14, 0x15, 0x16};
for(i = 0; i < 5; ++i) {
password[i] ^= hash[i];
}
return strncmp(password, desiredPassword, 5);
}
int main()
{
personalData();
}
在std控制台中:
gcc -g find_passwd.c
gdb .\a.exe -q
进入gdb控制台:
(gdb) b correctPassword
Breakpoint 1 at 0x4006b9: file find_passwd.c, line 32.
(gdb) r
Starting program: /home/xxx/workspace/c/xx/a.out
Please enter your password
garbagePasswd
Breakpoint 1, correctPassword
32 int i = 0;
(gdb) n
33 char desiredPassword[5] = {0x12, 0x13, 0x14, 0x15, 0x16};
(gdb)
34 for(i = 0; i < 5; ++i) {
(gdb) set password[0] = hash[0] ^ desiredPassword[0]
(gdb) set password[1] = hash[1] ^ desiredPassword[1]
(gdb) set password[2] = hash[2] ^ desiredPassword[2]
(gdb) set password[3] = hash[3] ^ desiredPassword[3]
(gdb) set password[4] = hash[4] ^ desiredPassword[4]
(gdb) c
Continuing.
Hello master
[Inferior 1 (process 19557) exited normally]